@odoo/owl 2.0.0-alpha.1 → 2.0.0-beta-4

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
@@ -209,7 +209,8 @@ function updateClass(val, oldVal) {
209
209
  }
210
210
  function makePropSetter(name) {
211
211
  return function setProp(value) {
212
- this[name] = value;
212
+ // support 0, fallback to empty string for other falsy values
213
+ this[name] = value === 0 ? 0 : value || "";
213
214
  };
214
215
  }
215
216
  function isProp(tag, key) {
@@ -263,10 +264,14 @@ function createElementHandler(evName, capture = false) {
263
264
  this[eventKey] = data;
264
265
  this.addEventListener(evName, listener, { capture });
265
266
  }
267
+ function remove() {
268
+ delete this[eventKey];
269
+ this.removeEventListener(evName, listener, { capture });
270
+ }
266
271
  function update(data) {
267
272
  this[eventKey] = data;
268
273
  }
269
- return { setup, update };
274
+ return { setup, update, remove };
270
275
  }
271
276
  // Synthetic handler: a form of event delegation that allows placing only one
272
277
  // listener per event type.
@@ -283,7 +288,10 @@ function createSyntheticHandler(evName, capture = false) {
283
288
  _data[currentId] = data;
284
289
  this[eventKey] = _data;
285
290
  }
286
- return { setup, update: setup };
291
+ function remove() {
292
+ delete this[eventKey];
293
+ }
294
+ return { setup, update: setup, remove };
287
295
  }
288
296
  function nativeToSyntheticEvent(eventKey, event) {
289
297
  let dom = event.target;
@@ -508,7 +516,7 @@ const characterDataProto = CharacterData.prototype;
508
516
  const characterDataSetData = getDescriptor$1(characterDataProto, "data").set;
509
517
  const nodeGetFirstChild = getDescriptor$1(nodeProto$2, "firstChild").get;
510
518
  const nodeGetNextSibling = getDescriptor$1(nodeProto$2, "nextSibling").get;
511
- const NO_OP$1 = () => { };
519
+ const NO_OP = () => { };
512
520
  const cache$1 = {};
513
521
  /**
514
522
  * Compiling blocks is a multi-step process:
@@ -807,13 +815,12 @@ function updateCtx(ctx, tree) {
807
815
  break;
808
816
  }
809
817
  case "ref":
810
- const index = ctx.refList.push(NO_OP$1) - 1;
811
- ctx.cbRefs.push(info.idx);
818
+ const index = ctx.cbRefs.push(info.idx) - 1;
812
819
  ctx.locations.push({
813
820
  idx: info.idx,
814
821
  refIdx: info.refIdx,
815
822
  setData: makeRefSetter(index, ctx.refList),
816
- updateData: NO_OP$1,
823
+ updateData: NO_OP,
817
824
  });
818
825
  }
819
826
  }
@@ -826,10 +833,12 @@ function buildBlock(template, ctx) {
826
833
  if (ctx.cbRefs.length) {
827
834
  const cbRefs = ctx.cbRefs;
828
835
  const refList = ctx.refList;
836
+ let cbRefsNumber = cbRefs.length;
829
837
  B = class extends B {
830
838
  mount(parent, afterNode) {
839
+ refList.push(new Array(cbRefsNumber));
831
840
  super.mount(parent, afterNode);
832
- for (let cbRef of refList) {
841
+ for (let cbRef of refList.pop()) {
833
842
  cbRef();
834
843
  }
835
844
  }
@@ -991,7 +1000,7 @@ function setText(value) {
991
1000
  }
992
1001
  function makeRefSetter(index, refs) {
993
1002
  return function setRef(fn) {
994
- refs[index] = () => fn(this);
1003
+ refs[refs.length - 1][index] = () => fn(this);
995
1004
  };
996
1005
  }
997
1006
 
@@ -1277,6 +1286,75 @@ function html(str) {
1277
1286
  return new VHtml(str);
1278
1287
  }
1279
1288
 
1289
+ function createCatcher(eventsSpec) {
1290
+ let setupFns = [];
1291
+ let removeFns = [];
1292
+ for (let name in eventsSpec) {
1293
+ let index = eventsSpec[name];
1294
+ let { setup, remove } = createEventHandler(name);
1295
+ setupFns[index] = setup;
1296
+ removeFns[index] = remove;
1297
+ }
1298
+ let n = setupFns.length;
1299
+ class VCatcher {
1300
+ constructor(child, handlers) {
1301
+ this.afterNode = null;
1302
+ this.child = child;
1303
+ this.handlers = handlers;
1304
+ }
1305
+ mount(parent, afterNode) {
1306
+ this.parentEl = parent;
1307
+ this.afterNode = afterNode;
1308
+ this.child.mount(parent, afterNode);
1309
+ for (let i = 0; i < n; i++) {
1310
+ let origFn = this.handlers[i][0];
1311
+ const self = this;
1312
+ this.handlers[i][0] = function (ev) {
1313
+ const target = ev.target;
1314
+ let currentNode = self.child.firstNode();
1315
+ const afterNode = self.afterNode;
1316
+ while (currentNode !== afterNode) {
1317
+ if (currentNode.contains(target)) {
1318
+ return origFn.call(this, ev);
1319
+ }
1320
+ currentNode = currentNode.nextSibling;
1321
+ }
1322
+ };
1323
+ setupFns[i].call(parent, this.handlers[i]);
1324
+ }
1325
+ }
1326
+ moveBefore(other, afterNode) {
1327
+ this.afterNode = null;
1328
+ this.child.moveBefore(other ? other.child : null, afterNode);
1329
+ }
1330
+ patch(other, withBeforeRemove) {
1331
+ if (this === other) {
1332
+ return;
1333
+ }
1334
+ this.handlers = other.handlers;
1335
+ this.child.patch(other.child, withBeforeRemove);
1336
+ }
1337
+ beforeRemove() {
1338
+ this.child.beforeRemove();
1339
+ }
1340
+ remove() {
1341
+ for (let i = 0; i < n; i++) {
1342
+ removeFns[i].call(this.parentEl);
1343
+ }
1344
+ this.child.remove();
1345
+ }
1346
+ firstNode() {
1347
+ return this.child.firstNode();
1348
+ }
1349
+ toString() {
1350
+ return this.child.toString();
1351
+ }
1352
+ }
1353
+ return function (child, handlers) {
1354
+ return new VCatcher(child, handlers);
1355
+ };
1356
+ }
1357
+
1280
1358
  function mount$1(vnode, fixture, afterNode = null) {
1281
1359
  vnode.mount(fixture, afterNode);
1282
1360
  }
@@ -1290,143 +1368,464 @@ function remove(vnode, withBeforeRemove = false) {
1290
1368
  vnode.remove();
1291
1369
  }
1292
1370
 
1371
+ const mainEventHandler = (data, ev, currentTarget) => {
1372
+ const { data: _data, modifiers } = filterOutModifiersFromData(data);
1373
+ data = _data;
1374
+ let stopped = false;
1375
+ if (modifiers.length) {
1376
+ let selfMode = false;
1377
+ const isSelf = ev.target === currentTarget;
1378
+ for (const mod of modifiers) {
1379
+ switch (mod) {
1380
+ case "self":
1381
+ selfMode = true;
1382
+ if (isSelf) {
1383
+ continue;
1384
+ }
1385
+ else {
1386
+ return stopped;
1387
+ }
1388
+ case "prevent":
1389
+ if ((selfMode && isSelf) || !selfMode)
1390
+ ev.preventDefault();
1391
+ continue;
1392
+ case "stop":
1393
+ if ((selfMode && isSelf) || !selfMode)
1394
+ ev.stopPropagation();
1395
+ stopped = true;
1396
+ continue;
1397
+ }
1398
+ }
1399
+ }
1400
+ // If handler is empty, the array slot 0 will also be empty, and data will not have the property 0
1401
+ // We check this rather than data[0] being truthy (or typeof function) so that it crashes
1402
+ // as expected when there is a handler expression that evaluates to a falsy value
1403
+ if (Object.hasOwnProperty.call(data, 0)) {
1404
+ const handler = data[0];
1405
+ if (typeof handler !== "function") {
1406
+ throw new Error(`Invalid handler (expected a function, received: '${handler}')`);
1407
+ }
1408
+ let node = data[1] ? data[1].__owl__ : null;
1409
+ if (node ? node.status === 1 /* MOUNTED */ : true) {
1410
+ handler.call(node ? node.component : null, ev);
1411
+ }
1412
+ }
1413
+ return stopped;
1414
+ };
1415
+
1416
+ // Allows to get the target of a Reactive (used for making a new Reactive from the underlying object)
1417
+ const TARGET = Symbol("Target");
1418
+ // Escape hatch to prevent reactivity system to turn something into a reactive
1419
+ const SKIP = Symbol("Skip");
1420
+ // Special key to subscribe to, to be notified of key creation/deletion
1421
+ const KEYCHANGES = Symbol("Key changes");
1422
+ const objectToString = Object.prototype.toString;
1423
+ const objectHasOwnProperty = Object.prototype.hasOwnProperty;
1424
+ const SUPPORTED_RAW_TYPES = new Set(["Object", "Array", "Set", "Map", "WeakMap"]);
1425
+ const COLLECTION_RAWTYPES = new Set(["Set", "Map", "WeakMap"]);
1293
1426
  /**
1294
- * Apply default props (only top level).
1427
+ * extract "RawType" from strings like "[object RawType]" => this lets us ignore
1428
+ * many native objects such as Promise (whose toString is [object Promise])
1429
+ * or Date ([object Date]), while also supporting collections without using
1430
+ * instanceof in a loop
1295
1431
  *
1296
- * Note that this method does modify in place the props
1432
+ * @param obj the object to check
1433
+ * @returns the raw type of the object
1297
1434
  */
