@odoo/owl 2.0.0-beta-8 → 2.0.0-beta-11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/owl.cjs.js CHANGED
@@ -171,6 +171,10 @@ function toClassObj(expr) {
171
171
  for (let key in expr) {
172
172
  const value = expr[key];
173
173
  if (value) {
174
+ key = trim.call(key);
175
+ if (!key) {
176
+ continue;
177
+ }
174
178
  const words = split.call(key, wordRegexp);
175
179
  for (let word of words) {
176
180
  result[word] = value;
@@ -1291,29 +1295,35 @@ function html(str) {
1291
1295
  }
1292
1296
 
1293
1297
  function createCatcher(eventsSpec) {
1294
- let setupFns = [];
1295
- let removeFns = [];
1296
- for (let name in eventsSpec) {
1297
- let index = eventsSpec[name];
1298
- let { setup, remove } = createEventHandler(name);
1299
- setupFns[index] = setup;
1300
- removeFns[index] = remove;
1301
- }
1302
- let n = setupFns.length;
1298
+ const n = Object.keys(eventsSpec).length;
1303
1299
  class VCatcher {
1304
1300
  constructor(child, handlers) {
1301
+ this.handlerFns = [];
1305
1302
  this.afterNode = null;
1306
1303
  this.child = child;
1307
- this.handlers = handlers;
1304
+ this.handlerData = handlers;
1308
1305
  }
1309
1306
  mount(parent, afterNode) {
1310
1307
  this.parentEl = parent;
1311
- this.afterNode = afterNode;
1312
1308
  this.child.mount(parent, afterNode);
1309
+ this.afterNode = document.createTextNode("");
1310
+ parent.insertBefore(this.afterNode, afterNode);
1311
+ this.wrapHandlerData();
1312
+ for (let name in eventsSpec) {
1313
+ const index = eventsSpec[name];
1314
+ const handler = createEventHandler(name);
1315
+ this.handlerFns[index] = handler;
1316
+ handler.setup.call(parent, this.handlerData[index]);
1317
+ }
1318
+ }
1319
+ wrapHandlerData() {
1313
1320
  for (let i = 0; i < n; i++) {
1314
- let origFn = this.handlers[i][0];
1321
+ let handler = this.handlerData[i];
1322
+ // handler = [...mods, fn, comp], so we need to replace second to last elem
1323
+ let idx = handler.length - 2;
1324
+ let origFn = handler[idx];
1315
1325
  const self = this;
1316
- this.handlers[i][0] = function (ev) {
1326
+ handler[idx] = function (ev) {
1317
1327
  const target = ev.target;
1318
1328
  let currentNode = self.child.firstNode();
1319
1329
  const afterNode = self.afterNode;
@@ -1324,18 +1334,21 @@ function createCatcher(eventsSpec) {
1324
1334
  currentNode = currentNode.nextSibling;
1325
1335
  }
1326
1336
  };
1327
- setupFns[i].call(parent, this.handlers[i]);
1328
1337
  }
1329
1338
  }
1330
1339
  moveBefore(other, afterNode) {
1331
- this.afterNode = null;
1332
1340
  this.child.moveBefore(other ? other.child : null, afterNode);
1341
+ this.parentEl.insertBefore(this.afterNode, afterNode);
1333
1342
  }
1334
1343
  patch(other, withBeforeRemove) {
1335
1344
  if (this === other) {
1336
1345
  return;
1337
1346
  }
1338
- this.handlers = other.handlers;
1347
+ this.handlerData = other.handlerData;
1348
+ this.wrapHandlerData();
1349
+ for (let i = 0; i < n; i++) {
1350
+ this.handlerFns[i].update.call(this.parentEl, this.handlerData[i]);
1351
+ }
1339
1352
  this.child.patch(other.child, withBeforeRemove);
1340
1353
  }
1341
1354
  beforeRemove() {
@@ -1343,9 +1356,10 @@ function createCatcher(eventsSpec) {
1343
1356
  }
1344
1357
  remove() {
1345
1358
  for (let i = 0; i < n; i++) {
1346
- removeFns[i].call(this.parentEl);
1359
+ this.handlerFns[i].remove.call(this.parentEl);
1347
1360
  }
1348
1361
  this.child.remove();
1362
+ this.afterNode.remove();
1349
1363
  }
1350
1364
  firstNode() {
1351
1365
  return this.child.firstNode();
@@ -1372,798 +1386,786 @@ function remove(vnode, withBeforeRemove = false) {
1372
1386
  vnode.remove();
1373
1387
  }
1374
1388
 
1375
- // Allows to get the target of a Reactive (used for making a new Reactive from the underlying object)
1376
- const TARGET = Symbol("Target");
1377
- // Escape hatch to prevent reactivity system to turn something into a reactive
1378
- const SKIP = Symbol("Skip");
1379
- // Special key to subscribe to, to be notified of key creation/deletion
1380
- const KEYCHANGES = Symbol("Key changes");
1381
- const objectToString = Object.prototype.toString;
1382
- const objectHasOwnProperty = Object.prototype.hasOwnProperty;
1383
- const SUPPORTED_RAW_TYPES = new Set(["Object", "Array", "Set", "Map", "WeakMap"]);
1384
- const COLLECTION_RAWTYPES = new Set(["Set", "Map", "WeakMap"]);
1385
- /**
1386
- * extract "RawType" from strings like "[object RawType]" => this lets us ignore
1387
- * many native objects such as Promise (whose toString is [object Promise])
1388
- * or Date ([object Date]), while also supporting collections without using
1389
- * instanceof in a loop
1390
- *
1391
- * @param obj the object to check
1392
- * @returns the raw type of the object
1393
- */
1394
- function rawType(obj) {
1395
- return objectToString.call(obj).slice(8, -1);
1396
- }
1397
- /**
1398
- * Checks whether a given value can be made into a reactive object.
1399
- *
1400
- * @param value the value to check
1401
- * @returns whether the value can be made reactive
1402
- */
1403
- function canBeMadeReactive(value) {
1404
- if (typeof value !== "object") {
1389
+ // Maps fibers to thrown errors
1390
+ const fibersInError = new WeakMap();
1391
+ const nodeErrorHandlers = new WeakMap();
1392
+ function _handleError(node, error) {
1393
+ if (!node) {
1405
1394
  return false;
1406
1395
  }
1407
- return SUPPORTED_RAW_TYPES.has(rawType(value));
1408
- }
1409
- /**
1410
- * Creates a reactive from the given object/callback if possible and returns it,
1411
- * returns the original object otherwise.
1412
- *
1413
- * @param value the value make reactive
1414
- * @returns a reactive for the given object when possible, the original otherwise
1415
- */
1416
- function possiblyReactive(val, cb) {
1417
- return canBeMadeReactive(val) ? reactive(val, cb) : val;
1418
- }
1419
- /**
1420
- * Mark an object or array so that it is ignored by the reactivity system
1421
- *
1422
- * @param value the value to mark
1423
- * @returns the object itself
1424
- */
1425
- function markRaw(value) {
1426
- value[SKIP] = true;
1427
- return value;
1428
- }
1429
- /**
1430
- * Given a reactive objet, return the raw (non reactive) underlying object
1431
- *
1432
- * @param value a reactive value
1433
- * @returns the underlying value
1434
- */
1435
- function toRaw(value) {
1436
- return value[TARGET] || value;
1437
- }
1438
- const targetToKeysToCallbacks = new WeakMap();
1439
- /**
1440
- * Observes a given key on a target with an callback. The callback will be
1441
- * called when the given key changes on the target.
1442
- *
1443
- * @param target the target whose key should be observed
1444
- * @param key the key to observe (or Symbol(KEYCHANGES) for key creation
1445
- * or deletion)
1446
- * @param callback the function to call when the key changes
1447
- */
1448
- function observeTargetKey(target, key, callback) {
1449
- if (!targetToKeysToCallbacks.get(target)) {
1450
- targetToKeysToCallbacks.set(target, new Map());
1396
+ const fiber = node.fiber;
1397
+ if (fiber) {
1398
+ fibersInError.set(fiber, error);
1451
1399
  }
1452
- const keyToCallbacks = targetToKeysToCallbacks.get(target);
1453
- if (!keyToCallbacks.get(key)) {
1454
- keyToCallbacks.set(key, new Set());
1400
+ const errorHandlers = nodeErrorHandlers.get(node);
1401
+ if (errorHandlers) {
1402
+ let handled = false;
1403
+ // execute in the opposite order
1404
+ for (let i = errorHandlers.length - 1; i >= 0; i--) {
1405
+ try {
1406
+ errorHandlers[i](error);
1407
+ handled = true;
1408
+ break;
1409
+ }
1410
+ catch (e) {
1411
+ error = e;
1412
+ }
1413
+ }
1414
+ if (handled) {
1415
+ return true;
1416
+ }
1455
1417
  }
1456
- keyToCallbacks.get(key).add(callback);
1457
- if (!callbacksToTargets.has(callback)) {
1458
- callbacksToTargets.set(callback, new Set());
1418
+ return _handleError(node.parent, error);
1419
+ }
1420
+ function handleError(params) {
1421
+ const error = params.error;
1422
+ const node = "node" in params ? params.node : params.fiber.node;
1423
+ const fiber = "fiber" in params ? params.fiber : node.fiber;
1424
+ // resets the fibers on components if possible. This is important so that
1425
+ // new renderings can be properly included in the initial one, if any.
1426
+ let current = fiber;
1427
+ do {
1428
+ current.node.fiber = current;
1429
+ current = current.parent;
1430
+ } while (current);
1431
+ fibersInError.set(fiber.root, error);
1432
+ const handled = _handleError(node, error);
1433
+ if (!handled) {
1434
+ console.warn(`[Owl] Unhandled error. Destroying the root component`);
1435
+ try {
1436
+ node.app.destroy();
1437
+ }
1438
+ catch (e) {
1439
+ console.error(e);
1440
+ }
1459
1441
  }
1460
- callbacksToTargets.get(callback).add(target);
1442
+ }
1443
+
1444
+ function makeChildFiber(node, parent) {
1445
+ let current = node.fiber;
1446
+ if (current) {
1447
+ cancelFibers(current.children);
1448
+ current.root = null;
1449
+ }
1450
+ return new Fiber(node, parent);
1461
1451
  }
1462
- /**
1463
- * Notify Reactives that are observing a given target that a key has changed on
1464
- * the target.
1465
- *
1466
- * @param target target whose Reactives should be notified that the target was
1467
- * changed.
1468
- * @param key the key that changed (or Symbol `KEYCHANGES` if a key was created
1469
- * or deleted)
1470
- */
1471
- function notifyReactives(target, key) {
1472
- const keyToCallbacks = targetToKeysToCallbacks.get(target);
1473
- if (!keyToCallbacks) {
1474
- return;
1452
+ function makeRootFiber(node) {
1453
+ let current = node.fiber;
1454
+ if (current) {
1455
+ let root = current.root;
1456
+ // lock root fiber because canceling children fibers may destroy components,
1457
+ // which means any arbitrary code can be run in onWillDestroy, which may
1458
+ // trigger new renderings
1459
+ root.locked = true;
1460
+ root.setCounter(root.counter + 1 - cancelFibers(current.children));
1461
+ root.locked = false;
1462
+ current.children = [];
1463
+ current.childrenMap = {};
1464
+ current.bdom = null;
1465
+ if (fibersInError.has(current)) {
1466
+ fibersInError.delete(current);
1467
+ fibersInError.delete(root);
1468
+ current.appliedToDom = false;
1469
+ }
1470
+ return current;
1475
1471
  }
1476
- const callbacks = keyToCallbacks.get(key);
1477
- if (!callbacks) {
1478
- return;
1472
+ const fiber = new RootFiber(node, null);
1473
+ if (node.willPatch.length) {
1474
+ fiber.willPatch.push(fiber);
1479
1475
  }
1480
- // Loop on copy because clearReactivesForCallback will modify the set in place
1481
- for (const callback of [...callbacks]) {
1482
- clearReactivesForCallback(callback);
1483
- callback();
1476
+ if (node.patched.length) {
1477
+ fiber.patched.push(fiber);
1484
1478
  }
1479
+ return fiber;
1480
+ }
1481
+ function throwOnRender() {
1482
+ throw new Error("Attempted to render cancelled fiber");
1485
1483
  }
1486
- const callbacksToTargets = new WeakMap();
1487
1484
  /**
1488
- * Clears all subscriptions of the Reactives associated with a given callback.
1489
- *
1490
- * @param callback the callback for which the reactives need to be cleared
1485
+ * @returns number of not-yet rendered fibers cancelled
1491
1486
  */
1492
- function clearReactivesForCallback(callback) {
1493
- const targetsToClear = callbacksToTargets.get(callback);
1494
- if (!targetsToClear) {
1495
- return;
1496
- }
1497
- for (const target of targetsToClear) {
1498
- const observedKeys = targetToKeysToCallbacks.get(target);
1499
- if (!observedKeys) {
1500
- continue;
1487
+ function cancelFibers(fibers) {
1488
+ let result = 0;
1489
+ for (let fiber of fibers) {
1490
+ let node = fiber.node;
1491
+ fiber.render = throwOnRender;
1492
+ if (node.status === 0 /* NEW */) {
1493
+ node.destroy();
1494
+ delete node.parent.children[node.parentKey];
1501
1495
  }
1502
- for (const callbacks of observedKeys.values()) {
1503
- callbacks.delete(callback);
1496
+ node.fiber = null;
1497
+ if (fiber.bdom) {
1498
+ // if fiber has been rendered, this means that the component props have
1499
+ // been updated. however, this fiber will not be patched to the dom, so
1500
+ // it could happen that the next render compare the current props with
1501
+ // the same props, and skip the render completely. With the next line,
1502
+ // we kindly request the component code to force a render, so it works as
1503
+ // expected.
1504
+ node.forceNextRender = true;
1505
+ }
1506
+ else {
1507
+ result++;
1504
1508
  }
1509
+ result += cancelFibers(fiber.children);
1505
1510
  }
1506
- targetsToClear.clear();
1507
- }
1508
- function getSubscriptions(callback) {
1509
- const targets = callbacksToTargets.get(callback) || [];
1510
- return [...targets].map((target) => {
1511
- const keysToCallbacks = targetToKeysToCallbacks.get(target);
1512
- return {
1513
- target,
1514
- keys: keysToCallbacks ? [...keysToCallbacks.keys()] : [],
1515
- };
1516
- });
1511
+ return result;
1517
1512
  }
1518
- const reactiveCache = new WeakMap();
1519
- /**
1520
- * Creates a reactive proxy for an object. Reading data on the reactive object
1521
- * subscribes to changes to the data. Writing data on the object will cause the
1522
- * notify callback to be called if there are suscriptions to that data. Nested
1523
- * objects and arrays are automatically made reactive as well.
1524
- *
1525
- * Whenever you are notified of a change, all subscriptions are cleared, and if
1526
- * you would like to be notified of any further changes, you should go read
1527
- * the underlying data again. We assume that if you don't go read it again after
1528
- * being notified, it means that you are no longer interested in that data.
1529
- *
1530
- * Subscriptions:
1531
- * + Reading a property on an object will subscribe you to changes in the value
1532
- * of that property.
1533
- * + Accessing an object keys (eg with Object.keys or with `for..in`) will
1534
- * subscribe you to the creation/deletion of keys. Checking the presence of a
1535
- * key on the object with 'in' has the same effect.
1536
- * - getOwnPropertyDescriptor does not currently subscribe you to the property.
1537
- * This is a choice that was made because changing a key's value will trigger
1538
- * this trap and we do not want to subscribe by writes. This also means that
1539
- * Object.hasOwnProperty doesn't subscribe as it goes through this trap.
1540
- *
1541
- * @param target the object for which to create a reactive proxy
1542
- * @param callback the function to call when an observed property of the
1543
- * reactive has changed
1544
- * @returns a proxy that tracks changes to it
1545
- */
1546
- function reactive(target, callback = () => { }) {
1547
- if (!canBeMadeReactive(target)) {
1548
- throw new Error(`Cannot make the given value reactive`);
1513
+ class Fiber {
1514
+ constructor(node, parent) {
1515
+ this.bdom = null;
1516
+ this.children = [];
1517
+ this.appliedToDom = false;
1518
+ this.deep = false;
1519
+ this.childrenMap = {};
1520
+ this.node = node;
1521
+ this.parent = parent;
1522
+ if (parent) {
1523
+ this.deep = parent.deep;
1524
+ const root = parent.root;
1525
+ root.setCounter(root.counter + 1);
1526
+ this.root = root;
1527
+ parent.children.push(this);
1528
+ }
1529
+ else {
1530
+ this.root = this;
1531
+ }
1549
1532
  }
1550
- if (SKIP in target) {
1551
- return target;
1533
+ render() {
1534
+ // if some parent has a fiber => register in followup
1535
+ let prev = this.root.node;
1536
+ let scheduler = prev.app.scheduler;
1537
+ let current = prev.parent;
1538
+ while (current) {
1539
+ if (current.fiber) {
1540
+ let root = current.fiber.root;
1541
+ if (root.counter === 0 && prev.parentKey in current.fiber.childrenMap) {
1542
+ current = root.node;
1543
+ }
1544
+ else {
1545
+ scheduler.delayedRenders.push(this);
1546
+ return;
1547
+ }
1548
+ }
1549
+ prev = current;
1550
+ current = current.parent;
1551
+ }
1552
+ // there are no current rendering from above => we can render
1553
+ this._render();
1552
1554
  }
1553
- const originalTarget = target[TARGET];
1554
- if (originalTarget) {
1555
- return reactive(originalTarget, callback);
1555
+ _render() {
1556
+ const node = this.node;
1557
+ const root = this.root;
1558
+ if (root) {
1559
+ try {
1560
+ this.bdom = true;
1561
+ this.bdom = node.renderFn();
1562
+ }
1563
+ catch (e) {
1564
+ handleError({ node, error: e });
1565
+ }
1566
+ root.setCounter(root.counter - 1);
1567
+ }
1556
1568
  }
1557
- if (!reactiveCache.has(target)) {
1558
- reactiveCache.set(target, new WeakMap());
1569
+ }
1570
+ class RootFiber extends Fiber {
1571
+ constructor() {
1572
+ super(...arguments);
1573
+ this.counter = 1;
1574
+ // only add stuff in this if they have registered some hooks
1575
+ this.willPatch = [];
1576
+ this.patched = [];
1577
+ this.mounted = [];
1578
+ // A fiber is typically locked when it is completing and the patch has not, or is being applied.
1579
+ // i.e.: render triggered in onWillUnmount or in willPatch will be delayed
1580
+ this.locked = false;
1559
1581
  }
1560
- const reactivesForTarget = reactiveCache.get(target);
1561
- if (!reactivesForTarget.has(callback)) {
1562
- const targetRawType = rawType(target);
1563
- const handler = COLLECTION_RAWTYPES.has(targetRawType)
1564
- ? collectionsProxyHandler(target, callback, targetRawType)
1565
- : basicProxyHandler(callback);
1566
- const proxy = new Proxy(target, handler);
1567
- reactivesForTarget.set(callback, proxy);
1582
+ complete() {
1583
+ const node = this.node;
1584
+ this.locked = true;
1585
+ let current = undefined;
1586
+ try {
1587
+ // Step 1: calling all willPatch lifecycle hooks
1588
+ for (current of this.willPatch) {
1589
+ // because of the asynchronous nature of the rendering, some parts of the
1590
+ // UI may have been rendered, then deleted in a followup rendering, and we
1591
+ // do not want to call onWillPatch in that case.
1592
+ let node = current.node;
1593
+ if (node.fiber === current) {
1594
+ const component = node.component;
1595
+ for (let cb of node.willPatch) {
1596
+ cb.call(component);
1597
+ }
1598
+ }
1599
+ }
1600
+ current = undefined;
1601
+ // Step 2: patching the dom
1602
+ node._patch();
1603
+ this.locked = false;
1604
+ // Step 4: calling all mounted lifecycle hooks
1605
+ let mountedFibers = this.mounted;
1606
+ while ((current = mountedFibers.pop())) {
1607
+ current = current;
1608
+ if (current.appliedToDom) {
1609
+ for (let cb of current.node.mounted) {
1610
+ cb();
1611
+ }
1612
+ }
1613
+ }
1614
+ // Step 5: calling all patched hooks
1615
+ let patchedFibers = this.patched;
1616
+ while ((current = patchedFibers.pop())) {
1617
+ current = current;
1618
+ if (current.appliedToDom) {
1619
+ for (let cb of current.node.patched) {
1620
+ cb();
1621
+ }
1622
+ }
1623
+ }
1624
+ }
1625
+ catch (e) {
1626
+ this.locked = false;
1627
+ handleError({ fiber: current || this, error: e });
1628
+ }
1629
+ }
1630
+ setCounter(newValue) {
1631
+ this.counter = newValue;
1632
+ if (newValue === 0) {
1633
+ this.node.app.scheduler.flush();
1634
+ }
1568
1635
  }
1569
- return reactivesForTarget.get(callback);
1570
1636
  }
1571
- /**
1572
- * Creates a basic proxy handler for regular objects and arrays.
1573
- *
1574
- * @param callback @see reactive
1575
- * @returns a proxy handler object
1576
- */
1577
- function basicProxyHandler(callback) {
1578
- return {
1579
- get(target, key, proxy) {
1580
- if (key === TARGET) {
1581
- return target;
1582
- }
1583
- // non-writable non-configurable properties cannot be made reactive
1584
- const desc = Object.getOwnPropertyDescriptor(target, key);
1585
- if (desc && !desc.writable && !desc.configurable) {
1586
- return Reflect.get(target, key, proxy);
1637
+ class MountFiber extends RootFiber {
1638
+ constructor(node, target, options = {}) {
1639
+ super(node, null);
1640
+ this.target = target;
1641
+ this.position = options.position || "last-child";
1642
+ }
1643
+ complete() {
1644
+ let current = this;
1645
+ try {
1646
+ const node = this.node;
1647
+ node.children = this.childrenMap;
1648
+ node.app.constructor.validateTarget(this.target);
1649
+ if (node.bdom) {
1650
+ // this is a complicated situation: if we mount a fiber with an existing
1651
+ // bdom, this means that this same fiber was already completed, mounted,
1652
+ // but a crash occurred in some mounted hook. Then, it was handled and
1653
+ // the new rendering is being applied.
1654
+ node.updateDom();
1587
1655
  }
1588
- observeTargetKey(target, key, callback);
1589
- return possiblyReactive(Reflect.get(target, key, proxy), callback);
1590
- },
1591
- set(target, key, value, proxy) {
1592
- const isNewKey = !objectHasOwnProperty.call(target, key);
1593
- const originalValue = Reflect.get(target, key, proxy);
1594
- const ret = Reflect.set(target, key, value, proxy);
1595
- if (isNewKey) {
1596
- notifyReactives(target, KEYCHANGES);
1656
+ else {
1657
+ node.bdom = this.bdom;
1658
+ if (this.position === "last-child" || this.target.childNodes.length === 0) {
1659
+ mount$1(node.bdom, this.target);
1660
+ }
1661
+ else {
1662
+ const firstChild = this.target.childNodes[0];
1663
+ mount$1(node.bdom, this.target, firstChild);
1664
+ }
1597
1665
  }
1598
- // While Array length may trigger the set trap, it's not actually set by this
1599
- // method but is updated behind the scenes, and the trap is not called with the
1600
- // new value. We disable the "same-value-optimization" for it because of that.
1601
- if (originalValue !== value || (Array.isArray(target) && key === "length")) {
1602
- notifyReactives(target, key);
1666
+ // unregistering the fiber before mounted since it can do another render
1667
+ // and that the current rendering is obviously completed
1668
+ node.fiber = null;
1669
+ node.status = 1 /* MOUNTED */;
1670
+ this.appliedToDom = true;
1671
+ let mountedFibers = this.mounted;
1672
+ while ((current = mountedFibers.pop())) {
1673
+ if (current.appliedToDom) {
1674
+ for (let cb of current.node.mounted) {
1675
+ cb();
1676
+ }
1677
+ }
1603
1678
  }
1604
- return ret;
1605
- },
1606
- deleteProperty(target, key) {
1607
- const ret = Reflect.deleteProperty(target, key);
1608
- // TODO: only notify when something was actually deleted
1609
- notifyReactives(target, KEYCHANGES);
1610
- notifyReactives(target, key);
1611
- return ret;
1612
- },
1613
- ownKeys(target) {
1614
- observeTargetKey(target, KEYCHANGES, callback);
1615
- return Reflect.ownKeys(target);
1616
- },
1617
- has(target, key) {
1618
- // TODO: this observes all key changes instead of only the presence of the argument key
1619
- // observing the key itself would observe value changes instead of presence changes
1620
- // so we may need a finer grained system to distinguish observing value vs presence.
1621
- observeTargetKey(target, KEYCHANGES, callback);
1622
- return Reflect.has(target, key);
1623
- },
1624
- };
1625
- }
1679
+ }
1680
+ catch (e) {
1681
+ handleError({ fiber: current, error: e });
1682
+ }
1683
+ }
1684
+ }
1685
+
1686
+ // Allows to get the target of a Reactive (used for making a new Reactive from the underlying object)
1687
+ const TARGET = Symbol("Target");
1688
+ // Escape hatch to prevent reactivity system to turn something into a reactive
1689
+ const SKIP = Symbol("Skip");
1690
+ // Special key to subscribe to, to be notified of key creation/deletion
1691
+ const KEYCHANGES = Symbol("Key changes");
1692
+ const objectToString = Object.prototype.toString;
1693
+ const objectHasOwnProperty = Object.prototype.hasOwnProperty;
1694
+ const SUPPORTED_RAW_TYPES = new Set(["Object", "Array", "Set", "Map", "WeakMap"]);
1695
+ const COLLECTION_RAWTYPES = new Set(["Set", "Map", "WeakMap"]);
1626
1696
  /**
1627
- * Creates a function that will observe the key that is passed to it when called
1628
- * and delegates to the underlying method.
1697
+ * extract "RawType" from strings like "[object RawType]" => this lets us ignore
1698
+ * many native objects such as Promise (whose toString is [object Promise])
1699
+ * or Date ([object Date]), while also supporting collections without using
1700
+ * instanceof in a loop
1629
1701
  *
1630
- * @param methodName name of the method to delegate to
1631
- * @param target @see reactive
1632
- * @param callback @see reactive
1702
+ * @param obj the object to check
1703
+ * @returns the raw type of the object
1633
1704
  */
1634
- function makeKeyObserver(methodName, target, callback) {
1635
- return (key) => {
1636
- key = toRaw(key);
1637
- observeTargetKey(target, key, callback);
1638
- return possiblyReactive(target[methodName](key), callback);
1639
- };
1705
+ function rawType(obj) {
1706
+ return objectToString.call(obj).slice(8, -1);
1640
1707
  }
1641
1708
  /**
1642
- * Creates an iterable that will delegate to the underlying iteration method and
1643
- * observe keys as necessary.
1709
+ * Checks whether a given value can be made into a reactive object.
1644
1710
  *
1645
- * @param methodName name of the method to delegate to
1646
- * @param target @see reactive
1647
- * @param callback @see reactive
1711
+ * @param value the value to check
1712
+ * @returns whether the value can be made reactive
1648
1713
  */
1649
- function makeIteratorObserver(methodName, target, callback) {
1650
- return function* () {
1651
- observeTargetKey(target, KEYCHANGES, callback);
1652
- const keys = target.keys();
1653
- for (const item of target[methodName]()) {
1654
- const key = keys.next().value;
1655
- observeTargetKey(target, key, callback);
1656
- yield possiblyReactive(item, callback);
1657
- }
1658
- };
1714
+ function canBeMadeReactive(value) {
1715
+ if (typeof value !== "object") {
1716
+ return false;
1717
+ }
1718
+ return SUPPORTED_RAW_TYPES.has(rawType(value));
1659
1719
  }
1660
1720
  /**
1661
- * Creates a forEach function that will delegate to forEach on the underlying
1662
- * collection while observing key changes, and keys as they're iterated over,
1663
- * and making the passed keys/values reactive.
1721
+ * Creates a reactive from the given object/callback if possible and returns it,
1722
+ * returns the original object otherwise.
1664
1723
  *
1665
- * @param target @see reactive
1666
- * @param callback @see reactive
1724
+ * @param value the value make reactive
1725
+ * @returns a reactive for the given object when possible, the original otherwise
1667
1726
  */
1668
- function makeForEachObserver(target, callback) {
1669
- return function forEach(forEachCb, thisArg) {
1670
- observeTargetKey(target, KEYCHANGES, callback);
1671
- target.forEach(function (val, key, targetObj) {
1672
- observeTargetKey(target, key, callback);
1673
- forEachCb.call(thisArg, possiblyReactive(val, callback), possiblyReactive(key, callback), possiblyReactive(targetObj, callback));
1674
- }, thisArg);
1675
- };
1727
+ function possiblyReactive(val, cb) {
1728
+ return canBeMadeReactive(val) ? reactive(val, cb) : val;
1676
1729
  }
1677
1730
  /**
1678
- * Creates a function that will delegate to an underlying method, and check if
1679
- * that method has modified the presence or value of a key, and notify the
1680
- * reactives appropriately.
1731
+ * Mark an object or array so that it is ignored by the reactivity system
1681
1732
  *
1682
- * @param setterName name of the method to delegate to
1683
- * @param getterName name of the method which should be used to retrieve the
1684
- * value before calling the delegate method for comparison purposes
1685
- * @param target @see reactive
1733
+ * @param value the value to mark
1734
+ * @returns the object itself
1686
1735
  */
1687
- function delegateAndNotify(setterName, getterName, target) {
1688
- return (key, value) => {
1689
- key = toRaw(key);
1690
- const hadKey = target.has(key);
1691
- const originalValue = target[getterName](key);
1692
- const ret = target[setterName](key, value);
1693
- const hasKey = target.has(key);
1694
- if (hadKey !== hasKey) {
1695
- notifyReactives(target, KEYCHANGES);
1696
- }
1697
- if (originalValue !== value) {
1698
- notifyReactives(target, key);
1699
- }
1700
- return ret;
1701
- };
1736
+ function markRaw(value) {
1737
+ value[SKIP] = true;
1738
+ return value;
1702
1739
  }
1703
1740
  /**
1704
- * Creates a function that will clear the underlying collection and notify that
1705
- * the keys of the collection have changed.
1741
+ * Given a reactive objet, return the raw (non reactive) underlying object
1706
1742
  *
1707
- * @param target @see reactive
1743
+ * @param value a reactive value
1744
+ * @returns the underlying value
1708
1745
  */
1709
- function makeClearNotifier(target) {
1710
- return () => {
1711
- const allKeys = [...target.keys()];
1712
- target.clear();
1713
- notifyReactives(target, KEYCHANGES);
1714
- for (const key of allKeys) {
1715
- notifyReactives(target, key);
1716
- }
1717
- };
1746
+ function toRaw(value) {
1747
+ return value[TARGET] || value;
1718
1748
  }
1749
+ const targetToKeysToCallbacks = new WeakMap();
1719
1750
  /**
1720
- * Maps raw type of an object to an object containing functions that can be used
1721
- * to build an appropritate proxy handler for that raw type. Eg: when making a
1722
- * reactive set, calling the has method should mark the key that is being
1723
- * retrieved as observed, and calling the add or delete method should notify the
1724
- * reactives that the key which is being added or deleted has been modified.
1725
- */
1726
- const rawTypeToFuncHandlers = {
1727
- Set: (target, callback) => ({
1728
- has: makeKeyObserver("has", target, callback),
1729
- add: delegateAndNotify("add", "has", target),
1730
- delete: delegateAndNotify("delete", "has", target),
1731
- keys: makeIteratorObserver("keys", target, callback),
1732
- values: makeIteratorObserver("values", target, callback),
1733
- entries: makeIteratorObserver("entries", target, callback),
1734
- [Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
1735
- forEach: makeForEachObserver(target, callback),
1736
- clear: makeClearNotifier(target),
1737
- get size() {
1738
- observeTargetKey(target, KEYCHANGES, callback);
1739
- return target.size;
1740
- },
1741
- }),
1742
- Map: (target, callback) => ({
1743
- has: makeKeyObserver("has", target, callback),
1744
- get: makeKeyObserver("get", target, callback),
1745
- set: delegateAndNotify("set", "get", target),
1746
- delete: delegateAndNotify("delete", "has", target),
1747
- keys: makeIteratorObserver("keys", target, callback),
1748
- values: makeIteratorObserver("values", target, callback),
1749
- entries: makeIteratorObserver("entries", target, callback),
1750
- [Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
1751
- forEach: makeForEachObserver(target, callback),
1752
- clear: makeClearNotifier(target),
1753
- get size() {
1754
- observeTargetKey(target, KEYCHANGES, callback);
1755
- return target.size;
1756
- },
1757
- }),
1758
- WeakMap: (target, callback) => ({
1759
- has: makeKeyObserver("has", target, callback),
1760
- get: makeKeyObserver("get", target, callback),
1761
- set: delegateAndNotify("set", "get", target),
1762
- delete: delegateAndNotify("delete", "has", target),
1763
- }),
1764
- };
1765
- /**
1766
- * Creates a proxy handler for collections (Set/Map/WeakMap)
1767
- *
1768
- * @param callback @see reactive
1769
- * @param target @see reactive
1770
- * @returns a proxy handler object
1771
- */
1772
- function collectionsProxyHandler(target, callback, targetRawType) {
1773
- // TODO: if performance is an issue we can create the special handlers lazily when each
1774
- // property is read.
1775
- const specialHandlers = rawTypeToFuncHandlers[targetRawType](target, callback);
1776
- return Object.assign(basicProxyHandler(callback), {
1777
- get(target, key) {
1778
- if (key === TARGET) {
1779
- return target;
1780
- }
1781
- if (objectHasOwnProperty.call(specialHandlers, key)) {
1782
- return specialHandlers[key];
1783
- }
1784
- observeTargetKey(target, key, callback);
1785
- return possiblyReactive(target[key], callback);
1786
- },
1787
- });
1788
- }
1789
-
1790
- /**
1791
- * Creates a batched version of a callback so that all calls to it in the same
1792
- * microtick will only call the original callback once.
1751
+ * Observes a given key on a target with an callback. The callback will be
1752
+ * called when the given key changes on the target.
1793
1753
  *
1794
- * @param callback the callback to batch
1795
- * @returns a batched version of the original callback
1754
+ * @param target the target whose key should be observed
1755
+ * @param key the key to observe (or Symbol(KEYCHANGES) for key creation
1756
+ * or deletion)
1757
+ * @param callback the function to call when the key changes
1796
1758
  */
1797
- function batched(callback) {
1798
- let called = false;
1799
- return async () => {
1800
- // This await blocks all calls to the callback here, then releases them sequentially
1801
- // in the next microtick. This line decides the granularity of the batch.
1802
- await Promise.resolve();
1803
- if (!called) {
1804
- called = true;
1805
- // wait for all calls in this microtick to fall through before resetting "called"
1806
- // so that only the first call to the batched function calls the original callback.
1807
- // Schedule this before calling the callback so that calls to the batched function
1808
- // within the callback will proceed only after resetting called to false, and have
1809
- // a chance to execute the callback again
1810
- Promise.resolve().then(() => (called = false));
1811
- callback();
1812
- }
1813
- };
1814
- }
1815
- function validateTarget(target) {
1816
- if (!(target instanceof HTMLElement)) {
1817
- throw new Error("Cannot mount component: the target is not a valid DOM element");
1759
+ function observeTargetKey(target, key, callback) {
1760
+ if (!targetToKeysToCallbacks.get(target)) {
1761
+ targetToKeysToCallbacks.set(target, new Map());
1818
1762
  }
1819
- if (!document.body.contains(target)) {
1820
- throw new Error("Cannot mount a component on a detached dom node");
1763
+ const keyToCallbacks = targetToKeysToCallbacks.get(target);
1764
+ if (!keyToCallbacks.get(key)) {
1765
+ keyToCallbacks.set(key, new Set());
1766
+ }
1767
+ keyToCallbacks.get(key).add(callback);
1768
+ if (!callbacksToTargets.has(callback)) {
1769
+ callbacksToTargets.set(callback, new Set());
1821
1770
  }
1771
+ callbacksToTargets.get(callback).add(target);
1822
1772
  }
1823
- class EventBus extends EventTarget {
1824
- trigger(name, payload) {
1825
- this.dispatchEvent(new CustomEvent(name, { detail: payload }));
1773
+ /**
1774
+ * Notify Reactives that are observing a given target that a key has changed on
1775
+ * the target.
1776
+ *
1777
+ * @param target target whose Reactives should be notified that the target was
1778
+ * changed.
1779
+ * @param key the key that changed (or Symbol `KEYCHANGES` if a key was created
1780
+ * or deleted)
1781
+ */
1782
+ function notifyReactives(target, key) {
1783
+ const keyToCallbacks = targetToKeysToCallbacks.get(target);
1784
+ if (!keyToCallbacks) {
1785
+ return;
1786
+ }
1787
+ const callbacks = keyToCallbacks.get(key);
1788
+ if (!callbacks) {
1789
+ return;
1790
+ }
1791
+ // Loop on copy because clearReactivesForCallback will modify the set in place
1792
+ for (const callback of [...callbacks]) {
1793
+ clearReactivesForCallback(callback);
1794
+ callback();
1826
1795
  }
1827
1796
  }
1828
- function whenReady(fn) {
1829
- return new Promise(function (resolve) {
1830
- if (document.readyState !== "loading") {
1831
- resolve(true);
1797
+ const callbacksToTargets = new WeakMap();
1798
+ /**
1799
+ * Clears all subscriptions of the Reactives associated with a given callback.
1800
+ *
1801
+ * @param callback the callback for which the reactives need to be cleared
1802
+ */
1803
+ function clearReactivesForCallback(callback) {
1804
+ const targetsToClear = callbacksToTargets.get(callback);
1805
+ if (!targetsToClear) {
1806
+ return;
1807
+ }
1808
+ for (const target of targetsToClear) {
1809
+ const observedKeys = targetToKeysToCallbacks.get(target);
1810
+ if (!observedKeys) {
1811
+ continue;
1832
1812
  }
1833
- else {
1834
- document.addEventListener("DOMContentLoaded", resolve, false);
1813
+ for (const callbacks of observedKeys.values()) {
1814
+ callbacks.delete(callback);
1835
1815
  }
1836
- }).then(fn || function () { });
1837
- }
1838
- async function loadFile(url) {
1839
- const result = await fetch(url);
1840
- if (!result.ok) {
1841
- throw new Error("Error while fetching xml templates");
1842
1816
  }
1843
- return await result.text();
1817
+ targetsToClear.clear();
1844
1818
  }
1845
- /*
1846
- * This class just transports the fact that a string is safe
1847
- * to be injected as HTML. Overriding a JS primitive is quite painful though
1848
- * so we need to redfine toString and valueOf.
1849
- */
1850
- class Markup extends String {
1819
+ function getSubscriptions(callback) {
1820
+ const targets = callbacksToTargets.get(callback) || [];
1821
+ return [...targets].map((target) => {
1822
+ const keysToCallbacks = targetToKeysToCallbacks.get(target);
1823
+ return {
1824
+ target,
1825
+ keys: keysToCallbacks ? [...keysToCallbacks.keys()] : [],
1826
+ };
1827
+ });
1851
1828
  }
1852
- /*
1853
- * Marks a value as safe, that is, a value that can be injected as HTML directly.
1854
- * It should be used to wrap the value passed to a t-out directive to allow a raw rendering.
1829
+ const reactiveCache = new WeakMap();
1830
+ /**
1831
+ * Creates a reactive proxy for an object. Reading data on the reactive object
1832
+ * subscribes to changes to the data. Writing data on the object will cause the
1833
+ * notify callback to be called if there are suscriptions to that data. Nested
1834
+ * objects and arrays are automatically made reactive as well.
1835
+ *
1836
+ * Whenever you are notified of a change, all subscriptions are cleared, and if
1837
+ * you would like to be notified of any further changes, you should go read
1838
+ * the underlying data again. We assume that if you don't go read it again after
1839
+ * being notified, it means that you are no longer interested in that data.
1840
+ *
1841
+ * Subscriptions:
1842
+ * + Reading a property on an object will subscribe you to changes in the value
1843
+ * of that property.
1844
+ * + Accessing an object keys (eg with Object.keys or with `for..in`) will
1845
+ * subscribe you to the creation/deletion of keys. Checking the presence of a
1846
+ * key on the object with 'in' has the same effect.
1847
+ * - getOwnPropertyDescriptor does not currently subscribe you to the property.
1848
+ * This is a choice that was made because changing a key's value will trigger
1849
+ * this trap and we do not want to subscribe by writes. This also means that
1850
+ * Object.hasOwnProperty doesn't subscribe as it goes through this trap.
1851
+ *
1852
+ * @param target the object for which to create a reactive proxy
1853
+ * @param callback the function to call when an observed property of the
1854
+ * reactive has changed
1855
+ * @returns a proxy that tracks changes to it
1855
1856
  */
1856
- function markup(value) {
1857
- return new Markup(value);
1858
- }
1859
-
1860
- class Component {
1861
- constructor(props, env, node) {
1862
- this.props = props;
1863
- this.env = env;
1864
- this.__owl__ = node;
1857
+ function reactive(target, callback = () => { }) {
1858
+ if (!canBeMadeReactive(target)) {
1859
+ throw new Error(`Cannot make the given value reactive`);
1865
1860
  }
1866
- setup() { }
1867
- render(deep = false) {
1868
- this.__owl__.render(deep === true);
1861
+ if (SKIP in target) {
1862
+ return target;
1869
1863
  }
1870
- }
1871
- Component.template = "";
1872
-
1873
- // Maps fibers to thrown errors
1874
- const fibersInError = new WeakMap();
1875
- const nodeErrorHandlers = new WeakMap();
1876
- function _handleError(node, error) {
1877
- if (!node) {
1878
- return false;
1864
+ const originalTarget = target[TARGET];
1865
+ if (originalTarget) {
1866
+ return reactive(originalTarget, callback);
1879
1867
  }
1880
- const fiber = node.fiber;
1881
- if (fiber) {
1882
- fibersInError.set(fiber, error);
1868
+ if (!reactiveCache.has(target)) {
1869
+ reactiveCache.set(target, new WeakMap());
1883
1870
  }
1884
- const errorHandlers = nodeErrorHandlers.get(node);
1885
- if (errorHandlers) {
1886
- let handled = false;
1887
- // execute in the opposite order
1888
- for (let i = errorHandlers.length - 1; i >= 0; i--) {
1889
- try {
1890
- errorHandlers[i](error);
1891
- handled = true;
1892
- break;
1893
- }
1894
- catch (e) {
1895
- error = e;
1896
- }
1897
- }
1898
- if (handled) {
1899
- return true;
1900
- }
1871
+ const reactivesForTarget = reactiveCache.get(target);
1872
+ if (!reactivesForTarget.has(callback)) {
1873
+ const targetRawType = rawType(target);
1874
+ const handler = COLLECTION_RAWTYPES.has(targetRawType)
1875
+ ? collectionsProxyHandler(target, callback, targetRawType)
1876
+ : basicProxyHandler(callback);
1877
+ const proxy = new Proxy(target, handler);
1878
+ reactivesForTarget.set(callback, proxy);
1901
1879
  }
1902
- return _handleError(node.parent, error);
1880
+ return reactivesForTarget.get(callback);
1903
1881
  }
1904
- function handleError(params) {
1905
- const error = params.error;
1906
- const node = "node" in params ? params.node : params.fiber.node;
1907
- const fiber = "fiber" in params ? params.fiber : node.fiber;
1908
- // resets the fibers on components if possible. This is important so that
1909
- // new renderings can be properly included in the initial one, if any.
1910
- let current = fiber;
1911
- do {
1912
- current.node.fiber = current;
1913
- current = current.parent;
1914
- } while (current);
1915
- fibersInError.set(fiber.root, error);
1916
- const handled = _handleError(node, error);
1917
- if (!handled) {
1918
- console.warn(`[Owl] Unhandled error. Destroying the root component`);
1919
- try {
1920
- node.app.destroy();
1921
- }
1922
- catch (e) {
1923
- console.error(e);
1924
- }
1925
- }
1926
- }
1927
-
1928
- function makeChildFiber(node, parent) {
1929
- let current = node.fiber;
1930
- if (current) {
1931
- cancelFibers(current.children);
1932
- current.root = null;
1933
- }
1934
- return new Fiber(node, parent);
1882
+ /**
1883
+ * Creates a basic proxy handler for regular objects and arrays.
1884
+ *
1885
+ * @param callback @see reactive
1886
+ * @returns a proxy handler object
1887
+ */
1888
+ function basicProxyHandler(callback) {
1889
+ return {
1890
+ get(target, key, proxy) {
1891
+ if (key === TARGET) {
1892
+ return target;
1893
+ }
1894
+ // non-writable non-configurable properties cannot be made reactive
1895
+ const desc = Object.getOwnPropertyDescriptor(target, key);
1896
+ if (desc && !desc.writable && !desc.configurable) {
1897
+ return Reflect.get(target, key, proxy);
1898
+ }
1899
+ observeTargetKey(target, key, callback);
1900
+ return possiblyReactive(Reflect.get(target, key, proxy), callback);
1901
+ },
1902
+ set(target, key, value, proxy) {
1903
+ const isNewKey = !objectHasOwnProperty.call(target, key);
1904
+ const originalValue = Reflect.get(target, key, proxy);
1905
+ const ret = Reflect.set(target, key, value, proxy);
1906
+ if (isNewKey) {
1907
+ notifyReactives(target, KEYCHANGES);
1908
+ }
1909
+ // While Array length may trigger the set trap, it's not actually set by this
1910
+ // method but is updated behind the scenes, and the trap is not called with the
1911
+ // new value. We disable the "same-value-optimization" for it because of that.
1912
+ if (originalValue !== value || (Array.isArray(target) && key === "length")) {
1913
+ notifyReactives(target, key);
1914
+ }
1915
+ return ret;
1916
+ },
1917
+ deleteProperty(target, key) {
1918
+ const ret = Reflect.deleteProperty(target, key);
1919
+ // TODO: only notify when something was actually deleted
1920
+ notifyReactives(target, KEYCHANGES);
1921
+ notifyReactives(target, key);
1922
+ return ret;
1923
+ },
1924
+ ownKeys(target) {
1925
+ observeTargetKey(target, KEYCHANGES, callback);
1926
+ return Reflect.ownKeys(target);
1927
+ },
1928
+ has(target, key) {
1929
+ // TODO: this observes all key changes instead of only the presence of the argument key
1930
+ // observing the key itself would observe value changes instead of presence changes
1931
+ // so we may need a finer grained system to distinguish observing value vs presence.
1932
+ observeTargetKey(target, KEYCHANGES, callback);
1933
+ return Reflect.has(target, key);
1934
+ },
1935
+ };
1935
1936
  }
1936
- function makeRootFiber(node) {
1937
- let current = node.fiber;
1938
- if (current) {
1939
- let root = current.root;
1940
- // lock root fiber because canceling children fibers may destroy components,
1941
- // which means any arbitrary code can be run in onWillDestroy, which may
1942
- // trigger new renderings
1943
- root.locked = true;
1944
- root.setCounter(root.counter + 1 - cancelFibers(current.children));
1945
- root.locked = false;
1946
- current.children = [];
1947
- current.childrenMap = {};
1948
- current.bdom = null;
1949
- if (fibersInError.has(current)) {
1950
- fibersInError.delete(current);
1951
- fibersInError.delete(root);
1952
- current.appliedToDom = false;
1937
+ /**
1938
+ * Creates a function that will observe the key that is passed to it when called
1939
+ * and delegates to the underlying method.
1940
+ *
1941
+ * @param methodName name of the method to delegate to
1942
+ * @param target @see reactive
1943
+ * @param callback @see reactive
1944
+ */
1945
+ function makeKeyObserver(methodName, target, callback) {
1946
+ return (key) => {
1947
+ key = toRaw(key);
1948
+ observeTargetKey(target, key, callback);
1949
+ return possiblyReactive(target[methodName](key), callback);
1950
+ };
1951
+ }
1952
+ /**
1953
+ * Creates an iterable that will delegate to the underlying iteration method and
1954
+ * observe keys as necessary.
1955
+ *
1956
+ * @param methodName name of the method to delegate to
1957
+ * @param target @see reactive
1958
+ * @param callback @see reactive
1959
+ */
1960
+ function makeIteratorObserver(methodName, target, callback) {
1961
+ return function* () {
1962
+ observeTargetKey(target, KEYCHANGES, callback);
1963
+ const keys = target.keys();
1964
+ for (const item of target[methodName]()) {
1965
+ const key = keys.next().value;
1966
+ observeTargetKey(target, key, callback);
1967
+ yield possiblyReactive(item, callback);
1953
1968
  }
1954
- return current;
1955
- }
1956
- const fiber = new RootFiber(node, null);
1957
- if (node.willPatch.length) {
1958
- fiber.willPatch.push(fiber);
1959
- }
1960
- if (node.patched.length) {
1961
- fiber.patched.push(fiber);
1962
- }
1963
- return fiber;
1969
+ };
1964
1970
  }
1965
- function throwOnRender() {
1966
- throw new Error("Attempted to render cancelled fiber");
1971
+ /**
1972
+ * Creates a forEach function that will delegate to forEach on the underlying
1973
+ * collection while observing key changes, and keys as they're iterated over,
1974
+ * and making the passed keys/values reactive.
1975
+ *
1976
+ * @param target @see reactive
1977
+ * @param callback @see reactive
1978
+ */
1979
+ function makeForEachObserver(target, callback) {
1980
+ return function forEach(forEachCb, thisArg) {
1981
+ observeTargetKey(target, KEYCHANGES, callback);
1982
+ target.forEach(function (val, key, targetObj) {
1983
+ observeTargetKey(target, key, callback);
1984
+ forEachCb.call(thisArg, possiblyReactive(val, callback), possiblyReactive(key, callback), possiblyReactive(targetObj, callback));
1985
+ }, thisArg);
1986
+ };
1967
1987
  }
1968
1988
  /**
1969
- * @returns number of not-yet rendered fibers cancelled
1989
+ * Creates a function that will delegate to an underlying method, and check if
1990
+ * that method has modified the presence or value of a key, and notify the
1991
+ * reactives appropriately.
1992
+ *
1993
+ * @param setterName name of the method to delegate to
1994
+ * @param getterName name of the method which should be used to retrieve the
1995
+ * value before calling the delegate method for comparison purposes
1996
+ * @param target @see reactive
1970
1997
  */
1971
- function cancelFibers(fibers) {
1972
- let result = 0;
1973
- for (let fiber of fibers) {
1974
- let node = fiber.node;
1975
- fiber.render = throwOnRender;
1976
- if (node.status === 0 /* NEW */) {
1977
- node.destroy();
1978
- }
1979
- node.fiber = null;
1980
- if (fiber.bdom) {
1981
- // if fiber has been rendered, this means that the component props have
1982
- // been updated. however, this fiber will not be patched to the dom, so
1983
- // it could happen that the next render compare the current props with
1984
- // the same props, and skip the render completely. With the next line,
1985
- // we kindly request the component code to force a render, so it works as
1986
- // expected.
1987
- node.forceNextRender = true;
1998
+ function delegateAndNotify(setterName, getterName, target) {
1999
+ return (key, value) => {
2000
+ key = toRaw(key);
2001
+ const hadKey = target.has(key);
2002
+ const originalValue = target[getterName](key);
2003
+ const ret = target[setterName](key, value);
2004
+ const hasKey = target.has(key);
2005
+ if (hadKey !== hasKey) {
2006
+ notifyReactives(target, KEYCHANGES);
1988
2007
  }
1989
- else {
1990
- result++;
2008
+ if (originalValue !== value) {
2009
+ notifyReactives(target, key);
1991
2010
  }
1992
- result += cancelFibers(fiber.children);
1993
- }
1994
- return result;
2011
+ return ret;
2012
+ };
1995
2013
  }
1996
- class Fiber {
1997
- constructor(node, parent) {
1998
- this.bdom = null;
1999
- this.children = [];
2000
- this.appliedToDom = false;
2001
- this.deep = false;
2002
- this.childrenMap = {};
2003
- this.node = node;
2004
- this.parent = parent;
2005
- if (parent) {
2006
- this.deep = parent.deep;
2007
- const root = parent.root;
2008
- root.setCounter(root.counter + 1);
2009
- this.root = root;
2010
- parent.children.push(this);
2011
- }
2012
- else {
2013
- this.root = this;
2014
- }
2015
- }
2016
- render() {
2017
- // if some parent has a fiber => register in followup
2018
- let prev = this.root.node;
2019
- let scheduler = prev.app.scheduler;
2020
- let current = prev.parent;
2021
- while (current) {
2022
- if (current.fiber) {
2023
- let root = current.fiber.root;
2024
- if (root.counter === 0 && prev.parentKey in current.fiber.childrenMap) {
2025
- current = root.node;
2026
- }
2027
- else {
2028
- scheduler.delayedRenders.push(this);
2029
- return;
2030
- }
2031
- }
2032
- prev = current;
2033
- current = current.parent;
2034
- }
2035
- // there are no current rendering from above => we can render
2036
- this._render();
2037
- }
2038
- _render() {
2039
- const node = this.node;
2040
- const root = this.root;
2041
- if (root) {
2042
- try {
2043
- this.bdom = true;
2044
- this.bdom = node.renderFn();
2045
- }
2046
- catch (e) {
2047
- handleError({ node, error: e });
2048
- }
2049
- root.setCounter(root.counter - 1);
2014
+ /**
2015
+ * Creates a function that will clear the underlying collection and notify that
2016
+ * the keys of the collection have changed.
2017
+ *
2018
+ * @param target @see reactive
2019
+ */
2020
+ function makeClearNotifier(target) {
2021
+ return () => {
2022
+ const allKeys = [...target.keys()];
2023
+ target.clear();
2024
+ notifyReactives(target, KEYCHANGES);
2025
+ for (const key of allKeys) {
2026
+ notifyReactives(target, key);
2050
2027
  }
2051
- }
2028
+ };
2052
2029
  }
2053
- class RootFiber extends Fiber {
2054
- constructor() {
2055
- super(...arguments);
2056
- this.counter = 1;
2057
- // only add stuff in this if they have registered some hooks
2058
- this.willPatch = [];
2059
- this.patched = [];
2060
- this.mounted = [];
2061
- // A fiber is typically locked when it is completing and the patch has not, or is being applied.
2062
- // i.e.: render triggered in onWillUnmount or in willPatch will be delayed
2063
- this.locked = false;
2064
- }
2065
- complete() {
2066
- const node = this.node;
2067
- this.locked = true;
2068
- let current = undefined;
2069
- try {
2070
- // Step 1: calling all willPatch lifecycle hooks
2071
- for (current of this.willPatch) {
2072
- // because of the asynchronous nature of the rendering, some parts of the
2073
- // UI may have been rendered, then deleted in a followup rendering, and we
2074
- // do not want to call onWillPatch in that case.
2075
- let node = current.node;
2076
- if (node.fiber === current) {
2077
- const component = node.component;
2078
- for (let cb of node.willPatch) {
2079
- cb.call(component);
2080
- }
2081
- }
2082
- }
2083
- current = undefined;
2084
- // Step 2: patching the dom
2085
- node._patch();
2086
- this.locked = false;
2087
- // Step 4: calling all mounted lifecycle hooks
2088
- let mountedFibers = this.mounted;
2089
- while ((current = mountedFibers.pop())) {
2090
- current = current;
2091
- if (current.appliedToDom) {
2092
- for (let cb of current.node.mounted) {
2093
- cb();
2094
- }
2095
- }
2030
+ /**
2031
+ * Maps raw type of an object to an object containing functions that can be used
2032
+ * to build an appropritate proxy handler for that raw type. Eg: when making a
2033
+ * reactive set, calling the has method should mark the key that is being
2034
+ * retrieved as observed, and calling the add or delete method should notify the
2035
+ * reactives that the key which is being added or deleted has been modified.
2036
+ */
2037
+ const rawTypeToFuncHandlers = {
2038
+ Set: (target, callback) => ({
2039
+ has: makeKeyObserver("has", target, callback),
2040
+ add: delegateAndNotify("add", "has", target),
2041
+ delete: delegateAndNotify("delete", "has", target),
2042
+ keys: makeIteratorObserver("keys", target, callback),
2043
+ values: makeIteratorObserver("values", target, callback),
2044
+ entries: makeIteratorObserver("entries", target, callback),
2045
+ [Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
2046
+ forEach: makeForEachObserver(target, callback),
2047
+ clear: makeClearNotifier(target),
2048
+ get size() {
2049
+ observeTargetKey(target, KEYCHANGES, callback);
2050
+ return target.size;
2051
+ },
2052
+ }),
2053
+ Map: (target, callback) => ({
2054
+ has: makeKeyObserver("has", target, callback),
2055
+ get: makeKeyObserver("get", target, callback),
2056
+ set: delegateAndNotify("set", "get", target),
2057
+ delete: delegateAndNotify("delete", "has", target),
2058
+ keys: makeIteratorObserver("keys", target, callback),
2059
+ values: makeIteratorObserver("values", target, callback),
2060
+ entries: makeIteratorObserver("entries", target, callback),
2061
+ [Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
2062
+ forEach: makeForEachObserver(target, callback),
2063
+ clear: makeClearNotifier(target),
2064
+ get size() {
2065
+ observeTargetKey(target, KEYCHANGES, callback);
2066
+ return target.size;
2067
+ },
2068
+ }),
2069
+ WeakMap: (target, callback) => ({
2070
+ has: makeKeyObserver("has", target, callback),
2071
+ get: makeKeyObserver("get", target, callback),
2072
+ set: delegateAndNotify("set", "get", target),
2073
+ delete: delegateAndNotify("delete", "has", target),
2074
+ }),
2075
+ };
2076
+ /**
2077
+ * Creates a proxy handler for collections (Set/Map/WeakMap)
2078
+ *
2079
+ * @param callback @see reactive
2080
+ * @param target @see reactive
2081
+ * @returns a proxy handler object
2082
+ */
2083
+ function collectionsProxyHandler(target, callback, targetRawType) {
2084
+ // TODO: if performance is an issue we can create the special handlers lazily when each
2085
+ // property is read.
2086
+ const specialHandlers = rawTypeToFuncHandlers[targetRawType](target, callback);
2087
+ return Object.assign(basicProxyHandler(callback), {
2088
+ get(target, key) {
2089
+ if (key === TARGET) {
2090
+ return target;
2096
2091
  }
2097
- // Step 5: calling all patched hooks
2098
- let patchedFibers = this.patched;
2099
- while ((current = patchedFibers.pop())) {
2100
- current = current;
2101
- if (current.appliedToDom) {
2102
- for (let cb of current.node.patched) {
2103
- cb();
2104
- }
2105
- }
2092
+ if (objectHasOwnProperty.call(specialHandlers, key)) {
2093
+ return specialHandlers[key];
2106
2094
  }
2095
+ observeTargetKey(target, key, callback);
2096
+ return possiblyReactive(target[key], callback);
2097
+ },
2098
+ });
2099
+ }
2100
+
2101
+ /**
2102
+ * Creates a batched version of a callback so that all calls to it in the same
2103
+ * microtick will only call the original callback once.
2104
+ *
2105
+ * @param callback the callback to batch
2106
+ * @returns a batched version of the original callback
2107
+ */
2108
+ function batched(callback) {
2109
+ let called = false;
2110
+ return async () => {
2111
+ // This await blocks all calls to the callback here, then releases them sequentially
2112
+ // in the next microtick. This line decides the granularity of the batch.
2113
+ await Promise.resolve();
2114
+ if (!called) {
2115
+ called = true;
2116
+ // wait for all calls in this microtick to fall through before resetting "called"
2117
+ // so that only the first call to the batched function calls the original callback.
2118
+ // Schedule this before calling the callback so that calls to the batched function
2119
+ // within the callback will proceed only after resetting called to false, and have
2120
+ // a chance to execute the callback again
2121
+ Promise.resolve().then(() => (called = false));
2122
+ callback();
2107
2123
  }
2108
- catch (e) {
2109
- this.locked = false;
2110
- handleError({ fiber: current || this, error: e });
2111
- }
2124
+ };
2125
+ }
2126
+ function validateTarget(target) {
2127
+ if (!(target instanceof HTMLElement)) {
2128
+ throw new Error("Cannot mount component: the target is not a valid DOM element");
2112
2129
  }
2113
- setCounter(newValue) {
2114
- this.counter = newValue;
2115
- if (newValue === 0) {
2116
- this.node.app.scheduler.flush();
2117
- }
2130
+ if (!document.body.contains(target)) {
2131
+ throw new Error("Cannot mount a component on a detached dom node");
2118
2132
  }
2119
2133
  }
2120
- class MountFiber extends RootFiber {
2121
- constructor(node, target, options = {}) {
2122
- super(node, null);
2123
- this.target = target;
2124
- this.position = options.position || "last-child";
2134
+ class EventBus extends EventTarget {
2135
+ trigger(name, payload) {
2136
+ this.dispatchEvent(new CustomEvent(name, { detail: payload }));
2125
2137
  }
2126
- complete() {
2127
- let current = this;
2128
- try {
2129
- const node = this.node;
2130
- node.children = this.childrenMap;
2131
- node.app.constructor.validateTarget(this.target);
2132
- if (node.bdom) {
2133
- // this is a complicated situation: if we mount a fiber with an existing
2134
- // bdom, this means that this same fiber was already completed, mounted,
2135
- // but a crash occurred in some mounted hook. Then, it was handled and
2136
- // the new rendering is being applied.
2137
- node.updateDom();
2138
- }
2139
- else {
2140
- node.bdom = this.bdom;
2141
- if (this.position === "last-child" || this.target.childNodes.length === 0) {
2142
- mount$1(node.bdom, this.target);
2143
- }
2144
- else {
2145
- const firstChild = this.target.childNodes[0];
2146
- mount$1(node.bdom, this.target, firstChild);
2147
- }
2148
- }
2149
- // unregistering the fiber before mounted since it can do another render
2150
- // and that the current rendering is obviously completed
2151
- node.fiber = null;
2152
- node.status = 1 /* MOUNTED */;
2153
- this.appliedToDom = true;
2154
- let mountedFibers = this.mounted;
2155
- while ((current = mountedFibers.pop())) {
2156
- if (current.appliedToDom) {
2157
- for (let cb of current.node.mounted) {
2158
- cb();
2159
- }
2160
- }
2161
- }
2138
+ }
2139
+ function whenReady(fn) {
2140
+ return new Promise(function (resolve) {
2141
+ if (document.readyState !== "loading") {
2142
+ resolve(true);
2162
2143
  }
2163
- catch (e) {
2164
- handleError({ fiber: current, error: e });
2144
+ else {
2145
+ document.addEventListener("DOMContentLoaded", resolve, false);
2165
2146
  }
2147
+ }).then(fn || function () { });
2148
+ }
2149
+ async function loadFile(url) {
2150
+ const result = await fetch(url);
2151
+ if (!result.ok) {
2152
+ throw new Error("Error while fetching xml templates");
2166
2153
  }
2154
+ return await result.text();
2155
+ }
2156
+ /*
2157
+ * This class just transports the fact that a string is safe
2158
+ * to be injected as HTML. Overriding a JS primitive is quite painful though
2159
+ * so we need to redfine toString and valueOf.
2160
+ */
2161
+ class Markup extends String {
2162
+ }
2163
+ /*
2164
+ * Marks a value as safe, that is, a value that can be injected as HTML directly.
2165
+ * It should be used to wrap the value passed to a t-out directive to allow a raw rendering.
2166
+ */
2167
+ function markup(value) {
2168
+ return new Markup(value);
2167
2169
  }
2168
2170
 
2169
2171
  let currentNode = null;
@@ -2178,8 +2180,6 @@ function useComponent() {
2178
2180
  }
2179
2181
  /**
2180
2182
  * Apply default props (only top level).
2181
- *
2182
- * Note that this method does modify in place the props
2183
2183
  */
2184
2184
  function applyDefaultProps(props, defaultProps) {
2185
2185
  for (let propName in defaultProps) {
@@ -2213,61 +2213,6 @@ function useState(state) {
2213
2213
  }
2214
2214
  return reactive(state, render);
2215
2215
  }
2216
- function arePropsDifferent(props1, props2) {
2217
- for (let k in props1) {
2218
- const prop1 = props1[k] && typeof props1[k] === "object" ? toRaw(props1[k]) : props1[k];
2219
- const prop2 = props2[k] && typeof props2[k] === "object" ? toRaw(props2[k]) : props2[k];
2220
- if (prop1 !== prop2) {
2221
- return true;
2222
- }
2223
- }
2224
- return Object.keys(props1).length !== Object.keys(props2).length;
2225
- }
2226
- function component(name, props, key, ctx, parent) {
2227
- let node = ctx.children[key];
2228
- let isDynamic = typeof name !== "string";
2229
- if (node && node.status === 2 /* DESTROYED */) {
2230
- node = undefined;
2231
- }
2232
- if (isDynamic && node && node.component.constructor !== name) {
2233
- node = undefined;
2234
- }
2235
- const parentFiber = ctx.fiber;
2236
- if (node) {
2237
- let shouldRender = node.forceNextRender;
2238
- if (shouldRender) {
2239
- node.forceNextRender = false;
2240
- }
2241
- else {
2242
- const currentProps = node.component.props;
2243
- shouldRender = parentFiber.deep || arePropsDifferent(currentProps, props);
2244
- }
2245
- if (shouldRender) {
2246
- node.updateAndRender(props, parentFiber);
2247
- }
2248
- }
2249
- else {
2250
- // new component
2251
- let C;
2252
- if (isDynamic) {
2253
- C = name;
2254
- }
2255
- else {
2256
- C = parent.constructor.components[name];
2257
- if (!C) {
2258
- throw new Error(`Cannot find the definition of component "${name}"`);
2259
- }
2260
- else if (!(C.prototype instanceof Component)) {
2261
- throw new Error(`"${name}" is not a Component. It must inherit from the Component class`);
2262
- }
2263
- }
2264
- node = new ComponentNode(C, props, ctx.app, ctx, key);
2265
- ctx.children[key] = node;
2266
- node.initiateRender(new Fiber(node, parentFiber));
2267
- }
2268
- parentFiber.childrenMap[key] = node;
2269
- return node;
2270
- }
2271
2216
  class ComponentNode {
2272
2217
  constructor(C, props, app, parent, parentKey) {
2273
2218
  this.fiber = null;
@@ -2286,9 +2231,11 @@ class ComponentNode {
2286
2231
  currentNode = this;
2287
2232
  this.app = app;
2288
2233
  this.parent = parent;
2234
+ this.props = props;
2289
2235
  this.parentKey = parentKey;
2290
2236
  this.level = parent ? parent.level + 1 : 0;
2291
2237
  const defaultProps = C.defaultProps;
2238
+ props = Object.assign({}, props);
2292
2239
  if (defaultProps) {
2293
2240
  applyDefaultProps(props, defaultProps);
2294
2241
  }
@@ -2401,6 +2348,8 @@ class ComponentNode {
2401
2348
  this.status = 2 /* DESTROYED */;
2402
2349
  }
2403
2350
  async updateAndRender(props, parentFiber) {
2351
+ const rawProps = props;
2352
+ props = Object.assign({}, props);
2404
2353
  // update
2405
2354
  const fiber = makeChildFiber(this, parentFiber);
2406
2355
  this.fiber = fiber;
@@ -2423,6 +2372,7 @@ class ComponentNode {
2423
2372
  return;
2424
2373
  }
2425
2374
  component.props = props;
2375
+ this.props = rawProps;
2426
2376
  fiber.render();
2427
2377
  const parentRoot = parentFiber.root;
2428
2378
  if (this.willPatch.length) {
@@ -2485,10 +2435,15 @@ class ComponentNode {
2485
2435
  }
2486
2436
  }
2487
2437
  _patch() {
2488
- const hasChildren = Object.keys(this.children).length > 0;
2489
- this.children = this.fiber.childrenMap;
2490
- this.bdom.patch(this.fiber.bdom, hasChildren);
2491
- this.fiber.appliedToDom = true;
2438
+ let hasChildren = false;
2439
+ for (let _k in this.children) {
2440
+ hasChildren = true;
2441
+ break;
2442
+ }
2443
+ const fiber = this.fiber;
2444
+ this.children = fiber.childrenMap;
2445
+ this.bdom.patch(fiber.bdom, hasChildren);
2446
+ fiber.appliedToDom = true;
2492
2447
  this.fiber = null;
2493
2448
  }
2494
2449
  beforeRemove() {
@@ -2616,6 +2571,19 @@ function onError(callback) {
2616
2571
  handlers.push(callback.bind(node.component));
2617
2572
  }
2618
2573
 
2574
+ class Component {
2575
+ constructor(props, env, node) {
2576
+ this.props = props;
2577
+ this.env = env;
2578
+ this.__owl__ = node;
2579
+ }
2580
+ setup() { }
2581
+ render(deep = false) {
2582
+ this.__owl__.render(deep === true);
2583
+ }
2584
+ }
2585
+ Component.template = "";
2586
+
2619
2587
  const VText = text("").constructor;
2620
2588
  class VPortal extends VText {
2621
2589
  constructor(selector, realBDom) {
@@ -2694,6 +2662,7 @@ Portal.props = {
2694
2662
  // -----------------------------------------------------------------------------
2695
2663
  const isUnionType = (t) => Array.isArray(t);
2696
2664
  const isBaseType = (t) => typeof t !== "object";
2665
+ const isValueType = (t) => typeof t === "object" && t && "value" in t;
2697
2666
  function isOptional(t) {
2698
2667
  return typeof t === "object" && "optional" in t ? t.optional || false : false;
2699
2668
  }
@@ -2707,6 +2676,9 @@ function describe(info) {
2707
2676
  else if (isUnionType(info)) {
2708
2677
  return info.map(describe).join(" or ");
2709
2678
  }
2679
+ else if (isValueType(info)) {
2680
+ return String(info.value);
2681
+ }
2710
2682
  if ("element" in info) {
2711
2683
  return `list of ${describe({ type: info.element, optional: false })}s`;
2712
2684
  }
@@ -2792,6 +2764,9 @@ function validateType(key, value, descr) {
2792
2764
  else if (isBaseType(descr)) {
2793
2765
  return validateBaseType(key, value, descr);
2794
2766
  }
2767
+ else if (isValueType(descr)) {
2768
+ return value === descr.value ? null : `'${key}' is not equal to '${descr.value}'`;
2769
+ }
2795
2770
  else if (isUnionType(descr)) {
2796
2771
  let validDescr = descr.find((p) => !validateType(key, value, p));
2797
2772
  return validDescr ? null : `'${key}' is not a ${describe(descr)}`;
@@ -2820,6 +2795,7 @@ function validateType(key, value, descr) {
2820
2795
  return result;
2821
2796
  }
2822
2797
 
2798
+ const ObjectCreate = Object.create;
2823
2799
  /**
2824
2800
  * This file contains utility functions that will be injected in each template,
2825
2801
  * to perform various useful tasks in the compiled code.
@@ -2831,7 +2807,7 @@ function callSlot(ctx, parent, key, name, dynamic, extra, defaultContent) {
2831
2807
  key = key + "__slot_" + name;
2832
2808
  const slots = ctx.props.slots || {};
2833
2809
  const { __render, __ctx, __scope } = slots[name] || {};
2834
- const slotScope = Object.create(__ctx || {});
2810
+ const slotScope = ObjectCreate(__ctx || {});
2835
2811
  if (__scope) {
2836
2812
  slotScope[__scope] = extra;
2837
2813
  }
@@ -2851,7 +2827,7 @@ function callSlot(ctx, parent, key, name, dynamic, extra, defaultContent) {
2851
2827
  }
2852
2828
  function capture(ctx) {
2853
2829
  const component = ctx.__owl__.component;
2854
- const result = Object.create(component);
2830
+ const result = ObjectCreate(component);
2855
2831
  for (let k in ctx) {
2856
2832
  result[k] = ctx[k];
2857
2833
  }
@@ -2904,13 +2880,14 @@ function shallowEqual(l1, l2) {
2904
2880
  return true;
2905
2881
  }
2906
2882
  class LazyValue {
2907
- constructor(fn, ctx, node) {
2883
+ constructor(fn, ctx, component, node) {
2908
2884
  this.fn = fn;
2909
2885
  this.ctx = capture(ctx);
2886
+ this.component = component;
2910
2887
  this.node = node;
2911
2888
  }
2912
2889
  evaluate() {
2913
- return this.fn(this.ctx, this.node);
2890
+ return this.fn.call(this.component, this.ctx, this.node);
2914
2891
  }
2915
2892
  toString() {
2916
2893
  return this.evaluate().toString();
@@ -2919,43 +2896,56 @@ class LazyValue {
2919
2896
  /*
2920
2897
  * Safely outputs `value` as a block depending on the nature of `value`
2921
2898
  */
2922
- function safeOutput(value) {
2923
- if (!value) {
2924
- return value;
2899
+ function safeOutput(value, defaultValue) {
2900
+ if (value === undefined) {
2901
+ return defaultValue ? toggler("default", defaultValue) : toggler("undefined", text(""));
2925
2902
  }
2926
2903
  let safeKey;
2927
2904
  let block;
2928
- if (value instanceof Markup) {
2929
- safeKey = `string_safe`;
2930
- block = html(value);
2931
- }
2932
- else if (value instanceof LazyValue) {
2933
- safeKey = `lazy_value`;
2934
- block = value.evaluate();
2935
- }
2936
- else if (value instanceof String || typeof value === "string") {
2937
- safeKey = "string_unsafe";
2938
- block = text(value);
2939
- }
2940
- else {
2941
- // Assuming it is a block
2942
- safeKey = "block_safe";
2943
- block = value;
2905
+ switch (typeof value) {
2906
+ case "object":
2907
+ if (value instanceof Markup) {
2908
+ safeKey = `string_safe`;
2909
+ block = html(value);
2910
+ }
2911
+ else if (value instanceof LazyValue) {
2912
+ safeKey = `lazy_value`;
2913
+ block = value.evaluate();
2914
+ }
2915
+ else if (value instanceof String) {
2916
+ safeKey = "string_unsafe";
2917
+ block = text(value);
2918
+ }
2919
+ else {
2920
+ // Assuming it is a block
2921
+ safeKey = "block_safe";
2922
+ block = value;
2923
+ }
2924
+ break;
2925
+ case "string":
2926
+ safeKey = "string_unsafe";
2927
+ block = text(value);
2928
+ break;
2929
+ default:
2930
+ safeKey = "string_unsafe";
2931
+ block = text(String(value));
2944
2932
  }
2945
2933
  return toggler(safeKey, block);
2946
2934
  }
2947
2935
  let boundFunctions = new WeakMap();
2936
+ const WeakMapGet = WeakMap.prototype.get;
2937
+ const WeakMapSet = WeakMap.prototype.set;
2948
2938
  function bind(ctx, fn) {
2949
2939
  let component = ctx.__owl__.component;
2950
- let boundFnMap = boundFunctions.get(component);
2940
+ let boundFnMap = WeakMapGet.call(boundFunctions, component);
2951
2941
  if (!boundFnMap) {
2952
2942
  boundFnMap = new WeakMap();
2953
- boundFunctions.set(component, boundFnMap);
2943
+ WeakMapSet.call(boundFunctions, component, boundFnMap);
2954
2944
  }
2955
- let boundFn = boundFnMap.get(fn);
2945
+ let boundFn = WeakMapGet.call(boundFnMap, fn);
2956
2946
  if (!boundFn) {
2957
2947
  boundFn = fn.bind(component);
2958
- boundFnMap.set(fn, boundFn);
2948
+ WeakMapSet.call(boundFnMap, fn, boundFn);
2959
2949
  }
2960
2950
  return boundFn;
2961
2951
  }
@@ -3031,7 +3021,7 @@ const helpers = {
3031
3021
  markRaw,
3032
3022
  };
3033
3023
 
3034
- const bdom = { text, createBlock, list, multi, html, toggler, component, comment };
3024
+ const bdom = { text, createBlock, list, multi, html, toggler, comment };
3035
3025
  function parseXML$1(xml) {
3036
3026
  const parser = new DOMParser();
3037
3027
  const doc = parser.parseFromString(xml, "text/xml");
@@ -3194,7 +3184,7 @@ const STATIC_TOKEN_MAP = Object.assign(Object.create(null), {
3194
3184
  });
3195
3185
  // note that the space after typeof is relevant. It makes sure that the formatted
3196
3186
  // expression has a space after typeof. Currently we don't support delete and void
3197
- const OPERATORS = "...,.,===,==,+,!==,!=,!,||,&&,>=,>,<=,<,?,-,*,/,%,typeof ,=>,=,;,in ,new ".split(",");
3187
+ const OPERATORS = "...,.,===,==,+,!==,!=,!,||,&&,>=,>,<=,<,?,-,*,/,%,typeof ,=>,=,;,in ,new ,|,&,^,~".split(",");
3198
3188
  let tokenizeString = function (expr) {
3199
3189
  let s = expr[0];
3200
3190
  let start = s;
@@ -3442,15 +3432,17 @@ function compileExpr(expr) {
3442
3432
  .map((t) => paddedValues.get(t.value) || t.value)
3443
3433
  .join("");
3444
3434
  }
3445
- const INTERP_REGEXP = /\{\{.*?\}\}/g;
3446
- const INTERP_GROUP_REGEXP = /\{\{.*?\}\}/g;
3447
- function interpolate(s) {
3435
+ const INTERP_REGEXP = /\{\{.*?\}\}|\#\{.*?\}/g;
3436
+ function replaceDynamicParts(s, replacer) {
3448
3437
  let matches = s.match(INTERP_REGEXP);
3449
3438
  if (matches && matches[0].length === s.length) {
3450
- return `(${compileExpr(s.slice(2, -2))})`;
3439
+ return `(${replacer(s.slice(2, matches[0][0] === "{" ? -2 : -1))})`;
3451
3440
  }
3452
- let r = s.replace(INTERP_GROUP_REGEXP, (s) => "${" + compileExpr(s.slice(2, -2)) + "}");
3441
+ let r = s.replace(INTERP_REGEXP, (s) => "${" + replacer(s.slice(2, s[0] === "{" ? -2 : -1)) + "}");
3453
3442
  return "`" + r + "`";
3443
+ }
3444
+ function interpolate(s) {
3445
+ return replaceDynamicParts(s, compileExpr);
3454
3446
  }
3455
3447
 
3456
3448
  // using a non-html document so that <inner/outer>HTML serializes as XML instead
@@ -3625,9 +3617,7 @@ class CodeGenerator {
3625
3617
  tKeyExpr: null,
3626
3618
  });
3627
3619
  // define blocks and utility functions
3628
- let mainCode = [
3629
- ` let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;`,
3630
- ];
3620
+ let mainCode = [` let { text, createBlock, list, multi, html, toggler, comment } = bdom;`];
3631
3621
  if (this.helpers.size) {
3632
3622
  mainCode.push(`let { ${[...this.helpers].join(", ")} } = helpers;`);
3633
3623
  }
@@ -3643,6 +3633,7 @@ class CodeGenerator {
3643
3633
  for (let block of this.blocks) {
3644
3634
  if (block.dom) {
3645
3635
  let xmlString = block.asXmlString();
3636
+ xmlString = xmlString.replace(/`/g, "\\`");
3646
3637
  if (block.dynamicTagName) {
3647
3638
  xmlString = xmlString.replace(/^<\w+/, `<\${tag || '${block.dom.nodeName}'}`);
3648
3639
  xmlString = xmlString.replace(/\w+>$/, `\${tag || '${block.dom.nodeName}'}>`);
@@ -3950,8 +3941,8 @@ class CodeGenerator {
3950
3941
  this.target.hasRef = true;
3951
3942
  const isDynamic = INTERP_REGEXP.test(ast.ref);
3952
3943
  if (isDynamic) {
3953
- const str = ast.ref.replace(INTERP_REGEXP, (expr) => "${" + this.captureExpression(expr.slice(2, -2), true) + "}");
3954
- const idx = block.insertData(`(el) => refs[\`${str}\`] = el`, "ref");
3944
+ const str = replaceDynamicParts(ast.ref, (expr) => this.captureExpression(expr, true));
3945
+ const idx = block.insertData(`(el) => refs[${str}] = el`, "ref");
3955
3946
  attrs["block-ref"] = String(idx);
3956
3947
  }
3957
3948
  else {
@@ -4080,16 +4071,24 @@ class CodeGenerator {
4080
4071
  this.insertAnchor(block);
4081
4072
  }
4082
4073
  block = this.createBlock(block, "html", ctx);
4083
- this.helpers.add(ast.expr === "0" ? "zero" : "safeOutput");
4084
- let expr = ast.expr === "0" ? "ctx[zero]" : `safeOutput(${compileExpr(ast.expr)})`;
4085
- if (ast.body) {
4086
- const nextId = BlockDescription.nextBlockId;
4074
+ let blockStr;
4075
+ if (ast.expr === "0") {
4076
+ this.helpers.add("zero");
4077
+ blockStr = `ctx[zero]`;
4078
+ }
4079
+ else if (ast.body) {
4080
+ let bodyValue = null;
4081
+ bodyValue = BlockDescription.nextBlockId;
4087
4082
  const subCtx = createContext(ctx);
4088
4083
  this.compileAST({ type: 3 /* Multi */, content: ast.body }, subCtx);
4089
- this.helpers.add("withDefault");
4090
- expr = `withDefault(${expr}, b${nextId})`;
4084
+ this.helpers.add("safeOutput");
4085
+ blockStr = `safeOutput(${compileExpr(ast.expr)}, b${bodyValue})`;
4086
+ }
4087
+ else {
4088
+ this.helpers.add("safeOutput");
4089
+ blockStr = `safeOutput(${compileExpr(ast.expr)})`;
4091
4090
  }
4092
- this.insertBlock(`${expr}`, block, ctx);
4091
+ this.insertBlock(blockStr, block, ctx);
4093
4092
  }
4094
4093
  compileTIf(ast, ctx, nextNode) {
4095
4094
  let { block, forceNewBlock, index } = ctx;
@@ -4282,16 +4281,21 @@ class CodeGenerator {
4282
4281
  }
4283
4282
  compileTCall(ast, ctx) {
4284
4283
  let { block, forceNewBlock } = ctx;
4284
+ let ctxVar = ctx.ctxVar || "ctx";
4285
+ if (ast.context) {
4286
+ ctxVar = generateId("ctx");
4287
+ this.addLine(`let ${ctxVar} = ${compileExpr(ast.context)};`);
4288
+ }
4285
4289
  if (ast.body) {
4286
- this.addLine(`ctx = Object.create(ctx);`);
4287
- this.addLine(`ctx[isBoundary] = 1;`);
4290
+ this.addLine(`${ctxVar} = Object.create(${ctxVar});`);
4291
+ this.addLine(`${ctxVar}[isBoundary] = 1;`);
4288
4292
  this.helpers.add("isBoundary");
4289
4293
  const nextId = BlockDescription.nextBlockId;
4290
- const subCtx = createContext(ctx, { preventRoot: true });
4294
+ const subCtx = createContext(ctx, { preventRoot: true, ctxVar });
4291
4295
  this.compileAST({ type: 3 /* Multi */, content: ast.body }, subCtx);
4292
4296
  if (nextId !== BlockDescription.nextBlockId) {
4293
4297
  this.helpers.add("zero");
4294
- this.addLine(`ctx[zero] = b${nextId};`);
4298
+ this.addLine(`${ctxVar}[zero] = b${nextId};`);
4295
4299
  }
4296
4300
  }
4297
4301
  const isDynamic = INTERP_REGEXP.test(ast.name);
@@ -4309,7 +4313,7 @@ class CodeGenerator {
4309
4313
  }
4310
4314
  this.define(templateVar, subTemplate);
4311
4315
  block = this.createBlock(block, "multi", ctx);
4312
- this.insertBlock(`call(this, ${templateVar}, ctx, node, ${key})`, block, {
4316
+ this.insertBlock(`call(this, ${templateVar}, ${ctxVar}, node, ${key})`, block, {
4313
4317
  ...ctx,
4314
4318
  forceNewBlock: !block,
4315
4319
  });
@@ -4318,13 +4322,13 @@ class CodeGenerator {
4318
4322
  const id = generateId(`callTemplate_`);
4319
4323
  this.staticDefs.push({ id, expr: `app.getTemplate(${subTemplate})` });
4320
4324
  block = this.createBlock(block, "multi", ctx);
4321
- this.insertBlock(`${id}.call(this, ctx, node, ${key})`, block, {
4325
+ this.insertBlock(`${id}.call(this, ${ctxVar}, node, ${key})`, block, {
4322
4326
  ...ctx,
4323
4327
  forceNewBlock: !block,
4324
4328
  });
4325
4329
  }
4326
4330
  if (ast.body && !ctx.isLast) {
4327
- this.addLine(`ctx = ctx.__proto__;`);
4331
+ this.addLine(`${ctxVar} = ${ctxVar}.__proto__;`);
4328
4332
  }
4329
4333
  }
4330
4334
  compileTCallBlock(ast, ctx) {
@@ -4345,7 +4349,7 @@ class CodeGenerator {
4345
4349
  this.helpers.add("LazyValue");
4346
4350
  const bodyAst = { type: 3 /* Multi */, content: ast.body };
4347
4351
  const name = this.compileInNewTarget("value", bodyAst, ctx);
4348
- let value = `new LazyValue(${name}, ctx, node)`;
4352
+ let value = `new LazyValue(${name}, ctx, this, node)`;
4349
4353
  value = ast.value ? (value ? `withDefault(${expr}, ${value})` : expr) : value;
4350
4354
  this.addLine(`ctx[\`${ast.name}\`] = ${value};`);
4351
4355
  }
@@ -4363,7 +4367,7 @@ class CodeGenerator {
4363
4367
  value = expr;
4364
4368
  }
4365
4369
  this.helpers.add("setContextValue");
4366
- this.addLine(`setContextValue(ctx, "${ast.name}", ${value});`);
4370
+ this.addLine(`setContextValue(${ctx.ctxVar || "ctx"}, "${ast.name}", ${value});`);
4367
4371
  }
4368
4372
  }
4369
4373
  generateComponentKey() {
@@ -4401,21 +4405,20 @@ class CodeGenerator {
4401
4405
  return `${name}: ${value || undefined}`;
4402
4406
  }
4403
4407
  formatPropObject(obj) {
4404
- const params = [];
4405
- for (const [n, v] of Object.entries(obj)) {
4406
- params.push(this.formatProp(n, v));
4408
+ return Object.entries(obj).map(([k, v]) => this.formatProp(k, v));
4409
+ }
4410
+ getPropString(props, dynProps) {
4411
+ let propString = `{${props.join(",")}}`;
4412
+ if (dynProps) {
4413
+ propString = `Object.assign({}, ${compileExpr(dynProps)}${props.length ? ", " + propString : ""})`;
4407
4414
  }
4408
- return params.join(", ");
4415
+ return propString;
4409
4416
  }
4410
4417
  compileComponent(ast, ctx) {
4411
4418
  let { block } = ctx;
4412
4419
  // props
4413
4420
  const hasSlotsProp = "slots" in (ast.props || {});
4414
- const props = [];
4415
- const propExpr = this.formatPropObject(ast.props || {});
4416
- if (propExpr) {
4417
- props.push(propExpr);
4418
- }
4421
+ const props = ast.props ? this.formatPropObject(ast.props) : [];
4419
4422
  // slots
4420
4423
  let slotDef = "";
4421
4424
  if (ast.slots) {
@@ -4438,7 +4441,7 @@ class CodeGenerator {
4438
4441
  params.push(`__scope: "${scope}"`);
4439
4442
  }
4440
4443
  if (ast.slots[slotName].attrs) {
4441
- params.push(this.formatPropObject(ast.slots[slotName].attrs));
4444
+ params.push(...this.formatPropObject(ast.slots[slotName].attrs));
4442
4445
  }
4443
4446
  const slotInfo = `{${params.join(", ")}}`;
4444
4447
  slotStr.push(`'${slotName}': ${slotInfo}`);
@@ -4449,11 +4452,7 @@ class CodeGenerator {
4449
4452
  this.helpers.add("markRaw");
4450
4453
  props.push(`slots: markRaw(${slotDef})`);
4451
4454
  }
4452
- const propStr = `{${props.join(",")}}`;
4453
- let propString = propStr;
4454
- if (ast.dynamicProps) {
4455
- propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)}${props.length ? ", " + propStr : ""})`;
4456
- }
4455
+ let propString = this.getPropString(props, ast.dynamicProps);
4457
4456
  let propVar;
4458
4457
  if ((slotDef && (ast.dynamicProps || hasSlotsProp)) || this.dev) {
4459
4458
  propVar = generateId("props");
@@ -4485,8 +4484,12 @@ class CodeGenerator {
4485
4484
  if (ctx.tKeyExpr) {
4486
4485
  keyArg = `${ctx.tKeyExpr} + ${keyArg}`;
4487
4486
  }
4488
- const blockArgs = `${expr}, ${propString}, ${keyArg}, node, ctx`;
4489
- let blockExpr = `component(${blockArgs})`;
4487
+ let id = generateId("comp");
4488
+ this.staticDefs.push({
4489
+ id,
4490
+ expr: `app.createComponent(${ast.isDynamic ? null : expr}, ${!ast.isDynamic}, ${!!ast.slots}, ${!!ast.dynamicProps}, ${!ast.props && !ast.dynamicProps})`,
4491
+ });
4492
+ let blockExpr = `${id}(${propString}, ${keyArg}, node, this, ${ast.isDynamic ? expr : null})`;
4490
4493
  if (ast.isDynamic) {
4491
4494
  blockExpr = `toggler(${expr}, ${blockExpr})`;
4492
4495
  }
@@ -4525,7 +4528,12 @@ class CodeGenerator {
4525
4528
  else {
4526
4529
  slotName = "'" + ast.name + "'";
4527
4530
  }
4528
- const scope = ast.attrs ? `{${this.formatPropObject(ast.attrs)}}` : null;
4531
+ const dynProps = ast.attrs ? ast.attrs["t-props"] : null;
4532
+ if (ast.attrs) {
4533
+ delete ast.attrs["t-props"];
4534
+ }
4535
+ const props = ast.attrs ? this.formatPropObject(ast.attrs) : [];
4536
+ const scope = this.getPropString(props, dynProps);
4529
4537
  if (ast.defaultContent) {
4530
4538
  const name = this.compileInNewTarget("defaultContent", ast.defaultContent, ctx);
4531
4539
  blockString = `callSlot(ctx, node, key, ${slotName}, ${dynamic}, ${scope}, ${name})`;
@@ -4566,10 +4574,15 @@ class CodeGenerator {
4566
4574
  if (this.target.loopLevel || !this.hasSafeContext) {
4567
4575
  ctxStr = generateId("ctx");
4568
4576
  this.helpers.add("capture");
4569
- this.define(ctxStr, `capture(ctx);`);
4577
+ this.define(ctxStr, `capture(ctx)`);
4570
4578
  }
4579
+ let id = generateId("comp");
4580
+ this.staticDefs.push({
4581
+ id,
4582
+ expr: `app.createComponent(null, false, true, false, false)`,
4583
+ });
4571
4584
  const target = compileExpr(ast.target);
4572
- const blockString = `component(Portal, {target: ${target},slots: {'default': {__render: ${name}, __ctx: ${ctxStr}}}}, key + \`${key}\`, node, ctx)`;
4585
+ const blockString = `${id}({target: ${target},slots: {'default': {__render: ${name}, __ctx: ${ctxStr}}}}, key + \`${key}\`, node, ctx, Portal)`;
4573
4586
  if (block) {
4574
4587
  this.insertAnchor(block);
4575
4588
  }
@@ -4909,10 +4922,12 @@ function parseTCall(node, ctx) {
4909
4922
  return null;
4910
4923
  }
4911
4924
  const subTemplate = node.getAttribute("t-call");
4925
+ const context = node.getAttribute("t-call-context");
4912
4926
  node.removeAttribute("t-call");
4927
+ node.removeAttribute("t-call-context");
4913
4928
  if (node.tagName !== "t") {
4914
4929
  const ast = parseNode(node, ctx);
4915
- const tcall = { type: 7 /* TCall */, name: subTemplate, body: null };
4930
+ const tcall = { type: 7 /* TCall */, name: subTemplate, body: null, context };
4916
4931
  if (ast && ast.type === 2 /* DomNode */) {
4917
4932
  ast.content = [tcall];
4918
4933
  return ast;
@@ -4929,6 +4944,7 @@ function parseTCall(node, ctx) {
4929
4944
  type: 7 /* TCall */,
4930
4945
  name: subTemplate,
4931
4946
  body: body.length ? body : null,
4947
+ context,
4932
4948
  };
4933
4949
  }
4934
4950
  // -----------------------------------------------------------------------------
@@ -5512,6 +5528,55 @@ class App extends TemplateSet {
5512
5528
  this.root.destroy();
5513
5529
  }
5514
5530
  }
5531
+ createComponent(name, isStatic, hasSlotsProp, hasDynamicPropList, hasNoProp) {
5532
+ const isDynamic = !isStatic;
5533
+ function _arePropsDifferent(props1, props2) {
5534
+ for (let k in props1) {
5535
+ if (props1[k] !== props2[k]) {
5536
+ return true;
5537
+ }
5538
+ }
5539
+ return hasDynamicPropList && Object.keys(props1).length !== Object.keys(props2).length;
5540
+ }
5541
+ const arePropsDifferent = hasSlotsProp
5542
+ ? (_1, _2) => true
5543
+ : hasNoProp
5544
+ ? (_1, _2) => false
5545
+ : _arePropsDifferent;
5546
+ const updateAndRender = ComponentNode.prototype.updateAndRender;
5547
+ const initiateRender = ComponentNode.prototype.initiateRender;
5548
+ return (props, key, ctx, parent, C) => {
5549
+ let children = ctx.children;
5550
+ let node = children[key];
5551
+ if (isDynamic && node && node.component.constructor !== C) {
5552
+ node = undefined;
5553
+ }
5554
+ const parentFiber = ctx.fiber;
5555
+ if (node) {
5556
+ if (arePropsDifferent(node.props, props) || parentFiber.deep || node.forceNextRender) {
5557
+ node.forceNextRender = false;
5558
+ updateAndRender.call(node, props, parentFiber);
5559
+ }
5560
+ }
5561
+ else {
5562
+ // new component
5563
+ if (isStatic) {
5564
+ C = parent.constructor.components[name];
5565
+ if (!C) {
5566
+ throw new Error(`Cannot find the definition of component "${name}"`);
5567
+ }
5568
+ else if (!(C.prototype instanceof Component)) {
5569
+ throw new Error(`"${name}" is not a Component. It must inherit from the Component class`);
5570
+ }
5571
+ }
5572
+ node = new ComponentNode(C, props, this, ctx, key);
5573
+ children[key] = node;
5574
+ initiateRender.call(node, new Fiber(node, parentFiber));
5575
+ }
5576
+ parentFiber.childrenMap[key] = node;
5577
+ return node;
5578
+ };
5579
+ }
5515
5580
  }
5516
5581
  App.validateTarget = validateTarget;
5517
5582
  async function mount(C, target, config = {}) {
@@ -5692,7 +5757,7 @@ exports.whenReady = whenReady;
5692
5757
  exports.xml = xml;
5693
5758
 
5694
5759
 
5695
- __info__.version = '2.0.0-beta-8';
5696
- __info__.date = '2022-05-31T12:25:19.319Z';
5697
- __info__.hash = 'b56a9c2';
5760
+ __info__.version = '2.0.0-beta-11';
5761
+ __info__.date = '2022-06-28T13:28:26.775Z';
5762
+ __info__.hash = '76c389a';
5698
5763
  __info__.url = 'https://github.com/odoo/owl';