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

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) {
@@ -1502,6 +1502,210 @@ function markup(value) {
1502
1502
  return new Markup(value);
1503
1503
  }
1504
1504
 
1505
+ // Allows to get the target of a Reactive (used for making a new Reactive from the underlying object)
1506
+ const TARGET = Symbol("Target");
1507
+ // Escape hatch to prevent reactivity system to turn something into a reactive
1508
+ const SKIP = Symbol("Skip");
1509
+ // Special key to subscribe to, to be notified of key creation/deletion
1510
+ const KEYCHANGES = Symbol("Key changes");
1511
+ const objectToString = Object.prototype.toString;
1512
+ /**
1513
+ * Checks whether a given value can be made into a reactive object.
1514
+ *
1515
+ * @param value the value to check
1516
+ * @returns whether the value can be made reactive
1517
+ */
1518
+ function canBeMadeReactive(value) {
1519
+ if (typeof value !== "object") {
1520
+ return false;
1521
+ }
1522
+ // extract "RawType" from strings like "[object RawType]" => this lets us
1523
+ // ignore many native objects such as Promise (whose toString is [object Promise])
1524
+ // or Date ([object Date]).
1525
+ const rawType = objectToString.call(value).slice(8, -1);
1526
+ return rawType === "Object" || rawType === "Array";
1527
+ }
1528
+ /**
1529
+ * Mark an object or array so that it is ignored by the reactivity system
1530
+ *
1531
+ * @param value the value to mark
1532
+ * @returns the object itself
1533
+ */
1534
+ function markRaw(value) {
1535
+ value[SKIP] = true;
1536
+ return value;
1537
+ }
1538
+ /**
1539
+ * Given a reactive objet, return the raw (non reactive) underlying object
1540
+ *
1541
+ * @param value a reactive value
1542
+ * @returns the underlying value
1543
+ */
1544
+ function toRaw(value) {
1545
+ return value[TARGET] || value;
1546
+ }
1547
+ const targetToKeysToCallbacks = new WeakMap();
1548
+ /**
1549
+ * Observes a given key on a target with an callback. The callback will be
1550
+ * called when the given key changes on the target.
1551
+ *
1552
+ * @param target the target whose key should be observed
1553
+ * @param key the key to observe (or Symbol(KEYCHANGES) for key creation
1554
+ * or deletion)
1555
+ * @param callback the function to call when the key changes
1556
+ */
1557
+ function observeTargetKey(target, key, callback) {
1558
+ if (!targetToKeysToCallbacks.get(target)) {
1559
+ targetToKeysToCallbacks.set(target, new Map());
1560
+ }
1561
+ const keyToCallbacks = targetToKeysToCallbacks.get(target);
1562
+ if (!keyToCallbacks.get(key)) {
1563
+ keyToCallbacks.set(key, new Set());
1564
+ }
1565
+ keyToCallbacks.get(key).add(callback);
1566
+ if (!callbacksToTargets.has(callback)) {
1567
+ callbacksToTargets.set(callback, new Set());
1568
+ }
1569
+ callbacksToTargets.get(callback).add(target);
1570
+ }
1571
+ /**
1572
+ * Notify Reactives that are observing a given target that a key has changed on
1573
+ * the target.
1574
+ *
1575
+ * @param target target whose Reactives should be notified that the target was
1576
+ * changed.
1577
+ * @param key the key that changed (or Symbol `KEYCHANGES` if a key was created
1578
+ * or deleted)
1579
+ */
1580
+ function notifyReactives(target, key) {
1581
+ const keyToCallbacks = targetToKeysToCallbacks.get(target);
1582
+ if (!keyToCallbacks) {
1583
+ return;
1584
+ }
1585
+ const callbacks = keyToCallbacks.get(key);
1586
+ if (!callbacks) {
1587
+ return;
1588
+ }
1589
+ // Loop on copy because clearReactivesForCallback will modify the set in place
1590
+ for (const callback of [...callbacks]) {
1591
+ clearReactivesForCallback(callback);
1592
+ callback();
1593
+ }
1594
+ }
1595
+ const callbacksToTargets = new WeakMap();
1596
+ /**
1597
+ * Clears all subscriptions of the Reactives associated with a given callback.
1598
+ *
1599
+ * @param callback the callback for which the reactives need to be cleared
1600
+ */
1601
+ function clearReactivesForCallback(callback) {
1602
+ const targetsToClear = callbacksToTargets.get(callback);
1603
+ if (!targetsToClear) {
1604
+ return;
1605
+ }
1606
+ for (const target of targetsToClear) {
1607
+ const observedKeys = targetToKeysToCallbacks.get(target);
1608
+ if (!observedKeys) {
1609
+ continue;
1610
+ }
1611
+ for (const callbacks of observedKeys.values()) {
1612
+ callbacks.delete(callback);
1613
+ }
1614
+ }
1615
+ targetsToClear.clear();
1616
+ }
1617
+ const reactiveCache = new WeakMap();
1618
+ /**
1619
+ * Creates a reactive proxy for an object. Reading data on the reactive object
1620
+ * subscribes to changes to the data. Writing data on the object will cause the
1621
+ * notify callback to be called if there are suscriptions to that data. Nested
1622
+ * objects and arrays are automatically made reactive as well.
1623
+ *
1624
+ * Whenever you are notified of a change, all subscriptions are cleared, and if
1625
+ * you would like to be notified of any further changes, you should go read
1626
+ * the underlying data again. We assume that if you don't go read it again after
1627
+ * being notified, it means that you are no longer interested in that data.
1628
+ *
1629
+ * Subscriptions:
1630
+ * + Reading a property on an object will subscribe you to changes in the value
1631
+ * of that property.
1632
+ * + Accessing an object keys (eg with Object.keys or with `for..in`) will
1633
+ * subscribe you to the creation/deletion of keys. Checking the presence of a
1634
+ * key on the object with 'in' has the same effect.
1635
+ * - getOwnPropertyDescriptor does not currently subscribe you to the property.
1636
+ * This is a choice that was made because changing a key's value will trigger
1637
+ * this trap and we do not want to subscribe by writes. This also means that
1638
+ * Object.hasOwnProperty doesn't subscribe as it goes through this trap.
1639
+ *
1640
+ * @param target the object for which to create a reactive proxy
1641
+ * @param callback the function to call when an observed property of the
1642
+ * reactive has changed
1643
+ * @returns a proxy that tracks changes to it
1644
+ */
1645
+ function reactive(target, callback = () => { }) {
1646
+ if (!canBeMadeReactive(target)) {
1647
+ throw new Error(`Cannot make the given value reactive`);
1648
+ }
1649
+ if (SKIP in target) {
1650
+ return target;
1651
+ }
1652
+ const originalTarget = target[TARGET];
1653
+ if (originalTarget) {
1654
+ return reactive(originalTarget, callback);
1655
+ }
1656
+ if (!reactiveCache.has(target)) {
1657
+ reactiveCache.set(target, new Map());
1658
+ }
1659
+ const reactivesForTarget = reactiveCache.get(target);
1660
+ if (!reactivesForTarget.has(callback)) {
1661
+ const proxy = new Proxy(target, {
1662
+ get(target, key, proxy) {
1663
+ if (key === TARGET) {
1664
+ return target;
1665
+ }
1666
+ observeTargetKey(target, key, callback);
1667
+ const value = Reflect.get(target, key, proxy);
1668
+ if (!canBeMadeReactive(value)) {
1669
+ return value;
1670
+ }
1671
+ return reactive(value, callback);
1672
+ },
1673
+ set(target, key, value, proxy) {
1674
+ const isNewKey = !Object.hasOwnProperty.call(target, key);
1675
+ const originalValue = Reflect.get(target, key, proxy);
1676
+ const ret = Reflect.set(target, key, value, proxy);
1677
+ if (isNewKey) {
1678
+ notifyReactives(target, KEYCHANGES);
1679
+ }
1680
+ // While Array length may trigger the set trap, it's not actually set by this
1681
+ // method but is updated behind the scenes, and the trap is not called with the
1682
+ // new value. We disable the "same-value-optimization" for it because of that.
1683
+ if (originalValue !== value || (Array.isArray(target) && key === "length")) {
1684
+ notifyReactives(target, key);
1685
+ }
1686
+ return ret;
1687
+ },
1688
+ deleteProperty(target, key) {
1689
+ const ret = Reflect.deleteProperty(target, key);
1690
+ notifyReactives(target, KEYCHANGES);
1691
+ notifyReactives(target, key);
1692
+ return ret;
1693
+ },
1694
+ ownKeys(target) {
1695
+ observeTargetKey(target, KEYCHANGES, callback);
1696
+ return Reflect.ownKeys(target);
1697
+ },
1698
+ has(target, key) {
1699
+ // TODO: this observes all key changes instead of only the presence of the argument key
1700
+ observeTargetKey(target, KEYCHANGES, callback);
1701
+ return Reflect.has(target, key);
1702
+ },
1703
+ });
1704
+ reactivesForTarget.set(callback, proxy);
1705
+ }
1706
+ return reactivesForTarget.get(callback);
1707
+ }
1708
+
1505
1709
  /**
1506
1710
  * This file contains utility functions that will be injected in each template,
1507
1711
  * to perform various useful tasks in the compiled code.
@@ -1511,7 +1715,7 @@ function withDefault(value, defaultValue) {
1511
1715
  }
1512
1716
  function callSlot(ctx, parent, key, name, dynamic, extra, defaultContent) {
1513
1717
  key = key + "__slot_" + name;
1514
- const slots = (ctx.props && ctx.props.slots) || {};
1718
+ const slots = ctx.props[TARGET].slots || {};
1515
1719
  const { __render, __ctx, __scope } = slots[name] || {};
1516
1720
  const slotScope = Object.create(__ctx || {});
1517
1721
  if (__scope) {
@@ -1779,8 +1983,7 @@ function handleError(params) {
1779
1983
  function makeChildFiber(node, parent) {
1780
1984
  let current = node.fiber;
1781
1985
  if (current) {
1782
- let root = parent.root;
1783
- cancelFibers(root, current.children);
1986
+ cancelFibers(current.children);
1784
1987
  current.root = null;
1785
1988
  }
1786
1989
  return new Fiber(node, parent);
@@ -1789,9 +1992,8 @@ function makeRootFiber(node) {
1789
1992
  let current = node.fiber;
1790
1993
  if (current) {
1791
1994
  let root = current.root;
1792
- root.counter -= cancelFibers(root, current.children);
1995
+ root.counter = root.counter + 1 - cancelFibers(current.children);
1793
1996
  current.children = [];
1794
- root.counter++;
1795
1997
  current.bdom = null;
1796
1998
  if (fibersInError.has(current)) {
1797
1999
  fibersInError.delete(current);
@@ -1812,15 +2014,14 @@ function makeRootFiber(node) {
1812
2014
  /**
1813
2015
  * @returns number of not-yet rendered fibers cancelled
1814
2016
  */
1815
- function cancelFibers(root, fibers) {
2017
+ function cancelFibers(fibers) {
1816
2018
  let result = 0;
1817
2019
  for (let fiber of fibers) {
1818
2020
  fiber.node.fiber = null;
1819
- fiber.root = root;
1820
2021
  if (!fiber.bdom) {
1821
2022
  result++;
1822
2023
  }
1823
- result += cancelFibers(root, fiber.children);
2024
+ result += cancelFibers(fiber.children);
1824
2025
  }
1825
2026
  return result;
1826
2027
  }
@@ -1829,9 +2030,11 @@ class Fiber {
1829
2030
  this.bdom = null;
1830
2031
  this.children = [];
1831
2032
  this.appliedToDom = false;
2033
+ this.deep = false;
1832
2034
  this.node = node;
1833
2035
  this.parent = parent;
1834
2036
  if (parent) {
2037
+ this.deep = parent.deep;
1835
2038
  const root = parent.root;
1836
2039
  root.counter++;
1837
2040
  this.root = root;
@@ -1874,7 +2077,7 @@ class RootFiber extends Fiber {
1874
2077
  }
1875
2078
  current = undefined;
1876
2079
  // Step 2: patching the dom
1877
- node.patch();
2080
+ node._patch();
1878
2081
  this.locked = false;
1879
2082
  // Step 4: calling all mounted lifecycle hooks
1880
2083
  let mountedFibers = this.mounted;
@@ -1961,6 +2164,39 @@ function getCurrent() {
1961
2164
  function useComponent() {
1962
2165
  return currentNode.component;
1963
2166
  }
2167
+ // -----------------------------------------------------------------------------
2168
+ // Integration with reactivity system (useState)
2169
+ // -----------------------------------------------------------------------------
2170
+ const batchedRenderFunctions = new WeakMap();
2171
+ /**
2172
+ * Creates a reactive object that will be observed by the current component.
2173
+ * Reading data from the returned object (eg during rendering) will cause the
2174
+ * component to subscribe to that data and be rerendered when it changes.
2175
+ *
2176
+ * @param state the state to observe
2177
+ * @returns a reactive object that will cause the component to re-render on
2178
+ * relevant changes
2179
+ * @see reactive
2180
+ */
2181
+ function useState(state) {
2182
+ const node = getCurrent();
2183
+ let render = batchedRenderFunctions.get(node);
2184
+ if (!render) {
2185
+ render = batched(node.render.bind(node));
2186
+ batchedRenderFunctions.set(node, render);
2187
+ // manual implementation of onWillDestroy to break cyclic dependency
2188
+ node.willDestroy.push(clearReactivesForCallback.bind(null, render));
2189
+ }
2190
+ return reactive(state, render);
2191
+ }
2192
+ function arePropsDifferent(props1, props2) {
2193
+ for (let k in props1) {
2194
+ if (props1[k] !== props2[k]) {
2195
+ return true;
2196
+ }
2197
+ }
2198
+ return Object.keys(props1).length !== Object.keys(props2).length;
2199
+ }
1964
2200
  function component(name, props, key, ctx, parent) {
1965
2201
  let node = ctx.children[key];
1966
2202
  let isDynamic = typeof name !== "string";
@@ -1978,7 +2214,10 @@ function component(name, props, key, ctx, parent) {
1978
2214
  }
1979
2215
  const parentFiber = ctx.fiber;
1980
2216
  if (node) {
1981
- node.updateAndRender(props, parentFiber);
2217
+ const currentProps = node.component.props[TARGET];
2218
+ if (parentFiber.deep || arePropsDifferent(currentProps, props)) {
2219
+ node.updateAndRender(props, parentFiber);
2220
+ }
1982
2221
  }
1983
2222
  else {
1984
2223
  // new component
@@ -1994,8 +2233,7 @@ function component(name, props, key, ctx, parent) {
1994
2233
  }
1995
2234
  node = new ComponentNode(C, props, ctx.app, ctx);
1996
2235
  ctx.children[key] = node;
1997
- const fiber = makeChildFiber(node, parentFiber);
1998
- node.initiateRender(fiber);
2236
+ node.initiateRender(new Fiber(node, parentFiber));
1999
2237
  }
2000
2238
  return node;
2001
2239
  }
@@ -2020,6 +2258,7 @@ class ComponentNode {
2020
2258
  applyDefaultProps(props, C);
2021
2259
  const env = (parent && parent.childEnv) || app.env;
2022
2260
  this.childEnv = env;
2261
+ props = useState(props);
2023
2262
  this.component = new C(props, env, this);
2024
2263
  this.renderFn = app.getTemplate(C.template).bind(this.component, this.component, this);
2025
2264
  this.component.setup();
@@ -2047,20 +2286,29 @@ class ComponentNode {
2047
2286
  this._render(fiber);
2048
2287
  }
2049
2288
  }
2050
- async render() {
2289
+ async render(deep = false) {
2051
2290
  let current = this.fiber;
2052
2291
  if (current && current.root.locked) {
2053
2292
  await Promise.resolve();
2054
2293
  // situation may have changed after the microtask tick
2055
2294
  current = this.fiber;
2056
2295
  }
2057
- if (current && !current.bdom && !fibersInError.has(current)) {
2058
- return;
2296
+ if (current) {
2297
+ if (!current.bdom && !fibersInError.has(current)) {
2298
+ if (deep) {
2299
+ // we want the render from this point on to be with deep=true
2300
+ current.deep = deep;
2301
+ }
2302
+ return;
2303
+ }
2304
+ // if current rendering was with deep=true, we want this one to be the same
2305
+ deep = deep || current.deep;
2059
2306
  }
2060
- if (!this.bdom && !current) {
2307
+ else if (!this.bdom) {
2061
2308
  return;
2062
2309
  }
2063
2310
  const fiber = makeRootFiber(this);
2311
+ fiber.deep = deep;
2064
2312
  this.fiber = fiber;
2065
2313
  this.app.scheduler.addFiber(fiber);
2066
2314
  await Promise.resolve();
@@ -2119,6 +2367,9 @@ class ComponentNode {
2119
2367
  this.fiber = fiber;
2120
2368
  const component = this.component;
2121
2369
  applyDefaultProps(props, component.constructor);
2370
+ currentNode = this;
2371
+ props = useState(props);
2372
+ currentNode = null;
2122
2373
  const prom = Promise.all(this.willUpdateProps.map((f) => f.call(component, props)));
2123
2374
  await prom;
2124
2375
  if (fiber !== this.fiber) {
@@ -2178,6 +2429,14 @@ class ComponentNode {
2178
2429
  this.bdom.moveBefore(other ? other.bdom : null, afterNode);
2179
2430
  }
2180
2431
  patch() {
2432
+ if (this.fiber && this.fiber.parent) {
2433
+ // we only patch here renderings coming from above. renderings initiated
2434
+ // by the component will be patched independently in the appropriate
2435
+ // fiber.complete
2436
+ this._patch();
2437
+ }
2438
+ }
2439
+ _patch() {
2181
2440
  const hasChildren = Object.keys(this.children).length > 0;
2182
2441
  this.bdom.patch(this.fiber.bdom, hasChildren);
2183
2442
  if (hasChildren) {
@@ -2207,53 +2466,86 @@ class ComponentNode {
2207
2466
  }
2208
2467
  }
2209
2468
 
2469
+ function wrapError(fn, hookName) {
2470
+ const error = new Error(`The following error occurred in ${hookName}: `);
2471
+ return (...args) => {
2472
+ try {
2473
+ const result = fn(...args);
2474
+ if (result instanceof Promise) {
2475
+ return result.catch((cause) => {
2476
+ error.cause = cause;
2477
+ if (cause instanceof Error) {
2478
+ error.message += `"${cause.message}"`;
2479
+ }
2480
+ throw error;
2481
+ });
2482
+ }
2483
+ return result;
2484
+ }
2485
+ catch (cause) {
2486
+ if (cause instanceof Error) {
2487
+ error.message += `"${cause.message}"`;
2488
+ }
2489
+ throw error;
2490
+ }
2491
+ };
2492
+ }
2210
2493
  // -----------------------------------------------------------------------------
2211
2494
  // hooks
2212
2495
  // -----------------------------------------------------------------------------
2213
2496
  function onWillStart(fn) {
2214
2497
  const node = getCurrent();
2215
- node.willStart.push(fn.bind(node.component));
2498
+ const decorate = node.app.dev ? wrapError : (fn) => fn;
2499
+ node.willStart.push(decorate(fn.bind(node.component), "onWillStart"));
2216
2500
  }
2217
2501
  function onWillUpdateProps(fn) {
2218
2502
  const node = getCurrent();
2219
- node.willUpdateProps.push(fn.bind(node.component));
2503
+ const decorate = node.app.dev ? wrapError : (fn) => fn;
2504
+ node.willUpdateProps.push(decorate(fn.bind(node.component), "onWillUpdateProps"));
2220
2505
  }
2221
2506
  function onMounted(fn) {
2222
2507
  const node = getCurrent();
2223
- node.mounted.push(fn.bind(node.component));
2508
+ const decorate = node.app.dev ? wrapError : (fn) => fn;
2509
+ node.mounted.push(decorate(fn.bind(node.component), "onMounted"));
2224
2510
  }
2225
2511
  function onWillPatch(fn) {
2226
2512
  const node = getCurrent();
2227
- node.willPatch.unshift(fn.bind(node.component));
2513
+ const decorate = node.app.dev ? wrapError : (fn) => fn;
2514
+ node.willPatch.unshift(decorate(fn.bind(node.component), "onWillPatch"));
2228
2515
  }
2229
2516
  function onPatched(fn) {
2230
2517
  const node = getCurrent();
2231
- node.patched.push(fn.bind(node.component));
2518
+ const decorate = node.app.dev ? wrapError : (fn) => fn;
2519
+ node.patched.push(decorate(fn.bind(node.component), "onPatched"));
2232
2520
  }
2233
2521
  function onWillUnmount(fn) {
2234
2522
  const node = getCurrent();
2235
- node.willUnmount.unshift(fn.bind(node.component));
2523
+ const decorate = node.app.dev ? wrapError : (fn) => fn;
2524
+ node.willUnmount.unshift(decorate(fn.bind(node.component), "onWillUnmount"));
2236
2525
  }
2237
2526
  function onWillDestroy(fn) {
2238
2527
  const node = getCurrent();
2239
- node.willDestroy.push(fn.bind(node.component));
2528
+ const decorate = node.app.dev ? wrapError : (fn) => fn;
2529
+ node.willDestroy.push(decorate(fn.bind(node.component), "onWillDestroy"));
2240
2530
  }
2241
2531
  function onWillRender(fn) {
2242
2532
  const node = getCurrent();
2243
2533
  const renderFn = node.renderFn;
2244
- node.renderFn = () => {
2534
+ const decorate = node.app.dev ? wrapError : (fn) => fn;
2535
+ node.renderFn = decorate(() => {
2245
2536
  fn.call(node.component);
2246
2537
  return renderFn();
2247
- };
2538
+ }, "onWillRender");
2248
2539
  }
2249
2540
  function onRendered(fn) {
2250
2541
  const node = getCurrent();
2251
2542
  const renderFn = node.renderFn;
2252
- node.renderFn = () => {
2543
+ const decorate = node.app.dev ? wrapError : (fn) => fn;
2544
+ node.renderFn = decorate(() => {
2253
2545
  const result = renderFn();
2254
2546
  fn.call(node.component);
2255
2547
  return result;
2256
- };
2548
+ }, "onRendered");
2257
2549
  }
2258
2550
  function onError(callback) {
2259
2551
  const node = getCurrent();
@@ -3550,17 +3842,13 @@ class CodeGenerator {
3550
3842
  slotDef = `{${slotStr.join(", ")}}`;
3551
3843
  }
3552
3844
  if (slotDef && !(ast.dynamicProps || hasSlotsProp)) {
3553
- props.push(`slots: ${slotDef}`);
3845
+ this.helpers.add("markRaw");
3846
+ props.push(`slots: markRaw(${slotDef})`);
3554
3847
  }
3555
3848
  const propStr = `{${props.join(",")}}`;
3556
3849
  let propString = propStr;
3557
3850
  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
- }
3851
+ propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)}${props.length ? ", " + propStr : ""})`;
3564
3852
  }
3565
3853
  let propVar;
3566
3854
  if ((slotDef && (ast.dynamicProps || hasSlotsProp)) || this.dev) {
@@ -3569,7 +3857,8 @@ class CodeGenerator {
3569
3857
  propString = propVar;
3570
3858
  }
3571
3859
  if (slotDef && (ast.dynamicProps || hasSlotsProp)) {
3572
- this.addLine(`${propVar}.slots = Object.assign(${slotDef}, ${propVar}.slots)`);
3860
+ this.helpers.add("markRaw");
3861
+ this.addLine(`${propVar}.slots = markRaw(Object.assign(${slotDef}, ${propVar}.slots))`);
3573
3862
  }
3574
3863
  // cmap key
3575
3864
  const key = this.generateComponentKey();
@@ -3769,6 +4058,9 @@ function parseDOMNode(node, ctx) {
3769
4058
  if (tagName === "t" && !dynamicTag) {
3770
4059
  return null;
3771
4060
  }
4061
+ if (tagName.startsWith("block-")) {
4062
+ throw new Error(`Invalid tag name: '${tagName}'`);
4063
+ }
3772
4064
  ctx = Object.assign({}, ctx);
3773
4065
  if (tagName === "pre") {
3774
4066
  ctx.inPreTag = true;
@@ -3834,6 +4126,9 @@ function parseDOMNode(node, ctx) {
3834
4126
  ctx.tModelInfo = model;
3835
4127
  }
3836
4128
  }
4129
+ else if (attr.startsWith("block-")) {
4130
+ throw new Error(`Invalid attribute: '${attr}'`);
4131
+ }
3837
4132
  else if (attr !== "t-name") {
3838
4133
  if (attr.startsWith("t-") && !attr.startsWith("t-att")) {
3839
4134
  throw new Error(`Unknown QWeb directive: '${attr}'`);
@@ -4451,7 +4746,13 @@ class TemplateSet {
4451
4746
  if (!(name in this.templates)) {
4452
4747
  const rawTemplate = this.rawTemplates[name];
4453
4748
  if (rawTemplate === undefined) {
4454
- throw new Error(`Missing template: "${name}"`);
4749
+ let extraInfo = "";
4750
+ try {
4751
+ const componentName = getCurrent().component.constructor.name;
4752
+ extraInfo = ` (for component "${componentName}")`;
4753
+ }
4754
+ catch { }
4755
+ throw new Error(`Missing template: "${name}"${extraInfo}`);
4455
4756
  }
4456
4757
  const templateFn = this._compileTemplate(name, rawTemplate);
4457
4758
  // first add a function to lazily get the template, in case there is a
@@ -4492,8 +4793,8 @@ class Component {
4492
4793
  this.__owl__ = node;
4493
4794
  }
4494
4795
  setup() { }
4495
- render() {
4496
- this.__owl__.render();
4796
+ render(deep = false) {
4797
+ this.__owl__.render(deep);
4497
4798
  }
4498
4799
  }
4499
4800
  Component.template = "";
@@ -4627,10 +4928,13 @@ class Scheduler {
4627
4928
  // interactions with other code, such as test frameworks that override them
4628
4929
  Scheduler.requestAnimationFrame = window.requestAnimationFrame.bind(window);
4629
4930
 
4630
- const DEV_MSG = `Owl is running in 'dev' mode.
4931
+ const DEV_MSG = () => {
4932
+ const hash = window.owl ? window.owl.__info__.hash : "master";
4933
+ return `Owl is running in 'dev' mode.
4631
4934
 
4632
4935
  This is not suitable for production use.
4633
- See https://github.com/odoo/owl/blob/master/doc/reference/config.md#mode for more information.`;
4936
+ See https://github.com/odoo/owl/blob/${hash}/doc/reference/app.md#configuration for more information.`;
4937
+ };
4634
4938
  class App extends TemplateSet {
4635
4939
  constructor(Root, config = {}) {
4636
4940
  super(config);
@@ -4641,7 +4945,7 @@ class App extends TemplateSet {
4641
4945
  this.dev = true;
4642
4946
  }
4643
4947
  if (this.dev && !config.test) {
4644
- console.info(DEV_MSG);
4948
+ console.info(DEV_MSG());
4645
4949
  }
4646
4950
  const descrs = Object.getOwnPropertyDescriptors(config.env || {});
4647
4951
  this.env = Object.freeze(Object.defineProperties({}, descrs));
@@ -4746,231 +5050,6 @@ function shallowEqual(p1, p2) {
4746
5050
  return true;
4747
5051
  }
4748
5052
 
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
5053
  // -----------------------------------------------------------------------------
4975
5054
  // useRef
4976
5055
  // -----------------------------------------------------------------------------
@@ -5076,6 +5155,7 @@ function useExternalListener(target, eventName, handler, eventParams) {
5076
5155
  config.shouldNormalizeDom = false;
5077
5156
  config.mainEventHandler = mainEventHandler;
5078
5157
  UTILS.Portal = Portal;
5158
+ UTILS.markRaw = markRaw;
5079
5159
  const blockDom = {
5080
5160
  config,
5081
5161
  // bdom entry points
@@ -5128,7 +5208,7 @@ exports.whenReady = whenReady;
5128
5208
  exports.xml = xml;
5129
5209
 
5130
5210
 
5131
- __info__.version = '2.0.0-alpha.2';
5132
- __info__.date = '2022-02-14T12:42:47.468Z';
5133
- __info__.hash = '4a922ed';
5211
+ __info__.version = '2.0.0-alpha.3';
5212
+ __info__.date = '2022-02-25T09:57:37.893Z';
5213
+ __info__.hash = '076b0d7';
5134
5214
  __info__.url = 'https://github.com/odoo/owl';