@odoo/owl 2.0.0-beta-9 → 2.0.0-beta-10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/owl.cjs.js CHANGED
@@ -1382,798 +1382,785 @@ function remove(vnode, withBeforeRemove = false) {
1382
1382
  vnode.remove();
1383
1383
  }
1384
1384
 
1385
- // Allows to get the target of a Reactive (used for making a new Reactive from the underlying object)
1386
- const TARGET = Symbol("Target");
1387
- // Escape hatch to prevent reactivity system to turn something into a reactive
1388
- const SKIP = Symbol("Skip");
1389
- // Special key to subscribe to, to be notified of key creation/deletion
1390
- const KEYCHANGES = Symbol("Key changes");
1391
- const objectToString = Object.prototype.toString;
1392
- const objectHasOwnProperty = Object.prototype.hasOwnProperty;
1393
- const SUPPORTED_RAW_TYPES = new Set(["Object", "Array", "Set", "Map", "WeakMap"]);
1394
- const COLLECTION_RAWTYPES = new Set(["Set", "Map", "WeakMap"]);
1395
- /**
1396
- * extract "RawType" from strings like "[object RawType]" => this lets us ignore
1397
- * many native objects such as Promise (whose toString is [object Promise])
1398
- * or Date ([object Date]), while also supporting collections without using
1399
- * instanceof in a loop
1400
- *
1401
- * @param obj the object to check
1402
- * @returns the raw type of the object
1403
- */
1404
- function rawType(obj) {
1405
- return objectToString.call(obj).slice(8, -1);
1406
- }
1407
- /**
1408
- * Checks whether a given value can be made into a reactive object.
1409
- *
1410
- * @param value the value to check
1411
- * @returns whether the value can be made reactive
1412
- */
1413
- function canBeMadeReactive(value) {
1414
- if (typeof value !== "object") {
1385
+ // Maps fibers to thrown errors
1386
+ const fibersInError = new WeakMap();
1387
+ const nodeErrorHandlers = new WeakMap();
1388
+ function _handleError(node, error) {
1389
+ if (!node) {
1415
1390
  return false;
1416
1391
  }
1417
- return SUPPORTED_RAW_TYPES.has(rawType(value));
1418
- }
1419
- /**
1420
- * Creates a reactive from the given object/callback if possible and returns it,
1421
- * returns the original object otherwise.
1422
- *
1423
- * @param value the value make reactive
1424
- * @returns a reactive for the given object when possible, the original otherwise
1425
- */
1426
- function possiblyReactive(val, cb) {
1427
- return canBeMadeReactive(val) ? reactive(val, cb) : val;
1428
- }
1429
- /**
1430
- * Mark an object or array so that it is ignored by the reactivity system
1431
- *
1432
- * @param value the value to mark
1433
- * @returns the object itself
1434
- */
1435
- function markRaw(value) {
1436
- value[SKIP] = true;
1437
- return value;
1438
- }
1439
- /**
1440
- * Given a reactive objet, return the raw (non reactive) underlying object
1441
- *
1442
- * @param value a reactive value
1443
- * @returns the underlying value
1444
- */
1445
- function toRaw(value) {
1446
- return value[TARGET] || value;
1447
- }
1448
- const targetToKeysToCallbacks = new WeakMap();
1449
- /**
1450
- * Observes a given key on a target with an callback. The callback will be
1451
- * called when the given key changes on the target.
1452
- *
1453
- * @param target the target whose key should be observed
1454
- * @param key the key to observe (or Symbol(KEYCHANGES) for key creation
1455
- * or deletion)
1456
- * @param callback the function to call when the key changes
1457
- */
1458
- function observeTargetKey(target, key, callback) {
1459
- if (!targetToKeysToCallbacks.get(target)) {
1460
- targetToKeysToCallbacks.set(target, new Map());
1392
+ const fiber = node.fiber;
1393
+ if (fiber) {
1394
+ fibersInError.set(fiber, error);
1461
1395
  }
1462
- const keyToCallbacks = targetToKeysToCallbacks.get(target);
1463
- if (!keyToCallbacks.get(key)) {
1464
- keyToCallbacks.set(key, new Set());
1396
+ const errorHandlers = nodeErrorHandlers.get(node);
1397
+ if (errorHandlers) {
1398
+ let handled = false;
1399
+ // execute in the opposite order
1400
+ for (let i = errorHandlers.length - 1; i >= 0; i--) {
1401
+ try {
1402
+ errorHandlers[i](error);
1403
+ handled = true;
1404
+ break;
1405
+ }
1406
+ catch (e) {
1407
+ error = e;
1408
+ }
1409
+ }
1410
+ if (handled) {
1411
+ return true;
1412
+ }
1465
1413
  }
1466
- keyToCallbacks.get(key).add(callback);
1467
- if (!callbacksToTargets.has(callback)) {
1468
- callbacksToTargets.set(callback, new Set());
1414
+ return _handleError(node.parent, error);
1415
+ }
1416
+ function handleError(params) {
1417
+ const error = params.error;
1418
+ const node = "node" in params ? params.node : params.fiber.node;
1419
+ const fiber = "fiber" in params ? params.fiber : node.fiber;
1420
+ // resets the fibers on components if possible. This is important so that
1421
+ // new renderings can be properly included in the initial one, if any.
1422
+ let current = fiber;
1423
+ do {
1424
+ current.node.fiber = current;
1425
+ current = current.parent;
1426
+ } while (current);
1427
+ fibersInError.set(fiber.root, error);
1428
+ const handled = _handleError(node, error);
1429
+ if (!handled) {
1430
+ console.warn(`[Owl] Unhandled error. Destroying the root component`);
1431
+ try {
1432
+ node.app.destroy();
1433
+ }
1434
+ catch (e) {
1435
+ console.error(e);
1436
+ }
1469
1437
  }
1470
- callbacksToTargets.get(callback).add(target);
1438
+ }
1439
+
1440
+ function makeChildFiber(node, parent) {
1441
+ let current = node.fiber;
1442
+ if (current) {
1443
+ cancelFibers(current.children);
1444
+ current.root = null;
1445
+ }
1446
+ return new Fiber(node, parent);
1471
1447
  }
1472
- /**
1473
- * Notify Reactives that are observing a given target that a key has changed on
1474
- * the target.
1475
- *
1476
- * @param target target whose Reactives should be notified that the target was
1477
- * changed.
1478
- * @param key the key that changed (or Symbol `KEYCHANGES` if a key was created
1479
- * or deleted)
1480
- */
1481
- function notifyReactives(target, key) {
1482
- const keyToCallbacks = targetToKeysToCallbacks.get(target);
1483
- if (!keyToCallbacks) {
1484
- return;
1448
+ function makeRootFiber(node) {
1449
+ let current = node.fiber;
1450
+ if (current) {
1451
+ let root = current.root;
1452
+ // lock root fiber because canceling children fibers may destroy components,
1453
+ // which means any arbitrary code can be run in onWillDestroy, which may
1454
+ // trigger new renderings
1455
+ root.locked = true;
1456
+ root.setCounter(root.counter + 1 - cancelFibers(current.children));
1457
+ root.locked = false;
1458
+ current.children = [];
1459
+ current.childrenMap = {};
1460
+ current.bdom = null;
1461
+ if (fibersInError.has(current)) {
1462
+ fibersInError.delete(current);
1463
+ fibersInError.delete(root);
1464
+ current.appliedToDom = false;
1465
+ }
1466
+ return current;
1485
1467
  }
1486
- const callbacks = keyToCallbacks.get(key);
1487
- if (!callbacks) {
1488
- return;
1468
+ const fiber = new RootFiber(node, null);
1469
+ if (node.willPatch.length) {
1470
+ fiber.willPatch.push(fiber);
1489
1471
  }
1490
- // Loop on copy because clearReactivesForCallback will modify the set in place
1491
- for (const callback of [...callbacks]) {
1492
- clearReactivesForCallback(callback);
1493
- callback();
1472
+ if (node.patched.length) {
1473
+ fiber.patched.push(fiber);
1494
1474
  }
1475
+ return fiber;
1476
+ }
1477
+ function throwOnRender() {
1478
+ throw new Error("Attempted to render cancelled fiber");
1495
1479
  }
1496
- const callbacksToTargets = new WeakMap();
1497
1480
  /**
1498
- * Clears all subscriptions of the Reactives associated with a given callback.
1499
- *
1500
- * @param callback the callback for which the reactives need to be cleared
1481
+ * @returns number of not-yet rendered fibers cancelled
1501
1482
  */
1502
- function clearReactivesForCallback(callback) {
1503
- const targetsToClear = callbacksToTargets.get(callback);
1504
- if (!targetsToClear) {
1505
- return;
1506
- }
1507
- for (const target of targetsToClear) {
1508
- const observedKeys = targetToKeysToCallbacks.get(target);
1509
- if (!observedKeys) {
1510
- continue;
1483
+ function cancelFibers(fibers) {
1484
+ let result = 0;
1485
+ for (let fiber of fibers) {
1486
+ let node = fiber.node;
1487
+ fiber.render = throwOnRender;
1488
+ if (node.status === 0 /* NEW */) {
1489
+ node.destroy();
1511
1490
  }
1512
- for (const callbacks of observedKeys.values()) {
1513
- callbacks.delete(callback);
1491
+ node.fiber = null;
1492
+ if (fiber.bdom) {
1493
+ // if fiber has been rendered, this means that the component props have
1494
+ // been updated. however, this fiber will not be patched to the dom, so
1495
+ // it could happen that the next render compare the current props with
1496
+ // the same props, and skip the render completely. With the next line,
1497
+ // we kindly request the component code to force a render, so it works as
1498
+ // expected.
1499
+ node.forceNextRender = true;
1500
+ }
1501
+ else {
1502
+ result++;
1514
1503
  }
1504
+ result += cancelFibers(fiber.children);
1515
1505
  }
1516
- targetsToClear.clear();
1517
- }
1518
- function getSubscriptions(callback) {
1519
- const targets = callbacksToTargets.get(callback) || [];
1520
- return [...targets].map((target) => {
1521
- const keysToCallbacks = targetToKeysToCallbacks.get(target);
1522
- return {
1523
- target,
1524
- keys: keysToCallbacks ? [...keysToCallbacks.keys()] : [],
1525
- };
1526
- });
1506
+ return result;
1527
1507
  }
1528
- const reactiveCache = new WeakMap();
1529
- /**
1530
- * Creates a reactive proxy for an object. Reading data on the reactive object
1531
- * subscribes to changes to the data. Writing data on the object will cause the
1532
- * notify callback to be called if there are suscriptions to that data. Nested
1533
- * objects and arrays are automatically made reactive as well.
1534
- *
1535
- * Whenever you are notified of a change, all subscriptions are cleared, and if
1536
- * you would like to be notified of any further changes, you should go read
1537
- * the underlying data again. We assume that if you don't go read it again after
1538
- * being notified, it means that you are no longer interested in that data.
1539
- *
1540
- * Subscriptions:
1541
- * + Reading a property on an object will subscribe you to changes in the value
1542
- * of that property.
1543
- * + Accessing an object keys (eg with Object.keys or with `for..in`) will
1544
- * subscribe you to the creation/deletion of keys. Checking the presence of a
1545
- * key on the object with 'in' has the same effect.
1546
- * - getOwnPropertyDescriptor does not currently subscribe you to the property.
1547
- * This is a choice that was made because changing a key's value will trigger
1548
- * this trap and we do not want to subscribe by writes. This also means that
1549
- * Object.hasOwnProperty doesn't subscribe as it goes through this trap.
1550
- *
1551
- * @param target the object for which to create a reactive proxy
1552
- * @param callback the function to call when an observed property of the
1553
- * reactive has changed
1554
- * @returns a proxy that tracks changes to it
1555
- */
1556
- function reactive(target, callback = () => { }) {
1557
- if (!canBeMadeReactive(target)) {
1558
- throw new Error(`Cannot make the given value reactive`);
1508
+ class Fiber {
1509
+ constructor(node, parent) {
1510
+ this.bdom = null;
1511
+ this.children = [];
1512
+ this.appliedToDom = false;
1513
+ this.deep = false;
1514
+ this.childrenMap = {};
1515
+ this.node = node;
1516
+ this.parent = parent;
1517
+ if (parent) {
1518
+ this.deep = parent.deep;
1519
+ const root = parent.root;
1520
+ root.setCounter(root.counter + 1);
1521
+ this.root = root;
1522
+ parent.children.push(this);
1523
+ }
1524
+ else {
1525
+ this.root = this;
1526
+ }
1559
1527
  }
1560
- if (SKIP in target) {
1561
- return target;
1528
+ render() {
1529
+ // if some parent has a fiber => register in followup
1530
+ let prev = this.root.node;
1531
+ let scheduler = prev.app.scheduler;
1532
+ let current = prev.parent;
1533
+ while (current) {
1534
+ if (current.fiber) {
1535
+ let root = current.fiber.root;
1536
+ if (root.counter === 0 && prev.parentKey in current.fiber.childrenMap) {
1537
+ current = root.node;
1538
+ }
1539
+ else {
1540
+ scheduler.delayedRenders.push(this);
1541
+ return;
1542
+ }
1543
+ }
1544
+ prev = current;
1545
+ current = current.parent;
1546
+ }
1547
+ // there are no current rendering from above => we can render
1548
+ this._render();
1562
1549
  }
1563
- const originalTarget = target[TARGET];
1564
- if (originalTarget) {
1565
- return reactive(originalTarget, callback);
1550
+ _render() {
1551
+ const node = this.node;
1552
+ const root = this.root;
1553
+ if (root) {
1554
+ try {
1555
+ this.bdom = true;
1556
+ this.bdom = node.renderFn();
1557
+ }
1558
+ catch (e) {
1559
+ handleError({ node, error: e });
1560
+ }
1561
+ root.setCounter(root.counter - 1);
1562
+ }
1566
1563
  }
1567
- if (!reactiveCache.has(target)) {
1568
- reactiveCache.set(target, new WeakMap());
1564
+ }
1565
+ class RootFiber extends Fiber {
1566
+ constructor() {
1567
+ super(...arguments);
1568
+ this.counter = 1;
1569
+ // only add stuff in this if they have registered some hooks
1570
+ this.willPatch = [];
1571
+ this.patched = [];
1572
+ this.mounted = [];
1573
+ // A fiber is typically locked when it is completing and the patch has not, or is being applied.
1574
+ // i.e.: render triggered in onWillUnmount or in willPatch will be delayed
1575
+ this.locked = false;
1569
1576
  }
1570
- const reactivesForTarget = reactiveCache.get(target);
1571
- if (!reactivesForTarget.has(callback)) {
1572
- const targetRawType = rawType(target);
1573
- const handler = COLLECTION_RAWTYPES.has(targetRawType)
1574
- ? collectionsProxyHandler(target, callback, targetRawType)
1575
- : basicProxyHandler(callback);
1576
- const proxy = new Proxy(target, handler);
1577
- reactivesForTarget.set(callback, proxy);
1577
+ complete() {
1578
+ const node = this.node;
1579
+ this.locked = true;
1580
+ let current = undefined;
1581
+ try {
1582
+ // Step 1: calling all willPatch lifecycle hooks
1583
+ for (current of this.willPatch) {
1584
+ // because of the asynchronous nature of the rendering, some parts of the
1585
+ // UI may have been rendered, then deleted in a followup rendering, and we
1586
+ // do not want to call onWillPatch in that case.
1587
+ let node = current.node;
1588
+ if (node.fiber === current) {
1589
+ const component = node.component;
1590
+ for (let cb of node.willPatch) {
1591
+ cb.call(component);
1592
+ }
1593
+ }
1594
+ }
1595
+ current = undefined;
1596
+ // Step 2: patching the dom
1597
+ node._patch();
1598
+ this.locked = false;
1599
+ // Step 4: calling all mounted lifecycle hooks
1600
+ let mountedFibers = this.mounted;
1601
+ while ((current = mountedFibers.pop())) {
1602
+ current = current;
1603
+ if (current.appliedToDom) {
1604
+ for (let cb of current.node.mounted) {
1605
+ cb();
1606
+ }
1607
+ }
1608
+ }
1609
+ // Step 5: calling all patched hooks
1610
+ let patchedFibers = this.patched;
1611
+ while ((current = patchedFibers.pop())) {
1612
+ current = current;
1613
+ if (current.appliedToDom) {
1614
+ for (let cb of current.node.patched) {
1615
+ cb();
1616
+ }
1617
+ }
1618
+ }
1619
+ }
1620
+ catch (e) {
1621
+ this.locked = false;
1622
+ handleError({ fiber: current || this, error: e });
1623
+ }
1624
+ }
1625
+ setCounter(newValue) {
1626
+ this.counter = newValue;
1627
+ if (newValue === 0) {
1628
+ this.node.app.scheduler.flush();
1629
+ }
1578
1630
  }
1579
- return reactivesForTarget.get(callback);
1580
1631
  }
1581
- /**
1582
- * Creates a basic proxy handler for regular objects and arrays.
1583
- *
1584
- * @param callback @see reactive
1585
- * @returns a proxy handler object
1586
- */
1587
- function basicProxyHandler(callback) {
1588
- return {
1589
- get(target, key, proxy) {
1590
- if (key === TARGET) {
1591
- return target;
1592
- }
1593
- // non-writable non-configurable properties cannot be made reactive
1594
- const desc = Object.getOwnPropertyDescriptor(target, key);
1595
- if (desc && !desc.writable && !desc.configurable) {
1596
- return Reflect.get(target, key, proxy);
1632
+ class MountFiber extends RootFiber {
1633
+ constructor(node, target, options = {}) {
1634
+ super(node, null);
1635
+ this.target = target;
1636
+ this.position = options.position || "last-child";
1637
+ }
1638
+ complete() {
1639
+ let current = this;
1640
+ try {
1641
+ const node = this.node;
1642
+ node.children = this.childrenMap;
1643
+ node.app.constructor.validateTarget(this.target);
1644
+ if (node.bdom) {
1645
+ // this is a complicated situation: if we mount a fiber with an existing
1646
+ // bdom, this means that this same fiber was already completed, mounted,
1647
+ // but a crash occurred in some mounted hook. Then, it was handled and
1648
+ // the new rendering is being applied.
1649
+ node.updateDom();
1597
1650
  }
1598
- observeTargetKey(target, key, callback);
1599
- return possiblyReactive(Reflect.get(target, key, proxy), callback);
1600
- },
1601
- set(target, key, value, proxy) {
1602
- const isNewKey = !objectHasOwnProperty.call(target, key);
1603
- const originalValue = Reflect.get(target, key, proxy);
1604
- const ret = Reflect.set(target, key, value, proxy);
1605
- if (isNewKey) {
1606
- notifyReactives(target, KEYCHANGES);
1651
+ else {
1652
+ node.bdom = this.bdom;
1653
+ if (this.position === "last-child" || this.target.childNodes.length === 0) {
1654
+ mount$1(node.bdom, this.target);
1655
+ }
1656
+ else {
1657
+ const firstChild = this.target.childNodes[0];
1658
+ mount$1(node.bdom, this.target, firstChild);
1659
+ }
1607
1660
  }
1608
- // While Array length may trigger the set trap, it's not actually set by this
1609
- // method but is updated behind the scenes, and the trap is not called with the
1610
- // new value. We disable the "same-value-optimization" for it because of that.
1611
- if (originalValue !== value || (Array.isArray(target) && key === "length")) {
1612
- notifyReactives(target, key);
1661
+ // unregistering the fiber before mounted since it can do another render
1662
+ // and that the current rendering is obviously completed
1663
+ node.fiber = null;
1664
+ node.status = 1 /* MOUNTED */;
1665
+ this.appliedToDom = true;
1666
+ let mountedFibers = this.mounted;
1667
+ while ((current = mountedFibers.pop())) {
1668
+ if (current.appliedToDom) {
1669
+ for (let cb of current.node.mounted) {
1670
+ cb();
1671
+ }
1672
+ }
1613
1673
  }
1614
- return ret;
1615
- },
1616
- deleteProperty(target, key) {
1617
- const ret = Reflect.deleteProperty(target, key);
1618
- // TODO: only notify when something was actually deleted
1619
- notifyReactives(target, KEYCHANGES);
1620
- notifyReactives(target, key);
1621
- return ret;
1622
- },
1623
- ownKeys(target) {
1624
- observeTargetKey(target, KEYCHANGES, callback);
1625
- return Reflect.ownKeys(target);
1626
- },
1627
- has(target, key) {
1628
- // TODO: this observes all key changes instead of only the presence of the argument key
1629
- // observing the key itself would observe value changes instead of presence changes
1630
- // so we may need a finer grained system to distinguish observing value vs presence.
1631
- observeTargetKey(target, KEYCHANGES, callback);
1632
- return Reflect.has(target, key);
1633
- },
1634
- };
1635
- }
1636
- /**
1637
- * Creates a function that will observe the key that is passed to it when called
1638
- * and delegates to the underlying method.
1639
- *
1640
- * @param methodName name of the method to delegate to
1641
- * @param target @see reactive
1642
- * @param callback @see reactive
1643
- */
1644
- function makeKeyObserver(methodName, target, callback) {
1645
- return (key) => {
1646
- key = toRaw(key);
1647
- observeTargetKey(target, key, callback);
1648
- return possiblyReactive(target[methodName](key), callback);
1649
- };
1650
- }
1674
+ }
1675
+ catch (e) {
1676
+ handleError({ fiber: current, error: e });
1677
+ }
1678
+ }
1679
+ }
1680
+
1681
+ // Allows to get the target of a Reactive (used for making a new Reactive from the underlying object)
1682
+ const TARGET = Symbol("Target");
1683
+ // Escape hatch to prevent reactivity system to turn something into a reactive
1684
+ const SKIP = Symbol("Skip");
1685
+ // Special key to subscribe to, to be notified of key creation/deletion
1686
+ const KEYCHANGES = Symbol("Key changes");
1687
+ const objectToString = Object.prototype.toString;
1688
+ const objectHasOwnProperty = Object.prototype.hasOwnProperty;
1689
+ const SUPPORTED_RAW_TYPES = new Set(["Object", "Array", "Set", "Map", "WeakMap"]);
1690
+ const COLLECTION_RAWTYPES = new Set(["Set", "Map", "WeakMap"]);
1651
1691
  /**
1652
- * Creates an iterable that will delegate to the underlying iteration method and
1653
- * observe keys as necessary.
1692
+ * extract "RawType" from strings like "[object RawType]" => this lets us ignore
1693
+ * many native objects such as Promise (whose toString is [object Promise])
1694
+ * or Date ([object Date]), while also supporting collections without using
1695
+ * instanceof in a loop
1654
1696
  *
1655
- * @param methodName name of the method to delegate to
1656
- * @param target @see reactive
1657
- * @param callback @see reactive
1697
+ * @param obj the object to check
1698
+ * @returns the raw type of the object
1658
1699
  */
1659
- function makeIteratorObserver(methodName, target, callback) {
1660
- return function* () {
1661
- observeTargetKey(target, KEYCHANGES, callback);
1662
- const keys = target.keys();
1663
- for (const item of target[methodName]()) {
1664
- const key = keys.next().value;
1665
- observeTargetKey(target, key, callback);
1666
- yield possiblyReactive(item, callback);
1667
- }
1668
- };
1700
+ function rawType(obj) {
1701
+ return objectToString.call(obj).slice(8, -1);
1669
1702
  }
1670
1703
  /**
1671
- * Creates a forEach function that will delegate to forEach on the underlying
1672
- * collection while observing key changes, and keys as they're iterated over,
1673
- * and making the passed keys/values reactive.
1704
+ * Checks whether a given value can be made into a reactive object.
1674
1705
  *
1675
- * @param target @see reactive
1676
- * @param callback @see reactive
1706
+ * @param value the value to check
1707
+ * @returns whether the value can be made reactive
1677
1708
  */
1678
- function makeForEachObserver(target, callback) {
1679
- return function forEach(forEachCb, thisArg) {
1680
- observeTargetKey(target, KEYCHANGES, callback);
1681
- target.forEach(function (val, key, targetObj) {
1682
- observeTargetKey(target, key, callback);
1683
- forEachCb.call(thisArg, possiblyReactive(val, callback), possiblyReactive(key, callback), possiblyReactive(targetObj, callback));
1684
- }, thisArg);
1685
- };
1709
+ function canBeMadeReactive(value) {
1710
+ if (typeof value !== "object") {
1711
+ return false;
1712
+ }
1713
+ return SUPPORTED_RAW_TYPES.has(rawType(value));
1686
1714
  }
1687
1715
  /**
1688
- * Creates a function that will delegate to an underlying method, and check if
1689
- * that method has modified the presence or value of a key, and notify the
1690
- * reactives appropriately.
1716
+ * Creates a reactive from the given object/callback if possible and returns it,
1717
+ * returns the original object otherwise.
1691
1718
  *
1692
- * @param setterName name of the method to delegate to
1693
- * @param getterName name of the method which should be used to retrieve the
1694
- * value before calling the delegate method for comparison purposes
1695
- * @param target @see reactive
1719
+ * @param value the value make reactive
1720
+ * @returns a reactive for the given object when possible, the original otherwise
1696
1721
  */
1697
- function delegateAndNotify(setterName, getterName, target) {
1698
- return (key, value) => {
1699
- key = toRaw(key);
1700
- const hadKey = target.has(key);
1701
- const originalValue = target[getterName](key);
1702
- const ret = target[setterName](key, value);
1703
- const hasKey = target.has(key);
1704
- if (hadKey !== hasKey) {
1705
- notifyReactives(target, KEYCHANGES);
1706
- }
1707
- if (originalValue !== value) {
1708
- notifyReactives(target, key);
1709
- }
1710
- return ret;
1711
- };
1722
+ function possiblyReactive(val, cb) {
1723
+ return canBeMadeReactive(val) ? reactive(val, cb) : val;
1712
1724
  }
1713
1725
  /**
1714
- * Creates a function that will clear the underlying collection and notify that
1715
- * the keys of the collection have changed.
1726
+ * Mark an object or array so that it is ignored by the reactivity system
1716
1727
  *
1717
- * @param target @see reactive
1728
+ * @param value the value to mark
1729
+ * @returns the object itself
1718
1730
  */
1719
- function makeClearNotifier(target) {
1720
- return () => {
1721
- const allKeys = [...target.keys()];
1722
- target.clear();
1723
- notifyReactives(target, KEYCHANGES);
1724
- for (const key of allKeys) {
1725
- notifyReactives(target, key);
1726
- }
1727
- };
1731
+ function markRaw(value) {
1732
+ value[SKIP] = true;
1733
+ return value;
1728
1734
  }
1729
1735
  /**
1730
- * Maps raw type of an object to an object containing functions that can be used
1731
- * to build an appropritate proxy handler for that raw type. Eg: when making a
1732
- * reactive set, calling the has method should mark the key that is being
1733
- * retrieved as observed, and calling the add or delete method should notify the
1734
- * reactives that the key which is being added or deleted has been modified.
1735
- */
1736
- const rawTypeToFuncHandlers = {
1737
- Set: (target, callback) => ({
1738
- has: makeKeyObserver("has", target, callback),
1739
- add: delegateAndNotify("add", "has", target),
1740
- delete: delegateAndNotify("delete", "has", target),
1741
- keys: makeIteratorObserver("keys", target, callback),
1742
- values: makeIteratorObserver("values", target, callback),
1743
- entries: makeIteratorObserver("entries", target, callback),
1744
- [Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
1745
- forEach: makeForEachObserver(target, callback),
1746
- clear: makeClearNotifier(target),
1747
- get size() {
1748
- observeTargetKey(target, KEYCHANGES, callback);
1749
- return target.size;
1750
- },
1751
- }),
1752
- Map: (target, callback) => ({
1753
- has: makeKeyObserver("has", target, callback),
1754
- get: makeKeyObserver("get", target, callback),
1755
- set: delegateAndNotify("set", "get", target),
1756
- delete: delegateAndNotify("delete", "has", target),
1757
- keys: makeIteratorObserver("keys", target, callback),
1758
- values: makeIteratorObserver("values", target, callback),
1759
- entries: makeIteratorObserver("entries", target, callback),
1760
- [Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
1761
- forEach: makeForEachObserver(target, callback),
1762
- clear: makeClearNotifier(target),
1763
- get size() {
1764
- observeTargetKey(target, KEYCHANGES, callback);
1765
- return target.size;
1766
- },
1767
- }),
1768
- WeakMap: (target, callback) => ({
1769
- has: makeKeyObserver("has", target, callback),
1770
- get: makeKeyObserver("get", target, callback),
1771
- set: delegateAndNotify("set", "get", target),
1772
- delete: delegateAndNotify("delete", "has", target),
1773
- }),
1774
- };
1775
- /**
1776
- * Creates a proxy handler for collections (Set/Map/WeakMap)
1736
+ * Given a reactive objet, return the raw (non reactive) underlying object
1777
1737
  *
1778
- * @param callback @see reactive
1779
- * @param target @see reactive
1780
- * @returns a proxy handler object
1738
+ * @param value a reactive value
1739
+ * @returns the underlying value
1781
1740
  */
1782
- function collectionsProxyHandler(target, callback, targetRawType) {
1783
- // TODO: if performance is an issue we can create the special handlers lazily when each
1784
- // property is read.
1785
- const specialHandlers = rawTypeToFuncHandlers[targetRawType](target, callback);
1786
- return Object.assign(basicProxyHandler(callback), {
1787
- get(target, key) {
1788
- if (key === TARGET) {
1789
- return target;
1790
- }
1791
- if (objectHasOwnProperty.call(specialHandlers, key)) {
1792
- return specialHandlers[key];
1793
- }
1794
- observeTargetKey(target, key, callback);
1795
- return possiblyReactive(target[key], callback);
1796
- },
1797
- });
1798
- }
1799
-
1741
+ function toRaw(value) {
1742
+ return value[TARGET] || value;
1743
+ }
1744
+ const targetToKeysToCallbacks = new WeakMap();
1800
1745
  /**
1801
- * Creates a batched version of a callback so that all calls to it in the same
1802
- * microtick will only call the original callback once.
1746
+ * Observes a given key on a target with an callback. The callback will be
1747
+ * called when the given key changes on the target.
1803
1748
  *
1804
- * @param callback the callback to batch
1805
- * @returns a batched version of the original callback
1749
+ * @param target the target whose key should be observed
1750
+ * @param key the key to observe (or Symbol(KEYCHANGES) for key creation
1751
+ * or deletion)
1752
+ * @param callback the function to call when the key changes
1806
1753
  */
1807
- function batched(callback) {
1808
- let called = false;
1809
- return async () => {
1810
- // This await blocks all calls to the callback here, then releases them sequentially
1811
- // in the next microtick. This line decides the granularity of the batch.
1812
- await Promise.resolve();
1813
- if (!called) {
1814
- called = true;
1815
- // wait for all calls in this microtick to fall through before resetting "called"
1816
- // so that only the first call to the batched function calls the original callback.
1817
- // Schedule this before calling the callback so that calls to the batched function
1818
- // within the callback will proceed only after resetting called to false, and have
1819
- // a chance to execute the callback again
1820
- Promise.resolve().then(() => (called = false));
1821
- callback();
1822
- }
1823
- };
1824
- }
1825
- function validateTarget(target) {
1826
- if (!(target instanceof HTMLElement)) {
1827
- throw new Error("Cannot mount component: the target is not a valid DOM element");
1754
+ function observeTargetKey(target, key, callback) {
1755
+ if (!targetToKeysToCallbacks.get(target)) {
1756
+ targetToKeysToCallbacks.set(target, new Map());
1828
1757
  }
1829
- if (!document.body.contains(target)) {
1830
- throw new Error("Cannot mount a component on a detached dom node");
1758
+ const keyToCallbacks = targetToKeysToCallbacks.get(target);
1759
+ if (!keyToCallbacks.get(key)) {
1760
+ keyToCallbacks.set(key, new Set());
1761
+ }
1762
+ keyToCallbacks.get(key).add(callback);
1763
+ if (!callbacksToTargets.has(callback)) {
1764
+ callbacksToTargets.set(callback, new Set());
1831
1765
  }
1766
+ callbacksToTargets.get(callback).add(target);
1832
1767
  }
1833
- class EventBus extends EventTarget {
1834
- trigger(name, payload) {
1835
- this.dispatchEvent(new CustomEvent(name, { detail: payload }));
1768
+ /**
1769
+ * Notify Reactives that are observing a given target that a key has changed on
1770
+ * the target.
1771
+ *
1772
+ * @param target target whose Reactives should be notified that the target was
1773
+ * changed.
1774
+ * @param key the key that changed (or Symbol `KEYCHANGES` if a key was created
1775
+ * or deleted)
1776
+ */
1777
+ function notifyReactives(target, key) {
1778
+ const keyToCallbacks = targetToKeysToCallbacks.get(target);
1779
+ if (!keyToCallbacks) {
1780
+ return;
1781
+ }
1782
+ const callbacks = keyToCallbacks.get(key);
1783
+ if (!callbacks) {
1784
+ return;
1785
+ }
1786
+ // Loop on copy because clearReactivesForCallback will modify the set in place
1787
+ for (const callback of [...callbacks]) {
1788
+ clearReactivesForCallback(callback);
1789
+ callback();
1836
1790
  }
1837
1791
  }
1838
- function whenReady(fn) {
1839
- return new Promise(function (resolve) {
1840
- if (document.readyState !== "loading") {
1841
- resolve(true);
1792
+ const callbacksToTargets = new WeakMap();
1793
+ /**
1794
+ * Clears all subscriptions of the Reactives associated with a given callback.
1795
+ *
1796
+ * @param callback the callback for which the reactives need to be cleared
1797
+ */
1798
+ function clearReactivesForCallback(callback) {
1799
+ const targetsToClear = callbacksToTargets.get(callback);
1800
+ if (!targetsToClear) {
1801
+ return;
1802
+ }
1803
+ for (const target of targetsToClear) {
1804
+ const observedKeys = targetToKeysToCallbacks.get(target);
1805
+ if (!observedKeys) {
1806
+ continue;
1842
1807
  }
1843
- else {
1844
- document.addEventListener("DOMContentLoaded", resolve, false);
1808
+ for (const callbacks of observedKeys.values()) {
1809
+ callbacks.delete(callback);
1845
1810
  }
1846
- }).then(fn || function () { });
1847
- }
1848
- async function loadFile(url) {
1849
- const result = await fetch(url);
1850
- if (!result.ok) {
1851
- throw new Error("Error while fetching xml templates");
1852
1811
  }
1853
- return await result.text();
1812
+ targetsToClear.clear();
1854
1813
  }
1855
- /*
1856
- * This class just transports the fact that a string is safe
1857
- * to be injected as HTML. Overriding a JS primitive is quite painful though
1858
- * so we need to redfine toString and valueOf.
1859
- */
1860
- class Markup extends String {
1814
+ function getSubscriptions(callback) {
1815
+ const targets = callbacksToTargets.get(callback) || [];
1816
+ return [...targets].map((target) => {
1817
+ const keysToCallbacks = targetToKeysToCallbacks.get(target);
1818
+ return {
1819
+ target,
1820
+ keys: keysToCallbacks ? [...keysToCallbacks.keys()] : [],
1821
+ };
1822
+ });
1861
1823
  }
1862
- /*
1863
- * Marks a value as safe, that is, a value that can be injected as HTML directly.
1864
- * It should be used to wrap the value passed to a t-out directive to allow a raw rendering.
1824
+ const reactiveCache = new WeakMap();
1825
+ /**
1826
+ * Creates a reactive proxy for an object. Reading data on the reactive object
1827
+ * subscribes to changes to the data. Writing data on the object will cause the
1828
+ * notify callback to be called if there are suscriptions to that data. Nested
1829
+ * objects and arrays are automatically made reactive as well.
1830
+ *
1831
+ * Whenever you are notified of a change, all subscriptions are cleared, and if
1832
+ * you would like to be notified of any further changes, you should go read
1833
+ * the underlying data again. We assume that if you don't go read it again after
1834
+ * being notified, it means that you are no longer interested in that data.
1835
+ *
1836
+ * Subscriptions:
1837
+ * + Reading a property on an object will subscribe you to changes in the value
1838
+ * of that property.
1839
+ * + Accessing an object keys (eg with Object.keys or with `for..in`) will
1840
+ * subscribe you to the creation/deletion of keys. Checking the presence of a
1841
+ * key on the object with 'in' has the same effect.
1842
+ * - getOwnPropertyDescriptor does not currently subscribe you to the property.
1843
+ * This is a choice that was made because changing a key's value will trigger
1844
+ * this trap and we do not want to subscribe by writes. This also means that
1845
+ * Object.hasOwnProperty doesn't subscribe as it goes through this trap.
1846
+ *
1847
+ * @param target the object for which to create a reactive proxy
1848
+ * @param callback the function to call when an observed property of the
1849
+ * reactive has changed
1850
+ * @returns a proxy that tracks changes to it
1865
1851
  */
1866
- function markup(value) {
1867
- return new Markup(value);
1868
- }
1869
-
1870
- class Component {
1871
- constructor(props, env, node) {
1872
- this.props = props;
1873
- this.env = env;
1874
- this.__owl__ = node;
1852
+ function reactive(target, callback = () => { }) {
1853
+ if (!canBeMadeReactive(target)) {
1854
+ throw new Error(`Cannot make the given value reactive`);
1875
1855
  }
1876
- setup() { }
1877
- render(deep = false) {
1878
- this.__owl__.render(deep === true);
1856
+ if (SKIP in target) {
1857
+ return target;
1879
1858
  }
1880
- }
1881
- Component.template = "";
1882
-
1883
- // Maps fibers to thrown errors
1884
- const fibersInError = new WeakMap();
1885
- const nodeErrorHandlers = new WeakMap();
1886
- function _handleError(node, error) {
1887
- if (!node) {
1888
- return false;
1859
+ const originalTarget = target[TARGET];
1860
+ if (originalTarget) {
1861
+ return reactive(originalTarget, callback);
1889
1862
  }
1890
- const fiber = node.fiber;
1891
- if (fiber) {
1892
- fibersInError.set(fiber, error);
1863
+ if (!reactiveCache.has(target)) {
1864
+ reactiveCache.set(target, new WeakMap());
1893
1865
  }
1894
- const errorHandlers = nodeErrorHandlers.get(node);
1895
- if (errorHandlers) {
1896
- let handled = false;
1897
- // execute in the opposite order
1898
- for (let i = errorHandlers.length - 1; i >= 0; i--) {
1899
- try {
1900
- errorHandlers[i](error);
1901
- handled = true;
1902
- break;
1866
+ const reactivesForTarget = reactiveCache.get(target);
1867
+ if (!reactivesForTarget.has(callback)) {
1868
+ const targetRawType = rawType(target);
1869
+ const handler = COLLECTION_RAWTYPES.has(targetRawType)
1870
+ ? collectionsProxyHandler(target, callback, targetRawType)
1871
+ : basicProxyHandler(callback);
1872
+ const proxy = new Proxy(target, handler);
1873
+ reactivesForTarget.set(callback, proxy);
1874
+ }
1875
+ return reactivesForTarget.get(callback);
1876
+ }
1877
+ /**
1878
+ * Creates a basic proxy handler for regular objects and arrays.
1879
+ *
1880
+ * @param callback @see reactive
1881
+ * @returns a proxy handler object
1882
+ */
1883
+ function basicProxyHandler(callback) {
1884
+ return {
1885
+ get(target, key, proxy) {
1886
+ if (key === TARGET) {
1887
+ return target;
1903
1888
  }
1904
- catch (e) {
1905
- error = e;
1889
+ // non-writable non-configurable properties cannot be made reactive
1890
+ const desc = Object.getOwnPropertyDescriptor(target, key);
1891
+ if (desc && !desc.writable && !desc.configurable) {
1892
+ return Reflect.get(target, key, proxy);
1906
1893
  }
1907
- }
1908
- if (handled) {
1909
- return true;
1910
- }
1911
- }
1912
- return _handleError(node.parent, error);
1894
+ observeTargetKey(target, key, callback);
1895
+ return possiblyReactive(Reflect.get(target, key, proxy), callback);
1896
+ },
1897
+ set(target, key, value, proxy) {
1898
+ const isNewKey = !objectHasOwnProperty.call(target, key);
1899
+ const originalValue = Reflect.get(target, key, proxy);
1900
+ const ret = Reflect.set(target, key, value, proxy);
1901
+ if (isNewKey) {
1902
+ notifyReactives(target, KEYCHANGES);
1903
+ }
1904
+ // While Array length may trigger the set trap, it's not actually set by this
1905
+ // method but is updated behind the scenes, and the trap is not called with the
1906
+ // new value. We disable the "same-value-optimization" for it because of that.
1907
+ if (originalValue !== value || (Array.isArray(target) && key === "length")) {
1908
+ notifyReactives(target, key);
1909
+ }
1910
+ return ret;
1911
+ },
1912
+ deleteProperty(target, key) {
1913
+ const ret = Reflect.deleteProperty(target, key);
1914
+ // TODO: only notify when something was actually deleted
1915
+ notifyReactives(target, KEYCHANGES);
1916
+ notifyReactives(target, key);
1917
+ return ret;
1918
+ },
1919
+ ownKeys(target) {
1920
+ observeTargetKey(target, KEYCHANGES, callback);
1921
+ return Reflect.ownKeys(target);
1922
+ },
1923
+ has(target, key) {
1924
+ // TODO: this observes all key changes instead of only the presence of the argument key
1925
+ // observing the key itself would observe value changes instead of presence changes
1926
+ // so we may need a finer grained system to distinguish observing value vs presence.
1927
+ observeTargetKey(target, KEYCHANGES, callback);
1928
+ return Reflect.has(target, key);
1929
+ },
1930
+ };
1913
1931
  }
1914
- function handleError(params) {
1915
- const error = params.error;
1916
- const node = "node" in params ? params.node : params.fiber.node;
1917
- const fiber = "fiber" in params ? params.fiber : node.fiber;
1918
- // resets the fibers on components if possible. This is important so that
1919
- // new renderings can be properly included in the initial one, if any.
1920
- let current = fiber;
1921
- do {
1922
- current.node.fiber = current;
1923
- current = current.parent;
1924
- } while (current);
1925
- fibersInError.set(fiber.root, error);
1926
- const handled = _handleError(node, error);
1927
- if (!handled) {
1928
- console.warn(`[Owl] Unhandled error. Destroying the root component`);
1929
- try {
1930
- node.app.destroy();
1931
- }
1932
- catch (e) {
1933
- console.error(e);
1934
- }
1935
- }
1936
- }
1937
-
1938
- function makeChildFiber(node, parent) {
1939
- let current = node.fiber;
1940
- if (current) {
1941
- cancelFibers(current.children);
1942
- current.root = null;
1943
- }
1944
- return new Fiber(node, parent);
1932
+ /**
1933
+ * Creates a function that will observe the key that is passed to it when called
1934
+ * and delegates to the underlying method.
1935
+ *
1936
+ * @param methodName name of the method to delegate to
1937
+ * @param target @see reactive
1938
+ * @param callback @see reactive
1939
+ */
1940
+ function makeKeyObserver(methodName, target, callback) {
1941
+ return (key) => {
1942
+ key = toRaw(key);
1943
+ observeTargetKey(target, key, callback);
1944
+ return possiblyReactive(target[methodName](key), callback);
1945
+ };
1945
1946
  }
1946
- function makeRootFiber(node) {
1947
- let current = node.fiber;
1948
- if (current) {
1949
- let root = current.root;
1950
- // lock root fiber because canceling children fibers may destroy components,
1951
- // which means any arbitrary code can be run in onWillDestroy, which may
1952
- // trigger new renderings
1953
- root.locked = true;
1954
- root.setCounter(root.counter + 1 - cancelFibers(current.children));
1955
- root.locked = false;
1956
- current.children = [];
1957
- current.childrenMap = {};
1958
- current.bdom = null;
1959
- if (fibersInError.has(current)) {
1960
- fibersInError.delete(current);
1961
- fibersInError.delete(root);
1962
- current.appliedToDom = false;
1947
+ /**
1948
+ * Creates an iterable that will delegate to the underlying iteration method and
1949
+ * observe keys as necessary.
1950
+ *
1951
+ * @param methodName name of the method to delegate to
1952
+ * @param target @see reactive
1953
+ * @param callback @see reactive
1954
+ */
1955
+ function makeIteratorObserver(methodName, target, callback) {
1956
+ return function* () {
1957
+ observeTargetKey(target, KEYCHANGES, callback);
1958
+ const keys = target.keys();
1959
+ for (const item of target[methodName]()) {
1960
+ const key = keys.next().value;
1961
+ observeTargetKey(target, key, callback);
1962
+ yield possiblyReactive(item, callback);
1963
1963
  }
1964
- return current;
1965
- }
1966
- const fiber = new RootFiber(node, null);
1967
- if (node.willPatch.length) {
1968
- fiber.willPatch.push(fiber);
1969
- }
1970
- if (node.patched.length) {
1971
- fiber.patched.push(fiber);
1972
- }
1973
- return fiber;
1964
+ };
1974
1965
  }
1975
- function throwOnRender() {
1976
- throw new Error("Attempted to render cancelled fiber");
1966
+ /**
1967
+ * Creates a forEach function that will delegate to forEach on the underlying
1968
+ * collection while observing key changes, and keys as they're iterated over,
1969
+ * and making the passed keys/values reactive.
1970
+ *
1971
+ * @param target @see reactive
1972
+ * @param callback @see reactive
1973
+ */
1974
+ function makeForEachObserver(target, callback) {
1975
+ return function forEach(forEachCb, thisArg) {
1976
+ observeTargetKey(target, KEYCHANGES, callback);
1977
+ target.forEach(function (val, key, targetObj) {
1978
+ observeTargetKey(target, key, callback);
1979
+ forEachCb.call(thisArg, possiblyReactive(val, callback), possiblyReactive(key, callback), possiblyReactive(targetObj, callback));
1980
+ }, thisArg);
1981
+ };
1977
1982
  }
1978
1983
  /**
1979
- * @returns number of not-yet rendered fibers cancelled
1984
+ * Creates a function that will delegate to an underlying method, and check if
1985
+ * that method has modified the presence or value of a key, and notify the
1986
+ * reactives appropriately.
1987
+ *
1988
+ * @param setterName name of the method to delegate to
1989
+ * @param getterName name of the method which should be used to retrieve the
1990
+ * value before calling the delegate method for comparison purposes
1991
+ * @param target @see reactive
1980
1992
  */
1981
- function cancelFibers(fibers) {
1982
- let result = 0;
1983
- for (let fiber of fibers) {
1984
- let node = fiber.node;
1985
- fiber.render = throwOnRender;
1986
- if (node.status === 0 /* NEW */) {
1987
- node.destroy();
1988
- }
1989
- node.fiber = null;
1990
- if (fiber.bdom) {
1991
- // if fiber has been rendered, this means that the component props have
1992
- // been updated. however, this fiber will not be patched to the dom, so
1993
- // it could happen that the next render compare the current props with
1994
- // the same props, and skip the render completely. With the next line,
1995
- // we kindly request the component code to force a render, so it works as
1996
- // expected.
1997
- node.forceNextRender = true;
1993
+ function delegateAndNotify(setterName, getterName, target) {
1994
+ return (key, value) => {
1995
+ key = toRaw(key);
1996
+ const hadKey = target.has(key);
1997
+ const originalValue = target[getterName](key);
1998
+ const ret = target[setterName](key, value);
1999
+ const hasKey = target.has(key);
2000
+ if (hadKey !== hasKey) {
2001
+ notifyReactives(target, KEYCHANGES);
1998
2002
  }
1999
- else {
2000
- result++;
2003
+ if (originalValue !== value) {
2004
+ notifyReactives(target, key);
2001
2005
  }
2002
- result += cancelFibers(fiber.children);
2003
- }
2004
- return result;
2006
+ return ret;
2007
+ };
2005
2008
  }
2006
- class Fiber {
2007
- constructor(node, parent) {
2008
- this.bdom = null;
2009
- this.children = [];
2010
- this.appliedToDom = false;
2011
- this.deep = false;
2012
- this.childrenMap = {};
2013
- this.node = node;
2014
- this.parent = parent;
2015
- if (parent) {
2016
- this.deep = parent.deep;
2017
- const root = parent.root;
2018
- root.setCounter(root.counter + 1);
2019
- this.root = root;
2020
- parent.children.push(this);
2021
- }
2022
- else {
2023
- this.root = this;
2024
- }
2025
- }
2026
- render() {
2027
- // if some parent has a fiber => register in followup
2028
- let prev = this.root.node;
2029
- let scheduler = prev.app.scheduler;
2030
- let current = prev.parent;
2031
- while (current) {
2032
- if (current.fiber) {
2033
- let root = current.fiber.root;
2034
- if (root.counter === 0 && prev.parentKey in current.fiber.childrenMap) {
2035
- current = root.node;
2036
- }
2037
- else {
2038
- scheduler.delayedRenders.push(this);
2039
- return;
2040
- }
2041
- }
2042
- prev = current;
2043
- current = current.parent;
2009
+ /**
2010
+ * Creates a function that will clear the underlying collection and notify that
2011
+ * the keys of the collection have changed.
2012
+ *
2013
+ * @param target @see reactive
2014
+ */
2015
+ function makeClearNotifier(target) {
2016
+ return () => {
2017
+ const allKeys = [...target.keys()];
2018
+ target.clear();
2019
+ notifyReactives(target, KEYCHANGES);
2020
+ for (const key of allKeys) {
2021
+ notifyReactives(target, key);
2044
2022
  }
2045
- // there are no current rendering from above => we can render
2046
- this._render();
2047
- }
2048
- _render() {
2049
- const node = this.node;
2050
- const root = this.root;
2051
- if (root) {
2052
- try {
2053
- this.bdom = true;
2054
- this.bdom = node.renderFn();
2023
+ };
2024
+ }
2025
+ /**
2026
+ * Maps raw type of an object to an object containing functions that can be used
2027
+ * to build an appropritate proxy handler for that raw type. Eg: when making a
2028
+ * reactive set, calling the has method should mark the key that is being
2029
+ * retrieved as observed, and calling the add or delete method should notify the
2030
+ * reactives that the key which is being added or deleted has been modified.
2031
+ */
2032
+ const rawTypeToFuncHandlers = {
2033
+ Set: (target, callback) => ({
2034
+ has: makeKeyObserver("has", target, callback),
2035
+ add: delegateAndNotify("add", "has", target),
2036
+ delete: delegateAndNotify("delete", "has", target),
2037
+ keys: makeIteratorObserver("keys", target, callback),
2038
+ values: makeIteratorObserver("values", target, callback),
2039
+ entries: makeIteratorObserver("entries", target, callback),
2040
+ [Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
2041
+ forEach: makeForEachObserver(target, callback),
2042
+ clear: makeClearNotifier(target),
2043
+ get size() {
2044
+ observeTargetKey(target, KEYCHANGES, callback);
2045
+ return target.size;
2046
+ },
2047
+ }),
2048
+ Map: (target, callback) => ({
2049
+ has: makeKeyObserver("has", target, callback),
2050
+ get: makeKeyObserver("get", target, callback),
2051
+ set: delegateAndNotify("set", "get", target),
2052
+ delete: delegateAndNotify("delete", "has", target),
2053
+ keys: makeIteratorObserver("keys", target, callback),
2054
+ values: makeIteratorObserver("values", target, callback),
2055
+ entries: makeIteratorObserver("entries", target, callback),
2056
+ [Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
2057
+ forEach: makeForEachObserver(target, callback),
2058
+ clear: makeClearNotifier(target),
2059
+ get size() {
2060
+ observeTargetKey(target, KEYCHANGES, callback);
2061
+ return target.size;
2062
+ },
2063
+ }),
2064
+ WeakMap: (target, callback) => ({
2065
+ has: makeKeyObserver("has", target, callback),
2066
+ get: makeKeyObserver("get", target, callback),
2067
+ set: delegateAndNotify("set", "get", target),
2068
+ delete: delegateAndNotify("delete", "has", target),
2069
+ }),
2070
+ };
2071
+ /**
2072
+ * Creates a proxy handler for collections (Set/Map/WeakMap)
2073
+ *
2074
+ * @param callback @see reactive
2075
+ * @param target @see reactive
2076
+ * @returns a proxy handler object
2077
+ */
2078
+ function collectionsProxyHandler(target, callback, targetRawType) {
2079
+ // TODO: if performance is an issue we can create the special handlers lazily when each
2080
+ // property is read.
2081
+ const specialHandlers = rawTypeToFuncHandlers[targetRawType](target, callback);
2082
+ return Object.assign(basicProxyHandler(callback), {
2083
+ get(target, key) {
2084
+ if (key === TARGET) {
2085
+ return target;
2055
2086
  }
2056
- catch (e) {
2057
- handleError({ node, error: e });
2087
+ if (objectHasOwnProperty.call(specialHandlers, key)) {
2088
+ return specialHandlers[key];
2058
2089
  }
2059
- root.setCounter(root.counter - 1);
2090
+ observeTargetKey(target, key, callback);
2091
+ return possiblyReactive(target[key], callback);
2092
+ },
2093
+ });
2094
+ }
2095
+
2096
+ /**
2097
+ * Creates a batched version of a callback so that all calls to it in the same
2098
+ * microtick will only call the original callback once.
2099
+ *
2100
+ * @param callback the callback to batch
2101
+ * @returns a batched version of the original callback
2102
+ */
2103
+ function batched(callback) {
2104
+ let called = false;
2105
+ return async () => {
2106
+ // This await blocks all calls to the callback here, then releases them sequentially
2107
+ // in the next microtick. This line decides the granularity of the batch.
2108
+ await Promise.resolve();
2109
+ if (!called) {
2110
+ called = true;
2111
+ // wait for all calls in this microtick to fall through before resetting "called"
2112
+ // so that only the first call to the batched function calls the original callback.
2113
+ // Schedule this before calling the callback so that calls to the batched function
2114
+ // within the callback will proceed only after resetting called to false, and have
2115
+ // a chance to execute the callback again
2116
+ Promise.resolve().then(() => (called = false));
2117
+ callback();
2060
2118
  }
2061
- }
2119
+ };
2062
2120
  }
2063
- class RootFiber extends Fiber {
2064
- constructor() {
2065
- super(...arguments);
2066
- this.counter = 1;
2067
- // only add stuff in this if they have registered some hooks
2068
- this.willPatch = [];
2069
- this.patched = [];
2070
- this.mounted = [];
2071
- // A fiber is typically locked when it is completing and the patch has not, or is being applied.
2072
- // i.e.: render triggered in onWillUnmount or in willPatch will be delayed
2073
- this.locked = false;
2074
- }
2075
- complete() {
2076
- const node = this.node;
2077
- this.locked = true;
2078
- let current = undefined;
2079
- try {
2080
- // Step 1: calling all willPatch lifecycle hooks
2081
- for (current of this.willPatch) {
2082
- // because of the asynchronous nature of the rendering, some parts of the
2083
- // UI may have been rendered, then deleted in a followup rendering, and we
2084
- // do not want to call onWillPatch in that case.
2085
- let node = current.node;
2086
- if (node.fiber === current) {
2087
- const component = node.component;
2088
- for (let cb of node.willPatch) {
2089
- cb.call(component);
2090
- }
2091
- }
2092
- }
2093
- current = undefined;
2094
- // Step 2: patching the dom
2095
- node._patch();
2096
- this.locked = false;
2097
- // Step 4: calling all mounted lifecycle hooks
2098
- let mountedFibers = this.mounted;
2099
- while ((current = mountedFibers.pop())) {
2100
- current = current;
2101
- if (current.appliedToDom) {
2102
- for (let cb of current.node.mounted) {
2103
- cb();
2104
- }
2105
- }
2106
- }
2107
- // Step 5: calling all patched hooks
2108
- let patchedFibers = this.patched;
2109
- while ((current = patchedFibers.pop())) {
2110
- current = current;
2111
- if (current.appliedToDom) {
2112
- for (let cb of current.node.patched) {
2113
- cb();
2114
- }
2115
- }
2116
- }
2117
- }
2118
- catch (e) {
2119
- this.locked = false;
2120
- handleError({ fiber: current || this, error: e });
2121
- }
2121
+ function validateTarget(target) {
2122
+ if (!(target instanceof HTMLElement)) {
2123
+ throw new Error("Cannot mount component: the target is not a valid DOM element");
2122
2124
  }
2123
- setCounter(newValue) {
2124
- this.counter = newValue;
2125
- if (newValue === 0) {
2126
- this.node.app.scheduler.flush();
2127
- }
2125
+ if (!document.body.contains(target)) {
2126
+ throw new Error("Cannot mount a component on a detached dom node");
2128
2127
  }
2129
2128
  }
2130
- class MountFiber extends RootFiber {
2131
- constructor(node, target, options = {}) {
2132
- super(node, null);
2133
- this.target = target;
2134
- this.position = options.position || "last-child";
2129
+ class EventBus extends EventTarget {
2130
+ trigger(name, payload) {
2131
+ this.dispatchEvent(new CustomEvent(name, { detail: payload }));
2135
2132
  }
2136
- complete() {
2137
- let current = this;
2138
- try {
2139
- const node = this.node;
2140
- node.children = this.childrenMap;
2141
- node.app.constructor.validateTarget(this.target);
2142
- if (node.bdom) {
2143
- // this is a complicated situation: if we mount a fiber with an existing
2144
- // bdom, this means that this same fiber was already completed, mounted,
2145
- // but a crash occurred in some mounted hook. Then, it was handled and
2146
- // the new rendering is being applied.
2147
- node.updateDom();
2148
- }
2149
- else {
2150
- node.bdom = this.bdom;
2151
- if (this.position === "last-child" || this.target.childNodes.length === 0) {
2152
- mount$1(node.bdom, this.target);
2153
- }
2154
- else {
2155
- const firstChild = this.target.childNodes[0];
2156
- mount$1(node.bdom, this.target, firstChild);
2157
- }
2158
- }
2159
- // unregistering the fiber before mounted since it can do another render
2160
- // and that the current rendering is obviously completed
2161
- node.fiber = null;
2162
- node.status = 1 /* MOUNTED */;
2163
- this.appliedToDom = true;
2164
- let mountedFibers = this.mounted;
2165
- while ((current = mountedFibers.pop())) {
2166
- if (current.appliedToDom) {
2167
- for (let cb of current.node.mounted) {
2168
- cb();
2169
- }
2170
- }
2171
- }
2133
+ }
2134
+ function whenReady(fn) {
2135
+ return new Promise(function (resolve) {
2136
+ if (document.readyState !== "loading") {
2137
+ resolve(true);
2172
2138
  }
2173
- catch (e) {
2174
- handleError({ fiber: current, error: e });
2139
+ else {
2140
+ document.addEventListener("DOMContentLoaded", resolve, false);
2175
2141
  }
2142
+ }).then(fn || function () { });
2143
+ }
2144
+ async function loadFile(url) {
2145
+ const result = await fetch(url);
2146
+ if (!result.ok) {
2147
+ throw new Error("Error while fetching xml templates");
2176
2148
  }
2149
+ return await result.text();
2150
+ }
2151
+ /*
2152
+ * This class just transports the fact that a string is safe
2153
+ * to be injected as HTML. Overriding a JS primitive is quite painful though
2154
+ * so we need to redfine toString and valueOf.
2155
+ */
2156
+ class Markup extends String {
2157
+ }
2158
+ /*
2159
+ * Marks a value as safe, that is, a value that can be injected as HTML directly.
2160
+ * It should be used to wrap the value passed to a t-out directive to allow a raw rendering.
2161
+ */
2162
+ function markup(value) {
2163
+ return new Markup(value);
2177
2164
  }
2178
2165
 
2179
2166
  let currentNode = null;
@@ -2190,13 +2177,11 @@ function useComponent() {
2190
2177
  * Apply default props (only top level).
2191
2178
  */
2192
2179
  function applyDefaultProps(props, defaultProps) {
2193
- const result = Object.assign({}, props);
2194
2180
  for (let propName in defaultProps) {
2195
2181
  if (props[propName] === undefined) {
2196
- result[propName] = defaultProps[propName];
2182
+ props[propName] = defaultProps[propName];
2197
2183
  }
2198
2184
  }
2199
- return result;
2200
2185
  }
2201
2186
  // -----------------------------------------------------------------------------
2202
2187
  // Integration with reactivity system (useState)
@@ -2223,61 +2208,6 @@ function useState(state) {
2223
2208
  }
2224
2209
  return reactive(state, render);
2225
2210
  }
2226
- function arePropsDifferent(props1, props2) {
2227
- for (let k in props1) {
2228
- const prop1 = props1[k] && typeof props1[k] === "object" ? toRaw(props1[k]) : props1[k];
2229
- const prop2 = props2[k] && typeof props2[k] === "object" ? toRaw(props2[k]) : props2[k];
2230
- if (prop1 !== prop2) {
2231
- return true;
2232
- }
2233
- }
2234
- return Object.keys(props1).length !== Object.keys(props2).length;
2235
- }
2236
- function component(name, props, key, ctx, parent) {
2237
- let node = ctx.children[key];
2238
- let isDynamic = typeof name !== "string";
2239
- if (node && node.status === 2 /* DESTROYED */) {
2240
- node = undefined;
2241
- }
2242
- if (isDynamic && node && node.component.constructor !== name) {
2243
- node = undefined;
2244
- }
2245
- const parentFiber = ctx.fiber;
2246
- if (node) {
2247
- let shouldRender = node.forceNextRender;
2248
- if (shouldRender) {
2249
- node.forceNextRender = false;
2250
- }
2251
- else {
2252
- const currentProps = node.props;
2253
- shouldRender = parentFiber.deep || arePropsDifferent(currentProps, props);
2254
- }
2255
- if (shouldRender) {
2256
- node.updateAndRender(props, parentFiber);
2257
- }
2258
- }
2259
- else {
2260
- // new component
2261
- let C;
2262
- if (isDynamic) {
2263
- C = name;
2264
- }
2265
- else {
2266
- C = parent.constructor.components[name];
2267
- if (!C) {
2268
- throw new Error(`Cannot find the definition of component "${name}"`);
2269
- }
2270
- else if (!(C.prototype instanceof Component)) {
2271
- throw new Error(`"${name}" is not a Component. It must inherit from the Component class`);
2272
- }
2273
- }
2274
- node = new ComponentNode(C, props, ctx.app, ctx, key);
2275
- ctx.children[key] = node;
2276
- node.initiateRender(new Fiber(node, parentFiber));
2277
- }
2278
- parentFiber.childrenMap[key] = node;
2279
- return node;
2280
- }
2281
2211
  class ComponentNode {
2282
2212
  constructor(C, props, app, parent, parentKey) {
2283
2213
  this.fiber = null;
@@ -2300,8 +2230,9 @@ class ComponentNode {
2300
2230
  this.parentKey = parentKey;
2301
2231
  this.level = parent ? parent.level + 1 : 0;
2302
2232
  const defaultProps = C.defaultProps;
2233
+ props = Object.assign({}, props);
2303
2234
  if (defaultProps) {
2304
- props = applyDefaultProps(props, defaultProps);
2235
+ applyDefaultProps(props, defaultProps);
2305
2236
  }
2306
2237
  const env = (parent && parent.childEnv) || app.env;
2307
2238
  this.childEnv = env;
@@ -2413,13 +2344,14 @@ class ComponentNode {
2413
2344
  }
2414
2345
  async updateAndRender(props, parentFiber) {
2415
2346
  const rawProps = props;
2347
+ props = Object.assign({}, props);
2416
2348
  // update
2417
2349
  const fiber = makeChildFiber(this, parentFiber);
2418
2350
  this.fiber = fiber;
2419
2351
  const component = this.component;
2420
2352
  const defaultProps = component.constructor.defaultProps;
2421
2353
  if (defaultProps) {
2422
- props = applyDefaultProps(props, defaultProps);
2354
+ applyDefaultProps(props, defaultProps);
2423
2355
  }
2424
2356
  currentNode = this;
2425
2357
  for (const key in props) {
@@ -2498,10 +2430,15 @@ class ComponentNode {
2498
2430
  }
2499
2431
  }
2500
2432
  _patch() {
2501
- const hasChildren = Object.keys(this.children).length > 0;
2502
- this.children = this.fiber.childrenMap;
2503
- this.bdom.patch(this.fiber.bdom, hasChildren);
2504
- this.fiber.appliedToDom = true;
2433
+ let hasChildren = false;
2434
+ for (let _k in this.children) {
2435
+ hasChildren = true;
2436
+ break;
2437
+ }
2438
+ const fiber = this.fiber;
2439
+ this.children = fiber.childrenMap;
2440
+ this.bdom.patch(fiber.bdom, hasChildren);
2441
+ fiber.appliedToDom = true;
2505
2442
  this.fiber = null;
2506
2443
  }
2507
2444
  beforeRemove() {
@@ -2629,6 +2566,19 @@ function onError(callback) {
2629
2566
  handlers.push(callback.bind(node.component));
2630
2567
  }
2631
2568
 
2569
+ class Component {
2570
+ constructor(props, env, node) {
2571
+ this.props = props;
2572
+ this.env = env;
2573
+ this.__owl__ = node;
2574
+ }
2575
+ setup() { }
2576
+ render(deep = false) {
2577
+ this.__owl__.render(deep === true);
2578
+ }
2579
+ }
2580
+ Component.template = "";
2581
+
2632
2582
  const VText = text("").constructor;
2633
2583
  class VPortal extends VText {
2634
2584
  constructor(selector, realBDom) {
@@ -2707,6 +2657,7 @@ Portal.props = {
2707
2657
  // -----------------------------------------------------------------------------
2708
2658
  const isUnionType = (t) => Array.isArray(t);
2709
2659
  const isBaseType = (t) => typeof t !== "object";
2660
+ const isValueType = (t) => typeof t === "object" && t && "value" in t;
2710
2661
  function isOptional(t) {
2711
2662
  return typeof t === "object" && "optional" in t ? t.optional || false : false;
2712
2663
  }
@@ -2720,6 +2671,9 @@ function describe(info) {
2720
2671
  else if (isUnionType(info)) {
2721
2672
  return info.map(describe).join(" or ");
2722
2673
  }
2674
+ else if (isValueType(info)) {
2675
+ return String(info.value);
2676
+ }
2723
2677
  if ("element" in info) {
2724
2678
  return `list of ${describe({ type: info.element, optional: false })}s`;
2725
2679
  }
@@ -2805,6 +2759,9 @@ function validateType(key, value, descr) {
2805
2759
  else if (isBaseType(descr)) {
2806
2760
  return validateBaseType(key, value, descr);
2807
2761
  }
2762
+ else if (isValueType(descr)) {
2763
+ return value === descr.value ? null : `'${key}' is not equal to '${descr.value}'`;
2764
+ }
2808
2765
  else if (isUnionType(descr)) {
2809
2766
  let validDescr = descr.find((p) => !validateType(key, value, p));
2810
2767
  return validDescr ? null : `'${key}' is not a ${describe(descr)}`;
@@ -2833,6 +2790,7 @@ function validateType(key, value, descr) {
2833
2790
  return result;
2834
2791
  }
2835
2792
 
2793
+ const ObjectCreate = Object.create;
2836
2794
  /**
2837
2795
  * This file contains utility functions that will be injected in each template,
2838
2796
  * to perform various useful tasks in the compiled code.
@@ -2844,7 +2802,7 @@ function callSlot(ctx, parent, key, name, dynamic, extra, defaultContent) {
2844
2802
  key = key + "__slot_" + name;
2845
2803
  const slots = ctx.props.slots || {};
2846
2804
  const { __render, __ctx, __scope } = slots[name] || {};
2847
- const slotScope = Object.create(__ctx || {});
2805
+ const slotScope = ObjectCreate(__ctx || {});
2848
2806
  if (__scope) {
2849
2807
  slotScope[__scope] = extra;
2850
2808
  }
@@ -2864,7 +2822,7 @@ function callSlot(ctx, parent, key, name, dynamic, extra, defaultContent) {
2864
2822
  }
2865
2823
  function capture(ctx) {
2866
2824
  const component = ctx.__owl__.component;
2867
- const result = Object.create(component);
2825
+ const result = ObjectCreate(component);
2868
2826
  for (let k in ctx) {
2869
2827
  result[k] = ctx[k];
2870
2828
  }
@@ -2969,17 +2927,19 @@ function safeOutput(value) {
2969
2927
  return toggler(safeKey, block);
2970
2928
  }
2971
2929
  let boundFunctions = new WeakMap();
2930
+ const WeakMapGet = WeakMap.prototype.get;
2931
+ const WeakMapSet = WeakMap.prototype.set;
2972
2932
  function bind(ctx, fn) {
2973
2933
  let component = ctx.__owl__.component;
2974
- let boundFnMap = boundFunctions.get(component);
2934
+ let boundFnMap = WeakMapGet.call(boundFunctions, component);
2975
2935
  if (!boundFnMap) {
2976
2936
  boundFnMap = new WeakMap();
2977
- boundFunctions.set(component, boundFnMap);
2937
+ WeakMapSet.call(boundFunctions, component, boundFnMap);
2978
2938
  }
2979
- let boundFn = boundFnMap.get(fn);
2939
+ let boundFn = WeakMapGet.call(boundFnMap, fn);
2980
2940
  if (!boundFn) {
2981
2941
  boundFn = fn.bind(component);
2982
- boundFnMap.set(fn, boundFn);
2942
+ WeakMapSet.call(boundFnMap, fn, boundFn);
2983
2943
  }
2984
2944
  return boundFn;
2985
2945
  }
@@ -3055,7 +3015,7 @@ const helpers = {
3055
3015
  markRaw,
3056
3016
  };
3057
3017
 
3058
- const bdom = { text, createBlock, list, multi, html, toggler, component, comment };
3018
+ const bdom = { text, createBlock, list, multi, html, toggler, comment };
3059
3019
  function parseXML$1(xml) {
3060
3020
  const parser = new DOMParser();
3061
3021
  const doc = parser.parseFromString(xml, "text/xml");
@@ -3651,9 +3611,7 @@ class CodeGenerator {
3651
3611
  tKeyExpr: null,
3652
3612
  });
3653
3613
  // define blocks and utility functions
3654
- let mainCode = [
3655
- ` let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;`,
3656
- ];
3614
+ let mainCode = [` let { text, createBlock, list, multi, html, toggler, comment } = bdom;`];
3657
3615
  if (this.helpers.size) {
3658
3616
  mainCode.push(`let { ${[...this.helpers].join(", ")} } = helpers;`);
3659
3617
  }
@@ -3669,6 +3627,7 @@ class CodeGenerator {
3669
3627
  for (let block of this.blocks) {
3670
3628
  if (block.dom) {
3671
3629
  let xmlString = block.asXmlString();
3630
+ xmlString = xmlString.replace(/`/g, "\\`");
3672
3631
  if (block.dynamicTagName) {
3673
3632
  xmlString = xmlString.replace(/^<\w+/, `<\${tag || '${block.dom.nodeName}'}`);
3674
3633
  xmlString = xmlString.replace(/\w+>$/, `\${tag || '${block.dom.nodeName}'}>`);
@@ -4506,8 +4465,12 @@ class CodeGenerator {
4506
4465
  if (ctx.tKeyExpr) {
4507
4466
  keyArg = `${ctx.tKeyExpr} + ${keyArg}`;
4508
4467
  }
4509
- const blockArgs = `${expr}, ${propString}, ${keyArg}, node, ctx`;
4510
- let blockExpr = `component(${blockArgs})`;
4468
+ let id = generateId("comp");
4469
+ this.staticDefs.push({
4470
+ id,
4471
+ expr: `app.createComponent(${ast.isDynamic ? null : expr}, ${!ast.isDynamic}, ${!!ast.slots}, ${!!ast.dynamicProps}, ${!ast.props && !ast.dynamicProps})`,
4472
+ });
4473
+ let blockExpr = `${id}(${propString}, ${keyArg}, node, ctx, ${ast.isDynamic ? expr : null})`;
4511
4474
  if (ast.isDynamic) {
4512
4475
  blockExpr = `toggler(${expr}, ${blockExpr})`;
4513
4476
  }
@@ -4592,10 +4555,15 @@ class CodeGenerator {
4592
4555
  if (this.target.loopLevel || !this.hasSafeContext) {
4593
4556
  ctxStr = generateId("ctx");
4594
4557
  this.helpers.add("capture");
4595
- this.define(ctxStr, `capture(ctx);`);
4558
+ this.define(ctxStr, `capture(ctx)`);
4596
4559
  }
4560
+ let id = generateId("comp");
4561
+ this.staticDefs.push({
4562
+ id,
4563
+ expr: `app.createComponent(null, false, true, false, false)`,
4564
+ });
4597
4565
  const target = compileExpr(ast.target);
4598
- const blockString = `component(Portal, {target: ${target},slots: {'default': {__render: ${name}, __ctx: ${ctxStr}}}}, key + \`${key}\`, node, ctx)`;
4566
+ const blockString = `${id}({target: ${target},slots: {'default': {__render: ${name}, __ctx: ${ctxStr}}}}, key + \`${key}\`, node, ctx, Portal)`;
4599
4567
  if (block) {
4600
4568
  this.insertAnchor(block);
4601
4569
  }
@@ -5538,6 +5506,56 @@ class App extends TemplateSet {
5538
5506
  this.root.destroy();
5539
5507
  }
5540
5508
  }
5509
+ createComponent(name, isStatic, hasSlotsProp, hasDynamicPropList, hasNoProp) {
5510
+ const isDynamic = !isStatic;
5511
+ function _arePropsDifferent(props1, props2) {
5512
+ for (let k in props1) {
5513
+ if (props1[k] !== props2[k]) {
5514
+ return true;
5515
+ }
5516
+ }
5517
+ return hasDynamicPropList && Object.keys(props1).length !== Object.keys(props2).length;
5518
+ }
5519
+ const arePropsDifferent = hasSlotsProp
5520
+ ? (_1, _2) => true
5521
+ : hasNoProp
5522
+ ? (_1, _2) => false
5523
+ : _arePropsDifferent;
5524
+ const updateAndRender = ComponentNode.prototype.updateAndRender;
5525
+ const initiateRender = ComponentNode.prototype.initiateRender;
5526
+ return (props, key, ctx, parent, C) => {
5527
+ let children = ctx.children;
5528
+ let node = children[key];
5529
+ if (node &&
5530
+ (node.status === 2 /* DESTROYED */ || (isDynamic && node.component.constructor !== C))) {
5531
+ node = undefined;
5532
+ }
5533
+ const parentFiber = ctx.fiber;
5534
+ if (node) {
5535
+ if (arePropsDifferent(node.props, props) || parentFiber.deep || node.forceNextRender) {
5536
+ node.forceNextRender = false;
5537
+ updateAndRender.call(node, props, parentFiber);
5538
+ }
5539
+ }
5540
+ else {
5541
+ // new component
5542
+ if (isStatic) {
5543
+ C = parent.constructor.components[name];
5544
+ if (!C) {
5545
+ throw new Error(`Cannot find the definition of component "${name}"`);
5546
+ }
5547
+ else if (!(C.prototype instanceof Component)) {
5548
+ throw new Error(`"${name}" is not a Component. It must inherit from the Component class`);
5549
+ }
5550
+ }
5551
+ node = new ComponentNode(C, props, this, ctx, key);
5552
+ children[key] = node;
5553
+ initiateRender.call(node, new Fiber(node, parentFiber));
5554
+ }
5555
+ parentFiber.childrenMap[key] = node;
5556
+ return node;
5557
+ };
5558
+ }
5541
5559
  }
5542
5560
  App.validateTarget = validateTarget;
5543
5561
  async function mount(C, target, config = {}) {
@@ -5718,7 +5736,7 @@ exports.whenReady = whenReady;
5718
5736
  exports.xml = xml;
5719
5737
 
5720
5738
 
5721
- __info__.version = '2.0.0-beta-9';
5722
- __info__.date = '2022-06-09T12:32:25.382Z';
5723
- __info__.hash = '83d4471';
5739
+ __info__.version = '2.0.0-beta-10';
5740
+ __info__.date = '2022-06-22T08:33:26.205Z';
5741
+ __info__.hash = '2e03332';
5724
5742
  __info__.url = 'https://github.com/odoo/owl';