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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/owl.cjs.js CHANGED
@@ -213,7 +213,7 @@ function updateClass(val, oldVal) {
213
213
  }
214
214
  function makePropSetter(name) {
215
215
  return function setProp(value) {
216
- this[name] = value;
216
+ this[name] = value || "";
217
217
  };
218
218
  }
219
219
  function isProp(tag, key) {
@@ -811,8 +811,7 @@ function updateCtx(ctx, tree) {
811
811
  break;
812
812
  }
813
813
  case "ref":
814
- const index = ctx.refList.push(NO_OP$1) - 1;
815
- ctx.cbRefs.push(info.idx);
814
+ const index = ctx.cbRefs.push(info.idx) - 1;
816
815
  ctx.locations.push({
817
816
  idx: info.idx,
818
817
  refIdx: info.refIdx,
@@ -830,10 +829,12 @@ function buildBlock(template, ctx) {
830
829
  if (ctx.cbRefs.length) {
831
830
  const cbRefs = ctx.cbRefs;
832
831
  const refList = ctx.refList;
832
+ let cbRefsNumber = cbRefs.length;
833
833
  B = class extends B {
834
834
  mount(parent, afterNode) {
835
+ refList.push(new Array(cbRefsNumber));
835
836
  super.mount(parent, afterNode);
836
- for (let cbRef of refList) {
837
+ for (let cbRef of refList.pop()) {
837
838
  cbRef();
838
839
  }
839
840
  }
@@ -995,7 +996,7 @@ function setText(value) {
995
996
  }
996
997
  function makeRefSetter(index, refs) {
997
998
  return function setRef(fn) {
998
- refs[index] = () => fn(this);
999
+ refs[refs.length - 1][index] = () => fn(this);
999
1000
  };
1000
1001
  }
1001
1002
 
@@ -1294,428 +1295,510 @@ function remove(vnode, withBeforeRemove = false) {
1294
1295
  vnode.remove();
1295
1296
  }
1296
1297
 
1297
- /**
1298
- * Apply default props (only top level).
1299
- *
1300
- * Note that this method does modify in place the props
1301
- */
1302
- function applyDefaultProps(props, ComponentClass) {
1303
- const defaultProps = ComponentClass.defaultProps;
1304
- if (defaultProps) {
1305
- for (let propName in defaultProps) {
1306
- if (props[propName] === undefined) {
1307
- props[propName] = defaultProps[propName];
1308
- }
1309
- }
1310
- }
1311
- }
1312
- //------------------------------------------------------------------------------
1313
- // Prop validation helper
1314
- //------------------------------------------------------------------------------
1315
- function getPropDescription(staticProps) {
1316
- if (staticProps instanceof Array) {
1317
- return Object.fromEntries(staticProps.map((p) => (p.endsWith("?") ? [p.slice(0, -1), false] : [p, true])));
1318
- }
1319
- return staticProps || { "*": true };
1320
- }
1321
- /**
1322
- * Validate the component props (or next props) against the (static) props
1323
- * description. This is potentially an expensive operation: it may needs to
1324
- * visit recursively the props and all the children to check if they are valid.
1325
- * This is why it is only done in 'dev' mode.
1326
- */
1327
- function validateProps(name, props, parent) {
1328
- const ComponentClass = typeof name !== "string"
1329
- ? name
1330
- : parent.constructor.components[name];
1331
- if (!ComponentClass) {
1332
- // this is an error, wrong component. We silently return here instead so the
1333
- // error is triggered by the usual path ('component' function)
1334
- return;
1335
- }
1336
- applyDefaultProps(props, ComponentClass);
1337
- const defaultProps = ComponentClass.defaultProps || {};
1338
- let propsDef = getPropDescription(ComponentClass.props);
1339
- const allowAdditionalProps = "*" in propsDef;
1340
- for (let propName in propsDef) {
1341
- if (propName === "*") {
1342
- continue;
1343
- }
1344
- const propDef = propsDef[propName];
1345
- let isMandatory = !!propDef;
1346
- if (typeof propDef === "object" && "optional" in propDef) {
1347
- isMandatory = !propDef.optional;
1348
- }
1349
- if (isMandatory && propName in defaultProps) {
1350
- throw new Error(`A default value cannot be defined for a mandatory prop (name: '${propName}', component: ${ComponentClass.name})`);
1351
- }
1352
- if (props[propName] === undefined) {
1353
- if (isMandatory) {
1354
- throw new Error(`Missing props '${propName}' (component '${ComponentClass.name}')`);
1355
- }
1356
- else {
1357
- continue;
1358
- }
1359
- }
1360
- let isValid;
1361
- try {
1362
- isValid = isValidProp(props[propName], propDef);
1363
- }
1364
- catch (e) {
1365
- e.message = `Invalid prop '${propName}' in component ${ComponentClass.name} (${e.message})`;
1366
- throw e;
1367
- }
1368
- if (!isValid) {
1369
- throw new Error(`Invalid Prop '${propName}' in component '${ComponentClass.name}'`);
1370
- }
1371
- }
1372
- if (!allowAdditionalProps) {
1373
- for (let propName in props) {
1374
- if (!(propName in propsDef)) {
1375
- throw new Error(`Unknown prop '${propName}' given to component '${ComponentClass.name}'`);
1298
+ const mainEventHandler = (data, ev, currentTarget) => {
1299
+ const { data: _data, modifiers } = filterOutModifiersFromData(data);
1300
+ data = _data;
1301
+ let stopped = false;
1302
+ if (modifiers.length) {
1303
+ let selfMode = false;
1304
+ const isSelf = ev.target === currentTarget;
1305
+ for (const mod of modifiers) {
1306
+ switch (mod) {
1307
+ case "self":
1308
+ selfMode = true;
1309
+ if (isSelf) {
1310
+ continue;
1311
+ }
1312
+ else {
1313
+ return stopped;
1314
+ }
1315
+ case "prevent":
1316
+ if ((selfMode && isSelf) || !selfMode)
1317
+ ev.preventDefault();
1318
+ continue;
1319
+ case "stop":
1320
+ if ((selfMode && isSelf) || !selfMode)
1321
+ ev.stopPropagation();
1322
+ stopped = true;
1323
+ continue;
1376
1324
  }
1377
1325
  }
1378
1326
  }
1379
- }
1380
- /**
1381
- * Check if an invidual prop value matches its (static) prop definition
1382
- */
1383
- function isValidProp(prop, propDef) {
1384
- if (propDef === true) {
1385
- return true;
1386
- }
1387
- if (typeof propDef === "function") {
1388
- // Check if a value is constructed by some Constructor. Note that there is a
1389
- // slight abuse of language: we want to consider primitive values as well.
1390
- //
1391
- // So, even though 1 is not an instance of Number, we want to consider that
1392
- // it is valid.
1393
- if (typeof prop === "object") {
1394
- return prop instanceof propDef;
1395
- }
1396
- return typeof prop === propDef.name.toLowerCase();
1397
- }
1398
- else if (propDef instanceof Array) {
1399
- // If this code is executed, this means that we want to check if a prop
1400
- // matches at least one of its descriptor.
1401
- let result = false;
1402
- for (let i = 0, iLen = propDef.length; i < iLen; i++) {
1403
- result = result || isValidProp(prop, propDef[i]);
1404
- }
1405
- return result;
1406
- }
1407
- // propsDef is an object
1408
- if (propDef.optional && prop === undefined) {
1409
- return true;
1410
- }
1411
- let result = propDef.type ? isValidProp(prop, propDef.type) : true;
1412
- if (propDef.validate) {
1413
- result = result && propDef.validate(prop);
1414
- }
1415
- if (propDef.type === Array && propDef.element) {
1416
- for (let i = 0, iLen = prop.length; i < iLen; i++) {
1417
- result = result && isValidProp(prop[i], propDef.element);
1418
- }
1419
- }
1420
- if (propDef.type === Object && propDef.shape) {
1421
- const shape = propDef.shape;
1422
- for (let key in shape) {
1423
- result = result && isValidProp(prop[key], shape[key]);
1327
+ // If handler is empty, the array slot 0 will also be empty, and data will not have the property 0
1328
+ // We check this rather than data[0] being truthy (or typeof function) so that it crashes
1329
+ // as expected when there is a handler expression that evaluates to a falsy value
1330
+ if (Object.hasOwnProperty.call(data, 0)) {
1331
+ const handler = data[0];
1332
+ if (typeof handler !== "function") {
1333
+ throw new Error(`Invalid handler (expected a function, received: '${handler}')`);
1424
1334
  }
1425
- if (result) {
1426
- for (let propName in prop) {
1427
- if (!(propName in shape)) {
1428
- throw new Error(`unknown prop '${propName}'`);
1429
- }
1430
- }
1335
+ let node = data[1] ? data[1].__owl__ : null;
1336
+ if (node ? node.status === 1 /* MOUNTED */ : true) {
1337
+ handler.call(node ? node.component : null, ev);
1431
1338
  }
1432
1339
  }
1433
- return result;
1434
- }
1340
+ return stopped;
1341
+ };
1435
1342
 
1343
+ // Allows to get the target of a Reactive (used for making a new Reactive from the underlying object)
1344
+ const TARGET = Symbol("Target");
1345
+ // Escape hatch to prevent reactivity system to turn something into a reactive
1346
+ const SKIP = Symbol("Skip");
1347
+ // Special key to subscribe to, to be notified of key creation/deletion
1348
+ const KEYCHANGES = Symbol("Key changes");
1349
+ const objectToString = Object.prototype.toString;
1350
+ const objectHasOwnProperty = Object.prototype.hasOwnProperty;
1351
+ const SUPPORTED_RAW_TYPES = new Set(["Object", "Array", "Set", "Map", "WeakMap"]);
1352
+ const COLLECTION_RAWTYPES = new Set(["Set", "Map", "WeakMap"]);
1436
1353
  /**
1437
- * Creates a batched version of a callback so that all calls to it in the same
1438
- * microtick will only call the original callback once.
1354
+ * extract "RawType" from strings like "[object RawType]" => this lets us ignore
1355
+ * many native objects such as Promise (whose toString is [object Promise])
1356
+ * or Date ([object Date]), while also supporting collections without using
1357
+ * instanceof in a loop
1439
1358
  *
1440
- * @param callback the callback to batch
1441
- * @returns a batched version of the original callback
1442
- */
1443
- function batched(callback) {
1444
- let called = false;
1445
- return async () => {
1446
- // This await blocks all calls to the callback here, then releases them sequentially
1447
- // in the next microtick. This line decides the granularity of the batch.
1448
- await Promise.resolve();
1449
- if (!called) {
1450
- called = true;
1451
- callback();
1452
- // wait for all calls in this microtick to fall through before resetting "called"
1453
- // so that only the first call to the batched function calls the original callback
1454
- await Promise.resolve();
1455
- called = false;
1456
- }
1457
- };
1458
- }
1459
- function validateTarget(target) {
1460
- if (!(target instanceof HTMLElement)) {
1461
- throw new Error("Cannot mount component: the target is not a valid DOM element");
1462
- }
1463
- if (!document.body.contains(target)) {
1464
- throw new Error("Cannot mount a component on a detached dom node");
1465
- }
1466
- }
1467
- class EventBus extends EventTarget {
1468
- trigger(name, payload) {
1469
- this.dispatchEvent(new CustomEvent(name, { detail: payload }));
1470
- }
1471
- }
1472
- function whenReady(fn) {
1473
- return new Promise(function (resolve) {
1474
- if (document.readyState !== "loading") {
1475
- resolve(true);
1476
- }
1477
- else {
1478
- document.addEventListener("DOMContentLoaded", resolve, false);
1479
- }
1480
- }).then(fn || function () { });
1481
- }
1482
- async function loadFile(url) {
1483
- const result = await fetch(url);
1484
- if (!result.ok) {
1485
- throw new Error("Error while fetching xml templates");
1486
- }
1487
- return await result.text();
1488
- }
1489
- /*
1490
- * This class just transports the fact that a string is safe
1491
- * to be injected as HTML. Overriding a JS primitive is quite painful though
1492
- * so we need to redfine toString and valueOf.
1359
+ * @param obj the object to check
1360
+ * @returns the raw type of the object
1493
1361
  */
1494
- class Markup extends String {
1362
+ function rawType(obj) {
1363
+ return objectToString.call(obj).slice(8, -1);
1495
1364
  }
1496
- /*
1497
- * Marks a value as safe, that is, a value that can be injected as HTML directly.
1498
- * It should be used to wrap the value passed to a t-out directive to allow a raw rendering.
1499
- */
1500
- function markup(value) {
1501
- return new Markup(value);
1502
- }
1503
-
1504
1365
  /**
1505
- * This file contains utility functions that will be injected in each template,
1506
- * to perform various useful tasks in the compiled code.
1507
- */
1508
- function withDefault(value, defaultValue) {
1509
- return value === undefined || value === null || value === false ? defaultValue : value;
1510
- }
1511
- function callSlot(ctx, parent, key, name, dynamic, extra, defaultContent) {
1512
- key = key + "__slot_" + name;
1513
- const slots = (ctx.props && ctx.props.slots) || {};
1514
- const { __render, __ctx, __scope } = slots[name] || {};
1515
- const slotScope = Object.create(__ctx || {});
1516
- if (__scope) {
1517
- slotScope[__scope] = extra || {};
1518
- }
1519
- const slotBDom = __render ? __render.call(__ctx.__owl__.component, slotScope, parent, key) : null;
1520
- if (defaultContent) {
1521
- let child1 = undefined;
1522
- let child2 = undefined;
1523
- if (slotBDom) {
1524
- child1 = dynamic ? toggler(name, slotBDom) : slotBDom;
1525
- }
1526
- else {
1527
- child2 = defaultContent.call(ctx.__owl__.component, ctx, parent, key);
1528
- }
1529
- return multi([child1, child2]);
1530
- }
1531
- return slotBDom || text("");
1532
- }
1533
- function capture(ctx) {
1534
- const component = ctx.__owl__.component;
1535
- const result = Object.create(component);
1536
- for (let k in ctx) {
1537
- result[k] = ctx[k];
1538
- }
1539
- return result;
1540
- }
1541
- function withKey(elem, k) {
1542
- elem.key = k;
1543
- return elem;
1544
- }
1545
- function prepareList(collection) {
1546
- let keys;
1547
- let values;
1548
- if (Array.isArray(collection)) {
1549
- keys = collection;
1550
- values = collection;
1551
- }
1552
- else if (collection) {
1553
- values = Object.keys(collection);
1554
- keys = Object.values(collection);
1555
- }
1556
- else {
1557
- throw new Error("Invalid loop expression");
1366
+ * Checks whether a given value can be made into a reactive object.
1367
+ *
1368
+ * @param value the value to check
1369
+ * @returns whether the value can be made reactive
1370
+ */
1371
+ function canBeMadeReactive(value) {
1372
+ if (typeof value !== "object") {
1373
+ return false;
1558
1374
  }
1559
- const n = values.length;
1560
- return [keys, values, n, new Array(n)];
1375
+ return SUPPORTED_RAW_TYPES.has(rawType(value));
1561
1376
  }
1562
- const isBoundary = Symbol("isBoundary");
1563
- function setContextValue(ctx, key, value) {
1564
- const ctx0 = ctx;
1565
- while (!ctx.hasOwnProperty(key) && !ctx.hasOwnProperty(isBoundary)) {
1566
- const newCtx = ctx.__proto__;
1567
- if (!newCtx) {
1568
- ctx = ctx0;
1569
- break;
1570
- }
1571
- ctx = newCtx;
1572
- }
1573
- ctx[key] = value;
1377
+ /**
1378
+ * Creates a reactive from the given object/callback if possible and returns it,
1379
+ * returns the original object otherwise.
1380
+ *
1381
+ * @param value the value make reactive
1382
+ * @returns a reactive for the given object when possible, the original otherwise
1383
+ */
1384
+ function possiblyReactive(val, cb) {
1385
+ return canBeMadeReactive(val) ? reactive(val, cb) : val;
1574
1386
  }
1575
- function toNumber(val) {
1576
- const n = parseFloat(val);
1577
- return isNaN(n) ? val : n;
1387
+ /**
1388
+ * Mark an object or array so that it is ignored by the reactivity system
1389
+ *
1390
+ * @param value the value to mark
1391
+ * @returns the object itself
1392
+ */
1393
+ function markRaw(value) {
1394
+ value[SKIP] = true;
1395
+ return value;
1578
1396
  }
1579
- function shallowEqual$1(l1, l2) {
1580
- for (let i = 0, l = l1.length; i < l; i++) {
1581
- if (l1[i] !== l2[i]) {
1582
- return false;
1583
- }
1584
- }
1585
- return true;
1397
+ /**
1398
+ * Given a reactive objet, return the raw (non reactive) underlying object
1399
+ *
1400
+ * @param value a reactive value
1401
+ * @returns the underlying value
1402
+ */
1403
+ function toRaw(value) {
1404
+ return value[TARGET] || value;
1586
1405
  }
1587
- class LazyValue {
1588
- constructor(fn, ctx, node) {
1589
- this.fn = fn;
1590
- this.ctx = capture(ctx);
1591
- this.node = node;
1406
+ const targetToKeysToCallbacks = new WeakMap();
1407
+ /**
1408
+ * Observes a given key on a target with an callback. The callback will be
1409
+ * called when the given key changes on the target.
1410
+ *
1411
+ * @param target the target whose key should be observed
1412
+ * @param key the key to observe (or Symbol(KEYCHANGES) for key creation
1413
+ * or deletion)
1414
+ * @param callback the function to call when the key changes
1415
+ */
1416
+ function observeTargetKey(target, key, callback) {
1417
+ if (!targetToKeysToCallbacks.get(target)) {
1418
+ targetToKeysToCallbacks.set(target, new Map());
1592
1419
  }
1593
- evaluate() {
1594
- return this.fn(this.ctx, this.node);
1420
+ const keyToCallbacks = targetToKeysToCallbacks.get(target);
1421
+ if (!keyToCallbacks.get(key)) {
1422
+ keyToCallbacks.set(key, new Set());
1595
1423
  }
1596
- toString() {
1597
- return this.evaluate().toString();
1424
+ keyToCallbacks.get(key).add(callback);
1425
+ if (!callbacksToTargets.has(callback)) {
1426
+ callbacksToTargets.set(callback, new Set());
1598
1427
  }
1428
+ callbacksToTargets.get(callback).add(target);
1599
1429
  }
1600
- /*
1601
- * Safely outputs `value` as a block depending on the nature of `value`
1430
+ /**
1431
+ * Notify Reactives that are observing a given target that a key has changed on
1432
+ * the target.
1433
+ *
1434
+ * @param target target whose Reactives should be notified that the target was
1435
+ * changed.
1436
+ * @param key the key that changed (or Symbol `KEYCHANGES` if a key was created
1437
+ * or deleted)
1602
1438
  */
1603
- function safeOutput(value) {
1604
- if (!value) {
1605
- return value;
1439
+ function notifyReactives(target, key) {
1440
+ const keyToCallbacks = targetToKeysToCallbacks.get(target);
1441
+ if (!keyToCallbacks) {
1442
+ return;
1606
1443
  }
1607
- let safeKey;
1608
- let block;
1609
- if (value instanceof Markup) {
1610
- safeKey = `string_safe`;
1611
- block = html(value);
1444
+ const callbacks = keyToCallbacks.get(key);
1445
+ if (!callbacks) {
1446
+ return;
1612
1447
  }
1613
- else if (value instanceof LazyValue) {
1614
- safeKey = `lazy_value`;
1615
- block = value.evaluate();
1448
+ // Loop on copy because clearReactivesForCallback will modify the set in place
1449
+ for (const callback of [...callbacks]) {
1450
+ clearReactivesForCallback(callback);
1451
+ callback();
1616
1452
  }
1617
- else if (typeof value === "string") {
1618
- safeKey = "string_unsafe";
1619
- block = text(value);
1453
+ }
1454
+ const callbacksToTargets = new WeakMap();
1455
+ /**
1456
+ * Clears all subscriptions of the Reactives associated with a given callback.
1457
+ *
1458
+ * @param callback the callback for which the reactives need to be cleared
1459
+ */
1460
+ function clearReactivesForCallback(callback) {
1461
+ const targetsToClear = callbacksToTargets.get(callback);
1462
+ if (!targetsToClear) {
1463
+ return;
1620
1464
  }
1621
- else {
1622
- // Assuming it is a block
1623
- safeKey = "block_safe";
1624
- block = value;
1465
+ for (const target of targetsToClear) {
1466
+ const observedKeys = targetToKeysToCallbacks.get(target);
1467
+ if (!observedKeys) {
1468
+ continue;
1469
+ }
1470
+ for (const callbacks of observedKeys.values()) {
1471
+ callbacks.delete(callback);
1472
+ }
1625
1473
  }
1626
- return toggler(safeKey, block);
1474
+ targetsToClear.clear();
1627
1475
  }
1628
- let boundFunctions = new WeakMap();
1629
- function bind(ctx, fn) {
1630
- let component = ctx.__owl__.component;
1631
- let boundFnMap = boundFunctions.get(component);
1632
- if (!boundFnMap) {
1633
- boundFnMap = new WeakMap();
1634
- boundFunctions.set(component, boundFnMap);
1476
+ const reactiveCache = new WeakMap();
1477
+ /**
1478
+ * Creates a reactive proxy for an object. Reading data on the reactive object
1479
+ * subscribes to changes to the data. Writing data on the object will cause the
1480
+ * notify callback to be called if there are suscriptions to that data. Nested
1481
+ * objects and arrays are automatically made reactive as well.
1482
+ *
1483
+ * Whenever you are notified of a change, all subscriptions are cleared, and if
1484
+ * you would like to be notified of any further changes, you should go read
1485
+ * the underlying data again. We assume that if you don't go read it again after
1486
+ * being notified, it means that you are no longer interested in that data.
1487
+ *
1488
+ * Subscriptions:
1489
+ * + Reading a property on an object will subscribe you to changes in the value
1490
+ * of that property.
1491
+ * + Accessing an object keys (eg with Object.keys or with `for..in`) will
1492
+ * subscribe you to the creation/deletion of keys. Checking the presence of a
1493
+ * key on the object with 'in' has the same effect.
1494
+ * - getOwnPropertyDescriptor does not currently subscribe you to the property.
1495
+ * This is a choice that was made because changing a key's value will trigger
1496
+ * this trap and we do not want to subscribe by writes. This also means that
1497
+ * Object.hasOwnProperty doesn't subscribe as it goes through this trap.
1498
+ *
1499
+ * @param target the object for which to create a reactive proxy
1500
+ * @param callback the function to call when an observed property of the
1501
+ * reactive has changed
1502
+ * @returns a proxy that tracks changes to it
1503
+ */
1504
+ function reactive(target, callback = () => { }) {
1505
+ if (!canBeMadeReactive(target)) {
1506
+ throw new Error(`Cannot make the given value reactive`);
1635
1507
  }
1636
- let boundFn = boundFnMap.get(fn);
1637
- if (!boundFn) {
1638
- boundFn = fn.bind(component);
1639
- boundFnMap.set(fn, boundFn);
1508
+ if (SKIP in target) {
1509
+ return target;
1640
1510
  }
1641
- return boundFn;
1511
+ const originalTarget = target[TARGET];
1512
+ if (originalTarget) {
1513
+ return reactive(originalTarget, callback);
1514
+ }
1515
+ if (!reactiveCache.has(target)) {
1516
+ reactiveCache.set(target, new Map());
1517
+ }
1518
+ const reactivesForTarget = reactiveCache.get(target);
1519
+ if (!reactivesForTarget.has(callback)) {
1520
+ const targetRawType = rawType(target);
1521
+ const handler = COLLECTION_RAWTYPES.has(targetRawType)
1522
+ ? collectionsProxyHandler(target, callback, targetRawType)
1523
+ : basicProxyHandler(callback);
1524
+ const proxy = new Proxy(target, handler);
1525
+ reactivesForTarget.set(callback, proxy);
1526
+ }
1527
+ return reactivesForTarget.get(callback);
1642
1528
  }
1643
- function multiRefSetter(refs, name) {
1644
- let count = 0;
1645
- return (el) => {
1646
- if (el) {
1647
- count++;
1648
- if (count > 1) {
1649
- throw new Error("Cannot have 2 elements with same ref name at the same time");
1529
+ /**
1530
+ * Creates a basic proxy handler for regular objects and arrays.
1531
+ *
1532
+ * @param callback @see reactive
1533
+ * @returns a proxy handler object
1534
+ */
1535
+ function basicProxyHandler(callback) {
1536
+ return {
1537
+ get(target, key, proxy) {
1538
+ if (key === TARGET) {
1539
+ return target;
1540
+ }
1541
+ observeTargetKey(target, key, callback);
1542
+ return possiblyReactive(Reflect.get(target, key, proxy), callback);
1543
+ },
1544
+ set(target, key, value, proxy) {
1545
+ const isNewKey = !objectHasOwnProperty.call(target, key);
1546
+ const originalValue = Reflect.get(target, key, proxy);
1547
+ const ret = Reflect.set(target, key, value, proxy);
1548
+ if (isNewKey) {
1549
+ notifyReactives(target, KEYCHANGES);
1550
+ }
1551
+ // While Array length may trigger the set trap, it's not actually set by this
1552
+ // method but is updated behind the scenes, and the trap is not called with the
1553
+ // new value. We disable the "same-value-optimization" for it because of that.
1554
+ if (originalValue !== value || (Array.isArray(target) && key === "length")) {
1555
+ notifyReactives(target, key);
1650
1556
  }
1557
+ return ret;
1558
+ },
1559
+ deleteProperty(target, key) {
1560
+ const ret = Reflect.deleteProperty(target, key);
1561
+ // TODO: only notify when something was actually deleted
1562
+ notifyReactives(target, KEYCHANGES);
1563
+ notifyReactives(target, key);
1564
+ return ret;
1565
+ },
1566
+ ownKeys(target) {
1567
+ observeTargetKey(target, KEYCHANGES, callback);
1568
+ return Reflect.ownKeys(target);
1569
+ },
1570
+ has(target, key) {
1571
+ // TODO: this observes all key changes instead of only the presence of the argument key
1572
+ // observing the key itself would observe value changes instead of presence changes
1573
+ // so we may need a finer grained system to distinguish observing value vs presence.
1574
+ observeTargetKey(target, KEYCHANGES, callback);
1575
+ return Reflect.has(target, key);
1576
+ },
1577
+ };
1578
+ }
1579
+ /**
1580
+ * Creates a function that will observe the key that is passed to it when called
1581
+ * and delegates to the underlying method.
1582
+ *
1583
+ * @param methodName name of the method to delegate to
1584
+ * @param target @see reactive
1585
+ * @param callback @see reactive
1586
+ */
1587
+ function makeKeyObserver(methodName, target, callback) {
1588
+ return (key) => {
1589
+ key = toRaw(key);
1590
+ observeTargetKey(target, key, callback);
1591
+ return possiblyReactive(target[methodName](key), callback);
1592
+ };
1593
+ }
1594
+ /**
1595
+ * Creates an iterable that will delegate to the underlying iteration method and
1596
+ * observe keys as necessary.
1597
+ *
1598
+ * @param methodName name of the method to delegate to
1599
+ * @param target @see reactive
1600
+ * @param callback @see reactive
1601
+ */
1602
+ function makeIteratorObserver(methodName, target, callback) {
1603
+ return function* () {
1604
+ observeTargetKey(target, KEYCHANGES, callback);
1605
+ const keys = target.keys();
1606
+ for (const item of target[methodName]()) {
1607
+ const key = keys.next().value;
1608
+ observeTargetKey(target, key, callback);
1609
+ yield possiblyReactive(item, callback);
1651
1610
  }
1652
- if (count === 0 || el) {
1653
- refs[name] = el;
1611
+ };
1612
+ }
1613
+ /**
1614
+ * Creates a function that will delegate to an underlying method, and check if
1615
+ * that method has modified the presence or value of a key, and notify the
1616
+ * reactives appropriately.
1617
+ *
1618
+ * @param setterName name of the method to delegate to
1619
+ * @param getterName name of the method which should be used to retrieve the
1620
+ * value before calling the delegate method for comparison purposes
1621
+ * @param target @see reactive
1622
+ */
1623
+ function delegateAndNotify(setterName, getterName, target) {
1624
+ return (key, value) => {
1625
+ key = toRaw(key);
1626
+ const hadKey = target.has(key);
1627
+ const originalValue = target[getterName](key);
1628
+ const ret = target[setterName](key, value);
1629
+ const hasKey = target.has(key);
1630
+ if (hadKey !== hasKey) {
1631
+ notifyReactives(target, KEYCHANGES);
1632
+ }
1633
+ if (originalValue !== value) {
1634
+ notifyReactives(target, key);
1635
+ }
1636
+ return ret;
1637
+ };
1638
+ }
1639
+ /**
1640
+ * Creates a function that will clear the underlying collection and notify that
1641
+ * the keys of the collection have changed.
1642
+ *
1643
+ * @param target @see reactive
1644
+ */
1645
+ function makeClearNotifier(target) {
1646
+ return () => {
1647
+ const allKeys = [...target.keys()];
1648
+ target.clear();
1649
+ notifyReactives(target, KEYCHANGES);
1650
+ for (const key of allKeys) {
1651
+ notifyReactives(target, key);
1652
+ }
1653
+ };
1654
+ }
1655
+ /**
1656
+ * Maps raw type of an object to an object containing functions that can be used
1657
+ * to build an appropritate proxy handler for that raw type. Eg: when making a
1658
+ * reactive set, calling the has method should mark the key that is being
1659
+ * retrieved as observed, and calling the add or delete method should notify the
1660
+ * reactives that the key which is being added or deleted has been modified.
1661
+ */
1662
+ const rawTypeToFuncHandlers = {
1663
+ Set: (target, callback) => ({
1664
+ has: makeKeyObserver("has", target, callback),
1665
+ add: delegateAndNotify("add", "has", target),
1666
+ delete: delegateAndNotify("delete", "has", target),
1667
+ keys: makeIteratorObserver("keys", target, callback),
1668
+ values: makeIteratorObserver("values", target, callback),
1669
+ entries: makeIteratorObserver("entries", target, callback),
1670
+ [Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
1671
+ clear: makeClearNotifier(target),
1672
+ get size() {
1673
+ observeTargetKey(target, KEYCHANGES, callback);
1674
+ return target.size;
1675
+ },
1676
+ }),
1677
+ Map: (target, callback) => ({
1678
+ has: makeKeyObserver("has", target, callback),
1679
+ get: makeKeyObserver("get", target, callback),
1680
+ set: delegateAndNotify("set", "get", target),
1681
+ delete: delegateAndNotify("delete", "has", target),
1682
+ keys: makeIteratorObserver("keys", target, callback),
1683
+ values: makeIteratorObserver("values", target, callback),
1684
+ entries: makeIteratorObserver("entries", target, callback),
1685
+ [Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
1686
+ clear: makeClearNotifier(target),
1687
+ get size() {
1688
+ observeTargetKey(target, KEYCHANGES, callback);
1689
+ return target.size;
1690
+ },
1691
+ }),
1692
+ WeakMap: (target, callback) => ({
1693
+ has: makeKeyObserver("has", target, callback),
1694
+ get: makeKeyObserver("get", target, callback),
1695
+ set: delegateAndNotify("set", "get", target),
1696
+ delete: delegateAndNotify("delete", "has", target),
1697
+ }),
1698
+ };
1699
+ /**
1700
+ * Creates a proxy handler for collections (Set/Map/WeakMap)
1701
+ *
1702
+ * @param callback @see reactive
1703
+ * @param target @see reactive
1704
+ * @returns a proxy handler object
1705
+ */
1706
+ function collectionsProxyHandler(target, callback, targetRawType) {
1707
+ // TODO: if performance is an issue we can create the special handlers lazily when each
1708
+ // property is read.
1709
+ const specialHandlers = rawTypeToFuncHandlers[targetRawType](target, callback);
1710
+ return Object.assign(basicProxyHandler(callback), {
1711
+ get(target, key) {
1712
+ if (key === TARGET) {
1713
+ return target;
1714
+ }
1715
+ if (objectHasOwnProperty.call(specialHandlers, key)) {
1716
+ return specialHandlers[key];
1717
+ }
1718
+ observeTargetKey(target, key, callback);
1719
+ return possiblyReactive(target[key], callback);
1720
+ },
1721
+ });
1722
+ }
1723
+
1724
+ /**
1725
+ * Creates a batched version of a callback so that all calls to it in the same
1726
+ * microtick will only call the original callback once.
1727
+ *
1728
+ * @param callback the callback to batch
1729
+ * @returns a batched version of the original callback
1730
+ */
1731
+ function batched(callback) {
1732
+ let called = false;
1733
+ return async () => {
1734
+ // This await blocks all calls to the callback here, then releases them sequentially
1735
+ // in the next microtick. This line decides the granularity of the batch.
1736
+ await Promise.resolve();
1737
+ if (!called) {
1738
+ called = true;
1739
+ callback();
1740
+ // wait for all calls in this microtick to fall through before resetting "called"
1741
+ // so that only the first call to the batched function calls the original callback
1742
+ await Promise.resolve();
1743
+ called = false;
1654
1744
  }
1655
1745
  };
1656
1746
  }
1657
- const UTILS = {
1658
- withDefault,
1659
- zero: Symbol("zero"),
1660
- isBoundary,
1661
- callSlot,
1662
- capture,
1663
- withKey,
1664
- prepareList,
1665
- setContextValue,
1666
- multiRefSetter,
1667
- shallowEqual: shallowEqual$1,
1668
- toNumber,
1669
- validateProps,
1670
- LazyValue,
1671
- safeOutput,
1672
- bind,
1673
- };
1674
-
1675
- const mainEventHandler = (data, ev, currentTarget) => {
1676
- const { data: _data, modifiers } = filterOutModifiersFromData(data);
1677
- data = _data;
1678
- let stopped = false;
1679
- if (modifiers.length) {
1680
- let selfMode = false;
1681
- const isSelf = ev.target === currentTarget;
1682
- for (const mod of modifiers) {
1683
- switch (mod) {
1684
- case "self":
1685
- selfMode = true;
1686
- if (isSelf) {
1687
- continue;
1688
- }
1689
- else {
1690
- return stopped;
1691
- }
1692
- case "prevent":
1693
- if ((selfMode && isSelf) || !selfMode)
1694
- ev.preventDefault();
1695
- continue;
1696
- case "stop":
1697
- if ((selfMode && isSelf) || !selfMode)
1698
- ev.stopPropagation();
1699
- stopped = true;
1700
- continue;
1701
- }
1702
- }
1747
+ function validateTarget(target) {
1748
+ if (!(target instanceof HTMLElement)) {
1749
+ throw new Error("Cannot mount component: the target is not a valid DOM element");
1703
1750
  }
1704
- // If handler is empty, the array slot 0 will also be empty, and data will not have the property 0
1705
- // We check this rather than data[0] being truthy (or typeof function) so that it crashes
1706
- // as expected when there is a handler expression that evaluates to a falsy value
1707
- if (Object.hasOwnProperty.call(data, 0)) {
1708
- const handler = data[0];
1709
- if (typeof handler !== "function") {
1710
- throw new Error(`Invalid handler (expected a function, received: '${handler}')`);
1751
+ if (!document.body.contains(target)) {
1752
+ throw new Error("Cannot mount a component on a detached dom node");
1753
+ }
1754
+ }
1755
+ class EventBus extends EventTarget {
1756
+ trigger(name, payload) {
1757
+ this.dispatchEvent(new CustomEvent(name, { detail: payload }));
1758
+ }
1759
+ }
1760
+ function whenReady(fn) {
1761
+ return new Promise(function (resolve) {
1762
+ if (document.readyState !== "loading") {
1763
+ resolve(true);
1711
1764
  }
1712
- let node = data[1] ? data[1].__owl__ : null;
1713
- if (node ? node.status === 1 /* MOUNTED */ : true) {
1714
- handler.call(node ? node.component : null, ev);
1765
+ else {
1766
+ document.addEventListener("DOMContentLoaded", resolve, false);
1715
1767
  }
1768
+ }).then(fn || function () { });
1769
+ }
1770
+ async function loadFile(url) {
1771
+ const result = await fetch(url);
1772
+ if (!result.ok) {
1773
+ throw new Error("Error while fetching xml templates");
1716
1774
  }
1717
- return stopped;
1718
- };
1775
+ return await result.text();
1776
+ }
1777
+ /*
1778
+ * This class just transports the fact that a string is safe
1779
+ * to be injected as HTML. Overriding a JS primitive is quite painful though
1780
+ * so we need to redfine toString and valueOf.
1781
+ */
1782
+ class Markup extends String {
1783
+ }
1784
+ /*
1785
+ * Marks a value as safe, that is, a value that can be injected as HTML directly.
1786
+ * It should be used to wrap the value passed to a t-out directive to allow a raw rendering.
1787
+ */
1788
+ function markup(value) {
1789
+ return new Markup(value);
1790
+ }
1791
+ // -----------------------------------------------------------------------------
1792
+ // xml tag helper
1793
+ // -----------------------------------------------------------------------------
1794
+ const globalTemplates = {};
1795
+ function xml(...args) {
1796
+ const name = `__template__${xml.nextId++}`;
1797
+ const value = String.raw(...args);
1798
+ globalTemplates[name] = value;
1799
+ return name;
1800
+ }
1801
+ xml.nextId = 1;
1719
1802
 
1720
1803
  // Maps fibers to thrown errors
1721
1804
  const fibersInError = new WeakMap();
@@ -1778,8 +1861,7 @@ function handleError(params) {
1778
1861
  function makeChildFiber(node, parent) {
1779
1862
  let current = node.fiber;
1780
1863
  if (current) {
1781
- let root = parent.root;
1782
- cancelFibers(root, current.children);
1864
+ cancelFibers(current.children);
1783
1865
  current.root = null;
1784
1866
  }
1785
1867
  return new Fiber(node, parent);
@@ -1788,9 +1870,8 @@ function makeRootFiber(node) {
1788
1870
  let current = node.fiber;
1789
1871
  if (current) {
1790
1872
  let root = current.root;
1791
- root.counter -= cancelFibers(root, current.children);
1873
+ root.counter = root.counter + 1 - cancelFibers(current.children);
1792
1874
  current.children = [];
1793
- root.counter++;
1794
1875
  current.bdom = null;
1795
1876
  if (fibersInError.has(current)) {
1796
1877
  fibersInError.delete(current);
@@ -1811,15 +1892,14 @@ function makeRootFiber(node) {
1811
1892
  /**
1812
1893
  * @returns number of not-yet rendered fibers cancelled
1813
1894
  */
1814
- function cancelFibers(root, fibers) {
1895
+ function cancelFibers(fibers) {
1815
1896
  let result = 0;
1816
1897
  for (let fiber of fibers) {
1817
1898
  fiber.node.fiber = null;
1818
- fiber.root = root;
1819
1899
  if (!fiber.bdom) {
1820
1900
  result++;
1821
1901
  }
1822
- result += cancelFibers(root, fiber.children);
1902
+ result += cancelFibers(fiber.children);
1823
1903
  }
1824
1904
  return result;
1825
1905
  }
@@ -1828,9 +1908,11 @@ class Fiber {
1828
1908
  this.bdom = null;
1829
1909
  this.children = [];
1830
1910
  this.appliedToDom = false;
1911
+ this.deep = false;
1831
1912
  this.node = node;
1832
1913
  this.parent = parent;
1833
1914
  if (parent) {
1915
+ this.deep = parent.deep;
1834
1916
  const root = parent.root;
1835
1917
  root.counter++;
1836
1918
  this.root = root;
@@ -1873,7 +1955,7 @@ class RootFiber extends Fiber {
1873
1955
  }
1874
1956
  current = undefined;
1875
1957
  // Step 2: patching the dom
1876
- node.patch();
1958
+ node._patch();
1877
1959
  this.locked = false;
1878
1960
  // Step 4: calling all mounted lifecycle hooks
1879
1961
  let mountedFibers = this.mounted;
@@ -1950,6 +2032,145 @@ class MountFiber extends RootFiber {
1950
2032
  }
1951
2033
  }
1952
2034
 
2035
+ /**
2036
+ * Apply default props (only top level).
2037
+ *
2038
+ * Note that this method does modify in place the props
2039
+ */
2040
+ function applyDefaultProps(props, ComponentClass) {
2041
+ const defaultProps = ComponentClass.defaultProps;
2042
+ if (defaultProps) {
2043
+ for (let propName in defaultProps) {
2044
+ if (props[propName] === undefined) {
2045
+ props[propName] = defaultProps[propName];
2046
+ }
2047
+ }
2048
+ }
2049
+ }
2050
+ //------------------------------------------------------------------------------
2051
+ // Prop validation helper
2052
+ //------------------------------------------------------------------------------
2053
+ function getPropDescription(staticProps) {
2054
+ if (staticProps instanceof Array) {
2055
+ return Object.fromEntries(staticProps.map((p) => (p.endsWith("?") ? [p.slice(0, -1), false] : [p, true])));
2056
+ }
2057
+ return staticProps || { "*": true };
2058
+ }
2059
+ /**
2060
+ * Validate the component props (or next props) against the (static) props
2061
+ * description. This is potentially an expensive operation: it may needs to
2062
+ * visit recursively the props and all the children to check if they are valid.
2063
+ * This is why it is only done in 'dev' mode.
2064
+ */
2065
+ function validateProps(name, props, parent) {
2066
+ const ComponentClass = typeof name !== "string"
2067
+ ? name
2068
+ : parent.constructor.components[name];
2069
+ if (!ComponentClass) {
2070
+ // this is an error, wrong component. We silently return here instead so the
2071
+ // error is triggered by the usual path ('component' function)
2072
+ return;
2073
+ }
2074
+ applyDefaultProps(props, ComponentClass);
2075
+ const defaultProps = ComponentClass.defaultProps || {};
2076
+ let propsDef = getPropDescription(ComponentClass.props);
2077
+ const allowAdditionalProps = "*" in propsDef;
2078
+ for (let propName in propsDef) {
2079
+ if (propName === "*") {
2080
+ continue;
2081
+ }
2082
+ const propDef = propsDef[propName];
2083
+ let isMandatory = !!propDef;
2084
+ if (typeof propDef === "object" && "optional" in propDef) {
2085
+ isMandatory = !propDef.optional;
2086
+ }
2087
+ if (isMandatory && propName in defaultProps) {
2088
+ throw new Error(`A default value cannot be defined for a mandatory prop (name: '${propName}', component: ${ComponentClass.name})`);
2089
+ }
2090
+ if (props[propName] === undefined) {
2091
+ if (isMandatory) {
2092
+ throw new Error(`Missing props '${propName}' (component '${ComponentClass.name}')`);
2093
+ }
2094
+ else {
2095
+ continue;
2096
+ }
2097
+ }
2098
+ let isValid;
2099
+ try {
2100
+ isValid = isValidProp(props[propName], propDef);
2101
+ }
2102
+ catch (e) {
2103
+ e.message = `Invalid prop '${propName}' in component ${ComponentClass.name} (${e.message})`;
2104
+ throw e;
2105
+ }
2106
+ if (!isValid) {
2107
+ throw new Error(`Invalid Prop '${propName}' in component '${ComponentClass.name}'`);
2108
+ }
2109
+ }
2110
+ if (!allowAdditionalProps) {
2111
+ for (let propName in props) {
2112
+ if (!(propName in propsDef)) {
2113
+ throw new Error(`Unknown prop '${propName}' given to component '${ComponentClass.name}'`);
2114
+ }
2115
+ }
2116
+ }
2117
+ }
2118
+ /**
2119
+ * Check if an invidual prop value matches its (static) prop definition
2120
+ */
2121
+ function isValidProp(prop, propDef) {
2122
+ if (propDef === true) {
2123
+ return true;
2124
+ }
2125
+ if (typeof propDef === "function") {
2126
+ // Check if a value is constructed by some Constructor. Note that there is a
2127
+ // slight abuse of language: we want to consider primitive values as well.
2128
+ //
2129
+ // So, even though 1 is not an instance of Number, we want to consider that
2130
+ // it is valid.
2131
+ if (typeof prop === "object") {
2132
+ return prop instanceof propDef;
2133
+ }
2134
+ return typeof prop === propDef.name.toLowerCase();
2135
+ }
2136
+ else if (propDef instanceof Array) {
2137
+ // If this code is executed, this means that we want to check if a prop
2138
+ // matches at least one of its descriptor.
2139
+ let result = false;
2140
+ for (let i = 0, iLen = propDef.length; i < iLen; i++) {
2141
+ result = result || isValidProp(prop, propDef[i]);
2142
+ }
2143
+ return result;
2144
+ }
2145
+ // propsDef is an object
2146
+ if (propDef.optional && prop === undefined) {
2147
+ return true;
2148
+ }
2149
+ let result = propDef.type ? isValidProp(prop, propDef.type) : true;
2150
+ if (propDef.validate) {
2151
+ result = result && propDef.validate(prop);
2152
+ }
2153
+ if (propDef.type === Array && propDef.element) {
2154
+ for (let i = 0, iLen = prop.length; i < iLen; i++) {
2155
+ result = result && isValidProp(prop[i], propDef.element);
2156
+ }
2157
+ }
2158
+ if (propDef.type === Object && propDef.shape) {
2159
+ const shape = propDef.shape;
2160
+ for (let key in shape) {
2161
+ result = result && isValidProp(prop[key], shape[key]);
2162
+ }
2163
+ if (result) {
2164
+ for (let propName in prop) {
2165
+ if (!(propName in shape)) {
2166
+ throw new Error(`unknown prop '${propName}'`);
2167
+ }
2168
+ }
2169
+ }
2170
+ }
2171
+ return result;
2172
+ }
2173
+
1953
2174
  let currentNode = null;
1954
2175
  function getCurrent() {
1955
2176
  if (!currentNode) {
@@ -1960,6 +2181,39 @@ function getCurrent() {
1960
2181
  function useComponent() {
1961
2182
  return currentNode.component;
1962
2183
  }
2184
+ // -----------------------------------------------------------------------------
2185
+ // Integration with reactivity system (useState)
2186
+ // -----------------------------------------------------------------------------
2187
+ const batchedRenderFunctions = new WeakMap();
2188
+ /**
2189
+ * Creates a reactive object that will be observed by the current component.
2190
+ * Reading data from the returned object (eg during rendering) will cause the
2191
+ * component to subscribe to that data and be rerendered when it changes.
2192
+ *
2193
+ * @param state the state to observe
2194
+ * @returns a reactive object that will cause the component to re-render on
2195
+ * relevant changes
2196
+ * @see reactive
2197
+ */
2198
+ function useState(state) {
2199
+ const node = getCurrent();
2200
+ let render = batchedRenderFunctions.get(node);
2201
+ if (!render) {
2202
+ render = batched(node.render.bind(node));
2203
+ batchedRenderFunctions.set(node, render);
2204
+ // manual implementation of onWillDestroy to break cyclic dependency
2205
+ node.willDestroy.push(clearReactivesForCallback.bind(null, render));
2206
+ }
2207
+ return reactive(state, render);
2208
+ }
2209
+ function arePropsDifferent(props1, props2) {
2210
+ for (let k in props1) {
2211
+ if (props1[k] !== props2[k]) {
2212
+ return true;
2213
+ }
2214
+ }
2215
+ return Object.keys(props1).length !== Object.keys(props2).length;
2216
+ }
1963
2217
  function component(name, props, key, ctx, parent) {
1964
2218
  let node = ctx.children[key];
1965
2219
  let isDynamic = typeof name !== "string";
@@ -1977,7 +2231,10 @@ function component(name, props, key, ctx, parent) {
1977
2231
  }
1978
2232
  const parentFiber = ctx.fiber;
1979
2233
  if (node) {
1980
- node.updateAndRender(props, parentFiber);
2234
+ const currentProps = node.component.props[TARGET];
2235
+ if (parentFiber.deep || arePropsDifferent(currentProps, props)) {
2236
+ node.updateAndRender(props, parentFiber);
2237
+ }
1981
2238
  }
1982
2239
  else {
1983
2240
  // new component
@@ -1993,8 +2250,7 @@ function component(name, props, key, ctx, parent) {
1993
2250
  }
1994
2251
  node = new ComponentNode(C, props, ctx.app, ctx);
1995
2252
  ctx.children[key] = node;
1996
- const fiber = makeChildFiber(node, parentFiber);
1997
- node.initiateRender(fiber);
2253
+ node.initiateRender(new Fiber(node, parentFiber));
1998
2254
  }
1999
2255
  return node;
2000
2256
  }
@@ -2019,6 +2275,7 @@ class ComponentNode {
2019
2275
  applyDefaultProps(props, C);
2020
2276
  const env = (parent && parent.childEnv) || app.env;
2021
2277
  this.childEnv = env;
2278
+ props = useState(props);
2022
2279
  this.component = new C(props, env, this);
2023
2280
  this.renderFn = app.getTemplate(C.template).bind(this.component, this.component, this);
2024
2281
  this.component.setup();
@@ -2046,20 +2303,29 @@ class ComponentNode {
2046
2303
  this._render(fiber);
2047
2304
  }
2048
2305
  }
2049
- async render() {
2306
+ async render(deep = false) {
2050
2307
  let current = this.fiber;
2051
2308
  if (current && current.root.locked) {
2052
2309
  await Promise.resolve();
2053
2310
  // situation may have changed after the microtask tick
2054
2311
  current = this.fiber;
2055
2312
  }
2056
- if (current && !current.bdom && !fibersInError.has(current)) {
2057
- return;
2313
+ if (current) {
2314
+ if (!current.bdom && !fibersInError.has(current)) {
2315
+ if (deep) {
2316
+ // we want the render from this point on to be with deep=true
2317
+ current.deep = deep;
2318
+ }
2319
+ return;
2320
+ }
2321
+ // if current rendering was with deep=true, we want this one to be the same
2322
+ deep = deep || current.deep;
2058
2323
  }
2059
- if (!this.bdom && !current) {
2324
+ else if (!this.bdom) {
2060
2325
  return;
2061
2326
  }
2062
2327
  const fiber = makeRootFiber(this);
2328
+ fiber.deep = deep;
2063
2329
  this.fiber = fiber;
2064
2330
  this.app.scheduler.addFiber(fiber);
2065
2331
  await Promise.resolve();
@@ -2118,6 +2384,9 @@ class ComponentNode {
2118
2384
  this.fiber = fiber;
2119
2385
  const component = this.component;
2120
2386
  applyDefaultProps(props, component.constructor);
2387
+ currentNode = this;
2388
+ props = useState(props);
2389
+ currentNode = null;
2121
2390
  const prom = Promise.all(this.willUpdateProps.map((f) => f.call(component, props)));
2122
2391
  await prom;
2123
2392
  if (fiber !== this.fiber) {
@@ -2177,6 +2446,14 @@ class ComponentNode {
2177
2446
  this.bdom.moveBefore(other ? other.bdom : null, afterNode);
2178
2447
  }
2179
2448
  patch() {
2449
+ if (this.fiber && this.fiber.parent) {
2450
+ // we only patch here renderings coming from above. renderings initiated
2451
+ // by the component will be patched independently in the appropriate
2452
+ // fiber.complete
2453
+ this._patch();
2454
+ }
2455
+ }
2456
+ _patch() {
2180
2457
  const hasChildren = Object.keys(this.children).length > 0;
2181
2458
  this.bdom.patch(this.fiber.bdom, hasChildren);
2182
2459
  if (hasChildren) {
@@ -2207,62 +2484,69 @@ class ComponentNode {
2207
2484
  }
2208
2485
 
2209
2486
  // -----------------------------------------------------------------------------
2210
- // hooks
2487
+ // Scheduler
2211
2488
  // -----------------------------------------------------------------------------
2212
- function onWillStart(fn) {
2213
- const node = getCurrent();
2214
- node.willStart.push(fn.bind(node.component));
2215
- }
2216
- function onWillUpdateProps(fn) {
2217
- const node = getCurrent();
2218
- node.willUpdateProps.push(fn.bind(node.component));
2219
- }
2220
- function onMounted(fn) {
2221
- const node = getCurrent();
2222
- node.mounted.push(fn.bind(node.component));
2223
- }
2224
- function onWillPatch(fn) {
2225
- const node = getCurrent();
2226
- node.willPatch.unshift(fn.bind(node.component));
2227
- }
2228
- function onPatched(fn) {
2229
- const node = getCurrent();
2230
- node.patched.push(fn.bind(node.component));
2231
- }
2232
- function onWillUnmount(fn) {
2233
- const node = getCurrent();
2234
- node.willUnmount.unshift(fn.bind(node.component));
2235
- }
2236
- function onWillDestroy(fn) {
2237
- const node = getCurrent();
2238
- node.willDestroy.push(fn.bind(node.component));
2239
- }
2240
- function onWillRender(fn) {
2241
- const node = getCurrent();
2242
- const renderFn = node.renderFn;
2243
- node.renderFn = () => {
2244
- fn.call(node.component);
2245
- return renderFn();
2246
- };
2247
- }
2248
- function onRendered(fn) {
2249
- const node = getCurrent();
2250
- const renderFn = node.renderFn;
2251
- node.renderFn = () => {
2252
- const result = renderFn();
2253
- fn.call(node.component);
2254
- return result;
2255
- };
2256
- }
2257
- function onError(callback) {
2258
- const node = getCurrent();
2259
- let handlers = nodeErrorHandlers.get(node);
2260
- if (!handlers) {
2261
- handlers = [];
2262
- nodeErrorHandlers.set(node, handlers);
2489
+ class Scheduler {
2490
+ constructor() {
2491
+ this.tasks = new Set();
2492
+ this.isRunning = false;
2493
+ this.requestAnimationFrame = Scheduler.requestAnimationFrame;
2263
2494
  }
2264
- handlers.push(callback.bind(node.component));
2265
- }
2495
+ start() {
2496
+ this.isRunning = true;
2497
+ this.scheduleTasks();
2498
+ }
2499
+ stop() {
2500
+ this.isRunning = false;
2501
+ }
2502
+ addFiber(fiber) {
2503
+ this.tasks.add(fiber.root);
2504
+ if (!this.isRunning) {
2505
+ this.start();
2506
+ }
2507
+ }
2508
+ /**
2509
+ * Process all current tasks. This only applies to the fibers that are ready.
2510
+ * Other tasks are left unchanged.
2511
+ */
2512
+ flush() {
2513
+ this.tasks.forEach((fiber) => {
2514
+ if (fiber.root !== fiber) {
2515
+ this.tasks.delete(fiber);
2516
+ return;
2517
+ }
2518
+ const hasError = fibersInError.has(fiber);
2519
+ if (hasError && fiber.counter !== 0) {
2520
+ this.tasks.delete(fiber);
2521
+ return;
2522
+ }
2523
+ if (fiber.node.status === 2 /* DESTROYED */) {
2524
+ this.tasks.delete(fiber);
2525
+ return;
2526
+ }
2527
+ if (fiber.counter === 0) {
2528
+ if (!hasError) {
2529
+ fiber.complete();
2530
+ }
2531
+ this.tasks.delete(fiber);
2532
+ }
2533
+ });
2534
+ if (this.tasks.size === 0) {
2535
+ this.stop();
2536
+ }
2537
+ }
2538
+ scheduleTasks() {
2539
+ this.requestAnimationFrame(() => {
2540
+ this.flush();
2541
+ if (this.isRunning) {
2542
+ this.scheduleTasks();
2543
+ }
2544
+ });
2545
+ }
2546
+ }
2547
+ // capture the value of requestAnimationFrame as soon as possible, to avoid
2548
+ // interactions with other code, such as test frameworks that override them
2549
+ Scheduler.requestAnimationFrame = window.requestAnimationFrame.bind(window);
2266
2550
 
2267
2551
  /**
2268
2552
  * Owl QWeb Expression Parser
@@ -3549,17 +3833,13 @@ class CodeGenerator {
3549
3833
  slotDef = `{${slotStr.join(", ")}}`;
3550
3834
  }
3551
3835
  if (slotDef && !(ast.dynamicProps || hasSlotsProp)) {
3552
- props.push(`slots: ${slotDef}`);
3836
+ this.helpers.add("markRaw");
3837
+ props.push(`slots: markRaw(${slotDef})`);
3553
3838
  }
3554
3839
  const propStr = `{${props.join(",")}}`;
3555
3840
  let propString = propStr;
3556
3841
  if (ast.dynamicProps) {
3557
- if (!props.length) {
3558
- propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)})`;
3559
- }
3560
- else {
3561
- propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)}, ${propStr})`;
3562
- }
3842
+ propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)}${props.length ? ", " + propStr : ""})`;
3563
3843
  }
3564
3844
  let propVar;
3565
3845
  if ((slotDef && (ast.dynamicProps || hasSlotsProp)) || this.dev) {
@@ -3568,7 +3848,8 @@ class CodeGenerator {
3568
3848
  propString = propVar;
3569
3849
  }
3570
3850
  if (slotDef && (ast.dynamicProps || hasSlotsProp)) {
3571
- this.addLine(`${propVar}.slots = Object.assign(${slotDef}, ${propVar}.slots)`);
3851
+ this.helpers.add("markRaw");
3852
+ this.addLine(`${propVar}.slots = markRaw(Object.assign(${slotDef}, ${propVar}.slots))`);
3572
3853
  }
3573
3854
  // cmap key
3574
3855
  const key = this.generateComponentKey();
@@ -3768,6 +4049,9 @@ function parseDOMNode(node, ctx) {
3768
4049
  if (tagName === "t" && !dynamicTag) {
3769
4050
  return null;
3770
4051
  }
4052
+ if (tagName.startsWith("block-")) {
4053
+ throw new Error(`Invalid tag name: '${tagName}'`);
4054
+ }
3771
4055
  ctx = Object.assign({}, ctx);
3772
4056
  if (tagName === "pre") {
3773
4057
  ctx.inPreTag = true;
@@ -3833,6 +4117,9 @@ function parseDOMNode(node, ctx) {
3833
4117
  ctx.tModelInfo = model;
3834
4118
  }
3835
4119
  }
4120
+ else if (attr.startsWith("block-")) {
4121
+ throw new Error(`Invalid attribute: '${attr}'`);
4122
+ }
3836
4123
  else if (attr !== "t-name") {
3837
4124
  if (attr.startsWith("t-") && !attr.startsWith("t-att")) {
3838
4125
  throw new Error(`Unknown QWeb directive: '${attr}'`);
@@ -4381,108 +4668,96 @@ function compile(template, options = {}) {
4381
4668
  return new Function("bdom, helpers", code);
4382
4669
  }
4383
4670
 
4384
- const bdom = { text, createBlock, list, multi, html, toggler, component, comment };
4385
- const globalTemplates = {};
4386
- function parseXML(xml) {
4387
- const parser = new DOMParser();
4388
- const doc = parser.parseFromString(xml, "text/xml");
4389
- if (doc.getElementsByTagName("parsererror").length) {
4390
- let msg = "Invalid XML in template.";
4391
- const parsererrorText = doc.getElementsByTagName("parsererror")[0].textContent;
4392
- if (parsererrorText) {
4393
- msg += "\nThe parser has produced the following error message:\n" + parsererrorText;
4394
- const re = /\d+/g;
4395
- const firstMatch = re.exec(parsererrorText);
4396
- if (firstMatch) {
4397
- const lineNumber = Number(firstMatch[0]);
4398
- const line = xml.split("\n")[lineNumber - 1];
4399
- const secondMatch = re.exec(parsererrorText);
4400
- if (line && secondMatch) {
4401
- const columnIndex = Number(secondMatch[0]) - 1;
4402
- if (line[columnIndex]) {
4403
- msg +=
4404
- `\nThe error might be located at xml line ${lineNumber} column ${columnIndex}\n` +
4405
- `${line}\n${"-".repeat(columnIndex - 1)}^`;
4671
+ function wrapError(fn, hookName) {
4672
+ const error = new Error(`The following error occurred in ${hookName}: `);
4673
+ return (...args) => {
4674
+ try {
4675
+ const result = fn(...args);
4676
+ if (result instanceof Promise) {
4677
+ return result.catch((cause) => {
4678
+ error.cause = cause;
4679
+ if (cause instanceof Error) {
4680
+ error.message += `"${cause.message}"`;
4406
4681
  }
4407
- }
4682
+ throw error;
4683
+ });
4408
4684
  }
4685
+ return result;
4409
4686
  }
4410
- throw new Error(msg);
4411
- }
4412
- return doc;
4413
- }
4414
- class TemplateSet {
4415
- constructor(config = {}) {
4416
- this.rawTemplates = Object.create(globalTemplates);
4417
- this.templates = {};
4418
- this.utils = Object.assign({}, UTILS, {
4419
- call: (owner, subTemplate, ctx, parent, key) => {
4420
- const template = this.getTemplate(subTemplate);
4421
- return toggler(subTemplate, template.call(owner, ctx, parent, key));
4422
- },
4423
- getTemplate: (name) => this.getTemplate(name),
4424
- });
4425
- this.dev = config.dev || false;
4426
- this.translateFn = config.translateFn;
4427
- this.translatableAttributes = config.translatableAttributes;
4428
- if (config.templates) {
4429
- this.addTemplates(config.templates);
4430
- }
4431
- }
4432
- addTemplate(name, template, options = {}) {
4433
- if (name in this.rawTemplates && !options.allowDuplicate) {
4434
- throw new Error(`Template ${name} already defined`);
4435
- }
4436
- this.rawTemplates[name] = template;
4437
- }
4438
- addTemplates(xml, options = {}) {
4439
- if (!xml) {
4440
- // empty string
4441
- return;
4442
- }
4443
- xml = xml instanceof Document ? xml : parseXML(xml);
4444
- for (const template of xml.querySelectorAll("[t-name]")) {
4445
- const name = template.getAttribute("t-name");
4446
- this.addTemplate(name, template, options);
4447
- }
4448
- }
4449
- getTemplate(name) {
4450
- if (!(name in this.templates)) {
4451
- const rawTemplate = this.rawTemplates[name];
4452
- if (rawTemplate === undefined) {
4453
- throw new Error(`Missing template: "${name}"`);
4687
+ catch (cause) {
4688
+ if (cause instanceof Error) {
4689
+ error.message += `"${cause.message}"`;
4454
4690
  }
4455
- const templateFn = this._compileTemplate(name, rawTemplate);
4456
- // first add a function to lazily get the template, in case there is a
4457
- // recursive call to the template name
4458
- const templates = this.templates;
4459
- this.templates[name] = function (context, parent) {
4460
- return templates[name].call(this, context, parent);
4461
- };
4462
- const template = templateFn(bdom, this.utils);
4463
- this.templates[name] = template;
4691
+ throw error;
4464
4692
  }
4465
- return this.templates[name];
4466
- }
4467
- _compileTemplate(name, template) {
4468
- return compile(template, {
4469
- name,
4470
- dev: this.dev,
4471
- translateFn: this.translateFn,
4472
- translatableAttributes: this.translatableAttributes,
4473
- });
4474
- }
4693
+ };
4475
4694
  }
4476
4695
  // -----------------------------------------------------------------------------
4477
- // xml tag helper
4696
+ // hooks
4478
4697
  // -----------------------------------------------------------------------------
4479
- function xml(...args) {
4480
- const name = `__template__${xml.nextId++}`;
4481
- const value = String.raw(...args);
4482
- globalTemplates[name] = value;
4483
- return name;
4698
+ function onWillStart(fn) {
4699
+ const node = getCurrent();
4700
+ const decorate = node.app.dev ? wrapError : (fn) => fn;
4701
+ node.willStart.push(decorate(fn.bind(node.component), "onWillStart"));
4702
+ }
4703
+ function onWillUpdateProps(fn) {
4704
+ const node = getCurrent();
4705
+ const decorate = node.app.dev ? wrapError : (fn) => fn;
4706
+ node.willUpdateProps.push(decorate(fn.bind(node.component), "onWillUpdateProps"));
4707
+ }
4708
+ function onMounted(fn) {
4709
+ const node = getCurrent();
4710
+ const decorate = node.app.dev ? wrapError : (fn) => fn;
4711
+ node.mounted.push(decorate(fn.bind(node.component), "onMounted"));
4712
+ }
4713
+ function onWillPatch(fn) {
4714
+ const node = getCurrent();
4715
+ const decorate = node.app.dev ? wrapError : (fn) => fn;
4716
+ node.willPatch.unshift(decorate(fn.bind(node.component), "onWillPatch"));
4717
+ }
4718
+ function onPatched(fn) {
4719
+ const node = getCurrent();
4720
+ const decorate = node.app.dev ? wrapError : (fn) => fn;
4721
+ node.patched.push(decorate(fn.bind(node.component), "onPatched"));
4722
+ }
4723
+ function onWillUnmount(fn) {
4724
+ const node = getCurrent();
4725
+ const decorate = node.app.dev ? wrapError : (fn) => fn;
4726
+ node.willUnmount.unshift(decorate(fn.bind(node.component), "onWillUnmount"));
4727
+ }
4728
+ function onWillDestroy(fn) {
4729
+ const node = getCurrent();
4730
+ const decorate = node.app.dev ? wrapError : (fn) => fn;
4731
+ node.willDestroy.push(decorate(fn.bind(node.component), "onWillDestroy"));
4732
+ }
4733
+ function onWillRender(fn) {
4734
+ const node = getCurrent();
4735
+ const renderFn = node.renderFn;
4736
+ const decorate = node.app.dev ? wrapError : (fn) => fn;
4737
+ node.renderFn = decorate(() => {
4738
+ fn.call(node.component);
4739
+ return renderFn();
4740
+ }, "onWillRender");
4484
4741
  }
4485
- xml.nextId = 1;
4742
+ function onRendered(fn) {
4743
+ const node = getCurrent();
4744
+ const renderFn = node.renderFn;
4745
+ const decorate = node.app.dev ? wrapError : (fn) => fn;
4746
+ node.renderFn = decorate(() => {
4747
+ const result = renderFn();
4748
+ fn.call(node.component);
4749
+ return result;
4750
+ }, "onRendered");
4751
+ }
4752
+ function onError(callback) {
4753
+ const node = getCurrent();
4754
+ let handlers = nodeErrorHandlers.get(node);
4755
+ if (!handlers) {
4756
+ handlers = [];
4757
+ nodeErrorHandlers.set(node, handlers);
4758
+ }
4759
+ handlers.push(callback.bind(node.component));
4760
+ }
4486
4761
 
4487
4762
  class Component {
4488
4763
  constructor(props, env, node) {
@@ -4491,8 +4766,8 @@ class Component {
4491
4766
  this.__owl__ = node;
4492
4767
  }
4493
4768
  setup() { }
4494
- render() {
4495
- this.__owl__.render();
4769
+ render(deep = false) {
4770
+ this.__owl__.render(deep);
4496
4771
  }
4497
4772
  }
4498
4773
  Component.template = "";
@@ -4561,75 +4836,291 @@ Portal.props = {
4561
4836
  slots: true,
4562
4837
  };
4563
4838
 
4564
- // -----------------------------------------------------------------------------
4565
- // Scheduler
4566
- // -----------------------------------------------------------------------------
4567
- class Scheduler {
4568
- constructor() {
4569
- this.tasks = new Set();
4570
- this.isRunning = false;
4571
- this.requestAnimationFrame = Scheduler.requestAnimationFrame;
4839
+ /**
4840
+ * This file contains utility functions that will be injected in each template,
4841
+ * to perform various useful tasks in the compiled code.
4842
+ */
4843
+ function withDefault(value, defaultValue) {
4844
+ return value === undefined || value === null || value === false ? defaultValue : value;
4845
+ }
4846
+ function callSlot(ctx, parent, key, name, dynamic, extra, defaultContent) {
4847
+ key = key + "__slot_" + name;
4848
+ const slots = ctx.props[TARGET].slots || {};
4849
+ const { __render, __ctx, __scope } = slots[name] || {};
4850
+ const slotScope = Object.create(__ctx || {});
4851
+ if (__scope) {
4852
+ slotScope[__scope] = extra || {};
4572
4853
  }
4573
- start() {
4574
- this.isRunning = true;
4575
- this.scheduleTasks();
4854
+ const slotBDom = __render ? __render.call(__ctx.__owl__.component, slotScope, parent, key) : null;
4855
+ if (defaultContent) {
4856
+ let child1 = undefined;
4857
+ let child2 = undefined;
4858
+ if (slotBDom) {
4859
+ child1 = dynamic ? toggler(name, slotBDom) : slotBDom;
4860
+ }
4861
+ else {
4862
+ child2 = defaultContent.call(ctx.__owl__.component, ctx, parent, key);
4863
+ }
4864
+ return multi([child1, child2]);
4576
4865
  }
4577
- stop() {
4578
- this.isRunning = false;
4866
+ return slotBDom || text("");
4867
+ }
4868
+ function capture(ctx) {
4869
+ const component = ctx.__owl__.component;
4870
+ const result = Object.create(component);
4871
+ for (let k in ctx) {
4872
+ result[k] = ctx[k];
4579
4873
  }
4580
- addFiber(fiber) {
4581
- this.tasks.add(fiber.root);
4582
- if (!this.isRunning) {
4583
- this.start();
4874
+ return result;
4875
+ }
4876
+ function withKey(elem, k) {
4877
+ elem.key = k;
4878
+ return elem;
4879
+ }
4880
+ function prepareList(collection) {
4881
+ let keys;
4882
+ let values;
4883
+ if (Array.isArray(collection)) {
4884
+ keys = collection;
4885
+ values = collection;
4886
+ }
4887
+ else if (collection) {
4888
+ values = Object.keys(collection);
4889
+ keys = Object.values(collection);
4890
+ }
4891
+ else {
4892
+ throw new Error("Invalid loop expression");
4893
+ }
4894
+ const n = values.length;
4895
+ return [keys, values, n, new Array(n)];
4896
+ }
4897
+ const isBoundary = Symbol("isBoundary");
4898
+ function setContextValue(ctx, key, value) {
4899
+ const ctx0 = ctx;
4900
+ while (!ctx.hasOwnProperty(key) && !ctx.hasOwnProperty(isBoundary)) {
4901
+ const newCtx = ctx.__proto__;
4902
+ if (!newCtx) {
4903
+ ctx = ctx0;
4904
+ break;
4905
+ }
4906
+ ctx = newCtx;
4907
+ }
4908
+ ctx[key] = value;
4909
+ }
4910
+ function toNumber(val) {
4911
+ const n = parseFloat(val);
4912
+ return isNaN(n) ? val : n;
4913
+ }
4914
+ function shallowEqual(l1, l2) {
4915
+ for (let i = 0, l = l1.length; i < l; i++) {
4916
+ if (l1[i] !== l2[i]) {
4917
+ return false;
4918
+ }
4919
+ }
4920
+ return true;
4921
+ }
4922
+ class LazyValue {
4923
+ constructor(fn, ctx, node) {
4924
+ this.fn = fn;
4925
+ this.ctx = capture(ctx);
4926
+ this.node = node;
4927
+ }
4928
+ evaluate() {
4929
+ return this.fn(this.ctx, this.node);
4930
+ }
4931
+ toString() {
4932
+ return this.evaluate().toString();
4933
+ }
4934
+ }
4935
+ /*
4936
+ * Safely outputs `value` as a block depending on the nature of `value`
4937
+ */
4938
+ function safeOutput(value) {
4939
+ if (!value) {
4940
+ return value;
4941
+ }
4942
+ let safeKey;
4943
+ let block;
4944
+ if (value instanceof Markup) {
4945
+ safeKey = `string_safe`;
4946
+ block = html(value);
4947
+ }
4948
+ else if (value instanceof LazyValue) {
4949
+ safeKey = `lazy_value`;
4950
+ block = value.evaluate();
4951
+ }
4952
+ else if (value instanceof String || typeof value === "string") {
4953
+ safeKey = "string_unsafe";
4954
+ block = text(value);
4955
+ }
4956
+ else {
4957
+ // Assuming it is a block
4958
+ safeKey = "block_safe";
4959
+ block = value;
4960
+ }
4961
+ return toggler(safeKey, block);
4962
+ }
4963
+ let boundFunctions = new WeakMap();
4964
+ function bind(ctx, fn) {
4965
+ let component = ctx.__owl__.component;
4966
+ let boundFnMap = boundFunctions.get(component);
4967
+ if (!boundFnMap) {
4968
+ boundFnMap = new WeakMap();
4969
+ boundFunctions.set(component, boundFnMap);
4970
+ }
4971
+ let boundFn = boundFnMap.get(fn);
4972
+ if (!boundFn) {
4973
+ boundFn = fn.bind(component);
4974
+ boundFnMap.set(fn, boundFn);
4975
+ }
4976
+ return boundFn;
4977
+ }
4978
+ function multiRefSetter(refs, name) {
4979
+ let count = 0;
4980
+ return (el) => {
4981
+ if (el) {
4982
+ count++;
4983
+ if (count > 1) {
4984
+ throw new Error("Cannot have 2 elements with same ref name at the same time");
4985
+ }
4986
+ }
4987
+ if (count === 0 || el) {
4988
+ refs[name] = el;
4989
+ }
4990
+ };
4991
+ }
4992
+ const helpers = {
4993
+ withDefault,
4994
+ zero: Symbol("zero"),
4995
+ isBoundary,
4996
+ callSlot,
4997
+ capture,
4998
+ withKey,
4999
+ prepareList,
5000
+ setContextValue,
5001
+ multiRefSetter,
5002
+ shallowEqual,
5003
+ toNumber,
5004
+ validateProps,
5005
+ LazyValue,
5006
+ safeOutput,
5007
+ bind,
5008
+ };
5009
+
5010
+ const bdom = { text, createBlock, list, multi, html, toggler, component, comment };
5011
+ function parseXML(xml) {
5012
+ const parser = new DOMParser();
5013
+ const doc = parser.parseFromString(xml, "text/xml");
5014
+ if (doc.getElementsByTagName("parsererror").length) {
5015
+ let msg = "Invalid XML in template.";
5016
+ const parsererrorText = doc.getElementsByTagName("parsererror")[0].textContent;
5017
+ if (parsererrorText) {
5018
+ msg += "\nThe parser has produced the following error message:\n" + parsererrorText;
5019
+ const re = /\d+/g;
5020
+ const firstMatch = re.exec(parsererrorText);
5021
+ if (firstMatch) {
5022
+ const lineNumber = Number(firstMatch[0]);
5023
+ const line = xml.split("\n")[lineNumber - 1];
5024
+ const secondMatch = re.exec(parsererrorText);
5025
+ if (line && secondMatch) {
5026
+ const columnIndex = Number(secondMatch[0]) - 1;
5027
+ if (line[columnIndex]) {
5028
+ msg +=
5029
+ `\nThe error might be located at xml line ${lineNumber} column ${columnIndex}\n` +
5030
+ `${line}\n${"-".repeat(columnIndex - 1)}^`;
5031
+ }
5032
+ }
5033
+ }
5034
+ }
5035
+ throw new Error(msg);
5036
+ }
5037
+ return doc;
5038
+ }
5039
+ /**
5040
+ * Returns the helpers object that will be injected in each template closure
5041
+ * function
5042
+ */
5043
+ function makeHelpers(getTemplate) {
5044
+ return Object.assign({}, helpers, {
5045
+ Portal,
5046
+ markRaw,
5047
+ getTemplate,
5048
+ call: (owner, subTemplate, ctx, parent, key) => {
5049
+ const template = getTemplate(subTemplate);
5050
+ return toggler(subTemplate, template.call(owner, ctx, parent, key));
5051
+ },
5052
+ });
5053
+ }
5054
+ class TemplateSet {
5055
+ constructor(config = {}) {
5056
+ this.rawTemplates = Object.create(globalTemplates);
5057
+ this.templates = {};
5058
+ this.dev = config.dev || false;
5059
+ this.translateFn = config.translateFn;
5060
+ this.translatableAttributes = config.translatableAttributes;
5061
+ if (config.templates) {
5062
+ this.addTemplates(config.templates);
5063
+ }
5064
+ this.helpers = makeHelpers(this.getTemplate.bind(this));
5065
+ }
5066
+ addTemplate(name, template, options = {}) {
5067
+ if (name in this.rawTemplates && !options.allowDuplicate) {
5068
+ throw new Error(`Template ${name} already defined`);
5069
+ }
5070
+ this.rawTemplates[name] = template;
5071
+ }
5072
+ addTemplates(xml, options = {}) {
5073
+ if (!xml) {
5074
+ // empty string
5075
+ return;
5076
+ }
5077
+ xml = xml instanceof Document ? xml : parseXML(xml);
5078
+ for (const template of xml.querySelectorAll("[t-name]")) {
5079
+ const name = template.getAttribute("t-name");
5080
+ this.addTemplate(name, template, options);
4584
5081
  }
4585
5082
  }
4586
- /**
4587
- * Process all current tasks. This only applies to the fibers that are ready.
4588
- * Other tasks are left unchanged.
4589
- */
4590
- flush() {
4591
- this.tasks.forEach((fiber) => {
4592
- if (fiber.root !== fiber) {
4593
- this.tasks.delete(fiber);
4594
- return;
4595
- }
4596
- const hasError = fibersInError.has(fiber);
4597
- if (hasError && fiber.counter !== 0) {
4598
- this.tasks.delete(fiber);
4599
- return;
4600
- }
4601
- if (fiber.node.status === 2 /* DESTROYED */) {
4602
- this.tasks.delete(fiber);
4603
- return;
4604
- }
4605
- if (fiber.counter === 0) {
4606
- if (!hasError) {
4607
- fiber.complete();
5083
+ getTemplate(name) {
5084
+ if (!(name in this.templates)) {
5085
+ const rawTemplate = this.rawTemplates[name];
5086
+ if (rawTemplate === undefined) {
5087
+ let extraInfo = "";
5088
+ try {
5089
+ const componentName = getCurrent().component.constructor.name;
5090
+ extraInfo = ` (for component "${componentName}")`;
4608
5091
  }
4609
- this.tasks.delete(fiber);
5092
+ catch { }
5093
+ throw new Error(`Missing template: "${name}"${extraInfo}`);
4610
5094
  }
4611
- });
4612
- if (this.tasks.size === 0) {
4613
- this.stop();
5095
+ const templateFn = this._compileTemplate(name, rawTemplate);
5096
+ // first add a function to lazily get the template, in case there is a
5097
+ // recursive call to the template name
5098
+ const templates = this.templates;
5099
+ this.templates[name] = function (context, parent) {
5100
+ return templates[name].call(this, context, parent);
5101
+ };
5102
+ const template = templateFn(bdom, this.helpers);
5103
+ this.templates[name] = template;
4614
5104
  }
5105
+ return this.templates[name];
4615
5106
  }
4616
- scheduleTasks() {
4617
- this.requestAnimationFrame(() => {
4618
- this.flush();
4619
- if (this.isRunning) {
4620
- this.scheduleTasks();
4621
- }
5107
+ _compileTemplate(name, template) {
5108
+ return compile(template, {
5109
+ name,
5110
+ dev: this.dev,
5111
+ translateFn: this.translateFn,
5112
+ translatableAttributes: this.translatableAttributes,
4622
5113
  });
4623
5114
  }
4624
- }
4625
- // capture the value of requestAnimationFrame as soon as possible, to avoid
4626
- // interactions with other code, such as test frameworks that override them
4627
- Scheduler.requestAnimationFrame = window.requestAnimationFrame.bind(window);
5115
+ }
4628
5116
 
4629
- const DEV_MSG = `Owl is running in 'dev' mode.
5117
+ const DEV_MSG = () => {
5118
+ const hash = window.owl ? window.owl.__info__.hash : "master";
5119
+ return `Owl is running in 'dev' mode.
4630
5120
 
4631
5121
  This is not suitable for production use.
4632
- See https://github.com/odoo/owl/blob/master/doc/reference/config.md#mode for more information.`;
5122
+ See https://github.com/odoo/owl/blob/${hash}/doc/reference/app.md#configuration for more information.`;
5123
+ };
4633
5124
  class App extends TemplateSet {
4634
5125
  constructor(Root, config = {}) {
4635
5126
  super(config);
@@ -4640,7 +5131,7 @@ class App extends TemplateSet {
4640
5131
  this.dev = true;
4641
5132
  }
4642
5133
  if (this.dev && !config.test) {
4643
- console.info(DEV_MSG);
5134
+ console.info(DEV_MSG());
4644
5135
  }
4645
5136
  const descrs = Object.getOwnPropertyDescriptors(config.env || {});
4646
5137
  this.env = Object.freeze(Object.defineProperties({}, descrs));
@@ -4706,270 +5197,6 @@ function status(component) {
4706
5197
  }
4707
5198
  }
4708
5199
 
4709
- class Memo extends Component {
4710
- constructor(props, env, node) {
4711
- super(props, env, node);
4712
- // prevent patching process conditionally
4713
- let applyPatch = false;
4714
- const patchFn = node.patch;
4715
- node.patch = () => {
4716
- if (applyPatch) {
4717
- patchFn.call(node);
4718
- applyPatch = false;
4719
- }
4720
- };
4721
- // check props change, and render/apply patch if it changed
4722
- let prevProps = props;
4723
- const updateAndRender = node.updateAndRender;
4724
- node.updateAndRender = function (props, parentFiber) {
4725
- const shouldUpdate = !shallowEqual(prevProps, props);
4726
- if (shouldUpdate) {
4727
- prevProps = props;
4728
- updateAndRender.call(node, props, parentFiber);
4729
- applyPatch = true;
4730
- }
4731
- return Promise.resolve();
4732
- };
4733
- }
4734
- }
4735
- Memo.template = xml `<t t-slot="default"/>`;
4736
- /**
4737
- * we assume that each object have the same set of keys
4738
- */
4739
- function shallowEqual(p1, p2) {
4740
- for (let k in p1) {
4741
- if (k !== "slots" && p1[k] !== p2[k]) {
4742
- return false;
4743
- }
4744
- }
4745
- return true;
4746
- }
4747
-
4748
- // Allows to get the target of a Reactive (used for making a new Reactive from the underlying object)
4749
- const TARGET = Symbol("Target");
4750
- // Escape hatch to prevent reactivity system to turn something into a reactive
4751
- const SKIP = Symbol("Skip");
4752
- // Special key to subscribe to, to be notified of key creation/deletion
4753
- const KEYCHANGES = Symbol("Key changes");
4754
- const objectToString = Object.prototype.toString;
4755
- /**
4756
- * Checks whether a given value can be made into a reactive object.
4757
- *
4758
- * @param value the value to check
4759
- * @returns whether the value can be made reactive
4760
- */
4761
- function canBeMadeReactive(value) {
4762
- if (typeof value !== "object") {
4763
- return false;
4764
- }
4765
- // extract "RawType" from strings like "[object RawType]" => this lets us
4766
- // ignore many native objects such as Promise (whose toString is [object Promise])
4767
- // or Date ([object Date]).
4768
- const rawType = objectToString.call(value).slice(8, -1);
4769
- return rawType === "Object" || rawType === "Array";
4770
- }
4771
- /**
4772
- * Mark an object or array so that it is ignored by the reactivity system
4773
- *
4774
- * @param value the value to mark
4775
- * @returns the object itself
4776
- */
4777
- function markRaw(value) {
4778
- value[SKIP] = true;
4779
- return value;
4780
- }
4781
- /**
4782
- * Given a reactive objet, return the raw (non reactive) underlying object
4783
- *
4784
- * @param value a reactive value
4785
- * @returns the underlying value
4786
- */
4787
- function toRaw(value) {
4788
- return value[TARGET];
4789
- }
4790
- const targetToKeysToCallbacks = new WeakMap();
4791
- /**
4792
- * Observes a given key on a target with an callback. The callback will be
4793
- * called when the given key changes on the target.
4794
- *
4795
- * @param target the target whose key should be observed
4796
- * @param key the key to observe (or Symbol(KEYCHANGES) for key creation
4797
- * or deletion)
4798
- * @param callback the function to call when the key changes
4799
- */
4800
- function observeTargetKey(target, key, callback) {
4801
- if (!targetToKeysToCallbacks.get(target)) {
4802
- targetToKeysToCallbacks.set(target, new Map());
4803
- }
4804
- const keyToCallbacks = targetToKeysToCallbacks.get(target);
4805
- if (!keyToCallbacks.get(key)) {
4806
- keyToCallbacks.set(key, new Set());
4807
- }
4808
- keyToCallbacks.get(key).add(callback);
4809
- if (!callbacksToTargets.has(callback)) {
4810
- callbacksToTargets.set(callback, new Set());
4811
- }
4812
- callbacksToTargets.get(callback).add(target);
4813
- }
4814
- /**
4815
- * Notify Reactives that are observing a given target that a key has changed on
4816
- * the target.
4817
- *
4818
- * @param target target whose Reactives should be notified that the target was
4819
- * changed.
4820
- * @param key the key that changed (or Symbol `KEYCHANGES` if a key was created
4821
- * or deleted)
4822
- */
4823
- function notifyReactives(target, key) {
4824
- const keyToCallbacks = targetToKeysToCallbacks.get(target);
4825
- if (!keyToCallbacks) {
4826
- return;
4827
- }
4828
- const callbacks = keyToCallbacks.get(key);
4829
- if (!callbacks) {
4830
- return;
4831
- }
4832
- // Loop on copy because clearReactivesForCallback will modify the set in place
4833
- for (const callback of [...callbacks]) {
4834
- clearReactivesForCallback(callback);
4835
- callback();
4836
- }
4837
- }
4838
- const callbacksToTargets = new WeakMap();
4839
- /**
4840
- * Clears all subscriptions of the Reactives associated with a given callback.
4841
- *
4842
- * @param callback the callback for which the reactives need to be cleared
4843
- */
4844
- function clearReactivesForCallback(callback) {
4845
- const targetsToClear = callbacksToTargets.get(callback);
4846
- if (!targetsToClear) {
4847
- return;
4848
- }
4849
- for (const target of targetsToClear) {
4850
- const observedKeys = targetToKeysToCallbacks.get(target);
4851
- if (!observedKeys) {
4852
- continue;
4853
- }
4854
- for (const callbacks of observedKeys.values()) {
4855
- callbacks.delete(callback);
4856
- }
4857
- }
4858
- targetsToClear.clear();
4859
- }
4860
- const reactiveCache = new WeakMap();
4861
- /**
4862
- * Creates a reactive proxy for an object. Reading data on the reactive object
4863
- * subscribes to changes to the data. Writing data on the object will cause the
4864
- * notify callback to be called if there are suscriptions to that data. Nested
4865
- * objects and arrays are automatically made reactive as well.
4866
- *
4867
- * Whenever you are notified of a change, all subscriptions are cleared, and if
4868
- * you would like to be notified of any further changes, you should go read
4869
- * the underlying data again. We assume that if you don't go read it again after
4870
- * being notified, it means that you are no longer interested in that data.
4871
- *
4872
- * Subscriptions:
4873
- * + Reading a property on an object will subscribe you to changes in the value
4874
- * of that property.
4875
- * + Accessing an object keys (eg with Object.keys or with `for..in`) will
4876
- * subscribe you to the creation/deletion of keys. Checking the presence of a
4877
- * key on the object with 'in' has the same effect.
4878
- * - getOwnPropertyDescriptor does not currently subscribe you to the property.
4879
- * This is a choice that was made because changing a key's value will trigger
4880
- * this trap and we do not want to subscribe by writes. This also means that
4881
- * Object.hasOwnProperty doesn't subscribe as it goes through this trap.
4882
- *
4883
- * @param target the object for which to create a reactive proxy
4884
- * @param callback the function to call when an observed property of the
4885
- * reactive has changed
4886
- * @returns a proxy that tracks changes to it
4887
- */
4888
- function reactive(target, callback = () => { }) {
4889
- if (!canBeMadeReactive(target)) {
4890
- throw new Error(`Cannot make the given value reactive`);
4891
- }
4892
- if (SKIP in target) {
4893
- return target;
4894
- }
4895
- const originalTarget = target[TARGET];
4896
- if (originalTarget) {
4897
- return reactive(originalTarget, callback);
4898
- }
4899
- if (!reactiveCache.has(target)) {
4900
- reactiveCache.set(target, new Map());
4901
- }
4902
- const reactivesForTarget = reactiveCache.get(target);
4903
- if (!reactivesForTarget.has(callback)) {
4904
- const proxy = new Proxy(target, {
4905
- get(target, key, proxy) {
4906
- if (key === TARGET) {
4907
- return target;
4908
- }
4909
- observeTargetKey(target, key, callback);
4910
- const value = Reflect.get(target, key, proxy);
4911
- if (!canBeMadeReactive(value)) {
4912
- return value;
4913
- }
4914
- return reactive(value, callback);
4915
- },
4916
- set(target, key, value, proxy) {
4917
- const isNewKey = !Object.hasOwnProperty.call(target, key);
4918
- const originalValue = Reflect.get(target, key, proxy);
4919
- const ret = Reflect.set(target, key, value, proxy);
4920
- if (isNewKey) {
4921
- notifyReactives(target, KEYCHANGES);
4922
- }
4923
- // While Array length may trigger the set trap, it's not actually set by this
4924
- // method but is updated behind the scenes, and the trap is not called with the
4925
- // new value. We disable the "same-value-optimization" for it because of that.
4926
- if (originalValue !== value || (Array.isArray(target) && key === "length")) {
4927
- notifyReactives(target, key);
4928
- }
4929
- return ret;
4930
- },
4931
- deleteProperty(target, key) {
4932
- const ret = Reflect.deleteProperty(target, key);
4933
- notifyReactives(target, KEYCHANGES);
4934
- notifyReactives(target, key);
4935
- return ret;
4936
- },
4937
- ownKeys(target) {
4938
- observeTargetKey(target, KEYCHANGES, callback);
4939
- return Reflect.ownKeys(target);
4940
- },
4941
- has(target, key) {
4942
- // TODO: this observes all key changes instead of only the presence of the argument key
4943
- observeTargetKey(target, KEYCHANGES, callback);
4944
- return Reflect.has(target, key);
4945
- },
4946
- });
4947
- reactivesForTarget.set(callback, proxy);
4948
- }
4949
- return reactivesForTarget.get(callback);
4950
- }
4951
- const batchedRenderFunctions = new WeakMap();
4952
- /**
4953
- * Creates a reactive object that will be observed by the current component.
4954
- * Reading data from the returned object (eg during rendering) will cause the
4955
- * component to subscribe to that data and be rerendered when it changes.
4956
- *
4957
- * @param state the state to observe
4958
- * @returns a reactive object that will cause the component to re-render on
4959
- * relevant changes
4960
- * @see reactive
4961
- */
4962
- function useState(state) {
4963
- const node = getCurrent();
4964
- if (!batchedRenderFunctions.has(node)) {
4965
- batchedRenderFunctions.set(node, batched(() => node.render()));
4966
- onWillDestroy(() => clearReactivesForCallback(render));
4967
- }
4968
- const render = batchedRenderFunctions.get(node);
4969
- const reactiveState = reactive(state, render);
4970
- return reactiveState;
4971
- }
4972
-
4973
5200
  // -----------------------------------------------------------------------------
4974
5201
  // useRef
4975
5202
  // -----------------------------------------------------------------------------
@@ -5074,7 +5301,6 @@ function useExternalListener(target, eventName, handler, eventParams) {
5074
5301
 
5075
5302
  config.shouldNormalizeDom = false;
5076
5303
  config.mainEventHandler = mainEventHandler;
5077
- UTILS.Portal = Portal;
5078
5304
  const blockDom = {
5079
5305
  config,
5080
5306
  // bdom entry points
@@ -5095,7 +5321,6 @@ const __info__ = {};
5095
5321
  exports.App = App;
5096
5322
  exports.Component = Component;
5097
5323
  exports.EventBus = EventBus;
5098
- exports.Memo = Memo;
5099
5324
  exports.__info__ = __info__;
5100
5325
  exports.blockDom = blockDom;
5101
5326
  exports.loadFile = loadFile;
@@ -5127,7 +5352,7 @@ exports.whenReady = whenReady;
5127
5352
  exports.xml = xml;
5128
5353
 
5129
5354
 
5130
- __info__.version = '2.0.0-alpha.1';
5131
- __info__.date = '2022-02-08T12:38:37.037Z';
5132
- __info__.hash = 'e8632df';
5355
+ __info__.version = '2.0.0-beta.1';
5356
+ __info__.date = '2022-03-02T12:29:06.886Z';
5357
+ __info__.hash = 'd6348b8';
5133
5358
  __info__.url = 'https://github.com/odoo/owl';