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