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

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