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