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