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

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
@@ -170,6 +170,10 @@
170
170
  for (let key in expr) {
171
171
  const value = expr[key];
172
172
  if (value) {
173
+ key = trim.call(key);
174
+ if (!key) {
175
+ continue;
176
+ }
173
177
  const words = split.call(key, wordRegexp);
174
178
  for (let word of words) {
175
179
  result[word] = value;
@@ -1381,798 +1385,786 @@
1381
1385
  vnode.remove();
1382
1386
  }
1383
1387
 
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") {
1388
+ // Maps fibers to thrown errors
1389
+ const fibersInError = new WeakMap();
1390
+ const nodeErrorHandlers = new WeakMap();
1391
+ function _handleError(node, error) {
1392
+ if (!node) {
1414
1393
  return false;
1415
1394
  }
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());
1395
+ const fiber = node.fiber;
1396
+ if (fiber) {
1397
+ fibersInError.set(fiber, error);
1460
1398
  }
1461
- const keyToCallbacks = targetToKeysToCallbacks.get(target);
1462
- if (!keyToCallbacks.get(key)) {
1463
- keyToCallbacks.set(key, new Set());
1399
+ const errorHandlers = nodeErrorHandlers.get(node);
1400
+ if (errorHandlers) {
1401
+ let handled = false;
1402
+ // execute in the opposite order
1403
+ for (let i = errorHandlers.length - 1; i >= 0; i--) {
1404
+ try {
1405
+ errorHandlers[i](error);
1406
+ handled = true;
1407
+ break;
1408
+ }
1409
+ catch (e) {
1410
+ error = e;
1411
+ }
1412
+ }
1413
+ if (handled) {
1414
+ return true;
1415
+ }
1464
1416
  }
1465
- keyToCallbacks.get(key).add(callback);
1466
- if (!callbacksToTargets.has(callback)) {
1467
- callbacksToTargets.set(callback, new Set());
1417
+ return _handleError(node.parent, error);
1418
+ }
1419
+ function handleError(params) {
1420
+ const error = params.error;
1421
+ const node = "node" in params ? params.node : params.fiber.node;
1422
+ const fiber = "fiber" in params ? params.fiber : node.fiber;
1423
+ // resets the fibers on components if possible. This is important so that
1424
+ // new renderings can be properly included in the initial one, if any.
1425
+ let current = fiber;
1426
+ do {
1427
+ current.node.fiber = current;
1428
+ current = current.parent;
1429
+ } while (current);
1430
+ fibersInError.set(fiber.root, error);
1431
+ const handled = _handleError(node, error);
1432
+ if (!handled) {
1433
+ console.warn(`[Owl] Unhandled error. Destroying the root component`);
1434
+ try {
1435
+ node.app.destroy();
1436
+ }
1437
+ catch (e) {
1438
+ console.error(e);
1439
+ }
1468
1440
  }
1469
- callbacksToTargets.get(callback).add(target);
1441
+ }
1442
+
1443
+ function makeChildFiber(node, parent) {
1444
+ let current = node.fiber;
1445
+ if (current) {
1446
+ cancelFibers(current.children);
1447
+ current.root = null;
1448
+ }
1449
+ return new Fiber(node, parent);
1470
1450
  }
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;
1451
+ function makeRootFiber(node) {
1452
+ let current = node.fiber;
1453
+ if (current) {
1454
+ let root = current.root;
1455
+ // lock root fiber because canceling children fibers may destroy components,
1456
+ // which means any arbitrary code can be run in onWillDestroy, which may
1457
+ // trigger new renderings
1458
+ root.locked = true;
1459
+ root.setCounter(root.counter + 1 - cancelFibers(current.children));
1460
+ root.locked = false;
1461
+ current.children = [];
1462
+ current.childrenMap = {};
1463
+ current.bdom = null;
1464
+ if (fibersInError.has(current)) {
1465
+ fibersInError.delete(current);
1466
+ fibersInError.delete(root);
1467
+ current.appliedToDom = false;
1468
+ }
1469
+ return current;
1484
1470
  }
1485
- const callbacks = keyToCallbacks.get(key);
1486
- if (!callbacks) {
1487
- return;
1471
+ const fiber = new RootFiber(node, null);
1472
+ if (node.willPatch.length) {
1473
+ fiber.willPatch.push(fiber);
1488
1474
  }
1489
- // Loop on copy because clearReactivesForCallback will modify the set in place
1490
- for (const callback of [...callbacks]) {
1491
- clearReactivesForCallback(callback);
1492
- callback();
1475
+ if (node.patched.length) {
1476
+ fiber.patched.push(fiber);
1493
1477
  }
1478
+ return fiber;
1479
+ }
1480
+ function throwOnRender() {
1481
+ throw new Error("Attempted to render cancelled fiber");
1494
1482
  }
1495
- const callbacksToTargets = new WeakMap();
1496
1483
  /**
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
1484
+ * @returns number of not-yet rendered fibers cancelled
1500
1485
  */
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;
1486
+ function cancelFibers(fibers) {
1487
+ let result = 0;
1488
+ for (let fiber of fibers) {
1489
+ let node = fiber.node;
1490
+ fiber.render = throwOnRender;
1491
+ if (node.status === 0 /* NEW */) {
1492
+ node.destroy();
1493
+ delete node.parent.children[node.parentKey];
1510
1494
  }
1511
- for (const callbacks of observedKeys.values()) {
1512
- callbacks.delete(callback);
1495
+ node.fiber = null;
1496
+ if (fiber.bdom) {
1497
+ // if fiber has been rendered, this means that the component props have
1498
+ // been updated. however, this fiber will not be patched to the dom, so
1499
+ // it could happen that the next render compare the current props with
1500
+ // the same props, and skip the render completely. With the next line,
1501
+ // we kindly request the component code to force a render, so it works as
1502
+ // expected.
1503
+ node.forceNextRender = true;
1513
1504
  }
1505
+ else {
1506
+ result++;
1507
+ }
1508
+ result += cancelFibers(fiber.children);
1514
1509
  }
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
- });
1510
+ return result;
1526
1511
  }
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`);
1512
+ class Fiber {
1513
+ constructor(node, parent) {
1514
+ this.bdom = null;
1515
+ this.children = [];
1516
+ this.appliedToDom = false;
1517
+ this.deep = false;
1518
+ this.childrenMap = {};
1519
+ this.node = node;
1520
+ this.parent = parent;
1521
+ if (parent) {
1522
+ this.deep = parent.deep;
1523
+ const root = parent.root;
1524
+ root.setCounter(root.counter + 1);
1525
+ this.root = root;
1526
+ parent.children.push(this);
1527
+ }
1528
+ else {
1529
+ this.root = this;
1530
+ }
1558
1531
  }
1559
- if (SKIP in target) {
1560
- return target;
1532
+ render() {
1533
+ // if some parent has a fiber => register in followup
1534
+ let prev = this.root.node;
1535
+ let scheduler = prev.app.scheduler;
1536
+ let current = prev.parent;
1537
+ while (current) {
1538
+ if (current.fiber) {
1539
+ let root = current.fiber.root;
1540
+ if (root.counter === 0 && prev.parentKey in current.fiber.childrenMap) {
1541
+ current = root.node;
1542
+ }
1543
+ else {
1544
+ scheduler.delayedRenders.push(this);
1545
+ return;
1546
+ }
1547
+ }
1548
+ prev = current;
1549
+ current = current.parent;
1550
+ }
1551
+ // there are no current rendering from above => we can render
1552
+ this._render();
1561
1553
  }
1562
- const originalTarget = target[TARGET];
1563
- if (originalTarget) {
1564
- return reactive(originalTarget, callback);
1554
+ _render() {
1555
+ const node = this.node;
1556
+ const root = this.root;
1557
+ if (root) {
1558
+ try {
1559
+ this.bdom = true;
1560
+ this.bdom = node.renderFn();
1561
+ }
1562
+ catch (e) {
1563
+ handleError({ node, error: e });
1564
+ }
1565
+ root.setCounter(root.counter - 1);
1566
+ }
1565
1567
  }
1566
- if (!reactiveCache.has(target)) {
1567
- reactiveCache.set(target, new WeakMap());
1568
+ }
1569
+ class RootFiber extends Fiber {
1570
+ constructor() {
1571
+ super(...arguments);
1572
+ this.counter = 1;
1573
+ // only add stuff in this if they have registered some hooks
1574
+ this.willPatch = [];
1575
+ this.patched = [];
1576
+ this.mounted = [];
1577
+ // A fiber is typically locked when it is completing and the patch has not, or is being applied.
1578
+ // i.e.: render triggered in onWillUnmount or in willPatch will be delayed
1579
+ this.locked = false;
1568
1580
  }
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);
1581
+ complete() {
1582
+ const node = this.node;
1583
+ this.locked = true;
1584
+ let current = undefined;
1585
+ try {
1586
+ // Step 1: calling all willPatch lifecycle hooks
1587
+ for (current of this.willPatch) {
1588
+ // because of the asynchronous nature of the rendering, some parts of the
1589
+ // UI may have been rendered, then deleted in a followup rendering, and we
1590
+ // do not want to call onWillPatch in that case.
1591
+ let node = current.node;
1592
+ if (node.fiber === current) {
1593
+ const component = node.component;
1594
+ for (let cb of node.willPatch) {
1595
+ cb.call(component);
1596
+ }
1597
+ }
1598
+ }
1599
+ current = undefined;
1600
+ // Step 2: patching the dom
1601
+ node._patch();
1602
+ this.locked = false;
1603
+ // Step 4: calling all mounted lifecycle hooks
1604
+ let mountedFibers = this.mounted;
1605
+ while ((current = mountedFibers.pop())) {
1606
+ current = current;
1607
+ if (current.appliedToDom) {
1608
+ for (let cb of current.node.mounted) {
1609
+ cb();
1610
+ }
1611
+ }
1612
+ }
1613
+ // Step 5: calling all patched hooks
1614
+ let patchedFibers = this.patched;
1615
+ while ((current = patchedFibers.pop())) {
1616
+ current = current;
1617
+ if (current.appliedToDom) {
1618
+ for (let cb of current.node.patched) {
1619
+ cb();
1620
+ }
1621
+ }
1622
+ }
1623
+ }
1624
+ catch (e) {
1625
+ this.locked = false;
1626
+ handleError({ fiber: current || this, error: e });
1627
+ }
1628
+ }
1629
+ setCounter(newValue) {
1630
+ this.counter = newValue;
1631
+ if (newValue === 0) {
1632
+ this.node.app.scheduler.flush();
1633
+ }
1577
1634
  }
1578
- return reactivesForTarget.get(callback);
1579
1635
  }
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);
1636
+ class MountFiber extends RootFiber {
1637
+ constructor(node, target, options = {}) {
1638
+ super(node, null);
1639
+ this.target = target;
1640
+ this.position = options.position || "last-child";
1641
+ }
1642
+ complete() {
1643
+ let current = this;
1644
+ try {
1645
+ const node = this.node;
1646
+ node.children = this.childrenMap;
1647
+ node.app.constructor.validateTarget(this.target);
1648
+ if (node.bdom) {
1649
+ // this is a complicated situation: if we mount a fiber with an existing
1650
+ // bdom, this means that this same fiber was already completed, mounted,
1651
+ // but a crash occurred in some mounted hook. Then, it was handled and
1652
+ // the new rendering is being applied.
1653
+ node.updateDom();
1596
1654
  }
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);
1655
+ else {
1656
+ node.bdom = this.bdom;
1657
+ if (this.position === "last-child" || this.target.childNodes.length === 0) {
1658
+ mount$1(node.bdom, this.target);
1659
+ }
1660
+ else {
1661
+ const firstChild = this.target.childNodes[0];
1662
+ mount$1(node.bdom, this.target, firstChild);
1663
+ }
1606
1664
  }
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);
1665
+ // unregistering the fiber before mounted since it can do another render
1666
+ // and that the current rendering is obviously completed
1667
+ node.fiber = null;
1668
+ node.status = 1 /* MOUNTED */;
1669
+ this.appliedToDom = true;
1670
+ let mountedFibers = this.mounted;
1671
+ while ((current = mountedFibers.pop())) {
1672
+ if (current.appliedToDom) {
1673
+ for (let cb of current.node.mounted) {
1674
+ cb();
1675
+ }
1676
+ }
1612
1677
  }
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
- }
1678
+ }
1679
+ catch (e) {
1680
+ handleError({ fiber: current, error: e });
1681
+ }
1682
+ }
1683
+ }
1684
+
1685
+ // Allows to get the target of a Reactive (used for making a new Reactive from the underlying object)
1686
+ const TARGET = Symbol("Target");
1687
+ // Escape hatch to prevent reactivity system to turn something into a reactive
1688
+ const SKIP = Symbol("Skip");
1689
+ // Special key to subscribe to, to be notified of key creation/deletion
1690
+ const KEYCHANGES = Symbol("Key changes");
1691
+ const objectToString = Object.prototype.toString;
1692
+ const objectHasOwnProperty = Object.prototype.hasOwnProperty;
1693
+ const SUPPORTED_RAW_TYPES = new Set(["Object", "Array", "Set", "Map", "WeakMap"]);
1694
+ const COLLECTION_RAWTYPES = new Set(["Set", "Map", "WeakMap"]);
1635
1695
  /**
1636
- * Creates a function that will observe the key that is passed to it when called
1637
- * and delegates to the underlying method.
1696
+ * extract "RawType" from strings like "[object RawType]" => this lets us ignore
1697
+ * many native objects such as Promise (whose toString is [object Promise])
1698
+ * or Date ([object Date]), while also supporting collections without using
1699
+ * instanceof in a loop
1638
1700
  *
1639
- * @param methodName name of the method to delegate to
1640
- * @param target @see reactive
1641
- * @param callback @see reactive
1701
+ * @param obj the object to check
1702
+ * @returns the raw type of the object
1642
1703
  */
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
- };
1704
+ function rawType(obj) {
1705
+ return objectToString.call(obj).slice(8, -1);
1649
1706
  }
1650
1707
  /**
1651
- * Creates an iterable that will delegate to the underlying iteration method and
1652
- * observe keys as necessary.
1708
+ * Checks whether a given value can be made into a reactive object.
1653
1709
  *
1654
- * @param methodName name of the method to delegate to
1655
- * @param target @see reactive
1656
- * @param callback @see reactive
1710
+ * @param value the value to check
1711
+ * @returns whether the value can be made reactive
1657
1712
  */
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
- };
1713
+ function canBeMadeReactive(value) {
1714
+ if (typeof value !== "object") {
1715
+ return false;
1716
+ }
1717
+ return SUPPORTED_RAW_TYPES.has(rawType(value));
1668
1718
  }
1669
1719
  /**
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.
1720
+ * Creates a reactive from the given object/callback if possible and returns it,
1721
+ * returns the original object otherwise.
1673
1722
  *
1674
- * @param target @see reactive
1675
- * @param callback @see reactive
1723
+ * @param value the value make reactive
1724
+ * @returns a reactive for the given object when possible, the original otherwise
1676
1725
  */
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
- };
1726
+ function possiblyReactive(val, cb) {
1727
+ return canBeMadeReactive(val) ? reactive(val, cb) : val;
1685
1728
  }
1686
1729
  /**
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.
1730
+ * Mark an object or array so that it is ignored by the reactivity system
1690
1731
  *
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
1732
+ * @param value the value to mark
1733
+ * @returns the object itself
1695
1734
  */
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
- };
1735
+ function markRaw(value) {
1736
+ value[SKIP] = true;
1737
+ return value;
1711
1738
  }
1712
1739
  /**
1713
- * Creates a function that will clear the underlying collection and notify that
1714
- * the keys of the collection have changed.
1740
+ * Given a reactive objet, return the raw (non reactive) underlying object
1715
1741
  *
1716
- * @param target @see reactive
1742
+ * @param value a reactive value
1743
+ * @returns the underlying value
1717
1744
  */
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
- };
1745
+ function toRaw(value) {
1746
+ return value[TARGET] || value;
1727
1747
  }
1748
+ const targetToKeysToCallbacks = new WeakMap();
1728
1749
  /**
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)
1750
+ * Observes a given key on a target with an callback. The callback will be
1751
+ * called when the given key changes on the target.
1776
1752
  *
1777
- * @param callback @see reactive
1778
- * @param target @see reactive
1779
- * @returns a proxy handler object
1753
+ * @param target the target whose key should be observed
1754
+ * @param key the key to observe (or Symbol(KEYCHANGES) for key creation
1755
+ * or deletion)
1756
+ * @param callback the function to call when the key changes
1780
1757
  */
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
-
1758
+ function observeTargetKey(target, key, callback) {
1759
+ if (!targetToKeysToCallbacks.get(target)) {
1760
+ targetToKeysToCallbacks.set(target, new Map());
1761
+ }
1762
+ const keyToCallbacks = targetToKeysToCallbacks.get(target);
1763
+ if (!keyToCallbacks.get(key)) {
1764
+ keyToCallbacks.set(key, new Set());
1765
+ }
1766
+ keyToCallbacks.get(key).add(callback);
1767
+ if (!callbacksToTargets.has(callback)) {
1768
+ callbacksToTargets.set(callback, new Set());
1769
+ }
1770
+ callbacksToTargets.get(callback).add(target);
1771
+ }
1799
1772
  /**
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.
1773
+ * Notify Reactives that are observing a given target that a key has changed on
1774
+ * the target.
1802
1775
  *
1803
- * @param callback the callback to batch
1804
- * @returns a batched version of the original callback
1776
+ * @param target target whose Reactives should be notified that the target was
1777
+ * changed.
1778
+ * @param key the key that changed (or Symbol `KEYCHANGES` if a key was created
1779
+ * or deleted)
1805
1780
  */
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");
1781
+ function notifyReactives(target, key) {
1782
+ const keyToCallbacks = targetToKeysToCallbacks.get(target);
1783
+ if (!keyToCallbacks) {
1784
+ return;
1827
1785
  }
1828
- if (!document.body.contains(target)) {
1829
- throw new Error("Cannot mount a component on a detached dom node");
1786
+ const callbacks = keyToCallbacks.get(key);
1787
+ if (!callbacks) {
1788
+ return;
1830
1789
  }
1831
- }
1832
- class EventBus extends EventTarget {
1833
- trigger(name, payload) {
1834
- this.dispatchEvent(new CustomEvent(name, { detail: payload }));
1790
+ // Loop on copy because clearReactivesForCallback will modify the set in place
1791
+ for (const callback of [...callbacks]) {
1792
+ clearReactivesForCallback(callback);
1793
+ callback();
1835
1794
  }
1836
1795
  }
1837
- function whenReady(fn) {
1838
- return new Promise(function (resolve) {
1839
- if (document.readyState !== "loading") {
1840
- resolve(true);
1796
+ const callbacksToTargets = new WeakMap();
1797
+ /**
1798
+ * Clears all subscriptions of the Reactives associated with a given callback.
1799
+ *
1800
+ * @param callback the callback for which the reactives need to be cleared
1801
+ */
1802
+ function clearReactivesForCallback(callback) {
1803
+ const targetsToClear = callbacksToTargets.get(callback);
1804
+ if (!targetsToClear) {
1805
+ return;
1806
+ }
1807
+ for (const target of targetsToClear) {
1808
+ const observedKeys = targetToKeysToCallbacks.get(target);
1809
+ if (!observedKeys) {
1810
+ continue;
1841
1811
  }
1842
- else {
1843
- document.addEventListener("DOMContentLoaded", resolve, false);
1812
+ for (const callbacks of observedKeys.values()) {
1813
+ callbacks.delete(callback);
1844
1814
  }
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
1815
  }
1852
- return await result.text();
1816
+ targetsToClear.clear();
1853
1817
  }
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 {
1818
+ function getSubscriptions(callback) {
1819
+ const targets = callbacksToTargets.get(callback) || [];
1820
+ return [...targets].map((target) => {
1821
+ const keysToCallbacks = targetToKeysToCallbacks.get(target);
1822
+ return {
1823
+ target,
1824
+ keys: keysToCallbacks ? [...keysToCallbacks.keys()] : [],
1825
+ };
1826
+ });
1860
1827
  }
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.
1828
+ const reactiveCache = new WeakMap();
1829
+ /**
1830
+ * Creates a reactive proxy for an object. Reading data on the reactive object
1831
+ * subscribes to changes to the data. Writing data on the object will cause the
1832
+ * notify callback to be called if there are suscriptions to that data. Nested
1833
+ * objects and arrays are automatically made reactive as well.
1834
+ *
1835
+ * Whenever you are notified of a change, all subscriptions are cleared, and if
1836
+ * you would like to be notified of any further changes, you should go read
1837
+ * the underlying data again. We assume that if you don't go read it again after
1838
+ * being notified, it means that you are no longer interested in that data.
1839
+ *
1840
+ * Subscriptions:
1841
+ * + Reading a property on an object will subscribe you to changes in the value
1842
+ * of that property.
1843
+ * + Accessing an object keys (eg with Object.keys or with `for..in`) will
1844
+ * subscribe you to the creation/deletion of keys. Checking the presence of a
1845
+ * key on the object with 'in' has the same effect.
1846
+ * - getOwnPropertyDescriptor does not currently subscribe you to the property.
1847
+ * This is a choice that was made because changing a key's value will trigger
1848
+ * this trap and we do not want to subscribe by writes. This also means that
1849
+ * Object.hasOwnProperty doesn't subscribe as it goes through this trap.
1850
+ *
1851
+ * @param target the object for which to create a reactive proxy
1852
+ * @param callback the function to call when an observed property of the
1853
+ * reactive has changed
1854
+ * @returns a proxy that tracks changes to it
1864
1855
  */
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;
1856
+ function reactive(target, callback = () => { }) {
1857
+ if (!canBeMadeReactive(target)) {
1858
+ throw new Error(`Cannot make the given value reactive`);
1874
1859
  }
1875
- setup() { }
1876
- render(deep = false) {
1877
- this.__owl__.render(deep === true);
1860
+ if (SKIP in target) {
1861
+ return target;
1878
1862
  }
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;
1863
+ const originalTarget = target[TARGET];
1864
+ if (originalTarget) {
1865
+ return reactive(originalTarget, callback);
1888
1866
  }
1889
- const fiber = node.fiber;
1890
- if (fiber) {
1891
- fibersInError.set(fiber, error);
1867
+ if (!reactiveCache.has(target)) {
1868
+ reactiveCache.set(target, new WeakMap());
1892
1869
  }
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;
1902
- }
1903
- catch (e) {
1904
- error = e;
1905
- }
1906
- }
1907
- if (handled) {
1908
- return true;
1909
- }
1870
+ const reactivesForTarget = reactiveCache.get(target);
1871
+ if (!reactivesForTarget.has(callback)) {
1872
+ const targetRawType = rawType(target);
1873
+ const handler = COLLECTION_RAWTYPES.has(targetRawType)
1874
+ ? collectionsProxyHandler(target, callback, targetRawType)
1875
+ : basicProxyHandler(callback);
1876
+ const proxy = new Proxy(target, handler);
1877
+ reactivesForTarget.set(callback, proxy);
1910
1878
  }
1911
- return _handleError(node.parent, error);
1879
+ return reactivesForTarget.get(callback);
1912
1880
  }
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);
1881
+ /**
1882
+ * Creates a basic proxy handler for regular objects and arrays.
1883
+ *
1884
+ * @param callback @see reactive
1885
+ * @returns a proxy handler object
1886
+ */
1887
+ function basicProxyHandler(callback) {
1888
+ return {
1889
+ get(target, key, proxy) {
1890
+ if (key === TARGET) {
1891
+ return target;
1892
+ }
1893
+ // non-writable non-configurable properties cannot be made reactive
1894
+ const desc = Object.getOwnPropertyDescriptor(target, key);
1895
+ if (desc && !desc.writable && !desc.configurable) {
1896
+ return Reflect.get(target, key, proxy);
1897
+ }
1898
+ observeTargetKey(target, key, callback);
1899
+ return possiblyReactive(Reflect.get(target, key, proxy), callback);
1900
+ },
1901
+ set(target, key, value, proxy) {
1902
+ const isNewKey = !objectHasOwnProperty.call(target, key);
1903
+ const originalValue = Reflect.get(target, key, proxy);
1904
+ const ret = Reflect.set(target, key, value, proxy);
1905
+ if (isNewKey) {
1906
+ notifyReactives(target, KEYCHANGES);
1907
+ }
1908
+ // While Array length may trigger the set trap, it's not actually set by this
1909
+ // method but is updated behind the scenes, and the trap is not called with the
1910
+ // new value. We disable the "same-value-optimization" for it because of that.
1911
+ if (originalValue !== value || (Array.isArray(target) && key === "length")) {
1912
+ notifyReactives(target, key);
1913
+ }
1914
+ return ret;
1915
+ },
1916
+ deleteProperty(target, key) {
1917
+ const ret = Reflect.deleteProperty(target, key);
1918
+ // TODO: only notify when something was actually deleted
1919
+ notifyReactives(target, KEYCHANGES);
1920
+ notifyReactives(target, key);
1921
+ return ret;
1922
+ },
1923
+ ownKeys(target) {
1924
+ observeTargetKey(target, KEYCHANGES, callback);
1925
+ return Reflect.ownKeys(target);
1926
+ },
1927
+ has(target, key) {
1928
+ // TODO: this observes all key changes instead of only the presence of the argument key
1929
+ // observing the key itself would observe value changes instead of presence changes
1930
+ // so we may need a finer grained system to distinguish observing value vs presence.
1931
+ observeTargetKey(target, KEYCHANGES, callback);
1932
+ return Reflect.has(target, key);
1933
+ },
1934
+ };
1944
1935
  }
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;
1936
+ /**
1937
+ * Creates a function that will observe the key that is passed to it when called
1938
+ * and delegates to the underlying method.
1939
+ *
1940
+ * @param methodName name of the method to delegate to
1941
+ * @param target @see reactive
1942
+ * @param callback @see reactive
1943
+ */
1944
+ function makeKeyObserver(methodName, target, callback) {
1945
+ return (key) => {
1946
+ key = toRaw(key);
1947
+ observeTargetKey(target, key, callback);
1948
+ return possiblyReactive(target[methodName](key), callback);
1949
+ };
1950
+ }
1951
+ /**
1952
+ * Creates an iterable that will delegate to the underlying iteration method and
1953
+ * observe keys as necessary.
1954
+ *
1955
+ * @param methodName name of the method to delegate to
1956
+ * @param target @see reactive
1957
+ * @param callback @see reactive
1958
+ */
1959
+ function makeIteratorObserver(methodName, target, callback) {
1960
+ return function* () {
1961
+ observeTargetKey(target, KEYCHANGES, callback);
1962
+ const keys = target.keys();
1963
+ for (const item of target[methodName]()) {
1964
+ const key = keys.next().value;
1965
+ observeTargetKey(target, key, callback);
1966
+ yield possiblyReactive(item, callback);
1962
1967
  }
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;
1968
+ };
1973
1969
  }
1974
- function throwOnRender() {
1975
- throw new Error("Attempted to render cancelled fiber");
1970
+ /**
1971
+ * Creates a forEach function that will delegate to forEach on the underlying
1972
+ * collection while observing key changes, and keys as they're iterated over,
1973
+ * and making the passed keys/values reactive.
1974
+ *
1975
+ * @param target @see reactive
1976
+ * @param callback @see reactive
1977
+ */
1978
+ function makeForEachObserver(target, callback) {
1979
+ return function forEach(forEachCb, thisArg) {
1980
+ observeTargetKey(target, KEYCHANGES, callback);
1981
+ target.forEach(function (val, key, targetObj) {
1982
+ observeTargetKey(target, key, callback);
1983
+ forEachCb.call(thisArg, possiblyReactive(val, callback), possiblyReactive(key, callback), possiblyReactive(targetObj, callback));
1984
+ }, thisArg);
1985
+ };
1976
1986
  }
1977
1987
  /**
1978
- * @returns number of not-yet rendered fibers cancelled
1988
+ * Creates a function that will delegate to an underlying method, and check if
1989
+ * that method has modified the presence or value of a key, and notify the
1990
+ * reactives appropriately.
1991
+ *
1992
+ * @param setterName name of the method to delegate to
1993
+ * @param getterName name of the method which should be used to retrieve the
1994
+ * value before calling the delegate method for comparison purposes
1995
+ * @param target @see reactive
1979
1996
  */
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;
1997
+ function delegateAndNotify(setterName, getterName, target) {
1998
+ return (key, value) => {
1999
+ key = toRaw(key);
2000
+ const hadKey = target.has(key);
2001
+ const originalValue = target[getterName](key);
2002
+ const ret = target[setterName](key, value);
2003
+ const hasKey = target.has(key);
2004
+ if (hadKey !== hasKey) {
2005
+ notifyReactives(target, KEYCHANGES);
1997
2006
  }
1998
- else {
1999
- result++;
2007
+ if (originalValue !== value) {
2008
+ notifyReactives(target, key);
2000
2009
  }
2001
- result += cancelFibers(fiber.children);
2002
- }
2003
- return result;
2010
+ return ret;
2011
+ };
2004
2012
  }
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;
2013
+ /**
2014
+ * Creates a function that will clear the underlying collection and notify that
2015
+ * the keys of the collection have changed.
2016
+ *
2017
+ * @param target @see reactive
2018
+ */
2019
+ function makeClearNotifier(target) {
2020
+ return () => {
2021
+ const allKeys = [...target.keys()];
2022
+ target.clear();
2023
+ notifyReactives(target, KEYCHANGES);
2024
+ for (const key of allKeys) {
2025
+ notifyReactives(target, key);
2043
2026
  }
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();
2027
+ };
2028
+ }
2029
+ /**
2030
+ * Maps raw type of an object to an object containing functions that can be used
2031
+ * to build an appropritate proxy handler for that raw type. Eg: when making a
2032
+ * reactive set, calling the has method should mark the key that is being
2033
+ * retrieved as observed, and calling the add or delete method should notify the
2034
+ * reactives that the key which is being added or deleted has been modified.
2035
+ */
2036
+ const rawTypeToFuncHandlers = {
2037
+ Set: (target, callback) => ({
2038
+ has: makeKeyObserver("has", target, callback),
2039
+ add: delegateAndNotify("add", "has", target),
2040
+ delete: delegateAndNotify("delete", "has", target),
2041
+ keys: makeIteratorObserver("keys", target, callback),
2042
+ values: makeIteratorObserver("values", target, callback),
2043
+ entries: makeIteratorObserver("entries", target, callback),
2044
+ [Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
2045
+ forEach: makeForEachObserver(target, callback),
2046
+ clear: makeClearNotifier(target),
2047
+ get size() {
2048
+ observeTargetKey(target, KEYCHANGES, callback);
2049
+ return target.size;
2050
+ },
2051
+ }),
2052
+ Map: (target, callback) => ({
2053
+ has: makeKeyObserver("has", target, callback),
2054
+ get: makeKeyObserver("get", target, callback),
2055
+ set: delegateAndNotify("set", "get", target),
2056
+ delete: delegateAndNotify("delete", "has", target),
2057
+ keys: makeIteratorObserver("keys", target, callback),
2058
+ values: makeIteratorObserver("values", target, callback),
2059
+ entries: makeIteratorObserver("entries", target, callback),
2060
+ [Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
2061
+ forEach: makeForEachObserver(target, callback),
2062
+ clear: makeClearNotifier(target),
2063
+ get size() {
2064
+ observeTargetKey(target, KEYCHANGES, callback);
2065
+ return target.size;
2066
+ },
2067
+ }),
2068
+ WeakMap: (target, callback) => ({
2069
+ has: makeKeyObserver("has", target, callback),
2070
+ get: makeKeyObserver("get", target, callback),
2071
+ set: delegateAndNotify("set", "get", target),
2072
+ delete: delegateAndNotify("delete", "has", target),
2073
+ }),
2074
+ };
2075
+ /**
2076
+ * Creates a proxy handler for collections (Set/Map/WeakMap)
2077
+ *
2078
+ * @param callback @see reactive
2079
+ * @param target @see reactive
2080
+ * @returns a proxy handler object
2081
+ */
2082
+ function collectionsProxyHandler(target, callback, targetRawType) {
2083
+ // TODO: if performance is an issue we can create the special handlers lazily when each
2084
+ // property is read.
2085
+ const specialHandlers = rawTypeToFuncHandlers[targetRawType](target, callback);
2086
+ return Object.assign(basicProxyHandler(callback), {
2087
+ get(target, key) {
2088
+ if (key === TARGET) {
2089
+ return target;
2054
2090
  }
2055
- catch (e) {
2056
- handleError({ node, error: e });
2091
+ if (objectHasOwnProperty.call(specialHandlers, key)) {
2092
+ return specialHandlers[key];
2057
2093
  }
2058
- root.setCounter(root.counter - 1);
2094
+ observeTargetKey(target, key, callback);
2095
+ return possiblyReactive(target[key], callback);
2096
+ },
2097
+ });
2098
+ }
2099
+
2100
+ /**
2101
+ * Creates a batched version of a callback so that all calls to it in the same
2102
+ * microtick will only call the original callback once.
2103
+ *
2104
+ * @param callback the callback to batch
2105
+ * @returns a batched version of the original callback
2106
+ */
2107
+ function batched(callback) {
2108
+ let called = false;
2109
+ return async () => {
2110
+ // This await blocks all calls to the callback here, then releases them sequentially
2111
+ // in the next microtick. This line decides the granularity of the batch.
2112
+ await Promise.resolve();
2113
+ if (!called) {
2114
+ called = true;
2115
+ // wait for all calls in this microtick to fall through before resetting "called"
2116
+ // so that only the first call to the batched function calls the original callback.
2117
+ // Schedule this before calling the callback so that calls to the batched function
2118
+ // within the callback will proceed only after resetting called to false, and have
2119
+ // a chance to execute the callback again
2120
+ Promise.resolve().then(() => (called = false));
2121
+ callback();
2059
2122
  }
2060
- }
2123
+ };
2061
2124
  }
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
- }
2125
+ function validateTarget(target) {
2126
+ if (!(target instanceof HTMLElement)) {
2127
+ throw new Error("Cannot mount component: the target is not a valid DOM element");
2121
2128
  }
2122
- setCounter(newValue) {
2123
- this.counter = newValue;
2124
- if (newValue === 0) {
2125
- this.node.app.scheduler.flush();
2126
- }
2129
+ if (!document.body.contains(target)) {
2130
+ throw new Error("Cannot mount a component on a detached dom node");
2127
2131
  }
2128
2132
  }
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";
2133
+ class EventBus extends EventTarget {
2134
+ trigger(name, payload) {
2135
+ this.dispatchEvent(new CustomEvent(name, { detail: payload }));
2134
2136
  }
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
- }
2137
+ }
2138
+ function whenReady(fn) {
2139
+ return new Promise(function (resolve) {
2140
+ if (document.readyState !== "loading") {
2141
+ resolve(true);
2171
2142
  }
2172
- catch (e) {
2173
- handleError({ fiber: current, error: e });
2143
+ else {
2144
+ document.addEventListener("DOMContentLoaded", resolve, false);
2174
2145
  }
2146
+ }).then(fn || function () { });
2147
+ }
2148
+ async function loadFile(url) {
2149
+ const result = await fetch(url);
2150
+ if (!result.ok) {
2151
+ throw new Error("Error while fetching xml templates");
2175
2152
  }
2153
+ return await result.text();
2154
+ }
2155
+ /*
2156
+ * This class just transports the fact that a string is safe
2157
+ * to be injected as HTML. Overriding a JS primitive is quite painful though
2158
+ * so we need to redfine toString and valueOf.
2159
+ */
2160
+ class Markup extends String {
2161
+ }
2162
+ /*
2163
+ * Marks a value as safe, that is, a value that can be injected as HTML directly.
2164
+ * It should be used to wrap the value passed to a t-out directive to allow a raw rendering.
2165
+ */
2166
+ function markup(value) {
2167
+ return new Markup(value);
2176
2168
  }
2177
2169
 
2178
2170
  let currentNode = null;
@@ -2189,13 +2181,11 @@
2189
2181
  * Apply default props (only top level).
2190
2182
  */
2191
2183
  function applyDefaultProps(props, defaultProps) {
2192
- const result = Object.assign({}, props);
2193
2184
  for (let propName in defaultProps) {
2194
2185
  if (props[propName] === undefined) {
2195
- result[propName] = defaultProps[propName];
2186
+ props[propName] = defaultProps[propName];
2196
2187
  }
2197
2188
  }
2198
- return result;
2199
2189
  }
2200
2190
  // -----------------------------------------------------------------------------
2201
2191
  // Integration with reactivity system (useState)
@@ -2222,61 +2212,6 @@
2222
2212
  }
2223
2213
  return reactive(state, render);
2224
2214
  }
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
2215
  class ComponentNode {
2281
2216
  constructor(C, props, app, parent, parentKey) {
2282
2217
  this.fiber = null;
@@ -2299,8 +2234,9 @@
2299
2234
  this.parentKey = parentKey;
2300
2235
  this.level = parent ? parent.level + 1 : 0;
2301
2236
  const defaultProps = C.defaultProps;
2237
+ props = Object.assign({}, props);
2302
2238
  if (defaultProps) {
2303
- props = applyDefaultProps(props, defaultProps);
2239
+ applyDefaultProps(props, defaultProps);
2304
2240
  }
2305
2241
  const env = (parent && parent.childEnv) || app.env;
2306
2242
  this.childEnv = env;
@@ -2412,13 +2348,14 @@
2412
2348
  }
2413
2349
  async updateAndRender(props, parentFiber) {
2414
2350
  const rawProps = props;
2351
+ props = Object.assign({}, props);
2415
2352
  // update
2416
2353
  const fiber = makeChildFiber(this, parentFiber);
2417
2354
  this.fiber = fiber;
2418
2355
  const component = this.component;
2419
2356
  const defaultProps = component.constructor.defaultProps;
2420
2357
  if (defaultProps) {
2421
- props = applyDefaultProps(props, defaultProps);
2358
+ applyDefaultProps(props, defaultProps);
2422
2359
  }
2423
2360
  currentNode = this;
2424
2361
  for (const key in props) {
@@ -2497,10 +2434,15 @@
2497
2434
  }
2498
2435
  }
2499
2436
  _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;
2437
+ let hasChildren = false;
2438
+ for (let _k in this.children) {
2439
+ hasChildren = true;
2440
+ break;
2441
+ }
2442
+ const fiber = this.fiber;
2443
+ this.children = fiber.childrenMap;
2444
+ this.bdom.patch(fiber.bdom, hasChildren);
2445
+ fiber.appliedToDom = true;
2504
2446
  this.fiber = null;
2505
2447
  }
2506
2448
  beforeRemove() {
@@ -2628,6 +2570,19 @@
2628
2570
  handlers.push(callback.bind(node.component));
2629
2571
  }
2630
2572
 
2573
+ class Component {
2574
+ constructor(props, env, node) {
2575
+ this.props = props;
2576
+ this.env = env;
2577
+ this.__owl__ = node;
2578
+ }
2579
+ setup() { }
2580
+ render(deep = false) {
2581
+ this.__owl__.render(deep === true);
2582
+ }
2583
+ }
2584
+ Component.template = "";
2585
+
2631
2586
  const VText = text("").constructor;
2632
2587
  class VPortal extends VText {
2633
2588
  constructor(selector, realBDom) {
@@ -2706,6 +2661,7 @@
2706
2661
  // -----------------------------------------------------------------------------
2707
2662
  const isUnionType = (t) => Array.isArray(t);
2708
2663
  const isBaseType = (t) => typeof t !== "object";
2664
+ const isValueType = (t) => typeof t === "object" && t && "value" in t;
2709
2665
  function isOptional(t) {
2710
2666
  return typeof t === "object" && "optional" in t ? t.optional || false : false;
2711
2667
  }
@@ -2719,6 +2675,9 @@
2719
2675
  else if (isUnionType(info)) {
2720
2676
  return info.map(describe).join(" or ");
2721
2677
  }
2678
+ else if (isValueType(info)) {
2679
+ return String(info.value);
2680
+ }
2722
2681
  if ("element" in info) {
2723
2682
  return `list of ${describe({ type: info.element, optional: false })}s`;
2724
2683
  }
@@ -2804,6 +2763,9 @@
2804
2763
  else if (isBaseType(descr)) {
2805
2764
  return validateBaseType(key, value, descr);
2806
2765
  }
2766
+ else if (isValueType(descr)) {
2767
+ return value === descr.value ? null : `'${key}' is not equal to '${descr.value}'`;
2768
+ }
2807
2769
  else if (isUnionType(descr)) {
2808
2770
  let validDescr = descr.find((p) => !validateType(key, value, p));
2809
2771
  return validDescr ? null : `'${key}' is not a ${describe(descr)}`;
@@ -2832,6 +2794,7 @@
2832
2794
  return result;
2833
2795
  }
2834
2796
 
2797
+ const ObjectCreate = Object.create;
2835
2798
  /**
2836
2799
  * This file contains utility functions that will be injected in each template,
2837
2800
  * to perform various useful tasks in the compiled code.
@@ -2843,7 +2806,7 @@
2843
2806
  key = key + "__slot_" + name;
2844
2807
  const slots = ctx.props.slots || {};
2845
2808
  const { __render, __ctx, __scope } = slots[name] || {};
2846
- const slotScope = Object.create(__ctx || {});
2809
+ const slotScope = ObjectCreate(__ctx || {});
2847
2810
  if (__scope) {
2848
2811
  slotScope[__scope] = extra;
2849
2812
  }
@@ -2863,7 +2826,7 @@
2863
2826
  }
2864
2827
  function capture(ctx) {
2865
2828
  const component = ctx.__owl__.component;
2866
- const result = Object.create(component);
2829
+ const result = ObjectCreate(component);
2867
2830
  for (let k in ctx) {
2868
2831
  result[k] = ctx[k];
2869
2832
  }
@@ -2916,13 +2879,14 @@
2916
2879
  return true;
2917
2880
  }
2918
2881
  class LazyValue {
2919
- constructor(fn, ctx, node) {
2882
+ constructor(fn, ctx, component, node) {
2920
2883
  this.fn = fn;
2921
2884
  this.ctx = capture(ctx);
2885
+ this.component = component;
2922
2886
  this.node = node;
2923
2887
  }
2924
2888
  evaluate() {
2925
- return this.fn(this.ctx, this.node);
2889
+ return this.fn.call(this.component, this.ctx, this.node);
2926
2890
  }
2927
2891
  toString() {
2928
2892
  return this.evaluate().toString();
@@ -2931,9 +2895,9 @@
2931
2895
  /*
2932
2896
  * Safely outputs `value` as a block depending on the nature of `value`
2933
2897
  */
2934
- function safeOutput(value) {
2935
- if (!value) {
2936
- return value;
2898
+ function safeOutput(value, defaultValue) {
2899
+ if (value === undefined) {
2900
+ return defaultValue ? toggler("default", defaultValue) : toggler("undefined", text(""));
2937
2901
  }
2938
2902
  let safeKey;
2939
2903
  let block;
@@ -2968,17 +2932,19 @@
2968
2932
  return toggler(safeKey, block);
2969
2933
  }
2970
2934
  let boundFunctions = new WeakMap();
2935
+ const WeakMapGet = WeakMap.prototype.get;
2936
+ const WeakMapSet = WeakMap.prototype.set;
2971
2937
  function bind(ctx, fn) {
2972
2938
  let component = ctx.__owl__.component;
2973
- let boundFnMap = boundFunctions.get(component);
2939
+ let boundFnMap = WeakMapGet.call(boundFunctions, component);
2974
2940
  if (!boundFnMap) {
2975
2941
  boundFnMap = new WeakMap();
2976
- boundFunctions.set(component, boundFnMap);
2942
+ WeakMapSet.call(boundFunctions, component, boundFnMap);
2977
2943
  }
2978
- let boundFn = boundFnMap.get(fn);
2944
+ let boundFn = WeakMapGet.call(boundFnMap, fn);
2979
2945
  if (!boundFn) {
2980
2946
  boundFn = fn.bind(component);
2981
- boundFnMap.set(fn, boundFn);
2947
+ WeakMapSet.call(boundFnMap, fn, boundFn);
2982
2948
  }
2983
2949
  return boundFn;
2984
2950
  }
@@ -3054,7 +3020,7 @@
3054
3020
  markRaw,
3055
3021
  };
3056
3022
 
3057
- const bdom = { text, createBlock, list, multi, html, toggler, component, comment };
3023
+ const bdom = { text, createBlock, list, multi, html, toggler, comment };
3058
3024
  function parseXML$1(xml) {
3059
3025
  const parser = new DOMParser();
3060
3026
  const doc = parser.parseFromString(xml, "text/xml");
@@ -3217,7 +3183,7 @@
3217
3183
  });
3218
3184
  // note that the space after typeof is relevant. It makes sure that the formatted
3219
3185
  // expression has a space after typeof. Currently we don't support delete and void
3220
- const OPERATORS = "...,.,===,==,+,!==,!=,!,||,&&,>=,>,<=,<,?,-,*,/,%,typeof ,=>,=,;,in ,new ".split(",");
3186
+ const OPERATORS = "...,.,===,==,+,!==,!=,!,||,&&,>=,>,<=,<,?,-,*,/,%,typeof ,=>,=,;,in ,new ,|,&,^,~".split(",");
3221
3187
  let tokenizeString = function (expr) {
3222
3188
  let s = expr[0];
3223
3189
  let start = s;
@@ -3650,9 +3616,7 @@
3650
3616
  tKeyExpr: null,
3651
3617
  });
3652
3618
  // define blocks and utility functions
3653
- let mainCode = [
3654
- ` let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;`,
3655
- ];
3619
+ let mainCode = [` let { text, createBlock, list, multi, html, toggler, comment } = bdom;`];
3656
3620
  if (this.helpers.size) {
3657
3621
  mainCode.push(`let { ${[...this.helpers].join(", ")} } = helpers;`);
3658
3622
  }
@@ -3668,6 +3632,7 @@
3668
3632
  for (let block of this.blocks) {
3669
3633
  if (block.dom) {
3670
3634
  let xmlString = block.asXmlString();
3635
+ xmlString = xmlString.replace(/`/g, "\\`");
3671
3636
  if (block.dynamicTagName) {
3672
3637
  xmlString = xmlString.replace(/^<\w+/, `<\${tag || '${block.dom.nodeName}'}`);
3673
3638
  xmlString = xmlString.replace(/\w+>$/, `\${tag || '${block.dom.nodeName}'}>`);
@@ -3712,8 +3677,8 @@
3712
3677
  define(varName, expr) {
3713
3678
  this.addLine(`const ${varName} = ${expr};`);
3714
3679
  }
3715
- insertAnchor(block) {
3716
- const tag = `block-child-${block.children.length}`;
3680
+ insertAnchor(block, index = block.children.length) {
3681
+ const tag = `block-child-${index}`;
3717
3682
  const anchor = xmlDoc.createElement(tag);
3718
3683
  block.insert(anchor);
3719
3684
  }
@@ -4105,20 +4070,37 @@
4105
4070
  this.insertAnchor(block);
4106
4071
  }
4107
4072
  block = this.createBlock(block, "html", ctx);
4108
- this.helpers.add(ast.expr === "0" ? "zero" : "safeOutput");
4109
- let expr = ast.expr === "0" ? "ctx[zero]" : `safeOutput(${compileExpr(ast.expr)})`;
4110
- if (ast.body) {
4111
- const nextId = BlockDescription.nextBlockId;
4073
+ let blockStr;
4074
+ if (ast.expr === "0") {
4075
+ this.helpers.add("zero");
4076
+ blockStr = `ctx[zero]`;
4077
+ }
4078
+ else if (ast.body) {
4079
+ let bodyValue = null;
4080
+ bodyValue = BlockDescription.nextBlockId;
4112
4081
  const subCtx = createContext(ctx);
4113
4082
  this.compileAST({ type: 3 /* Multi */, content: ast.body }, subCtx);
4114
- this.helpers.add("withDefault");
4115
- expr = `withDefault(${expr}, b${nextId})`;
4083
+ this.helpers.add("safeOutput");
4084
+ blockStr = `safeOutput(${compileExpr(ast.expr)}, b${bodyValue})`;
4085
+ }
4086
+ else {
4087
+ this.helpers.add("safeOutput");
4088
+ blockStr = `safeOutput(${compileExpr(ast.expr)})`;
4089
+ }
4090
+ this.insertBlock(blockStr, block, ctx);
4091
+ }
4092
+ compileTIfBranch(content, block, ctx) {
4093
+ this.target.indentLevel++;
4094
+ let childN = block.children.length;
4095
+ this.compileAST(content, createContext(ctx, { block, index: ctx.index }));
4096
+ if (block.children.length > childN) {
4097
+ // we have some content => need to insert an anchor at correct index
4098
+ this.insertAnchor(block, childN);
4116
4099
  }
4117
- this.insertBlock(`${expr}`, block, ctx);
4100
+ this.target.indentLevel--;
4118
4101
  }
4119
4102
  compileTIf(ast, ctx, nextNode) {
4120
- let { block, forceNewBlock, index } = ctx;
4121
- let currentIndex = index;
4103
+ let { block, forceNewBlock } = ctx;
4122
4104
  const codeIdx = this.target.code.length;
4123
4105
  const isNewBlock = !block || (block.type !== "multi" && forceNewBlock);
4124
4106
  if (block) {
@@ -4128,28 +4110,16 @@
4128
4110
  block = this.createBlock(block, "multi", ctx);
4129
4111
  }
4130
4112
  this.addLine(`if (${compileExpr(ast.condition)}) {`);
4131
- this.target.indentLevel++;
4132
- this.insertAnchor(block);
4133
- const subCtx = createContext(ctx, { block, index: currentIndex });
4134
- this.compileAST(ast.content, subCtx);
4135
- this.target.indentLevel--;
4113
+ this.compileTIfBranch(ast.content, block, ctx);
4136
4114
  if (ast.tElif) {
4137
4115
  for (let clause of ast.tElif) {
4138
4116
  this.addLine(`} else if (${compileExpr(clause.condition)}) {`);
4139
- this.target.indentLevel++;
4140
- this.insertAnchor(block);
4141
- const subCtx = createContext(ctx, { block, index: currentIndex });
4142
- this.compileAST(clause.content, subCtx);
4143
- this.target.indentLevel--;
4117
+ this.compileTIfBranch(clause.content, block, ctx);
4144
4118
  }
4145
4119
  }
4146
4120
  if (ast.tElse) {
4147
4121
  this.addLine(`} else {`);
4148
- this.target.indentLevel++;
4149
- this.insertAnchor(block);
4150
- const subCtx = createContext(ctx, { block, index: currentIndex });
4151
- this.compileAST(ast.tElse, subCtx);
4152
- this.target.indentLevel--;
4122
+ this.compileTIfBranch(ast.tElse, block, ctx);
4153
4123
  }
4154
4124
  this.addLine("}");
4155
4125
  if (isNewBlock) {
@@ -4307,16 +4277,21 @@
4307
4277
  }
4308
4278
  compileTCall(ast, ctx) {
4309
4279
  let { block, forceNewBlock } = ctx;
4280
+ let ctxVar = ctx.ctxVar || "ctx";
4281
+ if (ast.context) {
4282
+ ctxVar = generateId("ctx");
4283
+ this.addLine(`let ${ctxVar} = ${compileExpr(ast.context)};`);
4284
+ }
4310
4285
  if (ast.body) {
4311
- this.addLine(`ctx = Object.create(ctx);`);
4312
- this.addLine(`ctx[isBoundary] = 1;`);
4286
+ this.addLine(`${ctxVar} = Object.create(${ctxVar});`);
4287
+ this.addLine(`${ctxVar}[isBoundary] = 1;`);
4313
4288
  this.helpers.add("isBoundary");
4314
4289
  const nextId = BlockDescription.nextBlockId;
4315
- const subCtx = createContext(ctx, { preventRoot: true });
4290
+ const subCtx = createContext(ctx, { preventRoot: true, ctxVar });
4316
4291
  this.compileAST({ type: 3 /* Multi */, content: ast.body }, subCtx);
4317
4292
  if (nextId !== BlockDescription.nextBlockId) {
4318
4293
  this.helpers.add("zero");
4319
- this.addLine(`ctx[zero] = b${nextId};`);
4294
+ this.addLine(`${ctxVar}[zero] = b${nextId};`);
4320
4295
  }
4321
4296
  }
4322
4297
  const isDynamic = INTERP_REGEXP.test(ast.name);
@@ -4334,7 +4309,7 @@
4334
4309
  }
4335
4310
  this.define(templateVar, subTemplate);
4336
4311
  block = this.createBlock(block, "multi", ctx);
4337
- this.insertBlock(`call(this, ${templateVar}, ctx, node, ${key})`, block, {
4312
+ this.insertBlock(`call(this, ${templateVar}, ${ctxVar}, node, ${key})`, block, {
4338
4313
  ...ctx,
4339
4314
  forceNewBlock: !block,
4340
4315
  });
@@ -4343,13 +4318,13 @@
4343
4318
  const id = generateId(`callTemplate_`);
4344
4319
  this.staticDefs.push({ id, expr: `app.getTemplate(${subTemplate})` });
4345
4320
  block = this.createBlock(block, "multi", ctx);
4346
- this.insertBlock(`${id}.call(this, ctx, node, ${key})`, block, {
4321
+ this.insertBlock(`${id}.call(this, ${ctxVar}, node, ${key})`, block, {
4347
4322
  ...ctx,
4348
4323
  forceNewBlock: !block,
4349
4324
  });
4350
4325
  }
4351
4326
  if (ast.body && !ctx.isLast) {
4352
- this.addLine(`ctx = ctx.__proto__;`);
4327
+ this.addLine(`${ctxVar} = ${ctxVar}.__proto__;`);
4353
4328
  }
4354
4329
  }
4355
4330
  compileTCallBlock(ast, ctx) {
@@ -4370,7 +4345,7 @@
4370
4345
  this.helpers.add("LazyValue");
4371
4346
  const bodyAst = { type: 3 /* Multi */, content: ast.body };
4372
4347
  const name = this.compileInNewTarget("value", bodyAst, ctx);
4373
- let value = `new LazyValue(${name}, ctx, node)`;
4348
+ let value = `new LazyValue(${name}, ctx, this, node)`;
4374
4349
  value = ast.value ? (value ? `withDefault(${expr}, ${value})` : expr) : value;
4375
4350
  this.addLine(`ctx[\`${ast.name}\`] = ${value};`);
4376
4351
  }
@@ -4388,7 +4363,7 @@
4388
4363
  value = expr;
4389
4364
  }
4390
4365
  this.helpers.add("setContextValue");
4391
- this.addLine(`setContextValue(ctx, "${ast.name}", ${value});`);
4366
+ this.addLine(`setContextValue(${ctx.ctxVar || "ctx"}, "${ast.name}", ${value});`);
4392
4367
  }
4393
4368
  }
4394
4369
  generateComponentKey() {
@@ -4505,8 +4480,12 @@
4505
4480
  if (ctx.tKeyExpr) {
4506
4481
  keyArg = `${ctx.tKeyExpr} + ${keyArg}`;
4507
4482
  }
4508
- const blockArgs = `${expr}, ${propString}, ${keyArg}, node, ctx`;
4509
- let blockExpr = `component(${blockArgs})`;
4483
+ let id = generateId("comp");
4484
+ this.staticDefs.push({
4485
+ id,
4486
+ expr: `app.createComponent(${ast.isDynamic ? null : expr}, ${!ast.isDynamic}, ${!!ast.slots}, ${!!ast.dynamicProps}, ${!ast.props && !ast.dynamicProps})`,
4487
+ });
4488
+ let blockExpr = `${id}(${propString}, ${keyArg}, node, this, ${ast.isDynamic ? expr : null})`;
4510
4489
  if (ast.isDynamic) {
4511
4490
  blockExpr = `toggler(${expr}, ${blockExpr})`;
4512
4491
  }
@@ -4591,10 +4570,15 @@
4591
4570
  if (this.target.loopLevel || !this.hasSafeContext) {
4592
4571
  ctxStr = generateId("ctx");
4593
4572
  this.helpers.add("capture");
4594
- this.define(ctxStr, `capture(ctx);`);
4573
+ this.define(ctxStr, `capture(ctx)`);
4595
4574
  }
4575
+ let id = generateId("comp");
4576
+ this.staticDefs.push({
4577
+ id,
4578
+ expr: `app.createComponent(null, false, true, false, false)`,
4579
+ });
4596
4580
  const target = compileExpr(ast.target);
4597
- const blockString = `component(Portal, {target: ${target},slots: {'default': {__render: ${name}, __ctx: ${ctxStr}}}}, key + \`${key}\`, node, ctx)`;
4581
+ const blockString = `${id}({target: ${target},slots: {'default': {__render: ${name}, __ctx: ${ctxStr}}}}, key + \`${key}\`, node, ctx, Portal)`;
4598
4582
  if (block) {
4599
4583
  this.insertAnchor(block);
4600
4584
  }
@@ -4934,10 +4918,12 @@
4934
4918
  return null;
4935
4919
  }
4936
4920
  const subTemplate = node.getAttribute("t-call");
4921
+ const context = node.getAttribute("t-call-context");
4937
4922
  node.removeAttribute("t-call");
4923
+ node.removeAttribute("t-call-context");
4938
4924
  if (node.tagName !== "t") {
4939
4925
  const ast = parseNode(node, ctx);
4940
- const tcall = { type: 7 /* TCall */, name: subTemplate, body: null };
4926
+ const tcall = { type: 7 /* TCall */, name: subTemplate, body: null, context };
4941
4927
  if (ast && ast.type === 2 /* DomNode */) {
4942
4928
  ast.content = [tcall];
4943
4929
  return ast;
@@ -4954,6 +4940,7 @@
4954
4940
  type: 7 /* TCall */,
4955
4941
  name: subTemplate,
4956
4942
  body: body.length ? body : null,
4943
+ context,
4957
4944
  };
4958
4945
  }
4959
4946
  // -----------------------------------------------------------------------------
@@ -5537,6 +5524,55 @@ See https://github.com/odoo/owl/blob/${hash}/doc/reference/app.md#configuration
5537
5524
  this.root.destroy();
5538
5525
  }
5539
5526
  }
5527
+ createComponent(name, isStatic, hasSlotsProp, hasDynamicPropList, hasNoProp) {
5528
+ const isDynamic = !isStatic;
5529
+ function _arePropsDifferent(props1, props2) {
5530
+ for (let k in props1) {
5531
+ if (props1[k] !== props2[k]) {
5532
+ return true;
5533
+ }
5534
+ }
5535
+ return hasDynamicPropList && Object.keys(props1).length !== Object.keys(props2).length;
5536
+ }
5537
+ const arePropsDifferent = hasSlotsProp
5538
+ ? (_1, _2) => true
5539
+ : hasNoProp
5540
+ ? (_1, _2) => false
5541
+ : _arePropsDifferent;
5542
+ const updateAndRender = ComponentNode.prototype.updateAndRender;
5543
+ const initiateRender = ComponentNode.prototype.initiateRender;
5544
+ return (props, key, ctx, parent, C) => {
5545
+ let children = ctx.children;
5546
+ let node = children[key];
5547
+ if (isDynamic && node && node.component.constructor !== C) {
5548
+ node = undefined;
5549
+ }
5550
+ const parentFiber = ctx.fiber;
5551
+ if (node) {
5552
+ if (arePropsDifferent(node.props, props) || parentFiber.deep || node.forceNextRender) {
5553
+ node.forceNextRender = false;
5554
+ updateAndRender.call(node, props, parentFiber);
5555
+ }
5556
+ }
5557
+ else {
5558
+ // new component
5559
+ if (isStatic) {
5560
+ C = parent.constructor.components[name];
5561
+ if (!C) {
5562
+ throw new Error(`Cannot find the definition of component "${name}"`);
5563
+ }
5564
+ else if (!(C.prototype instanceof Component)) {
5565
+ throw new Error(`"${name}" is not a Component. It must inherit from the Component class`);
5566
+ }
5567
+ }
5568
+ node = new ComponentNode(C, props, this, ctx, key);
5569
+ children[key] = node;
5570
+ initiateRender.call(node, new Fiber(node, parentFiber));
5571
+ }
5572
+ parentFiber.childrenMap[key] = node;
5573
+ return node;
5574
+ };
5575
+ }
5540
5576
  }
5541
5577
  App.validateTarget = validateTarget;
5542
5578
  async function mount(C, target, config = {}) {
@@ -5719,9 +5755,9 @@ See https://github.com/odoo/owl/blob/${hash}/doc/reference/app.md#configuration
5719
5755
  Object.defineProperty(exports, '__esModule', { value: true });
5720
5756
 
5721
5757
 
5722
- __info__.version = '2.0.0-beta-9';
5723
- __info__.date = '2022-06-09T12:32:25.382Z';
5724
- __info__.hash = '83d4471';
5758
+ __info__.version = '2.0.0-beta-12';
5759
+ __info__.date = '2022-06-29T09:13:12.680Z';
5760
+ __info__.hash = '6c72e0a';
5725
5761
  __info__.url = 'https://github.com/odoo/owl';
5726
5762
 
5727
5763