1298
- function applyDefaultProps(props, ComponentClass) {
1299
- const defaultProps = ComponentClass.defaultProps;
1300
- if (defaultProps) {
1301
- for (let propName in defaultProps) {
1302
- if (props[propName] === undefined) {
1303
- props[propName] = defaultProps[propName];
1304
- }
1305
- }
1435
+ function rawType(obj) {
1436
+ return objectToString.call(obj).slice(8, -1);
1437
+ }
1438
+ /**
1439
+ * Checks whether a given value can be made into a reactive object.
1440
+ *
1441
+ * @param value the value to check
1442
+ * @returns whether the value can be made reactive
1443
+ */
1444
+ function canBeMadeReactive(value) {
1445
+ if (typeof value !== "object") {
1446
+ return false;
1306
1447
  }
1448
+ return SUPPORTED_RAW_TYPES.has(rawType(value));
1307
1449
  }
1308
- //------------------------------------------------------------------------------
1309
- // Prop validation helper
1310
- //------------------------------------------------------------------------------
1311
- function getPropDescription(staticProps) {
1312
- if (staticProps instanceof Array) {
1313
- return Object.fromEntries(staticProps.map((p) => (p.endsWith("?") ? [p.slice(0, -1), false] : [p, true])));
1450
+ /**
1451
+ * Creates a reactive from the given object/callback if possible and returns it,
1452
+ * returns the original object otherwise.
1453
+ *
1454
+ * @param value the value make reactive
1455
+ * @returns a reactive for the given object when possible, the original otherwise
1456
+ */
1457
+ function possiblyReactive(val, cb) {
1458
+ return canBeMadeReactive(val) ? reactive(val, cb) : val;
1459
+ }
1460
+ /**
1461
+ * Mark an object or array so that it is ignored by the reactivity system
1462
+ *
1463
+ * @param value the value to mark
1464
+ * @returns the object itself
1465
+ */
1466
+ function markRaw(value) {
1467
+ value[SKIP] = true;
1468
+ return value;
1469
+ }
1470
+ /**
1471
+ * Given a reactive objet, return the raw (non reactive) underlying object
1472
+ *
1473
+ * @param value a reactive value
1474
+ * @returns the underlying value
1475
+ */
1476
+ function toRaw(value) {
1477
+ return value[TARGET] || value;
1478
+ }
1479
+ const targetToKeysToCallbacks = new WeakMap();
1480
+ /**
1481
+ * Observes a given key on a target with an callback. The callback will be
1482
+ * called when the given key changes on the target.
1483
+ *
1484
+ * @param target the target whose key should be observed
1485
+ * @param key the key to observe (or Symbol(KEYCHANGES) for key creation
1486
+ * or deletion)
1487
+ * @param callback the function to call when the key changes
1488
+ */
1489
+ function observeTargetKey(target, key, callback) {
1490
+ if (!targetToKeysToCallbacks.get(target)) {
1491
+ targetToKeysToCallbacks.set(target, new Map());
1314
1492
  }
1315
- return staticProps || { "*": true };
1493
+ const keyToCallbacks = targetToKeysToCallbacks.get(target);
1494
+ if (!keyToCallbacks.get(key)) {
1495
+ keyToCallbacks.set(key, new Set());
1496
+ }
1497
+ keyToCallbacks.get(key).add(callback);
1498
+ if (!callbacksToTargets.has(callback)) {
1499
+ callbacksToTargets.set(callback, new Set());
1500
+ }
1501
+ callbacksToTargets.get(callback).add(target);
1316
1502
  }
1317
1503
  /**
1318
- * Validate the component props (or next props) against the (static) props
1319
- * description. This is potentially an expensive operation: it may needs to
1320
- * visit recursively the props and all the children to check if they are valid.
1321
- * This is why it is only done in 'dev' mode.
1504
+ * Notify Reactives that are observing a given target that a key has changed on
1505
+ * the target.
1506
+ *
1507
+ * @param target target whose Reactives should be notified that the target was
1508
+ * changed.
1509
+ * @param key the key that changed (or Symbol `KEYCHANGES` if a key was created
1510
+ * or deleted)
1322
1511
  */
1323
- function validateProps(name, props, parent) {
1324
- const ComponentClass = typeof name !== "string"
1325
- ? name
1326
- : parent.constructor.components[name];
1327
- if (!ComponentClass) {
1328
- // this is an error, wrong component. We silently return here instead so the
1329
- // error is triggered by the usual path ('component' function)
1512
+ function notifyReactives(target, key) {
1513
+ const keyToCallbacks = targetToKeysToCallbacks.get(target);
1514
+ if (!keyToCallbacks) {
1330
1515
  return;
1331
1516
  }
1332
- applyDefaultProps(props, ComponentClass);
1333
- const defaultProps = ComponentClass.defaultProps || {};
1334
- let propsDef = getPropDescription(ComponentClass.props);
1335
- const allowAdditionalProps = "*" in propsDef;
1336
- for (let propName in propsDef) {
1337
- if (propName === "*") {
1338
- continue;
1339
- }
1340
- const propDef = propsDef[propName];
1341
- let isMandatory = !!propDef;
1342
- if (typeof propDef === "object" && "optional" in propDef) {
1343
- isMandatory = !propDef.optional;
1344
- }
1345
- if (isMandatory && propName in defaultProps) {
1346
- throw new Error(`A default value cannot be defined for a mandatory prop (name: '${propName}', component: ${ComponentClass.name})`);
1347
- }
1348
- if (props[propName] === undefined) {
1349
- if (isMandatory) {
1350
- throw new Error(`Missing props '${propName}' (component '${ComponentClass.name}')`);
1351
- }
1352
- else {
1353
- continue;
1354
- }
1355
- }
1356
- let isValid;
1357
- try {
1358
- isValid = isValidProp(props[propName], propDef);
1359
- }
1360
- catch (e) {
1361
- e.message = `Invalid prop '${propName}' in component ${ComponentClass.name} (${e.message})`;
1362
- throw e;
1363
- }
1364
- if (!isValid) {
1365
- throw new Error(`Invalid Prop '${propName}' in component '${ComponentClass.name}'`);
1366
- }
1517
+ const callbacks = keyToCallbacks.get(key);
1518
+ if (!callbacks) {
1519
+ return;
1367
1520
  }
1368
- if (!allowAdditionalProps) {
1369
- for (let propName in props) {
1370
- if (!(propName in propsDef)) {
1371
- throw new Error(`Unknown prop '${propName}' given to component '${ComponentClass.name}'`);
1372
- }
1373
- }
1521
+ // Loop on copy because clearReactivesForCallback will modify the set in place
1522
+ for (const callback of [...callbacks]) {
1523
+ clearReactivesForCallback(callback);
1524
+ callback();
1374
1525
  }
1375
1526
  }
1527
+ const callbacksToTargets = new WeakMap();
1376
1528
  /**
1377
- * Check if an invidual prop value matches its (static) prop definition
1529
+ * Clears all subscriptions of the Reactives associated with a given callback.
1530
+ *
1531
+ * @param callback the callback for which the reactives need to be cleared
1378
1532
  */
1379
- function isValidProp(prop, propDef) {
1380
- if (propDef === true) {
1381
- return true;
1533
+ function clearReactivesForCallback(callback) {
1534
+ const targetsToClear = callbacksToTargets.get(callback);
1535
+ if (!targetsToClear) {
1536
+ return;
1382
1537
  }
1383
- if (typeof propDef === "function") {
1384
- // Check if a value is constructed by some Constructor. Note that there is a
1385
- // slight abuse of language: we want to consider primitive values as well.
1386
- //
1387
- // So, even though 1 is not an instance of Number, we want to consider that
1388
- // it is valid.
1389
- if (typeof prop === "object") {
1390
- return prop instanceof propDef;
1538
+ for (const target of targetsToClear) {
1539
+ const observedKeys = targetToKeysToCallbacks.get(target);
1540
+ if (!observedKeys) {
1541
+ continue;
1391
1542
  }
1392
- return typeof prop === propDef.name.toLowerCase();
1393
- }
1394
- else if (propDef instanceof Array) {
1395
- // If this code is executed, this means that we want to check if a prop
1396
- // matches at least one of its descriptor.
1397
- let result = false;
1398
- for (let i = 0, iLen = propDef.length; i < iLen; i++) {
1399
- result = result || isValidProp(prop, propDef[i]);
1543
+ for (const callbacks of observedKeys.values()) {
1544
+ callbacks.delete(callback);
1400
1545
  }
1401
- return result;
1402
1546
  }
1403
- // propsDef is an object
1404
- if (propDef.optional && prop === undefined) {
1405
- return true;
1547
+ targetsToClear.clear();
1548
+ }
1549
+ function getSubscriptions(callback) {
1550
+ const targets = callbacksToTargets.get(callback) || [];
1551
+ return [...targets].map((target) => {
1552
+ const keysToCallbacks = targetToKeysToCallbacks.get(target);
1553
+ return {
1554
+ target,
1555
+ keys: keysToCallbacks ? [...keysToCallbacks.keys()] : [],
1556
+ };
1557
+ });
1558
+ }
1559
+ const reactiveCache = new WeakMap();
1560
+ /**
1561
+ * Creates a reactive proxy for an object. Reading data on the reactive object
1562
+ * subscribes to changes to the data. Writing data on the object will cause the
1563
+ * notify callback to be called if there are suscriptions to that data. Nested
1564
+ * objects and arrays are automatically made reactive as well.
1565
+ *
1566
+ * Whenever you are notified of a change, all subscriptions are cleared, and if
1567
+ * you would like to be notified of any further changes, you should go read
1568
+ * the underlying data again. We assume that if you don't go read it again after
1569
+ * being notified, it means that you are no longer interested in that data.
1570
+ *
1571
+ * Subscriptions:
1572
+ * + Reading a property on an object will subscribe you to changes in the value
1573
+ * of that property.
1574
+ * + Accessing an object keys (eg with Object.keys or with `for..in`) will
1575
+ * subscribe you to the creation/deletion of keys. Checking the presence of a
1576
+ * key on the object with 'in' has the same effect.
1577
+ * - getOwnPropertyDescriptor does not currently subscribe you to the property.
1578
+ * This is a choice that was made because changing a key's value will trigger
1579
+ * this trap and we do not want to subscribe by writes. This also means that
1580
+ * Object.hasOwnProperty doesn't subscribe as it goes through this trap.
1581
+ *
1582
+ * @param target the object for which to create a reactive proxy
1583
+ * @param callback the function to call when an observed property of the
1584
+ * reactive has changed
1585
+ * @returns a proxy that tracks changes to it
1586
+ */
1587
+ function reactive(target, callback = () => { }) {
1588
+ if (!canBeMadeReactive(target)) {
1589
+ throw new Error(`Cannot make the given value reactive`);
1406
1590
  }
1407
- let result = propDef.type ? isValidProp(prop, propDef.type) : true;
1408
- if (propDef.validate) {
1409
- result = result && propDef.validate(prop);
1591
+ if (SKIP in target) {
1592
+ return target;
1410
1593
  }
1411
- if (propDef.type === Array && propDef.element) {
1412
- for (let i = 0, iLen = prop.length; i < iLen; i++) {
1413
- result = result && isValidProp(prop[i], propDef.element);
1414
- }
1594
+ const originalTarget = target[TARGET];
1595
+ if (originalTarget) {
1596
+ return reactive(originalTarget, callback);
1415
1597
  }
1416
- if (propDef.type === Object && propDef.shape) {
1417
- const shape = propDef.shape;
1418
- for (let key in shape) {
1419
- result = result && isValidProp(prop[key], shape[key]);
1420
- }
1421
- if (result) {
1422
- for (let propName in prop) {
1423
- if (!(propName in shape)) {
1424
- throw new Error(`unknown prop '${propName}'`);
1425
- }
1598
+ if (!reactiveCache.has(target)) {
1599
+ reactiveCache.set(target, new Map());
1600
+ }
1601
+ const reactivesForTarget = reactiveCache.get(target);
1602
+ if (!reactivesForTarget.has(callback)) {
1603
+ const targetRawType = rawType(target);
1604
+ const handler = COLLECTION_RAWTYPES.has(targetRawType)
1605
+ ? collectionsProxyHandler(target, callback, targetRawType)
1606
+ : basicProxyHandler(callback);
1607
+ const proxy = new Proxy(target, handler);
1608
+ reactivesForTarget.set(callback, proxy);
1609
+ }
1610
+ return reactivesForTarget.get(callback);
1611
+ }
1612
+ /**
1613
+ * Creates a basic proxy handler for regular objects and arrays.
1614
+ *
1615
+ * @param callback @see reactive
1616
+ * @returns a proxy handler object
1617
+ */
1618
+ function basicProxyHandler(callback) {
1619
+ return {
1620
+ get(target, key, proxy) {
1621
+ if (key === TARGET) {
1622
+ return target;
1623
+ }
1624
+ // non-writable non-configurable properties cannot be made reactive
1625
+ const desc = Object.getOwnPropertyDescriptor(target, key);
1626
+ if (desc && !desc.writable && !desc.configurable) {
1627
+ return Reflect.get(target, key, proxy);
1628
+ }
1629
+ observeTargetKey(target, key, callback);
1630
+ return possiblyReactive(Reflect.get(target, key, proxy), callback);
1631
+ },
1632
+ set(target, key, value, proxy) {
1633
+ const isNewKey = !objectHasOwnProperty.call(target, key);
1634
+ const originalValue = Reflect.get(target, key, proxy);
1635
+ const ret = Reflect.set(target, key, value, proxy);
1636
+ if (isNewKey) {
1637
+ notifyReactives(target, KEYCHANGES);
1638
+ }
1639
+ // While Array length may trigger the set trap, it's not actually set by this
1640
+ // method but is updated behind the scenes, and the trap is not called with the
1641
+ // new value. We disable the "same-value-optimization" for it because of that.
1642
+ if (originalValue !== value || (Array.isArray(target) && key === "length")) {
1643
+ notifyReactives(target, key);
1426
1644
  }
1645
+ return ret;
1646
+ },
1647
+ deleteProperty(target, key) {
1648
+ const ret = Reflect.deleteProperty(target, key);
1649
+ // TODO: only notify when something was actually deleted
1650
+ notifyReactives(target, KEYCHANGES);
1651
+ notifyReactives(target, key);
1652
+ return ret;
1653
+ },
1654
+ ownKeys(target) {
1655
+ observeTargetKey(target, KEYCHANGES, callback);
1656
+ return Reflect.ownKeys(target);
1657
+ },
1658
+ has(target, key) {
1659
+ // TODO: this observes all key changes instead of only the presence of the argument key
1660
+ // observing the key itself would observe value changes instead of presence changes
1661
+ // so we may need a finer grained system to distinguish observing value vs presence.
1662
+ observeTargetKey(target, KEYCHANGES, callback);
1663
+ return Reflect.has(target, key);
1664
+ },
1665
+ };
1666
+ }
1667
+ /**
1668
+ * Creates a function that will observe the key that is passed to it when called
1669
+ * and delegates to the underlying method.
1670
+ *
1671
+ * @param methodName name of the method to delegate to
1672
+ * @param target @see reactive
1673
+ * @param callback @see reactive
1674
+ */
1675
+ function makeKeyObserver(methodName, target, callback) {
1676
+ return (key) => {
1677
+ key = toRaw(key);
1678
+ observeTargetKey(target, key, callback);
1679
+ return possiblyReactive(target[methodName](key), callback);
1680
+ };
1681
+ }
1682
+ /**
1683
+ * Creates an iterable that will delegate to the underlying iteration method and
1684
+ * observe keys as necessary.
1685
+ *
1686
+ * @param methodName name of the method to delegate to
1687
+ * @param target @see reactive
1688
+ * @param callback @see reactive
1689
+ */
1690
+ function makeIteratorObserver(methodName, target, callback) {
1691
+ return function* () {
1692
+ observeTargetKey(target, KEYCHANGES, callback);
1693
+ const keys = target.keys();
1694
+ for (const item of target[methodName]()) {
1695
+ const key = keys.next().value;
1696
+ observeTargetKey(target, key, callback);
1697
+ yield possiblyReactive(item, callback);
1427
1698
  }
1428
- }
1429
- return result;
1699
+ };
1700
+ }
1701
+ /**
1702
+ * Creates a forEach function that will delegate to forEach on the underlying
1703
+ * collection while observing key changes, and keys as they're iterated over,
1704
+ * and making the passed keys/values reactive.
1705
+ *
1706
+ * @param target @see reactive
1707
+ * @param callback @see reactive
1708
+ */
1709
+ function makeForEachObserver(target, callback) {
1710
+ return function forEach(forEachCb, thisArg) {
1711
+ observeTargetKey(target, KEYCHANGES, callback);
1712
+ target.forEach(function (val, key, targetObj) {
1713
+ observeTargetKey(target, key, callback);
1714
+ forEachCb.call(thisArg, possiblyReactive(val, callback), possiblyReactive(key, callback), possiblyReactive(targetObj, callback));
1715
+ }, thisArg);
1716
+ };
1717
+ }
1718
+ /**
1719
+ * Creates a function that will delegate to an underlying method, and check if
1720
+ * that method has modified the presence or value of a key, and notify the
1721
+ * reactives appropriately.
1722
+ *
1723
+ * @param setterName name of the method to delegate to
1724
+ * @param getterName name of the method which should be used to retrieve the
1725
+ * value before calling the delegate method for comparison purposes
1726
+ * @param target @see reactive
1727
+ */
1728
+ function delegateAndNotify(setterName, getterName, target) {
1729
+ return (key, value) => {
1730
+ key = toRaw(key);
1731
+ const hadKey = target.has(key);
1732
+ const originalValue = target[getterName](key);
1733
+ const ret = target[setterName](key, value);
1734
+ const hasKey = target.has(key);
1735
+ if (hadKey !== hasKey) {
1736
+ notifyReactives(target, KEYCHANGES);
1737
+ }
1738
+ if (originalValue !== value) {
1739
+ notifyReactives(target, key);
1740
+ }
1741
+ return ret;
1742
+ };
1743
+ }
1744
+ /**
1745
+ * Creates a function that will clear the underlying collection and notify that
1746
+ * the keys of the collection have changed.
1747
+ *
1748
+ * @param target @see reactive
1749
+ */
1750
+ function makeClearNotifier(target) {
1751
+ return () => {
1752
+ const allKeys = [...target.keys()];
1753
+ target.clear();
1754
+ notifyReactives(target, KEYCHANGES);
1755
+ for (const key of allKeys) {
1756
+ notifyReactives(target, key);
1757
+ }
1758
+ };
1759
+ }
1760
+ /**
1761
+ * Maps raw type of an object to an object containing functions that can be used
1762
+ * to build an appropritate proxy handler for that raw type. Eg: when making a
1763
+ * reactive set, calling the has method should mark the key that is being
1764
+ * retrieved as observed, and calling the add or delete method should notify the
1765
+ * reactives that the key which is being added or deleted has been modified.
1766
+ */
1767
+ const rawTypeToFuncHandlers = {
1768
+ Set: (target, callback) => ({
1769
+ has: makeKeyObserver("has", target, callback),
1770
+ add: delegateAndNotify("add", "has", target),
1771
+ delete: delegateAndNotify("delete", "has", target),
1772
+ keys: makeIteratorObserver("keys", target, callback),
1773
+ values: makeIteratorObserver("values", target, callback),
1774
+ entries: makeIteratorObserver("entries", target, callback),
1775
+ [Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
1776
+ forEach: makeForEachObserver(target, callback),
1777
+ clear: makeClearNotifier(target),
1778
+ get size() {
1779
+ observeTargetKey(target, KEYCHANGES, callback);
1780
+ return target.size;
1781
+ },
1782
+ }),
1783
+ Map: (target, callback) => ({
1784
+ has: makeKeyObserver("has", target, callback),
1785
+ get: makeKeyObserver("get", target, callback),
1786
+ set: delegateAndNotify("set", "get", target),
1787
+ delete: delegateAndNotify("delete", "has", target),
1788
+ keys: makeIteratorObserver("keys", target, callback),
1789
+ values: makeIteratorObserver("values", target, callback),
1790
+ entries: makeIteratorObserver("entries", target, callback),
1791
+ [Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
1792
+ forEach: makeForEachObserver(target, callback),
1793
+ clear: makeClearNotifier(target),
1794
+ get size() {
1795
+ observeTargetKey(target, KEYCHANGES, callback);
1796
+ return target.size;
1797
+ },
1798
+ }),
1799
+ WeakMap: (target, callback) => ({
1800
+ has: makeKeyObserver("has", target, callback),
1801
+ get: makeKeyObserver("get", target, callback),
1802
+ set: delegateAndNotify("set", "get", target),
1803
+ delete: delegateAndNotify("delete", "has", target),
1804
+ }),
1805
+ };
1806
+ /**
1807
+ * Creates a proxy handler for collections (Set/Map/WeakMap)
1808
+ *
1809
+ * @param callback @see reactive
1810
+ * @param target @see reactive
1811
+ * @returns a proxy handler object
1812
+ */
1813
+ function collectionsProxyHandler(target, callback, targetRawType) {
1814
+ // TODO: if performance is an issue we can create the special handlers lazily when each
1815
+ // property is read.
1816
+ const specialHandlers = rawTypeToFuncHandlers[targetRawType](target, callback);
1817
+ return Object.assign(basicProxyHandler(callback), {
1818
+ get(target, key) {
1819
+ if (key === TARGET) {
1820
+ return target;
1821
+ }
1822
+ if (objectHasOwnProperty.call(specialHandlers, key)) {
1823
+ return specialHandlers[key];
1824
+ }
1825
+ observeTargetKey(target, key, callback);
1826
+ return possiblyReactive(target[key], callback);
1827
+ },
1828
+ });
1430
1829
  }
1431
1830
 
1432
1831
  /**
@@ -1444,11 +1843,13 @@ function batched(callback) {
1444
1843
  await Promise.resolve();
1445
1844
  if (!called) {
1446
1845
  called = true;
1447
- callback();
1448
1846
  // wait for all calls in this microtick to fall through before resetting "called"
1449
- // so that only the first call to the batched function calls the original callback
1450
- await Promise.resolve();
1451
- called = false;
1847
+ // so that only the first call to the batched function calls the original callback.
1848
+ // Schedule this before calling the callback so that calls to the batched function
1849
+ // within the callback will proceed only after resetting called to false, and have
1850
+ // a chance to execute the callback again
1851
+ Promise.resolve().then(() => (called = false));
1852
+ callback();
1452
1853
  }
1453
1854
  };
1454
1855
  }
@@ -1495,223 +1896,18 @@ class Markup extends String {
1495
1896
  */
1496
1897
  function markup(value) {
1497
1898
  return new Markup(value);
1498
- }
1499
-
1500
- /**
1501
- * This file contains utility functions that will be injected in each template,
1502
- * to perform various useful tasks in the compiled code.
1503
- */
1504
- function withDefault(value, defaultValue) {
1505
- return value === undefined || value === null || value === false ? defaultValue : value;
1506
- }
1507
- function callSlot(ctx, parent, key, name, dynamic, extra, defaultContent) {
1508
- key = key + "__slot_" + name;
1509
- const slots = (ctx.props && ctx.props.slots) || {};
1510
- const { __render, __ctx, __scope } = slots[name] || {};
1511
- const slotScope = Object.create(__ctx || {});
1512
- if (__scope) {
1513
- slotScope[__scope] = extra || {};
1514
- }
1515
- const slotBDom = __render ? __render.call(__ctx.__owl__.component, slotScope, parent, key) : null;
1516
- if (defaultContent) {
1517
- let child1 = undefined;
1518
- let child2 = undefined;
1519
- if (slotBDom) {
1520
- child1 = dynamic ? toggler(name, slotBDom) : slotBDom;
1521
- }
1522
- else {
1523
- child2 = defaultContent.call(ctx.__owl__.component, ctx, parent, key);
1524
- }
1525
- return multi([child1, child2]);
1526
- }
1527
- return slotBDom || text("");
1528
- }
1529
- function capture(ctx) {
1530
- const component = ctx.__owl__.component;
1531
- const result = Object.create(component);
1532
- for (let k in ctx) {
1533
- result[k] = ctx[k];
1534
- }
1535
- return result;
1536
- }
1537
- function withKey(elem, k) {
1538
- elem.key = k;
1539
- return elem;
1540
- }
1541
- function prepareList(collection) {
1542
- let keys;
1543
- let values;
1544
- if (Array.isArray(collection)) {
1545
- keys = collection;
1546
- values = collection;
1547
- }
1548
- else if (collection) {
1549
- values = Object.keys(collection);
1550
- keys = Object.values(collection);
1551
- }
1552
- else {
1553
- throw new Error("Invalid loop expression");
1554
- }
1555
- const n = values.length;
1556
- return [keys, values, n, new Array(n)];
1557
1899
  }
1558
- const isBoundary = Symbol("isBoundary");
1559
- function setContextValue(ctx, key, value) {
1560
- const ctx0 = ctx;
1561
- while (!ctx.hasOwnProperty(key) && !ctx.hasOwnProperty(isBoundary)) {
1562
- const newCtx = ctx.__proto__;
1563
- if (!newCtx) {
1564
- ctx = ctx0;
1565
- break;
1566
- }
1567
- ctx = newCtx;
1568
- }
1569
- ctx[key] = value;
1570
- }
1571
- function toNumber(val) {
1572
- const n = parseFloat(val);
1573
- return isNaN(n) ? val : n;
1574
- }
1575
- function shallowEqual$1(l1, l2) {
1576
- for (let i = 0, l = l1.length; i < l; i++) {
1577
- if (l1[i] !== l2[i]) {
1578
- return false;
1579
- }
1580
- }
1581
- return true;
1582
- }
1583
- class LazyValue {
1584
- constructor(fn, ctx, node) {
1585
- this.fn = fn;
1586
- this.ctx = capture(ctx);
1587
- this.node = node;
1588
- }
1589
- evaluate() {
1590
- return this.fn(this.ctx, this.node);
1591
- }
1592
- toString() {
1593
- return this.evaluate().toString();
1594
- }
1595
- }
1596
- /*
1597
- * Safely outputs `value` as a block depending on the nature of `value`
1598
- */
1599
- function safeOutput(value) {
1600
- if (!value) {
1601
- return value;
1602
- }
1603
- let safeKey;
1604
- let block;
1605
- if (value instanceof Markup) {
1606
- safeKey = `string_safe`;
1607
- block = html(value);
1608
- }
1609
- else if (value instanceof LazyValue) {
1610
- safeKey = `lazy_value`;
1611
- block = value.evaluate();
1612
- }
1613
- else if (typeof value === "string") {
1614
- safeKey = "string_unsafe";
1615
- block = text(value);
1616
- }
1617
- else {
1618
- // Assuming it is a block
1619
- safeKey = "block_safe";
1620
- block = value;
1621
- }
1622
- return toggler(safeKey, block);
1623
- }
1624
- let boundFunctions = new WeakMap();
1625
- function bind(ctx, fn) {
1626
- let component = ctx.__owl__.component;
1627
- let boundFnMap = boundFunctions.get(component);
1628
- if (!boundFnMap) {
1629
- boundFnMap = new WeakMap();
1630
- boundFunctions.set(component, boundFnMap);
1631
- }
1632
- let boundFn = boundFnMap.get(fn);
1633
- if (!boundFn) {
1634
- boundFn = fn.bind(component);
1635
- boundFnMap.set(fn, boundFn);
1636
- }
1637
- return boundFn;
1638
- }
1639
- function multiRefSetter(refs, name) {
1640
- let count = 0;
1641
- return (el) => {
1642
- if (el) {
1643
- count++;
1644
- if (count > 1) {
1645
- throw new Error("Cannot have 2 elements with same ref name at the same time");
1646
- }
1647
- }
1648
- if (count === 0 || el) {
1649
- refs[name] = el;
1650
- }
1651
- };
1900
+ // -----------------------------------------------------------------------------
1901
+ // xml tag helper
1902
+ // -----------------------------------------------------------------------------
1903
+ const globalTemplates = {};
1904
+ function xml(...args) {
1905
+ const name = `__template__${xml.nextId++}`;
1906
+ const value = String.raw(...args);
1907
+ globalTemplates[name] = value;
1908
+ return name;
1652
1909
  }
1653
- const UTILS = {
1654
- withDefault,
1655
- zero: Symbol("zero"),
1656
- isBoundary,
1657
- callSlot,
1658
- capture,
1659
- withKey,
1660
- prepareList,
1661
- setContextValue,
1662
- multiRefSetter,
1663
- shallowEqual: shallowEqual$1,
1664
- toNumber,
1665
- validateProps,
1666
- LazyValue,
1667
- safeOutput,
1668
- bind,
1669
- };
1670
-
1671
- const mainEventHandler = (data, ev, currentTarget) => {
1672
- const { data: _data, modifiers } = filterOutModifiersFromData(data);
1673
- data = _data;
1674
- let stopped = false;
1675
- if (modifiers.length) {
1676
- let selfMode = false;
1677
- const isSelf = ev.target === currentTarget;
1678
- for (const mod of modifiers) {
1679
- switch (mod) {
1680
- case "self":
1681
- selfMode = true;
1682
- if (isSelf) {
1683
- continue;
1684
- }
1685
- else {
1686
- return stopped;
1687
- }
1688
- case "prevent":
1689
- if ((selfMode && isSelf) || !selfMode)
1690
- ev.preventDefault();
1691
- continue;
1692
- case "stop":
1693
- if ((selfMode && isSelf) || !selfMode)
1694
- ev.stopPropagation();
1695
- stopped = true;
1696
- continue;
1697
- }
1698
- }
1699
- }
1700
- // If handler is empty, the array slot 0 will also be empty, and data will not have the property 0
1701
- // We check this rather than data[0] being truthy (or typeof function) so that it crashes
1702
- // as expected when there is a handler expression that evaluates to a falsy value
1703
- if (Object.hasOwnProperty.call(data, 0)) {
1704
- const handler = data[0];
1705
- if (typeof handler !== "function") {
1706
- throw new Error(`Invalid handler (expected a function, received: '${handler}')`);
1707
- }
1708
- let node = data[1] ? data[1].__owl__ : null;
1709
- if (node ? node.status === 1 /* MOUNTED */ : true) {
1710
- handler.call(node ? node.component : null, ev);
1711
- }
1712
- }
1713
- return stopped;
1714
- };
1910
+ xml.nextId = 1;
1715
1911
 
1716
1912
  // Maps fibers to thrown errors
1717
1913
  const fibersInError = new WeakMap();
@@ -1740,7 +1936,8 @@ function _handleError(node, error, isFirstRound = false) {
1740
1936
  }
1741
1937
  if (stopped) {
1742
1938
  if (isFirstRound && fiber && fiber.node.fiber) {
1743
- fiber.root.counter--;
1939
+ const root = fiber.root;
1940
+ root.setCounter(root.counter - 1);
1744
1941
  }
1745
1942
  return true;
1746
1943
  }
@@ -1774,9 +1971,12 @@ function handleError(params) {
1774
1971
  function makeChildFiber(node, parent) {
1775
1972
  let current = node.fiber;
1776
1973
  if (current) {
1777
- let root = parent.root;
1778
- cancelFibers(root, current.children);
1974
+ cancelFibers(current.children);
1779
1975
  current.root = null;
1976
+ if (current instanceof RootFiber && current.delayedRenders.length) {
1977
+ let root = parent.root;
1978
+ root.delayedRenders = root.delayedRenders.concat(current.delayedRenders);
1979
+ }
1780
1980
  }
1781
1981
  return new Fiber(node, parent);
1782
1982
  }
@@ -1784,10 +1984,12 @@ function makeRootFiber(node) {
1784
1984
  let current = node.fiber;
1785
1985
  if (current) {
1786
1986
  let root = current.root;
1787
- root.counter -= cancelFibers(root, current.children);
1987
+ root.setCounter(root.counter + 1 - cancelFibers(current.children));
1788
1988
  current.children = [];
1789
- root.counter++;
1790
1989
  current.bdom = null;
1990
+ if (current === root) {
1991
+ root.reachedChildren = new WeakSet();
1992
+ }
1791
1993
  if (fibersInError.has(current)) {
1792
1994
  fibersInError.delete(current);
1793
1995
  fibersInError.delete(root);
@@ -1807,15 +2009,23 @@ function makeRootFiber(node) {
1807
2009
  /**
1808
2010
  * @returns number of not-yet rendered fibers cancelled
1809
2011
  */
1810
- function cancelFibers(root, fibers) {
2012
+ function cancelFibers(fibers) {
1811
2013
  let result = 0;
1812
2014
  for (let fiber of fibers) {
1813
2015
  fiber.node.fiber = null;
1814
- fiber.root = root;
1815
- if (!fiber.bdom) {
2016
+ if (fiber.bdom) {
2017
+ // if fiber has been rendered, this means that the component props have
2018
+ // been updated. however, this fiber will not be patched to the dom, so
2019
+ // it could happen that the next render compare the current props with
2020
+ // the same props, and skip the render completely. With the next line,
2021
+ // we kindly request the component code to force a render, so it works as
2022
+ // expected.
2023
+ fiber.node.forceNextRender = true;
2024
+ }
2025
+ else {
1816
2026
  result++;
1817
2027
  }
1818
- result += cancelFibers(root, fiber.children);
2028
+ result += cancelFibers(fiber.children);
1819
2029
  }
1820
2030
  return result;
1821
2031
  }
@@ -1824,11 +2034,13 @@ class Fiber {
1824
2034
  this.bdom = null;
1825
2035
  this.children = [];
1826
2036
  this.appliedToDom = false;
2037
+ this.deep = false;
1827
2038
  this.node = node;
1828
2039
  this.parent = parent;
1829
2040
  if (parent) {
2041
+ this.deep = parent.deep;
1830
2042
  const root = parent.root;
1831
- root.counter++;
2043
+ root.setCounter(root.counter + 1);
1832
2044
  this.root = root;
1833
2045
  parent.children.push(this);
1834
2046
  }
@@ -1836,6 +2048,45 @@ class Fiber {
1836
2048
  this.root = this;
1837
2049
  }
1838
2050
  }
2051
+ render() {
2052
+ // if some parent has a fiber => register in followup
2053
+ let prev = this.root.node;
2054
+ let current = prev.parent;
2055
+ while (current) {
2056
+ if (current.fiber) {
2057
+ let root = current.fiber.root;
2058
+ if (root.counter) {
2059
+ root.delayedRenders.push(this);
2060
+ return;
2061
+ }
2062
+ else {
2063
+ if (!root.reachedChildren.has(prev)) {
2064
+ // is dead
2065
+ this.node.app.scheduler.shouldClear = true;
2066
+ return;
2067
+ }
2068
+ current = root.node;
2069
+ }
2070
+ }
2071
+ prev = current;
2072
+ current = current.parent;
2073
+ }
2074
+ // there are no current rendering from above => we can render
2075
+ this._render();
2076
+ }
2077
+ _render() {
2078
+ const node = this.node;
2079
+ const root = this.root;
2080
+ if (root) {
2081
+ try {
2082
+ this.bdom = node.renderFn();
2083
+ root.setCounter(root.counter - 1);
2084
+ }
2085
+ catch (e) {
2086
+ handleError({ node, error: e });
2087
+ }
2088
+ }
2089
+ }
1839
2090
  }
1840
2091
  class RootFiber extends Fiber {
1841
2092
  constructor() {
@@ -1848,6 +2099,8 @@ class RootFiber extends Fiber {
1848
2099
  // A fiber is typically locked when it is completing and the patch has not, or is being applied.
1849
2100
  // i.e.: render triggered in onWillUnmount or in willPatch will be delayed
1850
2101
  this.locked = false;
2102
+ this.delayedRenders = [];
2103
+ this.reachedChildren = new WeakSet();
1851
2104
  }
1852
2105
  complete() {
1853
2106
  const node = this.node;
@@ -1869,7 +2122,7 @@ class RootFiber extends Fiber {
1869
2122
  }
1870
2123
  current = undefined;
1871
2124
  // Step 2: patching the dom
1872
- node.patch();
2125
+ node._patch();
1873
2126
  this.locked = false;
1874
2127
  // Step 4: calling all mounted lifecycle hooks
1875
2128
  let mountedFibers = this.mounted;
@@ -1897,6 +2150,20 @@ class RootFiber extends Fiber {
1897
2150
  handleError({ fiber: current || this, error: e });
1898
2151
  }
1899
2152
  }
2153
+ setCounter(newValue) {
2154
+ this.counter = newValue;
2155
+ if (newValue === 0) {
2156
+ if (this.delayedRenders.length) {
2157
+ for (let f of this.delayedRenders) {
2158
+ if (f.root) {
2159
+ f.render();
2160
+ }
2161
+ }
2162
+ this.delayedRenders = [];
2163
+ }
2164
+ this.node.app.scheduler.flush();
2165
+ }
2166
+ }
1900
2167
  }
1901
2168
  class MountFiber extends RootFiber {
1902
2169
  constructor(node, target, options = {}) {
@@ -1940,10 +2207,149 @@ class MountFiber extends RootFiber {
1940
2207
  }
1941
2208
  }
1942
2209
  }
1943
- catch (e) {
1944
- handleError({ fiber: current, error: e });
1945
- }
2210
+ catch (e) {
2211
+ handleError({ fiber: current, error: e });
2212
+ }
2213
+ }
2214
+ }
2215
+
2216
+ /**
2217
+ * Apply default props (only top level).
2218
+ *
2219
+ * Note that this method does modify in place the props
2220
+ */
2221
+ function applyDefaultProps(props, ComponentClass) {
2222
+ const defaultProps = ComponentClass.defaultProps;
2223
+ if (defaultProps) {
2224
+ for (let propName in defaultProps) {
2225
+ if (props[propName] === undefined) {
2226
+ props[propName] = defaultProps[propName];
2227
+ }
2228
+ }
2229
+ }
2230
+ }
2231
+ //------------------------------------------------------------------------------
2232
+ // Prop validation helper
2233
+ //------------------------------------------------------------------------------
2234
+ function getPropDescription(staticProps) {
2235
+ if (staticProps instanceof Array) {
2236
+ return Object.fromEntries(staticProps.map((p) => (p.endsWith("?") ? [p.slice(0, -1), false] : [p, true])));
2237
+ }
2238
+ return staticProps || { "*": true };
2239
+ }
2240
+ /**
2241
+ * Validate the component props (or next props) against the (static) props
2242
+ * description. This is potentially an expensive operation: it may needs to
2243
+ * visit recursively the props and all the children to check if they are valid.
2244
+ * This is why it is only done in 'dev' mode.
2245
+ */
2246
+ function validateProps(name, props, parent) {
2247
+ const ComponentClass = typeof name !== "string"
2248
+ ? name
2249
+ : parent.constructor.components[name];
2250
+ if (!ComponentClass) {
2251
+ // this is an error, wrong component. We silently return here instead so the
2252
+ // error is triggered by the usual path ('component' function)
2253
+ return;
2254
+ }
2255
+ applyDefaultProps(props, ComponentClass);
2256
+ const defaultProps = ComponentClass.defaultProps || {};
2257
+ let propsDef = getPropDescription(ComponentClass.props);
2258
+ const allowAdditionalProps = "*" in propsDef;
2259
+ for (let propName in propsDef) {
2260
+ if (propName === "*") {
2261
+ continue;
2262
+ }
2263
+ const propDef = propsDef[propName];
2264
+ let isMandatory = !!propDef;
2265
+ if (typeof propDef === "object" && "optional" in propDef) {
2266
+ isMandatory = !propDef.optional;
2267
+ }
2268
+ if (isMandatory && propName in defaultProps) {
2269
+ throw new Error(`A default value cannot be defined for a mandatory prop (name: '${propName}', component: ${ComponentClass.name})`);
2270
+ }
2271
+ if (props[propName] === undefined) {
2272
+ if (isMandatory) {
2273
+ throw new Error(`Missing props '${propName}' (component '${ComponentClass.name}')`);
2274
+ }
2275
+ else {
2276
+ continue;
2277
+ }
2278
+ }
2279
+ let isValid;
2280
+ try {
2281
+ isValid = isValidProp(props[propName], propDef);
2282
+ }
2283
+ catch (e) {
2284
+ e.message = `Invalid prop '${propName}' in component ${ComponentClass.name} (${e.message})`;
2285
+ throw e;
2286
+ }
2287
+ if (!isValid) {
2288
+ throw new Error(`Invalid Prop '${propName}' in component '${ComponentClass.name}'`);
2289
+ }
2290
+ }
2291
+ if (!allowAdditionalProps) {
2292
+ for (let propName in props) {
2293
+ if (!(propName in propsDef)) {
2294
+ throw new Error(`Unknown prop '${propName}' given to component '${ComponentClass.name}'`);
2295
+ }
2296
+ }
2297
+ }
2298
+ }
2299
+ /**
2300
+ * Check if an invidual prop value matches its (static) prop definition
2301
+ */
2302
+ function isValidProp(prop, propDef) {
2303
+ if (propDef === true) {
2304
+ return true;
2305
+ }
2306
+ if (typeof propDef === "function") {
2307
+ // Check if a value is constructed by some Constructor. Note that there is a
2308
+ // slight abuse of language: we want to consider primitive values as well.
2309
+ //
2310
+ // So, even though 1 is not an instance of Number, we want to consider that
2311
+ // it is valid.
2312
+ if (typeof prop === "object") {
2313
+ return prop instanceof propDef;
2314
+ }
2315
+ return typeof prop === propDef.name.toLowerCase();
2316
+ }
2317
+ else if (propDef instanceof Array) {
2318
+ // If this code is executed, this means that we want to check if a prop
2319
+ // matches at least one of its descriptor.
2320
+ let result = false;
2321
+ for (let i = 0, iLen = propDef.length; i < iLen; i++) {
2322
+ result = result || isValidProp(prop, propDef[i]);
2323
+ }
2324
+ return result;
2325
+ }
2326
+ // propsDef is an object
2327
+ if (propDef.optional && prop === undefined) {
2328
+ return true;
2329
+ }
2330
+ let result = propDef.type ? isValidProp(prop, propDef.type) : true;
2331
+ if (propDef.validate) {
2332
+ result = result && propDef.validate(prop);
2333
+ }
2334
+ if (propDef.type === Array && propDef.element) {
2335
+ for (let i = 0, iLen = prop.length; i < iLen; i++) {
2336
+ result = result && isValidProp(prop[i], propDef.element);
2337
+ }
2338
+ }
2339
+ if (propDef.type === Object && propDef.shape) {
2340
+ const shape = propDef.shape;
2341
+ for (let key in shape) {
2342
+ result = result && isValidProp(prop[key], shape[key]);
2343
+ }
2344
+ if (result) {
2345
+ for (let propName in prop) {
2346
+ if (!(propName in shape)) {
2347
+ throw new Error(`unknown prop '${propName}'`);
2348
+ }
2349
+ }
2350
+ }
1946
2351
  }
2352
+ return result;
1947
2353
  }
1948
2354
 
1949
2355
  let currentNode = null;
@@ -1956,6 +2362,39 @@ function getCurrent() {
1956
2362
  function useComponent() {
1957
2363
  return currentNode.component;
1958
2364
  }
2365
+ // -----------------------------------------------------------------------------
2366
+ // Integration with reactivity system (useState)
2367
+ // -----------------------------------------------------------------------------
2368
+ const batchedRenderFunctions = new WeakMap();
2369
+ /**
2370
+ * Creates a reactive object that will be observed by the current component.
2371
+ * Reading data from the returned object (eg during rendering) will cause the
2372
+ * component to subscribe to that data and be rerendered when it changes.
2373
+ *
2374
+ * @param state the state to observe
2375
+ * @returns a reactive object that will cause the component to re-render on
2376
+ * relevant changes
2377
+ * @see reactive
2378
+ */
2379
+ function useState(state) {
2380
+ const node = getCurrent();
2381
+ let render = batchedRenderFunctions.get(node);
2382
+ if (!render) {
2383
+ render = batched(node.render.bind(node));
2384
+ batchedRenderFunctions.set(node, render);
2385
+ // manual implementation of onWillDestroy to break cyclic dependency
2386
+ node.willDestroy.push(clearReactivesForCallback.bind(null, render));
2387
+ }
2388
+ return reactive(state, render);
2389
+ }
2390
+ function arePropsDifferent(props1, props2) {
2391
+ for (let k in props1) {
2392
+ if (props1[k] !== props2[k]) {
2393
+ return true;
2394
+ }
2395
+ }
2396
+ return Object.keys(props1).length !== Object.keys(props2).length;
2397
+ }
1959
2398
  function component(name, props, key, ctx, parent) {
1960
2399
  let node = ctx.children[key];
1961
2400
  let isDynamic = typeof name !== "string";
@@ -1973,7 +2412,17 @@ function component(name, props, key, ctx, parent) {
1973
2412
  }
1974
2413
  const parentFiber = ctx.fiber;
1975
2414
  if (node) {
1976
- node.updateAndRender(props, parentFiber);
2415
+ let shouldRender = node.forceNextRender;
2416
+ if (shouldRender) {
2417
+ node.forceNextRender = false;
2418
+ }
2419
+ else {
2420
+ const currentProps = node.component.props[TARGET];
2421
+ shouldRender = parentFiber.deep || arePropsDifferent(currentProps, props);
2422
+ }
2423
+ if (shouldRender) {
2424
+ node.updateAndRender(props, parentFiber);
2425
+ }
1977
2426
  }
1978
2427
  else {
1979
2428
  // new component
@@ -1989,9 +2438,9 @@ function component(name, props, key, ctx, parent) {
1989
2438
  }
1990
2439
  node = new ComponentNode(C, props, ctx.app, ctx);
1991
2440
  ctx.children[key] = node;
1992
- const fiber = makeChildFiber(node, parentFiber);
1993
- node.initiateRender(fiber);
2441
+ node.initiateRender(new Fiber(node, parentFiber));
1994
2442
  }
2443
+ parentFiber.root.reachedChildren.add(node);
1995
2444
  return node;
1996
2445
  }
1997
2446
  class ComponentNode {
@@ -1999,6 +2448,7 @@ class ComponentNode {
1999
2448
  this.fiber = null;
2000
2449
  this.bdom = null;
2001
2450
  this.status = 0 /* NEW */;
2451
+ this.forceNextRender = false;
2002
2452
  this.children = Object.create(null);
2003
2453
  this.refs = {};
2004
2454
  this.willStart = [];
@@ -2015,6 +2465,7 @@ class ComponentNode {
2015
2465
  applyDefaultProps(props, C);
2016
2466
  const env = (parent && parent.childEnv) || app.env;
2017
2467
  this.childEnv = env;
2468
+ props = useState(props);
2018
2469
  this.component = new C(props, env, this);
2019
2470
  this.renderFn = app.getTemplate(C.template).bind(this.component, this.component, this);
2020
2471
  this.component.setup();
@@ -2039,23 +2490,32 @@ class ComponentNode {
2039
2490
  return;
2040
2491
  }
2041
2492
  if (this.status === 0 /* NEW */ && this.fiber === fiber) {
2042
- this._render(fiber);
2493
+ fiber.render();
2043
2494
  }
2044
2495
  }
2045
- async render() {
2496
+ async render(deep = false) {
2046
2497
  let current = this.fiber;
2047
2498
  if (current && current.root.locked) {
2048
2499
  await Promise.resolve();
2049
2500
  // situation may have changed after the microtask tick
2050
2501
  current = this.fiber;
2051
2502
  }
2052
- if (current && !current.bdom && !fibersInError.has(current)) {
2053
- return;
2503
+ if (current) {
2504
+ if (!current.bdom && !fibersInError.has(current)) {
2505
+ if (deep) {
2506
+ // we want the render from this point on to be with deep=true
2507
+ current.deep = deep;
2508
+ }
2509
+ return;
2510
+ }
2511
+ // if current rendering was with deep=true, we want this one to be the same
2512
+ deep = deep || current.deep;
2054
2513
  }
2055
- if (!this.bdom && !current) {
2514
+ else if (!this.bdom) {
2056
2515
  return;
2057
2516
  }
2058
2517
  const fiber = makeRootFiber(this);
2518
+ fiber.deep = deep;
2059
2519
  this.fiber = fiber;
2060
2520
  this.app.scheduler.addFiber(fiber);
2061
2521
  await Promise.resolve();
@@ -2074,16 +2534,7 @@ class ComponentNode {
2074
2534
  // embedded in a rendering coming from above, so the fiber will be rendered
2075
2535
  // in the next microtick anyway, so we should not render it again.
2076
2536
  if (this.fiber === fiber && (current || !fiber.parent)) {
2077
- this._render(fiber);
2078
- }
2079
- }
2080
- _render(fiber) {
2081
- try {
2082
- fiber.bdom = this.renderFn();
2083
- fiber.root.counter--;
2084
- }
2085
- catch (e) {
2086
- handleError({ node: this, error: e });
2537
+ fiber.render();
2087
2538
  }
2088
2539
  }
2089
2540
  destroy() {
@@ -2114,13 +2565,16 @@ class ComponentNode {
2114
2565
  this.fiber = fiber;
2115
2566
  const component = this.component;
2116
2567
  applyDefaultProps(props, component.constructor);
2568
+ currentNode = this;
2569
+ props = useState(props);
2570
+ currentNode = null;
2117
2571
  const prom = Promise.all(this.willUpdateProps.map((f) => f.call(component, props)));
2118
2572
  await prom;
2119
2573
  if (fiber !== this.fiber) {
2120
2574
  return;
2121
2575
  }
2122
2576
  component.props = props;
2123
- this._render(fiber);
2577
+ fiber.render();
2124
2578
  const parentRoot = parentFiber.root;
2125
2579
  if (this.willPatch.length) {
2126
2580
  parentRoot.willPatch.push(fiber);
@@ -2173,6 +2627,14 @@ class ComponentNode {
2173
2627
  this.bdom.moveBefore(other ? other.bdom : null, afterNode);
2174
2628
  }
2175
2629
  patch() {
2630
+ if (this.fiber && this.fiber.parent) {
2631
+ // we only patch here renderings coming from above. renderings initiated
2632
+ // by the component will be patched independently in the appropriate
2633
+ // fiber.complete
2634
+ this._patch();
2635
+ }
2636
+ }
2637
+ _patch() {
2176
2638
  const hasChildren = Object.keys(this.children).length > 0;
2177
2639
  this.bdom.patch(this.fiber.bdom, hasChildren);
2178
2640
  if (hasChildren) {
@@ -2200,65 +2662,76 @@ class ComponentNode {
2200
2662
  }
2201
2663
  }
2202
2664
  }
2665
+ // ---------------------------------------------------------------------------
2666
+ // Some debug helpers
2667
+ // ---------------------------------------------------------------------------
2668
+ get name() {
2669
+ return this.component.constructor.name;
2670
+ }
2671
+ get subscriptions() {
2672
+ const render = batchedRenderFunctions.get(this);
2673
+ return render ? getSubscriptions(render) : [];
2674
+ }
2203
2675
  }
2204
2676
 
2205
2677
  // -----------------------------------------------------------------------------
2206
- // hooks
2678
+ // Scheduler
2207
2679
  // -----------------------------------------------------------------------------
2208
- function onWillStart(fn) {
2209
- const node = getCurrent();
2210
- node.willStart.push(fn.bind(node.component));
2211
- }
2212
- function onWillUpdateProps(fn) {
2213
- const node = getCurrent();
2214
- node.willUpdateProps.push(fn.bind(node.component));
2215
- }
2216
- function onMounted(fn) {
2217
- const node = getCurrent();
2218
- node.mounted.push(fn.bind(node.component));
2219
- }
2220
- function onWillPatch(fn) {
2221
- const node = getCurrent();
2222
- node.willPatch.unshift(fn.bind(node.component));
2223
- }
2224
- function onPatched(fn) {
2225
- const node = getCurrent();
2226
- node.patched.push(fn.bind(node.component));
2227
- }
2228
- function onWillUnmount(fn) {
2229
- const node = getCurrent();
2230
- node.willUnmount.unshift(fn.bind(node.component));
2231
- }
2232
- function onWillDestroy(fn) {
2233
- const node = getCurrent();
2234
- node.willDestroy.push(fn.bind(node.component));
2235
- }
2236
- function onWillRender(fn) {
2237
- const node = getCurrent();
2238
- const renderFn = node.renderFn;
2239
- node.renderFn = () => {
2240
- fn.call(node.component);
2241
- return renderFn();
2242
- };
2243
- }
2244
- function onRendered(fn) {
2245
- const node = getCurrent();
2246
- const renderFn = node.renderFn;
2247
- node.renderFn = () => {
2248
- const result = renderFn();
2249
- fn.call(node.component);
2250
- return result;
2251
- };
2252
- }
2253
- function onError(callback) {
2254
- const node = getCurrent();
2255
- let handlers = nodeErrorHandlers.get(node);
2256
- if (!handlers) {
2257
- handlers = [];
2258
- nodeErrorHandlers.set(node, handlers);
2680
+ class Scheduler {
2681
+ constructor() {
2682
+ this.tasks = new Set();
2683
+ this.frame = 0;
2684
+ this.shouldClear = false;
2685
+ this.requestAnimationFrame = Scheduler.requestAnimationFrame;
2259
2686
  }
2260
- handlers.push(callback.bind(node.component));
2261
- }
2687
+ addFiber(fiber) {
2688
+ this.tasks.add(fiber.root);
2689
+ }
2690
+ /**
2691
+ * Process all current tasks. This only applies to the fibers that are ready.
2692
+ * Other tasks are left unchanged.
2693
+ */
2694
+ flush() {
2695
+ if (this.frame === 0) {
2696
+ this.frame = this.requestAnimationFrame(() => {
2697
+ this.frame = 0;
2698
+ this.tasks.forEach((fiber) => this.processFiber(fiber));
2699
+ if (this.shouldClear) {
2700
+ this.shouldClear = false;
2701
+ for (let task of this.tasks) {
2702
+ if (task.node.status === 2 /* DESTROYED */) {
2703
+ this.tasks.delete(task);
2704
+ }
2705
+ }
2706
+ }
2707
+ });
2708
+ }
2709
+ }
2710
+ processFiber(fiber) {
2711
+ if (fiber.root !== fiber) {
2712
+ this.tasks.delete(fiber);
2713
+ return;
2714
+ }
2715
+ const hasError = fibersInError.has(fiber);
2716
+ if (hasError && fiber.counter !== 0) {
2717
+ this.tasks.delete(fiber);
2718
+ return;
2719
+ }
2720
+ if (fiber.node.status === 2 /* DESTROYED */) {
2721
+ this.tasks.delete(fiber);
2722
+ return;
2723
+ }
2724
+ if (fiber.counter === 0) {
2725
+ if (!hasError) {
2726
+ fiber.complete();
2727
+ }
2728
+ this.tasks.delete(fiber);
2729
+ }
2730
+ }
2731
+ }
2732
+ // capture the value of requestAnimationFrame as soon as possible, to avoid
2733
+ // interactions with other code, such as test frameworks that override them
2734
+ Scheduler.requestAnimationFrame = window.requestAnimationFrame.bind(window);
2262
2735
 
2263
2736
  /**
2264
2737
  * Owl QWeb Expression Parser
@@ -2412,24 +2885,31 @@ const TOKENIZERS = [
2412
2885
  function tokenize(expr) {
2413
2886
  const result = [];
2414
2887
  let token = true;
2415
- while (token) {
2416
- expr = expr.trim();
2417
- if (expr) {
2418
- for (let tokenizer of TOKENIZERS) {
2419
- token = tokenizer(expr);
2420
- if (token) {
2421
- result.push(token);
2422
- expr = expr.slice(token.size || token.value.length);
2423
- break;
2888
+ let error;
2889
+ let current = expr;
2890
+ try {
2891
+ while (token) {
2892
+ current = current.trim();
2893
+ if (current) {
2894
+ for (let tokenizer of TOKENIZERS) {
2895
+ token = tokenizer(current);
2896
+ if (token) {
2897
+ result.push(token);
2898
+ current = current.slice(token.size || token.value.length);
2899
+ break;
2900
+ }
2424
2901
  }
2425
2902
  }
2426
- }
2427
- else {
2428
- token = false;
2903
+ else {
2904
+ token = false;
2905
+ }
2429
2906
  }
2430
2907
  }
2431
- if (expr.length) {
2432
- throw new Error(`Tokenizer error: could not tokenize "${expr}"`);
2908
+ catch (e) {
2909
+ error = e; // Silence all errors and throw a generic error below
2910
+ }
2911
+ if (current.length || error) {
2912
+ throw new Error(`Tokenizer error: could not tokenize \`${expr}\``);
2433
2913
  }
2434
2914
  return result;
2435
2915
  }
@@ -2634,7 +3114,7 @@ function createContext(parentCtx, params) {
2634
3114
  }, params);
2635
3115
  }
2636
3116
  class CodeTarget {
2637
- constructor(name) {
3117
+ constructor(name, on) {
2638
3118
  this.indentLevel = 0;
2639
3119
  this.loopLevel = 0;
2640
3120
  this.code = [];
@@ -2645,6 +3125,7 @@ class CodeTarget {
2645
3125
  this.refInfo = {};
2646
3126
  this.shouldProtectScope = false;
2647
3127
  this.name = name;
3128
+ this.on = on || null;
2648
3129
  }
2649
3130
  addLine(line, idx) {
2650
3131
  const prefix = new Array(this.indentLevel + 2).join(" ");
@@ -2694,7 +3175,7 @@ class CodeGenerator {
2694
3175
  this.targets = [];
2695
3176
  this.target = new CodeTarget("template");
2696
3177
  this.translatableAttributes = TRANSLATABLE_ATTRS;
2697
- this.staticCalls = [];
3178
+ this.staticDefs = [];
2698
3179
  this.helpers = new Set();
2699
3180
  this.translateFn = options.translateFn || ((s) => s);
2700
3181
  if (options.translatableAttributes) {
@@ -2737,8 +3218,8 @@ class CodeGenerator {
2737
3218
  if (this.templateName) {
2738
3219
  mainCode.push(`// Template name: "${this.templateName}"`);
2739
3220
  }
2740
- for (let { id, template } of this.staticCalls) {
2741
- mainCode.push(`const ${id} = getTemplate(${template});`);
3221
+ for (let { id, expr } of this.staticDefs) {
3222
+ mainCode.push(`const ${id} = ${expr};`);
2742
3223
  }
2743
3224
  // define all blocks
2744
3225
  if (this.blocks.length) {
@@ -2774,19 +3255,21 @@ class CodeGenerator {
2774
3255
  }
2775
3256
  return code;
2776
3257
  }
2777
- compileInNewTarget(prefix, ast, ctx) {
3258
+ compileInNewTarget(prefix, ast, ctx, on) {
2778
3259
  const name = this.generateId(prefix);
2779
3260
  const initialTarget = this.target;
2780
- const target = new CodeTarget(name);
3261
+ const target = new CodeTarget(name, on);
2781
3262
  this.targets.push(target);
2782
3263
  this.target = target;
2783
- const subCtx = createContext(ctx);
2784
- this.compileAST(ast, subCtx);
3264
+ this.compileAST(ast, createContext(ctx));
2785
3265
  this.target = initialTarget;
2786
3266
  return name;
2787
3267
  }
2788
- addLine(line) {
2789
- this.target.addLine(line);
3268
+ addLine(line, idx) {
3269
+ this.target.addLine(line, idx);
3270
+ }
3271
+ define(varName, expr) {
3272
+ this.addLine(`const ${varName} = ${expr};`);
2790
3273
  }
2791
3274
  generateId(prefix = "") {
2792
3275
  this.ids[prefix] = (this.ids[prefix] || 0) + 1;
@@ -2828,10 +3311,13 @@ class CodeGenerator {
2828
3311
  blockExpr = `toggler(${tKeyExpr}, ${blockExpr})`;
2829
3312
  }
2830
3313
  if (block.isRoot && !ctx.preventRoot) {
3314
+ if (this.target.on) {
3315
+ blockExpr = this.wrapWithEventCatcher(blockExpr, this.target.on);
3316
+ }
2831
3317
  this.addLine(`return ${blockExpr};`);
2832
3318
  }
2833
3319
  else {
2834
- this.addLine(`let ${block.varName} = ${blockExpr};`);
3320
+ this.define(block.varName, blockExpr);
2835
3321
  }
2836
3322
  }
2837
3323
  /**
@@ -2859,7 +3345,7 @@ class CodeGenerator {
2859
3345
  if (!mapping.has(tok.varName)) {
2860
3346
  const varId = this.generateId("v");
2861
3347
  mapping.set(tok.varName, varId);
2862
- this.addLine(`const ${varId} = ${tok.value};`);
3348
+ this.define(varId, tok.value);
2863
3349
  }
2864
3350
  tok.value = mapping.get(tok.varName);
2865
3351
  }
@@ -2998,7 +3484,7 @@ class CodeGenerator {
2998
3484
  this.blocks.push(block);
2999
3485
  if (ast.dynamicTag) {
3000
3486
  const tagExpr = this.generateId("tag");
3001
- this.addLine(`let ${tagExpr} = ${compileExpr(ast.dynamicTag)};`);
3487
+ this.define(tagExpr, compileExpr(ast.dynamicTag));
3002
3488
  block.dynamicTagName = tagExpr;
3003
3489
  }
3004
3490
  }
@@ -3080,10 +3566,10 @@ class CodeGenerator {
3080
3566
  const { hasDynamicChildren, baseExpr, expr, eventType, shouldNumberize, shouldTrim, targetAttr, specialInitTargetAttr, } = ast.model;
3081
3567
  const baseExpression = compileExpr(baseExpr);
3082
3568
  const bExprId = this.generateId("bExpr");
3083
- this.addLine(`const ${bExprId} = ${baseExpression};`);
3569
+ this.define(bExprId, baseExpression);
3084
3570
  const expression = compileExpr(expr);
3085
3571
  const exprId = this.generateId("expr");
3086
- this.addLine(`const ${exprId} = ${expression};`);
3572
+ this.define(exprId, expression);
3087
3573
  const fullExpression = `${bExprId}[${exprId}]`;
3088
3574
  let idx;
3089
3575
  if (specialInitTargetAttr) {
@@ -3093,7 +3579,7 @@ class CodeGenerator {
3093
3579
  else if (hasDynamicChildren) {
3094
3580
  const bValueId = this.generateId("bValue");
3095
3581
  tModelSelectedExpr = `${bValueId}`;
3096
- this.addLine(`let ${tModelSelectedExpr} = ${fullExpression}`);
3582
+ this.define(tModelSelectedExpr, fullExpression);
3097
3583
  }
3098
3584
  else {
3099
3585
  idx = block.insertData(`${fullExpression}`, "attr");
@@ -3141,14 +3627,14 @@ class CodeGenerator {
3141
3627
  const children = block.children.slice();
3142
3628
  let current = children.shift();
3143
3629
  for (let i = codeIdx; i < code.length; i++) {
3144
- if (code[i].trimStart().startsWith(`let ${current.varName} `)) {
3145
- code[i] = code[i].replace(`let ${current.varName}`, current.varName);
3630
+ if (code[i].trimStart().startsWith(`const ${current.varName} `)) {
3631
+ code[i] = code[i].replace(`const ${current.varName}`, current.varName);
3146
3632
  current = children.shift();
3147
3633
  if (!current)
3148
3634
  break;
3149
3635
  }
3150
3636
  }
3151
- this.target.addLine(`let ${block.children.map((c) => c.varName)};`, codeIdx);
3637
+ this.addLine(`let ${block.children.map((c) => c.varName)};`, codeIdx);
3152
3638
  }
3153
3639
  }
3154
3640
  }
@@ -3236,14 +3722,14 @@ class CodeGenerator {
3236
3722
  const children = block.children.slice();
3237
3723
  let current = children.shift();
3238
3724
  for (let i = codeIdx; i < code.length; i++) {
3239
- if (code[i].trimStart().startsWith(`let ${current.varName} `)) {
3240
- code[i] = code[i].replace(`let ${current.varName}`, current.varName);
3725
+ if (code[i].trimStart().startsWith(`const ${current.varName} `)) {
3726
+ code[i] = code[i].replace(`const ${current.varName}`, current.varName);
3241
3727
  current = children.shift();
3242
3728
  if (!current)
3243
3729
  break;
3244
3730
  }
3245
3731
  }
3246
- this.target.addLine(`let ${block.children.map((c) => c.varName)};`, codeIdx);
3732
+ this.addLine(`let ${block.children.map((c) => c.varName)};`, codeIdx);
3247
3733
  }
3248
3734
  // note: this part is duplicated from end of compilemulti:
3249
3735
  const args = block.children.map((c) => c.varName).join(", ");
@@ -3264,10 +3750,10 @@ class CodeGenerator {
3264
3750
  const l = `l_block${block.id}`;
3265
3751
  const c = `c_block${block.id}`;
3266
3752
  this.helpers.add("prepareList");
3267
- this.addLine(`const [${keys}, ${vals}, ${l}, ${c}] = prepareList(${compileExpr(ast.collection)});`);
3753
+ this.define(`[${keys}, ${vals}, ${l}, ${c}]`, `prepareList(${compileExpr(ast.collection)});`);
3268
3754
  // Throw errors on duplicate keys in dev mode
3269
3755
  if (this.dev) {
3270
- this.addLine(`const keys${block.id} = new Set();`);
3756
+ this.define(`keys${block.id}`, `new Set()`);
3271
3757
  }
3272
3758
  this.addLine(`for (let ${loopVar} = 0; ${loopVar} < ${l}; ${loopVar}++) {`);
3273
3759
  this.target.indentLevel++;
@@ -3284,7 +3770,7 @@ class CodeGenerator {
3284
3770
  if (!ast.hasNoValue) {
3285
3771
  this.addLine(`ctx[\`${ast.elem}_value\`] = ${keys}[${loopVar}];`);
3286
3772
  }
3287
- this.addLine(`let key${this.target.loopLevel} = ${ast.key ? compileExpr(ast.key) : loopVar};`);
3773
+ this.define(`key${this.target.loopLevel}`, ast.key ? compileExpr(ast.key) : loopVar);
3288
3774
  if (this.dev) {
3289
3775
  // Throw error on duplicate keys in dev mode
3290
3776
  this.addLine(`if (keys${block.id}.has(key${this.target.loopLevel})) { throw new Error(\`Got duplicate key in t-foreach: \${key${this.target.loopLevel}}\`)}`);
@@ -3294,8 +3780,8 @@ class CodeGenerator {
3294
3780
  if (ast.memo) {
3295
3781
  this.target.hasCache = true;
3296
3782
  id = this.generateId();
3297
- this.addLine(`let memo${id} = ${compileExpr(ast.memo)}`);
3298
- this.addLine(`let vnode${id} = cache[key${this.target.loopLevel}];`);
3783
+ this.define(`memo${id}`, compileExpr(ast.memo));
3784
+ this.define(`vnode${id}`, `cache[key${this.target.loopLevel}];`);
3299
3785
  this.addLine(`if (vnode${id}) {`);
3300
3786
  this.target.indentLevel++;
3301
3787
  this.addLine(`if (shallowEqual(vnode${id}.memo, memo${id})) {`);
@@ -3323,7 +3809,7 @@ class CodeGenerator {
3323
3809
  }
3324
3810
  compileTKey(ast, ctx) {
3325
3811
  const tKeyExpr = this.generateId("tKey_");
3326
- this.addLine(`const ${tKeyExpr} = ${compileExpr(ast.expr)};`);
3812
+ this.define(tKeyExpr, compileExpr(ast.expr));
3327
3813
  ctx = createContext(ctx, {
3328
3814
  tKeyExpr,
3329
3815
  block: ctx.block,
@@ -3368,14 +3854,14 @@ class CodeGenerator {
3368
3854
  const children = block.children.slice();
3369
3855
  let current = children.shift();
3370
3856
  for (let i = codeIdx; i < code.length; i++) {
3371
- if (code[i].trimStart().startsWith(`let ${current.varName} `)) {
3372
- code[i] = code[i].replace(`let ${current.varName}`, current.varName);
3857
+ if (code[i].trimStart().startsWith(`const ${current.varName} `)) {
3858
+ code[i] = code[i].replace(`const ${current.varName}`, current.varName);
3373
3859
  current = children.shift();
3374
3860
  if (!current)
3375
3861
  break;
3376
3862
  }
3377
3863
  }
3378
- this.target.addLine(`let ${block.children.map((c) => c.varName)};`, codeIdx);
3864
+ this.addLine(`let ${block.children.map((c) => c.varName)};`, codeIdx);
3379
3865
  }
3380
3866
  }
3381
3867
  const args = block.children.map((c) => c.varName).join(", ");
@@ -3406,7 +3892,7 @@ class CodeGenerator {
3406
3892
  const key = `key + \`${this.generateComponentKey()}\``;
3407
3893
  if (isDynamic) {
3408
3894
  const templateVar = this.generateId("template");
3409
- this.addLine(`const ${templateVar} = ${subTemplate};`);
3895
+ this.define(templateVar, subTemplate);
3410
3896
  block = this.createBlock(block, "multi", ctx);
3411
3897
  this.helpers.add("call");
3412
3898
  this.insertBlock(`call(this, ${templateVar}, ctx, node, ${key})`, block, {
@@ -3417,7 +3903,7 @@ class CodeGenerator {
3417
3903
  else {
3418
3904
  const id = this.generateId(`callTemplate_`);
3419
3905
  this.helpers.add("getTemplate");
3420
- this.staticCalls.push({ id, template: subTemplate });
3906
+ this.staticDefs.push({ id, expr: `getTemplate(${subTemplate})` });
3421
3907
  block = this.createBlock(block, "multi", ctx);
3422
3908
  this.insertBlock(`${id}.call(this, ctx, node, ${key})`, block, {
3423
3909
  ...ctx,
@@ -3511,26 +3997,25 @@ class CodeGenerator {
3511
3997
  compileComponent(ast, ctx) {
3512
3998
  let { block } = ctx;
3513
3999
  // props
3514
- const hasSlotsProp = "slots" in ast.props;
4000
+ const hasSlotsProp = "slots" in (ast.props || {});
3515
4001
  const props = [];
3516
- const propExpr = this.formatPropObject(ast.props);
4002
+ const propExpr = this.formatPropObject(ast.props || {});
3517
4003
  if (propExpr) {
3518
4004
  props.push(propExpr);
3519
4005
  }
3520
4006
  // slots
3521
- const hasSlot = !!Object.keys(ast.slots).length;
3522
4007
  let slotDef = "";
3523
- if (hasSlot) {
4008
+ if (ast.slots) {
3524
4009
  let ctxStr = "ctx";
3525
4010
  if (this.target.loopLevel || !this.hasSafeContext) {
3526
4011
  ctxStr = this.generateId("ctx");
3527
4012
  this.helpers.add("capture");
3528
- this.addLine(`const ${ctxStr} = capture(ctx);`);
4013
+ this.define(ctxStr, `capture(ctx)`);
3529
4014
  }
3530
4015
  let slotStr = [];
3531
4016
  for (let slotName in ast.slots) {
3532
- const slotAst = ast.slots[slotName].content;
3533
- const name = this.compileInNewTarget("slot", slotAst, ctx);
4017
+ const slotAst = ast.slots[slotName];
4018
+ const name = this.compileInNewTarget("slot", slotAst.content, ctx, slotAst.on);
3534
4019
  const params = [`__render: ${name}, __ctx: ${ctxStr}`];
3535
4020
  const scope = ast.slots[slotName].scope;
3536
4021
  if (scope) {
@@ -3545,33 +4030,30 @@ class CodeGenerator {
3545
4030
  slotDef = `{${slotStr.join(", ")}}`;
3546
4031
  }
3547
4032
  if (slotDef && !(ast.dynamicProps || hasSlotsProp)) {
3548
- props.push(`slots: ${slotDef}`);
4033
+ this.helpers.add("markRaw");
4034
+ props.push(`slots: markRaw(${slotDef})`);
3549
4035
  }
3550
4036
  const propStr = `{${props.join(",")}}`;
3551
4037
  let propString = propStr;
3552
4038
  if (ast.dynamicProps) {
3553
- if (!props.length) {
3554
- propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)})`;
3555
- }
3556
- else {
3557
- propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)}, ${propStr})`;
3558
- }
4039
+ propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)}${props.length ? ", " + propStr : ""})`;
3559
4040
  }
3560
4041
  let propVar;
3561
4042
  if ((slotDef && (ast.dynamicProps || hasSlotsProp)) || this.dev) {
3562
4043
  propVar = this.generateId("props");
3563
- this.addLine(`const ${propVar} = ${propString};`);
4044
+ this.define(propVar, propString);
3564
4045
  propString = propVar;
3565
4046
  }
3566
4047
  if (slotDef && (ast.dynamicProps || hasSlotsProp)) {
3567
- this.addLine(`${propVar}.slots = Object.assign(${slotDef}, ${propVar}.slots)`);
4048
+ this.helpers.add("markRaw");
4049
+ this.addLine(`${propVar}.slots = markRaw(Object.assign(${slotDef}, ${propVar}.slots))`);
3568
4050
  }
3569
4051
  // cmap key
3570
4052
  const key = this.generateComponentKey();
3571
4053
  let expr;
3572
4054
  if (ast.isDynamic) {
3573
4055
  expr = this.generateId("Comp");
3574
- this.addLine(`let ${expr} = ${compileExpr(ast.name)};`);
4056
+ this.define(expr, compileExpr(ast.name));
3575
4057
  }
3576
4058
  else {
3577
4059
  expr = `\`${ast.name}\``;
@@ -3592,9 +4074,28 @@ class CodeGenerator {
3592
4074
  if (ast.isDynamic) {
3593
4075
  blockExpr = `toggler(${expr}, ${blockExpr})`;
3594
4076
  }
4077
+ // event handling
4078
+ if (ast.on) {
4079
+ blockExpr = this.wrapWithEventCatcher(blockExpr, ast.on);
4080
+ }
3595
4081
  block = this.createBlock(block, "multi", ctx);
3596
4082
  this.insertBlock(blockExpr, block, ctx);
3597
4083
  }
4084
+ wrapWithEventCatcher(expr, on) {
4085
+ this.helpers.add("createCatcher");
4086
+ let name = this.generateId("catcher");
4087
+ let spec = {};
4088
+ let handlers = [];
4089
+ for (let ev in on) {
4090
+ let handlerId = this.generateId("hdlr");
4091
+ let idx = handlers.push(handlerId) - 1;
4092
+ spec[ev] = idx;
4093
+ const handler = this.generateHandlerCode(ev, on[ev]);
4094
+ this.define(handlerId, handler);
4095
+ }
4096
+ this.staticDefs.push({ id: name, expr: `createCatcher(${JSON.stringify(spec)})` });
4097
+ return `${name}(${expr}, [${handlers.join(",")}])`;
4098
+ }
3598
4099
  compileTSlot(ast, ctx) {
3599
4100
  this.helpers.add("callSlot");
3600
4101
  let { block } = ctx;
@@ -3616,13 +4117,17 @@ class CodeGenerator {
3616
4117
  else {
3617
4118
  if (dynamic) {
3618
4119
  let name = this.generateId("slot");
3619
- this.addLine(`const ${name} = ${slotName};`);
4120
+ this.define(name, slotName);
3620
4121
  blockString = `toggler(${name}, callSlot(ctx, node, key, ${name}), ${dynamic}, ${scope})`;
3621
4122
  }
3622
4123
  else {
3623
4124
  blockString = `callSlot(ctx, node, key, ${slotName}, ${dynamic}, ${scope})`;
3624
4125
  }
3625
4126
  }
4127
+ // event handling
4128
+ if (ast.on) {
4129
+ blockString = this.wrapWithEventCatcher(blockString, ast.on);
4130
+ }
3626
4131
  if (block) {
3627
4132
  this.insertAnchor(block);
3628
4133
  }
@@ -3643,7 +4148,7 @@ class CodeGenerator {
3643
4148
  if (this.target.loopLevel || !this.hasSafeContext) {
3644
4149
  ctxStr = this.generateId("ctx");
3645
4150
  this.helpers.add("capture");
3646
- this.addLine(`const ${ctxStr} = capture(ctx);`);
4151
+ this.define(ctxStr, `capture(ctx);`);
3647
4152
  }
3648
4153
  const blockString = `component(Portal, {target: ${ast.target},slots: {'default': {__render: ${name}, __ctx: ${ctxStr}}}}, key + \`${key}\`, node, ctx)`;
3649
4154
  if (block) {
@@ -3764,6 +4269,9 @@ function parseDOMNode(node, ctx) {
3764
4269
  if (tagName === "t" && !dynamicTag) {
3765
4270
  return null;
3766
4271
  }
4272
+ if (tagName.startsWith("block-")) {
4273
+ throw new Error(`Invalid tag name: '${tagName}'`);
4274
+ }
3767
4275
  ctx = Object.assign({}, ctx);
3768
4276
  if (tagName === "pre") {
3769
4277
  ctx.inPreTag = true;
@@ -3774,8 +4282,8 @@ function parseDOMNode(node, ctx) {
3774
4282
  const ref = node.getAttribute("t-ref");
3775
4283
  node.removeAttribute("t-ref");
3776
4284
  const nodeAttrsNames = node.getAttributeNames();
3777
- const attrs = {};
3778
- const on = {};
4285
+ let attrs = null;
4286
+ let on = null;
3779
4287
  let model = null;
3780
4288
  for (let attr of nodeAttrsNames) {
3781
4289
  const value = node.getAttribute(attr);
@@ -3783,6 +4291,7 @@ function parseDOMNode(node, ctx) {
3783
4291
  if (attr === "t-on") {
3784
4292
  throw new Error("Missing event name with t-on directive");
3785
4293
  }
4294
+ on = on || {};
3786
4295
  on[attr.slice(5)] = value;
3787
4296
  }
3788
4297
  else if (attr.startsWith("t-model")) {
@@ -3820,6 +4329,7 @@ function parseDOMNode(node, ctx) {
3820
4329
  targetAttr: isCheckboxInput ? "checked" : "value",
3821
4330
  specialInitTargetAttr: isRadioInput ? "checked" : null,
3822
4331
  eventType,
4332
+ hasDynamicChildren: false,
3823
4333
  shouldTrim: hasTrimMod && (isOtherInput || isTextarea),
3824
4334
  shouldNumberize: hasNumberMod && (isOtherInput || isTextarea),
3825
4335
  };
@@ -3829,6 +4339,9 @@ function parseDOMNode(node, ctx) {
3829
4339
  ctx.tModelInfo = model;
3830
4340
  }
3831
4341
  }
4342
+ else if (attr.startsWith("block-")) {
4343
+ throw new Error(`Invalid attribute: '${attr}'`);
4344
+ }
3832
4345
  else if (attr !== "t-name") {
3833
4346
  if (attr.startsWith("t-") && !attr.startsWith("t-att")) {
3834
4347
  throw new Error(`Unknown QWeb directive: '${attr}'`);
@@ -3837,6 +4350,7 @@ function parseDOMNode(node, ctx) {
3837
4350
  if (tModel && ["t-att-value", "t-attf-value"].includes(attr)) {
3838
4351
  tModel.hasDynamicChildren = true;
3839
4352
  }
4353
+ attrs = attrs || {};
3840
4354
  attrs[attr] = value;
3841
4355
  }
3842
4356
  }
@@ -3987,7 +4501,7 @@ function parseTCall(node, ctx) {
3987
4501
  if (ast && ast.type === 11 /* TComponent */) {
3988
4502
  return {
3989
4503
  ...ast,
3990
- slots: { default: { content: tcall } },
4504
+ slots: { default: { content: tcall, scope: null, on: null, attrs: null } },
3991
4505
  };
3992
4506
  }
3993
4507
  }
@@ -4071,7 +4585,6 @@ function parseTSetNode(node, ctx) {
4071
4585
  // -----------------------------------------------------------------------------
4072
4586
  // Error messages when trying to use an unsupported directive on a component
4073
4587
  const directiveErrorMap = new Map([
4074
- ["t-on", "t-on is no longer supported on components. Consider passing a callback in props."],
4075
4588
  [
4076
4589
  "t-ref",
4077
4590
  "t-ref is no longer supported on components. Consider exposing only the public part of the component's API through a callback prop.",
@@ -4100,18 +4613,26 @@ function parseComponent(node, ctx) {
4100
4613
  node.removeAttribute("t-props");
4101
4614
  const defaultSlotScope = node.getAttribute("t-slot-scope");
4102
4615
  node.removeAttribute("t-slot-scope");
4103
- const props = {};
4616
+ let on = null;
4617
+ let props = null;
4104
4618
  for (let name of node.getAttributeNames()) {
4105
4619
  const value = node.getAttribute(name);
4106
4620
  if (name.startsWith("t-")) {
4107
- const message = directiveErrorMap.get(name.split("-").slice(0, 2).join("-"));
4108
- throw new Error(message || `unsupported directive on Component: ${name}`);
4621
+ if (name.startsWith("t-on-")) {
4622
+ on = on || {};
4623
+ on[name.slice(5)] = value;
4624
+ }
4625
+ else {
4626
+ const message = directiveErrorMap.get(name.split("-").slice(0, 2).join("-"));
4627
+ throw new Error(message || `unsupported directive on Component: ${name}`);
4628
+ }
4109
4629
  }
4110
4630
  else {
4631
+ props = props || {};
4111
4632
  props[name] = value;
4112
4633
  }
4113
4634
  }
4114
- const slots = {};
4635
+ let slots = null;
4115
4636
  if (node.hasChildNodes()) {
4116
4637
  const clone = node.cloneNode(true);
4117
4638
  // named slots
@@ -4139,32 +4660,36 @@ function parseComponent(node, ctx) {
4139
4660
  slotNode.remove();
4140
4661
  const slotAst = parseNode(slotNode, ctx);
4141
4662
  if (slotAst) {
4142
- const slotInfo = { content: slotAst };
4143
- const attrs = {};
4663
+ let on = null;
4664
+ let attrs = null;
4665
+ let scope = null;
4144
4666
  for (let attributeName of slotNode.getAttributeNames()) {
4145
4667
  const value = slotNode.getAttribute(attributeName);
4146
4668
  if (attributeName === "t-slot-scope") {
4147
- slotInfo.scope = value;
4669
+ scope = value;
4148
4670
  continue;
4149
4671
  }
4150
- attrs[attributeName] = value;
4151
- }
4152
- if (Object.keys(attrs).length) {
4153
- slotInfo.attrs = attrs;
4672
+ else if (attributeName.startsWith("t-on-")) {
4673
+ on = on || {};
4674
+ on[attributeName.slice(5)] = value;
4675
+ }
4676
+ else {
4677
+ attrs = attrs || {};
4678
+ attrs[attributeName] = value;
4679
+ }
4154
4680
  }
4155
- slots[name] = slotInfo;
4681
+ slots = slots || {};
4682
+ slots[name] = { content: slotAst, on, attrs, scope };
4156
4683
  }
4157
4684
  }
4158
4685
  // default slot
4159
4686
  const defaultContent = parseChildNodes(clone, ctx);
4160
4687
  if (defaultContent) {
4161
- slots.default = { content: defaultContent };
4162
- if (defaultSlotScope) {
4163
- slots.default.scope = defaultSlotScope;
4164
- }
4688
+ slots = slots || {};
4689
+ slots.default = { content: defaultContent, on, attrs: null, scope: defaultSlotScope };
4165
4690
  }
4166
4691
  }
4167
- return { type: 11 /* TComponent */, name, isDynamic, dynamicProps, props, slots };
4692
+ return { type: 11 /* TComponent */, name, isDynamic, dynamicProps, props, slots, on };
4168
4693
  }
4169
4694
  // -----------------------------------------------------------------------------
4170
4695
  // Slots
@@ -4175,15 +4700,24 @@ function parseTSlot(node, ctx) {
4175
4700
  }
4176
4701
  const name = node.getAttribute("t-slot");
4177
4702
  node.removeAttribute("t-slot");
4178
- const attrs = {};
4703
+ let attrs = null;
4704
+ let on = null;
4179
4705
  for (let attributeName of node.getAttributeNames()) {
4180
4706
  const value = node.getAttribute(attributeName);
4181
- attrs[attributeName] = value;
4707
+ if (attributeName.startsWith("t-on-")) {
4708
+ on = on || {};
4709
+ on[attributeName.slice(5)] = value;
4710
+ }
4711
+ else {
4712
+ attrs = attrs || {};
4713
+ attrs[attributeName] = value;
4714
+ }
4182
4715
  }
4183
4716
  return {
4184
4717
  type: 14 /* TSlot */,
4185
4718
  name,
4186
4719
  attrs,
4720
+ on,
4187
4721
  defaultContent: parseChildNodes(node, ctx),
4188
4722
  };
4189
4723
  }
@@ -4377,108 +4911,112 @@ function compile(template, options = {}) {
4377
4911
  return new Function("bdom, helpers", code);
4378
4912
  }
4379
4913
 
4380
- const bdom = { text, createBlock, list, multi, html, toggler, component, comment };
4381
- const globalTemplates = {};
4382
- function parseXML(xml) {
4383
- const parser = new DOMParser();
4384
- const doc = parser.parseFromString(xml, "text/xml");
4385
- if (doc.getElementsByTagName("parsererror").length) {
4386
- let msg = "Invalid XML in template.";
4387
- const parsererrorText = doc.getElementsByTagName("parsererror")[0].textContent;
4388
- if (parsererrorText) {
4389
- msg += "\nThe parser has produced the following error message:\n" + parsererrorText;
4390
- const re = /\d+/g;
4391
- const firstMatch = re.exec(parsererrorText);
4392
- if (firstMatch) {
4393
- const lineNumber = Number(firstMatch[0]);
4394
- const line = xml.split("\n")[lineNumber - 1];
4395
- const secondMatch = re.exec(parsererrorText);
4396
- if (line && secondMatch) {
4397
- const columnIndex = Number(secondMatch[0]) - 1;
4398
- if (line[columnIndex]) {
4399
- msg +=
4400
- `\nThe error might be located at xml line ${lineNumber} column ${columnIndex}\n` +
4401
- `${line}\n${"-".repeat(columnIndex - 1)}^`;
4402
- }
4914
+ const TIMEOUT = Symbol("timeout");
4915
+ function wrapError(fn, hookName) {
4916
+ const error = new Error(`The following error occurred in ${hookName}: `);
4917
+ const timeoutError = new Error(`${hookName}'s promise hasn't resolved after 3 seconds`);
4918
+ const node = getCurrent();
4919
+ return (...args) => {
4920
+ try {
4921
+ const result = fn(...args);
4922
+ if (result instanceof Promise) {
4923
+ if (hookName === "onWillStart" || hookName === "onWillUpdateProps") {
4924
+ const fiber = node.fiber;
4925
+ Promise.race([
4926
+ result,
4927
+ new Promise((resolve) => setTimeout(() => resolve(TIMEOUT), 3000)),
4928
+ ]).then((res) => {
4929
+ if (res === TIMEOUT && node.fiber === fiber) {
4930
+ console.warn(timeoutError);
4931
+ }
4932
+ });
4403
4933
  }
4934
+ return result.catch((cause) => {
4935
+ error.cause = cause;
4936
+ if (cause instanceof Error) {
4937
+ error.message += `"${cause.message}"`;
4938
+ }
4939
+ throw error;
4940
+ });
4404
4941
  }
4942
+ return result;
4405
4943
  }
4406
- throw new Error(msg);
4407
- }
4408
- return doc;
4409
- }
4410
- class TemplateSet {
4411
- constructor(config = {}) {
4412
- this.rawTemplates = Object.create(globalTemplates);
4413
- this.templates = {};
4414
- this.utils = Object.assign({}, UTILS, {
4415
- call: (owner, subTemplate, ctx, parent, key) => {
4416
- const template = this.getTemplate(subTemplate);
4417
- return toggler(subTemplate, template.call(owner, ctx, parent, key));
4418
- },
4419
- getTemplate: (name) => this.getTemplate(name),
4420
- });
4421
- this.dev = config.dev || false;
4422
- this.translateFn = config.translateFn;
4423
- this.translatableAttributes = config.translatableAttributes;
4424
- if (config.templates) {
4425
- this.addTemplates(config.templates);
4426
- }
4427
- }
4428
- addTemplate(name, template, options = {}) {
4429
- if (name in this.rawTemplates && !options.allowDuplicate) {
4430
- throw new Error(`Template ${name} already defined`);
4431
- }
4432
- this.rawTemplates[name] = template;
4433
- }
4434
- addTemplates(xml, options = {}) {
4435
- if (!xml) {
4436
- // empty string
4437
- return;
4438
- }
4439
- xml = xml instanceof Document ? xml : parseXML(xml);
4440
- for (const template of xml.querySelectorAll("[t-name]")) {
4441
- const name = template.getAttribute("t-name");
4442
- this.addTemplate(name, template, options);
4443
- }
4444
- }
4445
- getTemplate(name) {
4446
- if (!(name in this.templates)) {
4447
- const rawTemplate = this.rawTemplates[name];
4448
- if (rawTemplate === undefined) {
4449
- throw new Error(`Missing template: "${name}"`);
4944
+ catch (cause) {
4945
+ if (cause instanceof Error) {
4946
+ error.message += `"${cause.message}"`;
4450
4947
  }
4451
- const templateFn = this._compileTemplate(name, rawTemplate);
4452
- // first add a function to lazily get the template, in case there is a
4453
- // recursive call to the template name
4454
- const templates = this.templates;
4455
- this.templates[name] = function (context, parent) {
4456
- return templates[name].call(this, context, parent);
4457
- };
4458
- const template = templateFn(bdom, this.utils);
4459
- this.templates[name] = template;
4948
+ throw error;
4460
4949
  }
4461
- return this.templates[name];
4462
- }
4463
- _compileTemplate(name, template) {
4464
- return compile(template, {
4465
- name,
4466
- dev: this.dev,
4467
- translateFn: this.translateFn,
4468
- translatableAttributes: this.translatableAttributes,
4469
- });
4470
- }
4950
+ };
4951
+ }
4952
+ // -----------------------------------------------------------------------------
4953
+ // hooks
4954
+ // -----------------------------------------------------------------------------
4955
+ function onWillStart(fn) {
4956
+ const node = getCurrent();
4957
+ const decorate = node.app.dev ? wrapError : (fn) => fn;
4958
+ node.willStart.push(decorate(fn.bind(node.component), "onWillStart"));
4959
+ }
4960
+ function onWillUpdateProps(fn) {
4961
+ const node = getCurrent();
4962
+ const decorate = node.app.dev ? wrapError : (fn) => fn;
4963
+ node.willUpdateProps.push(decorate(fn.bind(node.component), "onWillUpdateProps"));
4964
+ }
4965
+ function onMounted(fn) {
4966
+ const node = getCurrent();
4967
+ const decorate = node.app.dev ? wrapError : (fn) => fn;
4968
+ node.mounted.push(decorate(fn.bind(node.component), "onMounted"));
4969
+ }
4970
+ function onWillPatch(fn) {
4971
+ const node = getCurrent();
4972
+ const decorate = node.app.dev ? wrapError : (fn) => fn;
4973
+ node.willPatch.unshift(decorate(fn.bind(node.component), "onWillPatch"));
4471
4974
  }
4472
- // -----------------------------------------------------------------------------
4473
- // xml tag helper
4474
- // -----------------------------------------------------------------------------
4475
- function xml(...args) {
4476
- const name = `__template__${xml.nextId++}`;
4477
- const value = String.raw(...args);
4478
- globalTemplates[name] = value;
4479
- return name;
4975
+ function onPatched(fn) {
4976
+ const node = getCurrent();
4977
+ const decorate = node.app.dev ? wrapError : (fn) => fn;
4978
+ node.patched.push(decorate(fn.bind(node.component), "onPatched"));
4480
4979
  }
4481
- xml.nextId = 1;
4980
+ function onWillUnmount(fn) {
4981
+ const node = getCurrent();
4982
+ const decorate = node.app.dev ? wrapError : (fn) => fn;
4983
+ node.willUnmount.unshift(decorate(fn.bind(node.component), "onWillUnmount"));
4984
+ }
4985
+ function onWillDestroy(fn) {
4986
+ const node = getCurrent();
4987
+ const decorate = node.app.dev ? wrapError : (fn) => fn;
4988
+ node.willDestroy.push(decorate(fn.bind(node.component), "onWillDestroy"));
4989
+ }
4990
+ function onWillRender(fn) {
4991
+ const node = getCurrent();
4992
+ const renderFn = node.renderFn;
4993
+ const decorate = node.app.dev ? wrapError : (fn) => fn;
4994
+ fn = decorate(fn.bind(node.component), "onWillRender");
4995
+ node.renderFn = () => {
4996
+ fn();
4997
+ return renderFn();
4998
+ };
4999
+ }
5000
+ function onRendered(fn) {
5001
+ const node = getCurrent();
5002
+ const renderFn = node.renderFn;
5003
+ const decorate = node.app.dev ? wrapError : (fn) => fn;
5004
+ fn = decorate(fn.bind(node.component), "onRendered");
5005
+ node.renderFn = () => {
5006
+ const result = renderFn();
5007
+ fn();
5008
+ return result;
5009
+ };
5010
+ }
5011
+ function onError(callback) {
5012
+ const node = getCurrent();
5013
+ let handlers = nodeErrorHandlers.get(node);
5014
+ if (!handlers) {
5015
+ handlers = [];
5016
+ nodeErrorHandlers.set(node, handlers);
5017
+ }
5018
+ handlers.push(callback.bind(node.component));
5019
+ }
4482
5020
 
4483
5021
  class Component {
4484
5022
  constructor(props, env, node) {
@@ -4487,8 +5025,8 @@ class Component {
4487
5025
  this.__owl__ = node;
4488
5026
  }
4489
5027
  setup() { }
4490
- render() {
4491
- this.__owl__.render();
5028
+ render(deep = false) {
5029
+ this.__owl__.render(deep);
4492
5030
  }
4493
5031
  }
4494
5032
  Component.template = "";
@@ -4557,75 +5095,293 @@ Portal.props = {
4557
5095
  slots: true,
4558
5096
  };
4559
5097
 
4560
- // -----------------------------------------------------------------------------
4561
- // Scheduler
4562
- // -----------------------------------------------------------------------------
4563
- class Scheduler {
4564
- constructor() {
4565
- this.tasks = new Set();
4566
- this.isRunning = false;
4567
- this.requestAnimationFrame = Scheduler.requestAnimationFrame;
5098
+ /**
5099
+ * This file contains utility functions that will be injected in each template,
5100
+ * to perform various useful tasks in the compiled code.
5101
+ */
5102
+ function withDefault(value, defaultValue) {
5103
+ return value === undefined || value === null || value === false ? defaultValue : value;
5104
+ }
5105
+ function callSlot(ctx, parent, key, name, dynamic, extra, defaultContent) {
5106
+ key = key + "__slot_" + name;
5107
+ const slots = ctx.props[TARGET].slots || {};
5108
+ const { __render, __ctx, __scope } = slots[name] || {};
5109
+ const slotScope = Object.create(__ctx || {});
5110
+ if (__scope) {
5111
+ slotScope[__scope] = extra;
5112
+ }
5113
+ const slotBDom = __render ? __render.call(__ctx.__owl__.component, slotScope, parent, key) : null;
5114
+ if (defaultContent) {
5115
+ let child1 = undefined;
5116
+ let child2 = undefined;
5117
+ if (slotBDom) {
5118
+ child1 = dynamic ? toggler(name, slotBDom) : slotBDom;
5119
+ }
5120
+ else {
5121
+ child2 = defaultContent.call(ctx.__owl__.component, ctx, parent, key);
5122
+ }
5123
+ return multi([child1, child2]);
5124
+ }
5125
+ return slotBDom || text("");
5126
+ }
5127
+ function capture(ctx) {
5128
+ const component = ctx.__owl__.component;
5129
+ const result = Object.create(component);
5130
+ for (let k in ctx) {
5131
+ result[k] = ctx[k];
5132
+ }
5133
+ return result;
5134
+ }
5135
+ function withKey(elem, k) {
5136
+ elem.key = k;
5137
+ return elem;
5138
+ }
5139
+ function prepareList(collection) {
5140
+ let keys;
5141
+ let values;
5142
+ if (Array.isArray(collection)) {
5143
+ keys = collection;
5144
+ values = collection;
5145
+ }
5146
+ else if (collection) {
5147
+ values = Object.keys(collection);
5148
+ keys = Object.values(collection);
5149
+ }
5150
+ else {
5151
+ throw new Error("Invalid loop expression");
5152
+ }
5153
+ const n = values.length;
5154
+ return [keys, values, n, new Array(n)];
5155
+ }
5156
+ const isBoundary = Symbol("isBoundary");
5157
+ function setContextValue(ctx, key, value) {
5158
+ const ctx0 = ctx;
5159
+ while (!ctx.hasOwnProperty(key) && !ctx.hasOwnProperty(isBoundary)) {
5160
+ const newCtx = ctx.__proto__;
5161
+ if (!newCtx) {
5162
+ ctx = ctx0;
5163
+ break;
5164
+ }
5165
+ ctx = newCtx;
5166
+ }
5167
+ ctx[key] = value;
5168
+ }
5169
+ function toNumber(val) {
5170
+ const n = parseFloat(val);
5171
+ return isNaN(n) ? val : n;
5172
+ }
5173
+ function shallowEqual(l1, l2) {
5174
+ for (let i = 0, l = l1.length; i < l; i++) {
5175
+ if (l1[i] !== l2[i]) {
5176
+ return false;
5177
+ }
5178
+ }
5179
+ return true;
5180
+ }
5181
+ class LazyValue {
5182
+ constructor(fn, ctx, node) {
5183
+ this.fn = fn;
5184
+ this.ctx = capture(ctx);
5185
+ this.node = node;
5186
+ }
5187
+ evaluate() {
5188
+ return this.fn(this.ctx, this.node);
5189
+ }
5190
+ toString() {
5191
+ return this.evaluate().toString();
5192
+ }
5193
+ }
5194
+ /*
5195
+ * Safely outputs `value` as a block depending on the nature of `value`
5196
+ */
5197
+ function safeOutput(value) {
5198
+ if (!value) {
5199
+ return value;
5200
+ }
5201
+ let safeKey;
5202
+ let block;
5203
+ if (value instanceof Markup) {
5204
+ safeKey = `string_safe`;
5205
+ block = html(value);
5206
+ }
5207
+ else if (value instanceof LazyValue) {
5208
+ safeKey = `lazy_value`;
5209
+ block = value.evaluate();
5210
+ }
5211
+ else if (value instanceof String || typeof value === "string") {
5212
+ safeKey = "string_unsafe";
5213
+ block = text(value);
5214
+ }
5215
+ else {
5216
+ // Assuming it is a block
5217
+ safeKey = "block_safe";
5218
+ block = value;
5219
+ }
5220
+ return toggler(safeKey, block);
5221
+ }
5222
+ let boundFunctions = new WeakMap();
5223
+ function bind(ctx, fn) {
5224
+ let component = ctx.__owl__.component;
5225
+ let boundFnMap = boundFunctions.get(component);
5226
+ if (!boundFnMap) {
5227
+ boundFnMap = new WeakMap();
5228
+ boundFunctions.set(component, boundFnMap);
5229
+ }
5230
+ let boundFn = boundFnMap.get(fn);
5231
+ if (!boundFn) {
5232
+ boundFn = fn.bind(component);
5233
+ boundFnMap.set(fn, boundFn);
5234
+ }
5235
+ return boundFn;
5236
+ }
5237
+ function multiRefSetter(refs, name) {
5238
+ let count = 0;
5239
+ return (el) => {
5240
+ if (el) {
5241
+ count++;
5242
+ if (count > 1) {
5243
+ throw new Error("Cannot have 2 elements with same ref name at the same time");
5244
+ }
5245
+ }
5246
+ if (count === 0 || el) {
5247
+ refs[name] = el;
5248
+ }
5249
+ };
5250
+ }
5251
+ const helpers = {
5252
+ withDefault,
5253
+ zero: Symbol("zero"),
5254
+ isBoundary,
5255
+ callSlot,
5256
+ capture,
5257
+ withKey,
5258
+ prepareList,
5259
+ setContextValue,
5260
+ multiRefSetter,
5261
+ shallowEqual,
5262
+ toNumber,
5263
+ validateProps,
5264
+ LazyValue,
5265
+ safeOutput,
5266
+ bind,
5267
+ createCatcher,
5268
+ };
5269
+
5270
+ const bdom = { text, createBlock, list, multi, html, toggler, component, comment };
5271
+ function parseXML(xml) {
5272
+ const parser = new DOMParser();
5273
+ const doc = parser.parseFromString(xml, "text/xml");
5274
+ if (doc.getElementsByTagName("parsererror").length) {
5275
+ let msg = "Invalid XML in template.";
5276
+ const parsererrorText = doc.getElementsByTagName("parsererror")[0].textContent;
5277
+ if (parsererrorText) {
5278
+ msg += "\nThe parser has produced the following error message:\n" + parsererrorText;
5279
+ const re = /\d+/g;
5280
+ const firstMatch = re.exec(parsererrorText);
5281
+ if (firstMatch) {
5282
+ const lineNumber = Number(firstMatch[0]);
5283
+ const line = xml.split("\n")[lineNumber - 1];
5284
+ const secondMatch = re.exec(parsererrorText);
5285
+ if (line && secondMatch) {
5286
+ const columnIndex = Number(secondMatch[0]) - 1;
5287
+ if (line[columnIndex]) {
5288
+ msg +=
5289
+ `\nThe error might be located at xml line ${lineNumber} column ${columnIndex}\n` +
5290
+ `${line}\n${"-".repeat(columnIndex - 1)}^`;
5291
+ }
5292
+ }
5293
+ }
5294
+ }
5295
+ throw new Error(msg);
4568
5296
  }
4569
- start() {
4570
- this.isRunning = true;
4571
- this.scheduleTasks();
5297
+ return doc;
5298
+ }
5299
+ /**
5300
+ * Returns the helpers object that will be injected in each template closure
5301
+ * function
5302
+ */
5303
+ function makeHelpers(getTemplate) {
5304
+ return Object.assign({}, helpers, {
5305
+ Portal,
5306
+ markRaw,
5307
+ getTemplate,
5308
+ call: (owner, subTemplate, ctx, parent, key) => {
5309
+ const template = getTemplate(subTemplate);
5310
+ return toggler(subTemplate, template.call(owner, ctx, parent, key));
5311
+ },
5312
+ });
5313
+ }
5314
+ class TemplateSet {
5315
+ constructor(config = {}) {
5316
+ this.rawTemplates = Object.create(globalTemplates);
5317
+ this.templates = {};
5318
+ this.dev = config.dev || false;
5319
+ this.translateFn = config.translateFn;
5320
+ this.translatableAttributes = config.translatableAttributes;
5321
+ if (config.templates) {
5322
+ this.addTemplates(config.templates);
5323
+ }
5324
+ this.helpers = makeHelpers(this.getTemplate.bind(this));
4572
5325
  }
4573
- stop() {
4574
- this.isRunning = false;
5326
+ addTemplate(name, template, options = {}) {
5327
+ if (name in this.rawTemplates && !options.allowDuplicate) {
5328
+ throw new Error(`Template ${name} already defined`);
5329
+ }
5330
+ this.rawTemplates[name] = template;
4575
5331
  }
4576
- addFiber(fiber) {
4577
- this.tasks.add(fiber.root);
4578
- if (!this.isRunning) {
4579
- this.start();
5332
+ addTemplates(xml, options = {}) {
5333
+ if (!xml) {
5334
+ // empty string
5335
+ return;
5336
+ }
5337
+ xml = xml instanceof Document ? xml : parseXML(xml);
5338
+ for (const template of xml.querySelectorAll("[t-name]")) {
5339
+ const name = template.getAttribute("t-name");
5340
+ this.addTemplate(name, template, options);
4580
5341
  }
4581
5342
  }
4582
- /**
4583
- * Process all current tasks. This only applies to the fibers that are ready.
4584
- * Other tasks are left unchanged.
4585
- */
4586
- flush() {
4587
- this.tasks.forEach((fiber) => {
4588
- if (fiber.root !== fiber) {
4589
- this.tasks.delete(fiber);
4590
- return;
4591
- }
4592
- const hasError = fibersInError.has(fiber);
4593
- if (hasError && fiber.counter !== 0) {
4594
- this.tasks.delete(fiber);
4595
- return;
4596
- }
4597
- if (fiber.node.status === 2 /* DESTROYED */) {
4598
- this.tasks.delete(fiber);
4599
- return;
4600
- }
4601
- if (fiber.counter === 0) {
4602
- if (!hasError) {
4603
- fiber.complete();
5343
+ getTemplate(name) {
5344
+ if (!(name in this.templates)) {
5345
+ const rawTemplate = this.rawTemplates[name];
5346
+ if (rawTemplate === undefined) {
5347
+ let extraInfo = "";
5348
+ try {
5349
+ const componentName = getCurrent().component.constructor.name;
5350
+ extraInfo = ` (for component "${componentName}")`;
4604
5351
  }
4605
- this.tasks.delete(fiber);
5352
+ catch { }
5353
+ throw new Error(`Missing template: "${name}"${extraInfo}`);
4606
5354
  }
4607
- });
4608
- if (this.tasks.size === 0) {
4609
- this.stop();
5355
+ const templateFn = this._compileTemplate(name, rawTemplate);
5356
+ // first add a function to lazily get the template, in case there is a
5357
+ // recursive call to the template name
5358
+ const templates = this.templates;
5359
+ this.templates[name] = function (context, parent) {
5360
+ return templates[name].call(this, context, parent);
5361
+ };
5362
+ const template = templateFn(bdom, this.helpers);
5363
+ this.templates[name] = template;
4610
5364
  }
5365
+ return this.templates[name];
4611
5366
  }
4612
- scheduleTasks() {
4613
- this.requestAnimationFrame(() => {
4614
- this.flush();
4615
- if (this.isRunning) {
4616
- this.scheduleTasks();
4617
- }
5367
+ _compileTemplate(name, template) {
5368
+ return compile(template, {
5369
+ name,
5370
+ dev: this.dev,
5371
+ translateFn: this.translateFn,
5372
+ translatableAttributes: this.translatableAttributes,
4618
5373
  });
4619
5374
  }
4620
- }
4621
- // capture the value of requestAnimationFrame as soon as possible, to avoid
4622
- // interactions with other code, such as test frameworks that override them
4623
- Scheduler.requestAnimationFrame = window.requestAnimationFrame.bind(window);
5375
+ }
4624
5376
 
4625
- const DEV_MSG = `Owl is running in 'dev' mode.
5377
+ let hasBeenLogged = false;
5378
+ const DEV_MSG = () => {
5379
+ const hash = window.owl ? window.owl.__info__.hash : "master";
5380
+ return `Owl is running in 'dev' mode.
4626
5381
 
4627
5382
  This is not suitable for production use.
4628
- See https://github.com/odoo/owl/blob/master/doc/reference/config.md#mode for more information.`;
5383
+ See https://github.com/odoo/owl/blob/${hash}/doc/reference/app.md#configuration for more information.`;
5384
+ };
4629
5385
  class App extends TemplateSet {
4630
5386
  constructor(Root, config = {}) {
4631
5387
  super(config);
@@ -4635,11 +5391,13 @@ class App extends TemplateSet {
4635
5391
  if (config.test) {
4636
5392
  this.dev = true;
4637
5393
  }
4638
- if (this.dev && !config.test) {
4639
- console.info(DEV_MSG);
5394
+ if (this.dev && !config.test && !hasBeenLogged) {
5395
+ console.info(DEV_MSG());
5396
+ hasBeenLogged = true;
4640
5397
  }
4641
- const descrs = Object.getOwnPropertyDescriptors(config.env || {});
4642
- this.env = Object.freeze(Object.defineProperties({}, descrs));
5398
+ const env = config.env || {};
5399
+ const descrs = Object.getOwnPropertyDescriptors(env);
5400
+ this.env = Object.freeze(Object.create(Object.getPrototypeOf(env), descrs));
4643
5401
  this.props = config.props || {};
4644
5402
  }
4645
5403
  mount(target, options) {
@@ -4682,6 +5440,7 @@ class App extends TemplateSet {
4682
5440
  }
4683
5441
  destroy() {
4684
5442
  if (this.root) {
5443
+ this.scheduler.flush();
4685
5444
  this.root.destroy();
4686
5445
  }
4687
5446
  }
@@ -4702,270 +5461,6 @@ function status(component) {
4702
5461
  }
4703
5462
  }
4704
5463
 
4705
- class Memo extends Component {
4706
- constructor(props, env, node) {
4707
- super(props, env, node);
4708
- // prevent patching process conditionally
4709
- let applyPatch = false;
4710
- const patchFn = node.patch;
4711
- node.patch = () => {
4712
- if (applyPatch) {
4713
- patchFn.call(node);
4714
- applyPatch = false;
4715
- }
4716
- };
4717
- // check props change, and render/apply patch if it changed
4718
- let prevProps = props;
4719
- const updateAndRender = node.updateAndRender;
4720
- node.updateAndRender = function (props, parentFiber) {
4721
- const shouldUpdate = !shallowEqual(prevProps, props);
4722
- if (shouldUpdate) {
4723
- prevProps = props;
4724
- updateAndRender.call(node, props, parentFiber);
4725
- applyPatch = true;
4726
- }
4727
- return Promise.resolve();
4728
- };
4729
- }
4730
- }
4731
- Memo.template = xml `<t t-slot="default"/>`;
4732
- /**
4733
- * we assume that each object have the same set of keys
4734
- */
4735
- function shallowEqual(p1, p2) {
4736
- for (let k in p1) {
4737
- if (k !== "slots" && p1[k] !== p2[k]) {
4738
- return false;
4739
- }
4740
- }
4741
- return true;
4742
- }
4743
-
4744
- // Allows to get the target of a Reactive (used for making a new Reactive from the underlying object)
4745
- const TARGET = Symbol("Target");
4746
- // Escape hatch to prevent reactivity system to turn something into a reactive
4747
- const SKIP = Symbol("Skip");
4748
- // Special key to subscribe to, to be notified of key creation/deletion
4749
- const KEYCHANGES = Symbol("Key changes");
4750
- const objectToString = Object.prototype.toString;
4751
- /**
4752
- * Checks whether a given value can be made into a reactive object.
4753
- *
4754
- * @param value the value to check
4755
- * @returns whether the value can be made reactive
4756
- */
4757
- function canBeMadeReactive(value) {
4758
- if (typeof value !== "object") {
4759
- return false;
4760
- }
4761
- // extract "RawType" from strings like "[object RawType]" => this lets us
4762
- // ignore many native objects such as Promise (whose toString is [object Promise])
4763
- // or Date ([object Date]).
4764
- const rawType = objectToString.call(value).slice(8, -1);
4765
- return rawType === "Object" || rawType === "Array";
4766
- }
4767
- /**
4768
- * Mark an object or array so that it is ignored by the reactivity system
4769
- *
4770
- * @param value the value to mark
4771
- * @returns the object itself
4772
- */
4773
- function markRaw(value) {
4774
- value[SKIP] = true;
4775
- return value;
4776
- }
4777
- /**
4778
- * Given a reactive objet, return the raw (non reactive) underlying object
4779
- *
4780
- * @param value a reactive value
4781
- * @returns the underlying value
4782
- */
4783
- function toRaw(value) {
4784
- return value[TARGET];
4785
- }
4786
- const targetToKeysToCallbacks = new WeakMap();
4787
- /**
4788
- * Observes a given key on a target with an callback. The callback will be
4789
- * called when the given key changes on the target.
4790
- *
4791
- * @param target the target whose key should be observed
4792
- * @param key the key to observe (or Symbol(KEYCHANGES) for key creation
4793
- * or deletion)
4794
- * @param callback the function to call when the key changes
4795
- */
4796
- function observeTargetKey(target, key, callback) {
4797
- if (!targetToKeysToCallbacks.get(target)) {
4798
- targetToKeysToCallbacks.set(target, new Map());
4799
- }
4800
- const keyToCallbacks = targetToKeysToCallbacks.get(target);
4801
- if (!keyToCallbacks.get(key)) {
4802
- keyToCallbacks.set(key, new Set());
4803
- }
4804
- keyToCallbacks.get(key).add(callback);
4805
- if (!callbacksToTargets.has(callback)) {
4806
- callbacksToTargets.set(callback, new Set());
4807
- }
4808
- callbacksToTargets.get(callback).add(target);
4809
- }
4810
- /**
4811
- * Notify Reactives that are observing a given target that a key has changed on
4812
- * the target.
4813
- *
4814
- * @param target target whose Reactives should be notified that the target was
4815
- * changed.
4816
- * @param key the key that changed (or Symbol `KEYCHANGES` if a key was created
4817
- * or deleted)
4818
- */
4819
- function notifyReactives(target, key) {
4820
- const keyToCallbacks = targetToKeysToCallbacks.get(target);
4821
- if (!keyToCallbacks) {
4822
- return;
4823
- }
4824
- const callbacks = keyToCallbacks.get(key);
4825
- if (!callbacks) {
4826
- return;
4827
- }
4828
- // Loop on copy because clearReactivesForCallback will modify the set in place
4829
- for (const callback of [...callbacks]) {
4830
- clearReactivesForCallback(callback);
4831
- callback();
4832
- }
4833
- }
4834
- const callbacksToTargets = new WeakMap();
4835
- /**
4836
- * Clears all subscriptions of the Reactives associated with a given callback.
4837
- *
4838
- * @param callback the callback for which the reactives need to be cleared
4839
- */
4840
- function clearReactivesForCallback(callback) {
4841
- const targetsToClear = callbacksToTargets.get(callback);
4842
- if (!targetsToClear) {
4843
- return;
4844
- }
4845
- for (const target of targetsToClear) {
4846
- const observedKeys = targetToKeysToCallbacks.get(target);
4847
- if (!observedKeys) {
4848
- continue;
4849
- }
4850
- for (const callbacks of observedKeys.values()) {
4851
- callbacks.delete(callback);
4852
- }
4853
- }
4854
- targetsToClear.clear();
4855
- }
4856
- const reactiveCache = new WeakMap();
4857
- /**
4858
- * Creates a reactive proxy for an object. Reading data on the reactive object
4859
- * subscribes to changes to the data. Writing data on the object will cause the
4860
- * notify callback to be called if there are suscriptions to that data. Nested
4861
- * objects and arrays are automatically made reactive as well.
4862
- *
4863
- * Whenever you are notified of a change, all subscriptions are cleared, and if
4864
- * you would like to be notified of any further changes, you should go read
4865
- * the underlying data again. We assume that if you don't go read it again after
4866
- * being notified, it means that you are no longer interested in that data.
4867
- *
4868
- * Subscriptions:
4869
- * + Reading a property on an object will subscribe you to changes in the value
4870
- * of that property.
4871
- * + Accessing an object keys (eg with Object.keys or with `for..in`) will
4872
- * subscribe you to the creation/deletion of keys. Checking the presence of a
4873
- * key on the object with 'in' has the same effect.
4874
- * - getOwnPropertyDescriptor does not currently subscribe you to the property.
4875
- * This is a choice that was made because changing a key's value will trigger
4876
- * this trap and we do not want to subscribe by writes. This also means that
4877
- * Object.hasOwnProperty doesn't subscribe as it goes through this trap.
4878
- *
4879
- * @param target the object for which to create a reactive proxy
4880
- * @param callback the function to call when an observed property of the
4881
- * reactive has changed
4882
- * @returns a proxy that tracks changes to it
4883
- */
4884
- function reactive(target, callback = () => { }) {
4885
- if (!canBeMadeReactive(target)) {
4886
- throw new Error(`Cannot make the given value reactive`);
4887
- }
4888
- if (SKIP in target) {
4889
- return target;
4890
- }
4891
- const originalTarget = target[TARGET];
4892
- if (originalTarget) {
4893
- return reactive(originalTarget, callback);
4894
- }
4895
- if (!reactiveCache.has(target)) {
4896
- reactiveCache.set(target, new Map());
4897
- }
4898
- const reactivesForTarget = reactiveCache.get(target);
4899
- if (!reactivesForTarget.has(callback)) {
4900
- const proxy = new Proxy(target, {
4901
- get(target, key, proxy) {
4902
- if (key === TARGET) {
4903
- return target;
4904
- }
4905
- observeTargetKey(target, key, callback);
4906
- const value = Reflect.get(target, key, proxy);
4907
- if (!canBeMadeReactive(value)) {
4908
- return value;
4909
- }
4910
- return reactive(value, callback);
4911
- },
4912
- set(target, key, value, proxy) {
4913
- const isNewKey = !Object.hasOwnProperty.call(target, key);
4914
- const originalValue = Reflect.get(target, key, proxy);
4915
- const ret = Reflect.set(target, key, value, proxy);
4916
- if (isNewKey) {
4917
- notifyReactives(target, KEYCHANGES);
4918
- }
4919
- // While Array length may trigger the set trap, it's not actually set by this
4920
- // method but is updated behind the scenes, and the trap is not called with the
4921
- // new value. We disable the "same-value-optimization" for it because of that.
4922
- if (originalValue !== value || (Array.isArray(target) && key === "length")) {
4923
- notifyReactives(target, key);
4924
- }
4925
- return ret;
4926
- },
4927
- deleteProperty(target, key) {
4928
- const ret = Reflect.deleteProperty(target, key);
4929
- notifyReactives(target, KEYCHANGES);
4930
- notifyReactives(target, key);
4931
- return ret;
4932
- },
4933
- ownKeys(target) {
4934
- observeTargetKey(target, KEYCHANGES, callback);
4935
- return Reflect.ownKeys(target);
4936
- },
4937
- has(target, key) {
4938
- // TODO: this observes all key changes instead of only the presence of the argument key
4939
- observeTargetKey(target, KEYCHANGES, callback);
4940
- return Reflect.has(target, key);
4941
- },
4942
- });
4943
- reactivesForTarget.set(callback, proxy);
4944
- }
4945
- return reactivesForTarget.get(callback);
4946
- }
4947
- const batchedRenderFunctions = new WeakMap();
4948
- /**
4949
- * Creates a reactive object that will be observed by the current component.
4950
- * Reading data from the returned object (eg during rendering) will cause the
4951
- * component to subscribe to that data and be rerendered when it changes.
4952
- *
4953
- * @param state the state to observe
4954
- * @returns a reactive object that will cause the component to re-render on
4955
- * relevant changes
4956
- * @see reactive
4957
- */
4958
- function useState(state) {
4959
- const node = getCurrent();
4960
- if (!batchedRenderFunctions.has(node)) {
4961
- batchedRenderFunctions.set(node, batched(() => node.render()));
4962
- onWillDestroy(() => clearReactivesForCallback(render));
4963
- }
4964
- const render = batchedRenderFunctions.get(node);
4965
- const reactiveState = reactive(state, render);
4966
- return reactiveState;
4967
- }
4968
-
4969
5464
  // -----------------------------------------------------------------------------
4970
5465
  // useRef
4971
5466
  // -----------------------------------------------------------------------------
@@ -5011,10 +5506,6 @@ function useChildSubEnv(envExtension) {
5011
5506
  const node = getCurrent();
5012
5507
  node.childEnv = extendEnv(node.childEnv, envExtension);
5013
5508
  }
5014
- // -----------------------------------------------------------------------------
5015
- // useEffect
5016
- // -----------------------------------------------------------------------------
5017
- const NO_OP = () => { };
5018
5509
  /**
5019
5510
  * This hook will run a callback when a component is mounted and patched, and
5020
5511
  * will run a cleanup function before patching and before unmounting the
@@ -5032,18 +5523,20 @@ function useEffect(effect, computeDependencies = () => [NaN]) {
5032
5523
  let dependencies;
5033
5524
  onMounted(() => {
5034
5525
  dependencies = computeDependencies();
5035
- cleanup = effect(...dependencies) || NO_OP;
5526
+ cleanup = effect(...dependencies);
5036
5527
  });
5037
5528
  onPatched(() => {
5038
5529
  const newDeps = computeDependencies();
5039
5530
  const shouldReapply = newDeps.some((val, i) => val !== dependencies[i]);
5040
5531
  if (shouldReapply) {
5041
5532
  dependencies = newDeps;
5042
- cleanup();
5043
- cleanup = effect(...dependencies) || NO_OP;
5533
+ if (cleanup) {
5534
+ cleanup();
5535
+ }
5536
+ cleanup = effect(...dependencies);
5044
5537
  }
5045
5538
  });
5046
- onWillUnmount(() => cleanup());
5539
+ onWillUnmount(() => cleanup && cleanup());
5047
5540
  }
5048
5541
  // -----------------------------------------------------------------------------
5049
5542
  // useExternalListener
@@ -5070,7 +5563,6 @@ function useExternalListener(target, eventName, handler, eventParams) {
5070
5563
 
5071
5564
  config.shouldNormalizeDom = false;
5072
5565
  config.mainEventHandler = mainEventHandler;
5073
- UTILS.Portal = Portal;
5074
5566
  const blockDom = {
5075
5567
  config,
5076
5568
  // bdom entry points
@@ -5088,10 +5580,10 @@ const blockDom = {
5088
5580
  };
5089
5581
  const __info__ = {};
5090
5582
 
5091
- export { App, Component, EventBus, Memo, __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, whenReady, xml };
5583
+ 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, whenReady, xml };
5092
5584
 
5093
5585
 
5094
- __info__.version = '2.0.0-alpha.1';
5095
- __info__.date = '2022-02-08T12:38:37.037Z';
5096
- __info__.hash = 'e8632df';
5586
+ __info__.version = '2.0.0-beta-4';
5587
+ __info__.date = '2022-03-29T13:50:04.545Z';
5588
+ __info__.hash = '55dbc01';
5097
5589
  __info__.url = 'https://github.com/odoo/owl';