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