@odoo/owl 2.8.1 → 3.0.0-alpha.10

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.
Files changed (73) hide show
  1. package/dist/compile_templates.mjs +92 -189
  2. package/dist/compiler.js +2378 -0
  3. package/dist/owl-devtools.zip +0 -0
  4. package/dist/owl.cjs.js +1371 -1261
  5. package/dist/owl.cjs.runtime.js +4094 -0
  6. package/dist/owl.es.js +1358 -1252
  7. package/dist/owl.es.runtime.js +4050 -0
  8. package/dist/owl.iife.js +1371 -1261
  9. package/dist/owl.iife.min.js +1 -1
  10. package/dist/owl.iife.runtime.js +4098 -0
  11. package/dist/owl.iife.runtime.min.js +1 -0
  12. package/dist/types/compiler/code_generator.d.ts +3 -5
  13. package/dist/types/compiler/index.d.ts +4 -4
  14. package/dist/types/compiler/inline_expressions.d.ts +1 -1
  15. package/dist/types/compiler/parser.d.ts +21 -28
  16. package/dist/types/owl.d.ts +299 -205
  17. package/dist/types/runtime/app.d.ts +29 -31
  18. package/dist/types/runtime/blockdom/block_compiler.d.ts +3 -3
  19. package/dist/types/runtime/blockdom/config.d.ts +1 -1
  20. package/dist/types/runtime/blockdom/event_catcher.d.ts +2 -2
  21. package/dist/types/runtime/blockdom/events.d.ts +1 -1
  22. package/dist/types/runtime/blockdom/index.d.ts +1 -1
  23. package/dist/types/runtime/cancellableContext.d.ts +15 -0
  24. package/dist/types/runtime/cancellablePromise.d.ts +15 -0
  25. package/dist/types/runtime/component.d.ts +5 -13
  26. package/dist/types/runtime/component_node.d.ts +15 -35
  27. package/dist/types/runtime/event_handling.d.ts +1 -1
  28. package/dist/types/runtime/executionContext.d.ts +0 -0
  29. package/dist/types/runtime/hooks.d.ts +7 -33
  30. package/dist/types/runtime/index.d.ts +15 -5
  31. package/dist/types/runtime/lifecycle_hooks.d.ts +1 -3
  32. package/dist/types/runtime/listOperation.d.ts +1 -0
  33. package/dist/types/runtime/plugins.d.ts +23 -0
  34. package/dist/types/runtime/portal.d.ts +4 -6
  35. package/dist/types/runtime/props.d.ts +65 -0
  36. package/dist/types/runtime/reactivity/computations.d.ts +31 -0
  37. package/dist/types/runtime/reactivity/computed.d.ts +7 -0
  38. package/dist/types/runtime/reactivity/derived.d.ts +7 -0
  39. package/dist/types/runtime/reactivity/effect.d.ts +2 -0
  40. package/dist/types/runtime/reactivity/proxy.d.ts +46 -0
  41. package/dist/types/runtime/reactivity/reactivity.d.ts +46 -0
  42. package/dist/types/runtime/reactivity/signal.d.ts +17 -0
  43. package/dist/types/runtime/reactivity/signals.d.ts +30 -0
  44. package/dist/types/runtime/registry.d.ts +19 -0
  45. package/dist/types/runtime/relationalModel/discussModel.d.ts +19 -0
  46. package/dist/types/runtime/relationalModel/discussModelTypes.d.ts +22 -0
  47. package/dist/types/runtime/relationalModel/field.d.ts +20 -0
  48. package/dist/types/runtime/relationalModel/model.d.ts +59 -0
  49. package/dist/types/runtime/relationalModel/modelData.d.ts +18 -0
  50. package/dist/types/runtime/relationalModel/modelRegistry.d.ts +3 -0
  51. package/dist/types/runtime/relationalModel/modelUtils.d.ts +4 -0
  52. package/dist/types/runtime/relationalModel/store.d.ts +16 -0
  53. package/dist/types/runtime/relationalModel/types.d.ts +83 -0
  54. package/dist/types/runtime/relationalModel/util.d.ts +1 -0
  55. package/dist/types/runtime/relationalModel/web/WebDataPoint.d.ts +25 -0
  56. package/dist/types/runtime/relationalModel/web/WebRecord.d.ts +131 -0
  57. package/dist/types/runtime/relationalModel/web/WebStaticList.d.ts +63 -0
  58. package/dist/types/runtime/relationalModel/web/webModel.d.ts +5 -0
  59. package/dist/types/runtime/relationalModel/web/webModelTypes.d.ts +139 -0
  60. package/dist/types/runtime/rendering/error_handling.d.ts +13 -0
  61. package/dist/types/runtime/rendering/fibers.d.ts +37 -0
  62. package/dist/types/runtime/rendering/scheduler.d.ts +21 -0
  63. package/dist/types/runtime/rendering/template_helpers.d.ts +50 -0
  64. package/dist/types/runtime/resource.d.ts +12 -0
  65. package/dist/types/runtime/signals.d.ts +19 -0
  66. package/dist/types/runtime/status.d.ts +2 -3
  67. package/dist/types/runtime/task.d.ts +12 -0
  68. package/dist/types/runtime/template_set.d.ts +3 -4
  69. package/dist/types/runtime/utils.d.ts +1 -2
  70. package/dist/types/runtime/validation.d.ts +6 -6
  71. package/dist/types/utils/registry.d.ts +15 -0
  72. package/dist/types/version.d.ts +1 -1
  73. package/package.json +9 -9
package/dist/owl.cjs.js CHANGED
@@ -33,6 +33,7 @@ const config = {
33
33
  // -----------------------------------------------------------------------------
34
34
  // Toggler node
35
35
  // -----------------------------------------------------------------------------
36
+ const txt = document.createTextNode("");
36
37
  class VToggler {
37
38
  constructor(key, child) {
38
39
  this.key = key;
@@ -58,11 +59,13 @@ class VToggler {
58
59
  child1.patch(child2, withBeforeRemove);
59
60
  }
60
61
  else {
61
- child2.mount(this.parentEl, child1.firstNode());
62
+ const firstNode = child1.firstNode();
63
+ firstNode.parentElement.insertBefore(txt, firstNode);
62
64
  if (withBeforeRemove) {
63
65
  child1.beforeRemove();
64
66
  }
65
67
  child1.remove();
68
+ child2.mount(this.parentEl, txt);
66
69
  this.child = child2;
67
70
  this.key = other.key;
68
71
  }
@@ -335,13 +338,6 @@ function whenReady(fn) {
335
338
  }
336
339
  }).then(fn || function () { });
337
340
  }
338
- async function loadFile(url) {
339
- const result = await fetch(url);
340
- if (!result.ok) {
341
- throw new OwlError("Error while fetching xml templates");
342
- }
343
- return await result.text();
344
- }
345
341
  /*
346
342
  * This class just transports the fact that a string is safe
347
343
  * to be injected as HTML. Overriding a JS primitive is quite painful though
@@ -902,7 +898,7 @@ function parentTree(tree) {
902
898
  function buildContext(tree, ctx, fromIdx) {
903
899
  if (!ctx) {
904
900
  const children = new Array(tree.info.filter((v) => v.type === "child").length);
905
- ctx = { collectors: [], locations: [], children, cbRefs: [], refN: tree.refN, refList: [] };
901
+ ctx = { collectors: [], locations: [], children, cbRefs: [], refN: tree.refN };
906
902
  fromIdx = 0;
907
903
  }
908
904
  if (tree.refN) {
@@ -1009,14 +1005,16 @@ function updateCtx(ctx, tree) {
1009
1005
  });
1010
1006
  break;
1011
1007
  }
1012
- case "ref":
1013
- const index = ctx.cbRefs.push(info.idx) - 1;
1008
+ case "ref": {
1014
1009
  ctx.locations.push({
1015
1010
  idx: info.idx,
1016
1011
  refIdx: info.refIdx,
1017
- setData: makeRefSetter(index, ctx.refList),
1012
+ setData: NO_OP,
1018
1013
  updateData: NO_OP,
1019
1014
  });
1015
+ ctx.cbRefs.push(info.idx);
1016
+ break;
1017
+ }
1020
1018
  }
1021
1019
  }
1022
1020
  }
@@ -1025,27 +1023,6 @@ function updateCtx(ctx, tree) {
1025
1023
  // -----------------------------------------------------------------------------
1026
1024
  function buildBlock(template, ctx) {
1027
1025
  let B = createBlockClass(template, ctx);
1028
- if (ctx.cbRefs.length) {
1029
- const cbRefs = ctx.cbRefs;
1030
- const refList = ctx.refList;
1031
- let cbRefsNumber = cbRefs.length;
1032
- B = class extends B {
1033
- mount(parent, afterNode) {
1034
- refList.push(new Array(cbRefsNumber));
1035
- super.mount(parent, afterNode);
1036
- for (let cbRef of refList.pop()) {
1037
- cbRef();
1038
- }
1039
- }
1040
- remove() {
1041
- super.remove();
1042
- for (let cbRef of cbRefs) {
1043
- let fn = this.data[cbRef];
1044
- fn(null);
1045
- }
1046
- }
1047
- };
1048
- }
1049
1026
  if (ctx.children.length) {
1050
1027
  B = class extends B {
1051
1028
  constructor(data, children) {
@@ -1059,14 +1036,9 @@ function buildBlock(template, ctx) {
1059
1036
  return (data) => new B(data);
1060
1037
  }
1061
1038
  function createBlockClass(template, ctx) {
1062
- const { refN, collectors, children } = ctx;
1039
+ const { refN, collectors, children, locations, cbRefs } = ctx;
1063
1040
  const colN = collectors.length;
1064
- ctx.locations.sort((a, b) => a.idx - b.idx);
1065
- const locations = ctx.locations.map((loc) => ({
1066
- refIdx: loc.refIdx,
1067
- setData: loc.setData,
1068
- updateData: loc.updateData,
1069
- }));
1041
+ locations.sort((a, b) => a.idx - b.idx);
1070
1042
  const locN = locations.length;
1071
1043
  const childN = children.length;
1072
1044
  const childrenLocs = children;
@@ -1142,6 +1114,15 @@ function createBlockClass(template, ctx) {
1142
1114
  }
1143
1115
  this.el = el;
1144
1116
  this.parentEl = parent;
1117
+ if (cbRefs.length) {
1118
+ const data = this.data;
1119
+ const refs = this.refs;
1120
+ for (let cbRef of cbRefs) {
1121
+ const { idx, refIdx } = locations[cbRef];
1122
+ const fn = data[idx];
1123
+ fn(refs[refIdx], null);
1124
+ }
1125
+ }
1145
1126
  };
1146
1127
  Block.prototype.patch = function patch(other, withBeforeRemove) {
1147
1128
  if (this === other) {
@@ -1190,16 +1171,23 @@ function createBlockClass(template, ctx) {
1190
1171
  }
1191
1172
  }
1192
1173
  };
1174
+ Block.prototype.remove = function remove() {
1175
+ if (cbRefs.length) {
1176
+ const data = this.data;
1177
+ const refs = this.refs;
1178
+ for (let cbRef of cbRefs) {
1179
+ const { idx, refIdx } = locations[cbRef];
1180
+ const fn = data[idx];
1181
+ fn(null, refs[refIdx]);
1182
+ }
1183
+ }
1184
+ elementRemove.call(this.el);
1185
+ };
1193
1186
  }
1194
1187
  return Block;
1195
1188
  }
1196
1189
  function setText(value) {
1197
1190
  characterDataSetData.call(this, toText(value));
1198
- }
1199
- function makeRefSetter(index, refs) {
1200
- return function setRef(fn) {
1201
- refs[refs.length - 1][index] = () => fn(this);
1202
- };
1203
1191
  }
1204
1192
 
1205
1193
  const getDescriptor = (o, p) => Object.getOwnPropertyDescriptor(o, p);
@@ -1596,9 +1584,160 @@ function remove(vnode, withBeforeRemove = false) {
1596
1584
  vnode.remove();
1597
1585
  }
1598
1586
 
1587
+ var ComputationState;
1588
+ (function (ComputationState) {
1589
+ ComputationState[ComputationState["EXECUTED"] = 0] = "EXECUTED";
1590
+ ComputationState[ComputationState["STALE"] = 1] = "STALE";
1591
+ ComputationState[ComputationState["PENDING"] = 2] = "PENDING";
1592
+ })(ComputationState || (ComputationState = {}));
1593
+ let Effects;
1594
+ let CurrentComputation;
1595
+ // export function computed<T>(fn: () => T, opts?: Opts) {
1596
+ // // todo: handle cleanup
1597
+ // let computedComputation: Computation = {
1598
+ // state: ComputationState.STALE,
1599
+ // sources: new Set(),
1600
+ // isEager: true,
1601
+ // compute: () => {
1602
+ // return fn();
1603
+ // },
1604
+ // value: undefined,
1605
+ // name: opts?.name,
1606
+ // };
1607
+ // updateComputation(computedComputation);
1608
+ // }
1609
+ function onReadAtom(atom) {
1610
+ if (!CurrentComputation)
1611
+ return;
1612
+ CurrentComputation.sources.add(atom);
1613
+ atom.observers.add(CurrentComputation);
1614
+ }
1615
+ function onWriteAtom(atom) {
1616
+ collectEffects(() => {
1617
+ for (const ctx of atom.observers) {
1618
+ if (ctx.state === ComputationState.EXECUTED) {
1619
+ if (ctx.isDerived)
1620
+ markDownstream(ctx);
1621
+ else
1622
+ Effects.push(ctx);
1623
+ }
1624
+ ctx.state = ComputationState.STALE;
1625
+ }
1626
+ });
1627
+ batchProcessEffects();
1628
+ }
1629
+ function collectEffects(fn) {
1630
+ if (Effects)
1631
+ return fn();
1632
+ Effects = [];
1633
+ try {
1634
+ return fn();
1635
+ }
1636
+ finally {
1637
+ }
1638
+ }
1639
+ const batchProcessEffects = batched(processEffects);
1640
+ function processEffects() {
1641
+ if (!Effects)
1642
+ return;
1643
+ for (const computation of Effects) {
1644
+ updateComputation(computation);
1645
+ }
1646
+ Effects = undefined;
1647
+ }
1648
+ function untrack(fn) {
1649
+ return runWithComputation(undefined, fn);
1650
+ }
1651
+ function getCurrentComputation() {
1652
+ return CurrentComputation;
1653
+ }
1654
+ function setComputation(computation) {
1655
+ CurrentComputation = computation;
1656
+ }
1657
+ // todo: should probably use updateComputation instead.
1658
+ function runWithComputation(computation, fn) {
1659
+ const previousComputation = CurrentComputation;
1660
+ CurrentComputation = computation;
1661
+ let result;
1662
+ try {
1663
+ result = fn();
1664
+ }
1665
+ finally {
1666
+ CurrentComputation = previousComputation;
1667
+ }
1668
+ return result;
1669
+ }
1670
+ function updateComputation(computation) {
1671
+ var _a;
1672
+ const state = computation.state;
1673
+ if (computation.isDerived)
1674
+ onReadAtom(computation);
1675
+ if (state === ComputationState.EXECUTED)
1676
+ return;
1677
+ if (state === ComputationState.PENDING) {
1678
+ computeSources(computation);
1679
+ // If the state is still not stale after processing the sources, it means
1680
+ // none of the dependencies have changed.
1681
+ // todo: test it
1682
+ if (computation.state !== ComputationState.STALE) {
1683
+ computation.state = ComputationState.EXECUTED;
1684
+ return;
1685
+ }
1686
+ }
1687
+ // todo: test performance. We might want to avoid removing the atoms to
1688
+ // directly re-add them at compute. Especially as we are making them stale.
1689
+ removeSources(computation);
1690
+ const previousComputation = CurrentComputation;
1691
+ CurrentComputation = computation;
1692
+ computation.value = (_a = computation.compute) === null || _a === void 0 ? void 0 : _a.call(computation);
1693
+ computation.state = ComputationState.EXECUTED;
1694
+ CurrentComputation = previousComputation;
1695
+ }
1696
+ function removeSources(computation) {
1697
+ const sources = computation.sources;
1698
+ for (const source of sources) {
1699
+ const observers = source.observers;
1700
+ observers.delete(computation);
1701
+ // todo: if source has no effect observer anymore, remove its sources too
1702
+ // todo: test it
1703
+ }
1704
+ sources.clear();
1705
+ }
1706
+ function markDownstream(derived) {
1707
+ for (const observer of derived.observers) {
1708
+ // if the state has already been marked, skip it
1709
+ if (observer.state)
1710
+ continue;
1711
+ observer.state = ComputationState.PENDING;
1712
+ if (observer.isDerived)
1713
+ markDownstream(observer);
1714
+ else
1715
+ Effects.push(observer);
1716
+ }
1717
+ }
1718
+ function computeSources(derived) {
1719
+ for (const source of derived.sources) {
1720
+ if (!("compute" in source))
1721
+ continue;
1722
+ updateComputation(source);
1723
+ }
1724
+ }
1725
+
1599
1726
  // Maps fibers to thrown errors
1600
1727
  const fibersInError = new WeakMap();
1601
1728
  const nodeErrorHandlers = new WeakMap();
1729
+ function destroyApp(app, error) {
1730
+ try {
1731
+ app.destroy();
1732
+ }
1733
+ catch (e) {
1734
+ // mute all errors here because we are in a corrupted state anyway
1735
+ }
1736
+ const e = Object.assign(new OwlError(`[Owl] Unhandled error. Destroying the root component`), {
1737
+ cause: error,
1738
+ });
1739
+ return e;
1740
+ }
1602
1741
  function _handleError(node, error) {
1603
1742
  if (!node) {
1604
1743
  return false;
@@ -1611,9 +1750,10 @@ function _handleError(node, error) {
1611
1750
  if (errorHandlers) {
1612
1751
  let handled = false;
1613
1752
  // execute in the opposite order
1753
+ const finalize = () => destroyApp(node.app, error);
1614
1754
  for (let i = errorHandlers.length - 1; i >= 0; i--) {
1615
1755
  try {
1616
- errorHandlers[i](error);
1756
+ errorHandlers[i](error, finalize);
1617
1757
  handled = true;
1618
1758
  break;
1619
1759
  }
@@ -1629,10 +1769,6 @@ function _handleError(node, error) {
1629
1769
  }
1630
1770
  function handleError(params) {
1631
1771
  let { error } = params;
1632
- // Wrap error if it wasn't wrapped by wrapError (ie when not in dev mode)
1633
- if (!(error instanceof OwlError)) {
1634
- error = Object.assign(new OwlError(`An error occured in the owl lifecycle (see this Error's "cause" property)`), { cause: error });
1635
- }
1636
1772
  const node = "node" in params ? params.node : params.fiber.node;
1637
1773
  const fiber = "fiber" in params ? params.fiber : node.fiber;
1638
1774
  if (fiber) {
@@ -1647,14 +1783,7 @@ function handleError(params) {
1647
1783
  }
1648
1784
  const handled = _handleError(node, error);
1649
1785
  if (!handled) {
1650
- console.warn(`[Owl] Unhandled error. Destroying the root component`);
1651
- try {
1652
- node.app.destroy();
1653
- }
1654
- catch (e) {
1655
- console.error(e);
1656
- }
1657
- throw error;
1786
+ throw destroyApp(node.app, error);
1658
1787
  }
1659
1788
  }
1660
1789
 
@@ -1713,7 +1842,7 @@ function cancelFibers(fibers) {
1713
1842
  for (let fiber of fibers) {
1714
1843
  let node = fiber.node;
1715
1844
  fiber.render = throwOnRender;
1716
- if (node.status === 0 /* NEW */) {
1845
+ if (node.status === 0 /* STATUS.NEW */) {
1717
1846
  node.cancel();
1718
1847
  }
1719
1848
  node.fiber = null;
@@ -1779,13 +1908,16 @@ class Fiber {
1779
1908
  const node = this.node;
1780
1909
  const root = this.root;
1781
1910
  if (root) {
1782
- try {
1783
- this.bdom = true;
1784
- this.bdom = node.renderFn();
1785
- }
1786
- catch (e) {
1787
- node.app.handleError({ node, error: e });
1788
- }
1911
+ // todo: should use updateComputation somewhere else.
1912
+ runWithComputation(node.signalComputation, () => {
1913
+ try {
1914
+ this.bdom = true;
1915
+ this.bdom = node.renderFn();
1916
+ }
1917
+ catch (e) {
1918
+ node.app.handleError({ node, error: e });
1919
+ }
1920
+ });
1789
1921
  root.setCounter(root.counter - 1);
1790
1922
  }
1791
1923
  }
@@ -1898,7 +2030,7 @@ class MountFiber extends RootFiber {
1898
2030
  // unregistering the fiber before mounted since it can do another render
1899
2031
  // and that the current rendering is obviously completed
1900
2032
  node.fiber = null;
1901
- node.status = 1 /* MOUNTED */;
2033
+ node.status = 1 /* STATUS.MOUNTED */;
1902
2034
  this.appliedToDom = true;
1903
2035
  let mountedFibers = this.mounted;
1904
2036
  while ((current = mountedFibers.pop())) {
@@ -1915,567 +2047,104 @@ class MountFiber extends RootFiber {
1915
2047
  }
1916
2048
  }
1917
2049
 
1918
- // Special key to subscribe to, to be notified of key creation/deletion
1919
- const KEYCHANGES = Symbol("Key changes");
1920
- // Used to specify the absence of a callback, can be used as WeakMap key but
1921
- // should only be used as a sentinel value and never called.
1922
- const NO_CALLBACK = () => {
1923
- throw new Error("Called NO_CALLBACK. Owl is broken, please report this to the maintainers.");
1924
- };
1925
- const objectToString = Object.prototype.toString;
1926
- const objectHasOwnProperty = Object.prototype.hasOwnProperty;
1927
- // Use arrays because Array.includes is faster than Set.has for small arrays
1928
- const SUPPORTED_RAW_TYPES = ["Object", "Array", "Set", "Map", "WeakMap"];
1929
- const COLLECTION_RAW_TYPES = ["Set", "Map", "WeakMap"];
1930
- /**
1931
- * extract "RawType" from strings like "[object RawType]" => this lets us ignore
1932
- * many native objects such as Promise (whose toString is [object Promise])
1933
- * or Date ([object Date]), while also supporting collections without using
1934
- * instanceof in a loop
1935
- *
1936
- * @param obj the object to check
1937
- * @returns the raw type of the object
1938
- */
1939
- function rawType(obj) {
1940
- return objectToString.call(toRaw(obj)).slice(8, -1);
2050
+ let currentNode = null;
2051
+ function saveCurrent() {
2052
+ let n = currentNode;
2053
+ return () => {
2054
+ currentNode = n;
2055
+ };
1941
2056
  }
1942
- /**
1943
- * Checks whether a given value can be made into a reactive object.
1944
- *
1945
- * @param value the value to check
1946
- * @returns whether the value can be made reactive
1947
- */
1948
- function canBeMadeReactive(value) {
1949
- if (typeof value !== "object") {
1950
- return false;
2057
+ function getCurrent() {
2058
+ if (!currentNode) {
2059
+ throw new OwlError("No active component (a hook function should only be called in 'setup')");
1951
2060
  }
1952
- return SUPPORTED_RAW_TYPES.includes(rawType(value));
1953
- }
1954
- /**
1955
- * Creates a reactive from the given object/callback if possible and returns it,
1956
- * returns the original object otherwise.
1957
- *
1958
- * @param value the value make reactive
1959
- * @returns a reactive for the given object when possible, the original otherwise
1960
- */
1961
- function possiblyReactive(val, cb) {
1962
- return canBeMadeReactive(val) ? reactive(val, cb) : val;
1963
- }
1964
- const skipped = new WeakSet();
1965
- /**
1966
- * Mark an object or array so that it is ignored by the reactivity system
1967
- *
1968
- * @param value the value to mark
1969
- * @returns the object itself
1970
- */
1971
- function markRaw(value) {
1972
- skipped.add(value);
1973
- return value;
1974
- }
1975
- /**
1976
- * Given a reactive objet, return the raw (non reactive) underlying object
1977
- *
1978
- * @param value a reactive value
1979
- * @returns the underlying value
1980
- */
1981
- function toRaw(value) {
1982
- return targets.has(value) ? targets.get(value) : value;
2061
+ return currentNode;
1983
2062
  }
1984
- const targetToKeysToCallbacks = new WeakMap();
1985
- /**
1986
- * Observes a given key on a target with an callback. The callback will be
1987
- * called when the given key changes on the target.
1988
- *
1989
- * @param target the target whose key should be observed
1990
- * @param key the key to observe (or Symbol(KEYCHANGES) for key creation
1991
- * or deletion)
1992
- * @param callback the function to call when the key changes
1993
- */
1994
- function observeTargetKey(target, key, callback) {
1995
- if (callback === NO_CALLBACK) {
1996
- return;
1997
- }
1998
- if (!targetToKeysToCallbacks.get(target)) {
1999
- targetToKeysToCallbacks.set(target, new Map());
2000
- }
2001
- const keyToCallbacks = targetToKeysToCallbacks.get(target);
2002
- if (!keyToCallbacks.get(key)) {
2003
- keyToCallbacks.set(key, new Set());
2004
- }
2005
- keyToCallbacks.get(key).add(callback);
2006
- if (!callbacksToTargets.has(callback)) {
2007
- callbacksToTargets.set(callback, new Set());
2008
- }
2009
- callbacksToTargets.get(callback).add(target);
2063
+ function useComponent() {
2064
+ return currentNode.component;
2010
2065
  }
2011
- /**
2012
- * Notify Reactives that are observing a given target that a key has changed on
2013
- * the target.
2014
- *
2015
- * @param target target whose Reactives should be notified that the target was
2016
- * changed.
2017
- * @param key the key that changed (or Symbol `KEYCHANGES` if a key was created
2018
- * or deleted)
2019
- */
2020
- function notifyReactives(target, key) {
2021
- const keyToCallbacks = targetToKeysToCallbacks.get(target);
2022
- if (!keyToCallbacks) {
2023
- return;
2024
- }
2025
- const callbacks = keyToCallbacks.get(key);
2026
- if (!callbacks) {
2027
- return;
2028
- }
2029
- // Loop on copy because clearReactivesForCallback will modify the set in place
2030
- for (const callback of [...callbacks]) {
2031
- clearReactivesForCallback(callback);
2032
- callback();
2066
+ class ComponentNode {
2067
+ constructor(C, props, app, parent, parentKey) {
2068
+ this.fiber = null;
2069
+ this.bdom = null;
2070
+ this.status = 0 /* STATUS.NEW */;
2071
+ this.forceNextRender = false;
2072
+ this.children = Object.create(null);
2073
+ this.willStart = [];
2074
+ this.willUpdateProps = [];
2075
+ this.willUnmount = [];
2076
+ this.mounted = [];
2077
+ this.willPatch = [];
2078
+ this.patched = [];
2079
+ this.willDestroy = [];
2080
+ this.name = C.name;
2081
+ currentNode = this;
2082
+ this.app = app;
2083
+ this.parent = parent;
2084
+ this.parentKey = parentKey;
2085
+ this.pluginManager = parent ? parent.pluginManager : app.pluginManager;
2086
+ this.signalComputation = {
2087
+ value: undefined,
2088
+ compute: () => this.render(false),
2089
+ sources: new Set(),
2090
+ state: ComputationState.EXECUTED,
2091
+ };
2092
+ this.props = Object.assign({}, props);
2093
+ const previousComputation = getCurrentComputation();
2094
+ setComputation(this.signalComputation);
2095
+ this.component = new C(this);
2096
+ const ctx = { this: this.component, __owl__: this };
2097
+ this.renderFn = app.getTemplate(C.template).bind(this.component, ctx, this);
2098
+ this.component.setup();
2099
+ setComputation(previousComputation);
2100
+ currentNode = null;
2033
2101
  }
2034
- }
2035
- const callbacksToTargets = new WeakMap();
2036
- /**
2037
- * Clears all subscriptions of the Reactives associated with a given callback.
2038
- *
2039
- * @param callback the callback for which the reactives need to be cleared
2040
- */
2041
- function clearReactivesForCallback(callback) {
2042
- const targetsToClear = callbacksToTargets.get(callback);
2043
- if (!targetsToClear) {
2044
- return;
2102
+ mountComponent(target, options) {
2103
+ const fiber = new MountFiber(this, target, options);
2104
+ this.app.scheduler.addFiber(fiber);
2105
+ this.initiateRender(fiber);
2045
2106
  }
2046
- for (const target of targetsToClear) {
2047
- const observedKeys = targetToKeysToCallbacks.get(target);
2048
- if (!observedKeys) {
2049
- continue;
2107
+ async initiateRender(fiber) {
2108
+ this.fiber = fiber;
2109
+ if (this.mounted.length) {
2110
+ fiber.root.mounted.push(fiber);
2050
2111
  }
2051
- for (const [key, callbacks] of observedKeys.entries()) {
2052
- callbacks.delete(callback);
2053
- if (!callbacks.size) {
2054
- observedKeys.delete(key);
2055
- }
2112
+ const component = this.component;
2113
+ try {
2114
+ let promises;
2115
+ runWithComputation(undefined, () => {
2116
+ promises = this.willStart.map((f) => f.call(component));
2117
+ });
2118
+ await Promise.all(promises);
2119
+ }
2120
+ catch (e) {
2121
+ this.app.handleError({ node: this, error: e });
2122
+ return;
2123
+ }
2124
+ if (this.status === 0 /* STATUS.NEW */ && this.fiber === fiber) {
2125
+ fiber.render();
2056
2126
  }
2057
2127
  }
2058
- targetsToClear.clear();
2059
- }
2060
- function getSubscriptions(callback) {
2061
- const targets = callbacksToTargets.get(callback) || [];
2062
- return [...targets].map((target) => {
2063
- const keysToCallbacks = targetToKeysToCallbacks.get(target);
2064
- let keys = [];
2065
- if (keysToCallbacks) {
2066
- for (const [key, cbs] of keysToCallbacks) {
2067
- if (cbs.has(callback)) {
2068
- keys.push(key);
2128
+ async render(deep) {
2129
+ if (this.status >= 2 /* STATUS.CANCELLED */) {
2130
+ return;
2131
+ }
2132
+ let current = this.fiber;
2133
+ if (current && (current.root.locked || current.bdom === true)) {
2134
+ await Promise.resolve();
2135
+ // situation may have changed after the microtask tick
2136
+ current = this.fiber;
2137
+ }
2138
+ if (current) {
2139
+ if (!current.bdom && !fibersInError.has(current)) {
2140
+ if (deep) {
2141
+ // we want the render from this point on to be with deep=true
2142
+ current.deep = deep;
2069
2143
  }
2144
+ return;
2070
2145
  }
2071
- }
2072
- return { target, keys };
2073
- });
2074
- }
2075
- // Maps reactive objects to the underlying target
2076
- const targets = new WeakMap();
2077
- const reactiveCache = new WeakMap();
2078
- /**
2079
- * Creates a reactive proxy for an object. Reading data on the reactive object
2080
- * subscribes to changes to the data. Writing data on the object will cause the
2081
- * notify callback to be called if there are suscriptions to that data. Nested
2082
- * objects and arrays are automatically made reactive as well.
2083
- *
2084
- * Whenever you are notified of a change, all subscriptions are cleared, and if
2085
- * you would like to be notified of any further changes, you should go read
2086
- * the underlying data again. We assume that if you don't go read it again after
2087
- * being notified, it means that you are no longer interested in that data.
2088
- *
2089
- * Subscriptions:
2090
- * + Reading a property on an object will subscribe you to changes in the value
2091
- * of that property.
2092
- * + Accessing an object's keys (eg with Object.keys or with `for..in`) will
2093
- * subscribe you to the creation/deletion of keys. Checking the presence of a
2094
- * key on the object with 'in' has the same effect.
2095
- * - getOwnPropertyDescriptor does not currently subscribe you to the property.
2096
- * This is a choice that was made because changing a key's value will trigger
2097
- * this trap and we do not want to subscribe by writes. This also means that
2098
- * Object.hasOwnProperty doesn't subscribe as it goes through this trap.
2099
- *
2100
- * @param target the object for which to create a reactive proxy
2101
- * @param callback the function to call when an observed property of the
2102
- * reactive has changed
2103
- * @returns a proxy that tracks changes to it
2104
- */
2105
- function reactive(target, callback = NO_CALLBACK) {
2106
- if (!canBeMadeReactive(target)) {
2107
- throw new OwlError(`Cannot make the given value reactive`);
2108
- }
2109
- if (skipped.has(target)) {
2110
- return target;
2111
- }
2112
- if (targets.has(target)) {
2113
- // target is reactive, create a reactive on the underlying object instead
2114
- return reactive(targets.get(target), callback);
2115
- }
2116
- if (!reactiveCache.has(target)) {
2117
- reactiveCache.set(target, new WeakMap());
2118
- }
2119
- const reactivesForTarget = reactiveCache.get(target);
2120
- if (!reactivesForTarget.has(callback)) {
2121
- const targetRawType = rawType(target);
2122
- const handler = COLLECTION_RAW_TYPES.includes(targetRawType)
2123
- ? collectionsProxyHandler(target, callback, targetRawType)
2124
- : basicProxyHandler(callback);
2125
- const proxy = new Proxy(target, handler);
2126
- reactivesForTarget.set(callback, proxy);
2127
- targets.set(proxy, target);
2128
- }
2129
- return reactivesForTarget.get(callback);
2130
- }
2131
- /**
2132
- * Creates a basic proxy handler for regular objects and arrays.
2133
- *
2134
- * @param callback @see reactive
2135
- * @returns a proxy handler object
2136
- */
2137
- function basicProxyHandler(callback) {
2138
- return {
2139
- get(target, key, receiver) {
2140
- // non-writable non-configurable properties cannot be made reactive
2141
- const desc = Object.getOwnPropertyDescriptor(target, key);
2142
- if (desc && !desc.writable && !desc.configurable) {
2143
- return Reflect.get(target, key, receiver);
2144
- }
2145
- observeTargetKey(target, key, callback);
2146
- return possiblyReactive(Reflect.get(target, key, receiver), callback);
2147
- },
2148
- set(target, key, value, receiver) {
2149
- const hadKey = objectHasOwnProperty.call(target, key);
2150
- const originalValue = Reflect.get(target, key, receiver);
2151
- const ret = Reflect.set(target, key, toRaw(value), receiver);
2152
- if (!hadKey && objectHasOwnProperty.call(target, key)) {
2153
- notifyReactives(target, KEYCHANGES);
2154
- }
2155
- // While Array length may trigger the set trap, it's not actually set by this
2156
- // method but is updated behind the scenes, and the trap is not called with the
2157
- // new value. We disable the "same-value-optimization" for it because of that.
2158
- if (originalValue !== Reflect.get(target, key, receiver) ||
2159
- (key === "length" && Array.isArray(target))) {
2160
- notifyReactives(target, key);
2161
- }
2162
- return ret;
2163
- },
2164
- deleteProperty(target, key) {
2165
- const ret = Reflect.deleteProperty(target, key);
2166
- // TODO: only notify when something was actually deleted
2167
- notifyReactives(target, KEYCHANGES);
2168
- notifyReactives(target, key);
2169
- return ret;
2170
- },
2171
- ownKeys(target) {
2172
- observeTargetKey(target, KEYCHANGES, callback);
2173
- return Reflect.ownKeys(target);
2174
- },
2175
- has(target, key) {
2176
- // TODO: this observes all key changes instead of only the presence of the argument key
2177
- // observing the key itself would observe value changes instead of presence changes
2178
- // so we may need a finer grained system to distinguish observing value vs presence.
2179
- observeTargetKey(target, KEYCHANGES, callback);
2180
- return Reflect.has(target, key);
2181
- },
2182
- };
2183
- }
2184
- /**
2185
- * Creates a function that will observe the key that is passed to it when called
2186
- * and delegates to the underlying method.
2187
- *
2188
- * @param methodName name of the method to delegate to
2189
- * @param target @see reactive
2190
- * @param callback @see reactive
2191
- */
2192
- function makeKeyObserver(methodName, target, callback) {
2193
- return (key) => {
2194
- key = toRaw(key);
2195
- observeTargetKey(target, key, callback);
2196
- return possiblyReactive(target[methodName](key), callback);
2197
- };
2198
- }
2199
- /**
2200
- * Creates an iterable that will delegate to the underlying iteration method and
2201
- * observe keys as necessary.
2202
- *
2203
- * @param methodName name of the method to delegate to
2204
- * @param target @see reactive
2205
- * @param callback @see reactive
2206
- */
2207
- function makeIteratorObserver(methodName, target, callback) {
2208
- return function* () {
2209
- observeTargetKey(target, KEYCHANGES, callback);
2210
- const keys = target.keys();
2211
- for (const item of target[methodName]()) {
2212
- const key = keys.next().value;
2213
- observeTargetKey(target, key, callback);
2214
- yield possiblyReactive(item, callback);
2215
- }
2216
- };
2217
- }
2218
- /**
2219
- * Creates a forEach function that will delegate to forEach on the underlying
2220
- * collection while observing key changes, and keys as they're iterated over,
2221
- * and making the passed keys/values reactive.
2222
- *
2223
- * @param target @see reactive
2224
- * @param callback @see reactive
2225
- */
2226
- function makeForEachObserver(target, callback) {
2227
- return function forEach(forEachCb, thisArg) {
2228
- observeTargetKey(target, KEYCHANGES, callback);
2229
- target.forEach(function (val, key, targetObj) {
2230
- observeTargetKey(target, key, callback);
2231
- forEachCb.call(thisArg, possiblyReactive(val, callback), possiblyReactive(key, callback), possiblyReactive(targetObj, callback));
2232
- }, thisArg);
2233
- };
2234
- }
2235
- /**
2236
- * Creates a function that will delegate to an underlying method, and check if
2237
- * that method has modified the presence or value of a key, and notify the
2238
- * reactives appropriately.
2239
- *
2240
- * @param setterName name of the method to delegate to
2241
- * @param getterName name of the method which should be used to retrieve the
2242
- * value before calling the delegate method for comparison purposes
2243
- * @param target @see reactive
2244
- */
2245
- function delegateAndNotify(setterName, getterName, target) {
2246
- return (key, value) => {
2247
- key = toRaw(key);
2248
- const hadKey = target.has(key);
2249
- const originalValue = target[getterName](key);
2250
- const ret = target[setterName](key, value);
2251
- const hasKey = target.has(key);
2252
- if (hadKey !== hasKey) {
2253
- notifyReactives(target, KEYCHANGES);
2254
- }
2255
- if (originalValue !== target[getterName](key)) {
2256
- notifyReactives(target, key);
2257
- }
2258
- return ret;
2259
- };
2260
- }
2261
- /**
2262
- * Creates a function that will clear the underlying collection and notify that
2263
- * the keys of the collection have changed.
2264
- *
2265
- * @param target @see reactive
2266
- */
2267
- function makeClearNotifier(target) {
2268
- return () => {
2269
- const allKeys = [...target.keys()];
2270
- target.clear();
2271
- notifyReactives(target, KEYCHANGES);
2272
- for (const key of allKeys) {
2273
- notifyReactives(target, key);
2274
- }
2275
- };
2276
- }
2277
- /**
2278
- * Maps raw type of an object to an object containing functions that can be used
2279
- * to build an appropritate proxy handler for that raw type. Eg: when making a
2280
- * reactive set, calling the has method should mark the key that is being
2281
- * retrieved as observed, and calling the add or delete method should notify the
2282
- * reactives that the key which is being added or deleted has been modified.
2283
- */
2284
- const rawTypeToFuncHandlers = {
2285
- Set: (target, callback) => ({
2286
- has: makeKeyObserver("has", target, callback),
2287
- add: delegateAndNotify("add", "has", target),
2288
- delete: delegateAndNotify("delete", "has", target),
2289
- keys: makeIteratorObserver("keys", target, callback),
2290
- values: makeIteratorObserver("values", target, callback),
2291
- entries: makeIteratorObserver("entries", target, callback),
2292
- [Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
2293
- forEach: makeForEachObserver(target, callback),
2294
- clear: makeClearNotifier(target),
2295
- get size() {
2296
- observeTargetKey(target, KEYCHANGES, callback);
2297
- return target.size;
2298
- },
2299
- }),
2300
- Map: (target, callback) => ({
2301
- has: makeKeyObserver("has", target, callback),
2302
- get: makeKeyObserver("get", target, callback),
2303
- set: delegateAndNotify("set", "get", target),
2304
- delete: delegateAndNotify("delete", "has", target),
2305
- keys: makeIteratorObserver("keys", target, callback),
2306
- values: makeIteratorObserver("values", target, callback),
2307
- entries: makeIteratorObserver("entries", target, callback),
2308
- [Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
2309
- forEach: makeForEachObserver(target, callback),
2310
- clear: makeClearNotifier(target),
2311
- get size() {
2312
- observeTargetKey(target, KEYCHANGES, callback);
2313
- return target.size;
2314
- },
2315
- }),
2316
- WeakMap: (target, callback) => ({
2317
- has: makeKeyObserver("has", target, callback),
2318
- get: makeKeyObserver("get", target, callback),
2319
- set: delegateAndNotify("set", "get", target),
2320
- delete: delegateAndNotify("delete", "has", target),
2321
- }),
2322
- };
2323
- /**
2324
- * Creates a proxy handler for collections (Set/Map/WeakMap)
2325
- *
2326
- * @param callback @see reactive
2327
- * @param target @see reactive
2328
- * @returns a proxy handler object
2329
- */
2330
- function collectionsProxyHandler(target, callback, targetRawType) {
2331
- // TODO: if performance is an issue we can create the special handlers lazily when each
2332
- // property is read.
2333
- const specialHandlers = rawTypeToFuncHandlers[targetRawType](target, callback);
2334
- return Object.assign(basicProxyHandler(callback), {
2335
- // FIXME: probably broken when part of prototype chain since we ignore the receiver
2336
- get(target, key) {
2337
- if (objectHasOwnProperty.call(specialHandlers, key)) {
2338
- return specialHandlers[key];
2339
- }
2340
- observeTargetKey(target, key, callback);
2341
- return possiblyReactive(target[key], callback);
2342
- },
2343
- });
2344
- }
2345
-
2346
- let currentNode = null;
2347
- function saveCurrent() {
2348
- let n = currentNode;
2349
- return () => {
2350
- currentNode = n;
2351
- };
2352
- }
2353
- function getCurrent() {
2354
- if (!currentNode) {
2355
- throw new OwlError("No active component (a hook function should only be called in 'setup')");
2356
- }
2357
- return currentNode;
2358
- }
2359
- function useComponent() {
2360
- return currentNode.component;
2361
- }
2362
- /**
2363
- * Apply default props (only top level).
2364
- */
2365
- function applyDefaultProps(props, defaultProps) {
2366
- for (let propName in defaultProps) {
2367
- if (props[propName] === undefined) {
2368
- props[propName] = defaultProps[propName];
2369
- }
2370
- }
2371
- }
2372
- // -----------------------------------------------------------------------------
2373
- // Integration with reactivity system (useState)
2374
- // -----------------------------------------------------------------------------
2375
- const batchedRenderFunctions = new WeakMap();
2376
- /**
2377
- * Creates a reactive object that will be observed by the current component.
2378
- * Reading data from the returned object (eg during rendering) will cause the
2379
- * component to subscribe to that data and be rerendered when it changes.
2380
- *
2381
- * @param state the state to observe
2382
- * @returns a reactive object that will cause the component to re-render on
2383
- * relevant changes
2384
- * @see reactive
2385
- */
2386
- function useState(state) {
2387
- const node = getCurrent();
2388
- let render = batchedRenderFunctions.get(node);
2389
- if (!render) {
2390
- render = batched(node.render.bind(node, false));
2391
- batchedRenderFunctions.set(node, render);
2392
- // manual implementation of onWillDestroy to break cyclic dependency
2393
- node.willDestroy.push(clearReactivesForCallback.bind(null, render));
2394
- }
2395
- return reactive(state, render);
2396
- }
2397
- class ComponentNode {
2398
- constructor(C, props, app, parent, parentKey) {
2399
- this.fiber = null;
2400
- this.bdom = null;
2401
- this.status = 0 /* NEW */;
2402
- this.forceNextRender = false;
2403
- this.nextProps = null;
2404
- this.children = Object.create(null);
2405
- this.refs = {};
2406
- this.willStart = [];
2407
- this.willUpdateProps = [];
2408
- this.willUnmount = [];
2409
- this.mounted = [];
2410
- this.willPatch = [];
2411
- this.patched = [];
2412
- this.willDestroy = [];
2413
- currentNode = this;
2414
- this.app = app;
2415
- this.parent = parent;
2416
- this.props = props;
2417
- this.parentKey = parentKey;
2418
- const defaultProps = C.defaultProps;
2419
- props = Object.assign({}, props);
2420
- if (defaultProps) {
2421
- applyDefaultProps(props, defaultProps);
2422
- }
2423
- const env = (parent && parent.childEnv) || app.env;
2424
- this.childEnv = env;
2425
- for (const key in props) {
2426
- const prop = props[key];
2427
- if (prop && typeof prop === "object" && targets.has(prop)) {
2428
- props[key] = useState(prop);
2429
- }
2430
- }
2431
- this.component = new C(props, env, this);
2432
- const ctx = Object.assign(Object.create(this.component), { this: this.component });
2433
- this.renderFn = app.getTemplate(C.template).bind(this.component, ctx, this);
2434
- this.component.setup();
2435
- currentNode = null;
2436
- }
2437
- mountComponent(target, options) {
2438
- const fiber = new MountFiber(this, target, options);
2439
- this.app.scheduler.addFiber(fiber);
2440
- this.initiateRender(fiber);
2441
- }
2442
- async initiateRender(fiber) {
2443
- this.fiber = fiber;
2444
- if (this.mounted.length) {
2445
- fiber.root.mounted.push(fiber);
2446
- }
2447
- const component = this.component;
2448
- try {
2449
- await Promise.all(this.willStart.map((f) => f.call(component)));
2450
- }
2451
- catch (e) {
2452
- this.app.handleError({ node: this, error: e });
2453
- return;
2454
- }
2455
- if (this.status === 0 /* NEW */ && this.fiber === fiber) {
2456
- fiber.render();
2457
- }
2458
- }
2459
- async render(deep) {
2460
- if (this.status >= 2 /* CANCELLED */) {
2461
- return;
2462
- }
2463
- let current = this.fiber;
2464
- if (current && (current.root.locked || current.bdom === true)) {
2465
- await Promise.resolve();
2466
- // situation may have changed after the microtask tick
2467
- current = this.fiber;
2468
- }
2469
- if (current) {
2470
- if (!current.bdom && !fibersInError.has(current)) {
2471
- if (deep) {
2472
- // we want the render from this point on to be with deep=true
2473
- current.deep = deep;
2474
- }
2475
- return;
2476
- }
2477
- // if current rendering was with deep=true, we want this one to be the same
2478
- deep = deep || current.deep;
2146
+ // if current rendering was with deep=true, we want this one to be the same
2147
+ deep = deep || current.deep;
2479
2148
  }
2480
2149
  else if (!this.bdom) {
2481
2150
  return;
@@ -2485,7 +2154,7 @@ class ComponentNode {
2485
2154
  this.fiber = fiber;
2486
2155
  this.app.scheduler.addFiber(fiber);
2487
2156
  await Promise.resolve();
2488
- if (this.status >= 2 /* CANCELLED */) {
2157
+ if (this.status >= 2 /* STATUS.CANCELLED */) {
2489
2158
  return;
2490
2159
  }
2491
2160
  // We only want to actually render the component if the following two
@@ -2509,14 +2178,14 @@ class ComponentNode {
2509
2178
  this.app.scheduler.scheduleDestroy(this);
2510
2179
  }
2511
2180
  _cancel() {
2512
- this.status = 2 /* CANCELLED */;
2181
+ this.status = 2 /* STATUS.CANCELLED */;
2513
2182
  const children = this.children;
2514
2183
  for (let childKey in children) {
2515
2184
  children[childKey]._cancel();
2516
2185
  }
2517
2186
  }
2518
2187
  destroy() {
2519
- let shouldRemove = this.status === 1 /* MOUNTED */;
2188
+ let shouldRemove = this.status === 1 /* STATUS.MOUNTED */;
2520
2189
  this._destroy();
2521
2190
  if (shouldRemove) {
2522
2191
  this.bdom.remove();
@@ -2524,7 +2193,7 @@ class ComponentNode {
2524
2193
  }
2525
2194
  _destroy() {
2526
2195
  const component = this.component;
2527
- if (this.status === 1 /* MOUNTED */) {
2196
+ if (this.status === 1 /* STATUS.MOUNTED */) {
2528
2197
  for (let cb of this.willUnmount) {
2529
2198
  cb.call(component);
2530
2199
  }
@@ -2542,33 +2211,23 @@ class ComponentNode {
2542
2211
  this.app.handleError({ error: e, node: this });
2543
2212
  }
2544
2213
  }
2545
- this.status = 3 /* DESTROYED */;
2214
+ this.status = 3 /* STATUS.DESTROYED */;
2546
2215
  }
2547
2216
  async updateAndRender(props, parentFiber) {
2548
- this.nextProps = props;
2549
2217
  props = Object.assign({}, props);
2550
2218
  // update
2551
2219
  const fiber = makeChildFiber(this, parentFiber);
2552
2220
  this.fiber = fiber;
2553
2221
  const component = this.component;
2554
- const defaultProps = component.constructor.defaultProps;
2555
- if (defaultProps) {
2556
- applyDefaultProps(props, defaultProps);
2557
- }
2558
- currentNode = this;
2559
- for (const key in props) {
2560
- const prop = props[key];
2561
- if (prop && typeof prop === "object" && targets.has(prop)) {
2562
- props[key] = useState(prop);
2563
- }
2564
- }
2565
- currentNode = null;
2566
- const prom = Promise.all(this.willUpdateProps.map((f) => f.call(component, props)));
2222
+ let prom;
2223
+ untrack(() => {
2224
+ prom = Promise.all(this.willUpdateProps.map((f) => f.call(component, props)));
2225
+ });
2567
2226
  await prom;
2568
2227
  if (fiber !== this.fiber) {
2569
2228
  return;
2570
2229
  }
2571
- component.props = props;
2230
+ this.props = props;
2572
2231
  fiber.render();
2573
2232
  const parentRoot = parentFiber.root;
2574
2233
  if (this.willPatch.length) {
@@ -2603,18 +2262,6 @@ class ComponentNode {
2603
2262
  this.fiber = null;
2604
2263
  }
2605
2264
  }
2606
- /**
2607
- * Sets a ref to a given HTMLElement.
2608
- *
2609
- * @param name the name of the ref to set
2610
- * @param el the HTMLElement to set the ref to. The ref is not set if the el
2611
- * is null, but useRef will not return elements that are not in the DOM
2612
- */
2613
- setRef(name, el) {
2614
- if (el) {
2615
- this.refs[name] = el;
2616
- }
2617
- }
2618
2265
  // ---------------------------------------------------------------------------
2619
2266
  // Block DOM methods
2620
2267
  // ---------------------------------------------------------------------------
@@ -2626,7 +2273,7 @@ class ComponentNode {
2626
2273
  const bdom = this.fiber.bdom;
2627
2274
  this.bdom = bdom;
2628
2275
  bdom.mount(parent, anchor);
2629
- this.status = 1 /* MOUNTED */;
2276
+ this.status = 1 /* STATUS.MOUNTED */;
2630
2277
  this.fiber.appliedToDom = true;
2631
2278
  this.children = this.fiber.childrenMap;
2632
2279
  this.fiber = null;
@@ -2643,7 +2290,6 @@ class ComponentNode {
2643
2290
  // by the component will be patched independently in the appropriate
2644
2291
  // fiber.complete
2645
2292
  this._patch();
2646
- this.props = this.nextProps;
2647
2293
  }
2648
2294
  }
2649
2295
  _patch() {
@@ -2665,120 +2311,130 @@ class ComponentNode {
2665
2311
  remove() {
2666
2312
  this.bdom.remove();
2667
2313
  }
2668
- // ---------------------------------------------------------------------------
2669
- // Some debug helpers
2670
- // ---------------------------------------------------------------------------
2671
- get name() {
2672
- return this.component.constructor.name;
2673
- }
2674
- get subscriptions() {
2675
- const render = batchedRenderFunctions.get(this);
2676
- return render ? getSubscriptions(render) : [];
2677
- }
2678
2314
  }
2679
2315
 
2680
- const TIMEOUT = Symbol("timeout");
2681
- const HOOK_TIMEOUT = {
2682
- onWillStart: 3000,
2683
- onWillUpdateProps: 3000,
2684
- };
2685
- function wrapError(fn, hookName) {
2686
- const error = new OwlError();
2687
- const timeoutError = new OwlError();
2688
- const node = getCurrent();
2689
- return (...args) => {
2690
- const onError = (cause) => {
2691
- error.cause = cause;
2692
- error.message =
2693
- cause instanceof Error
2694
- ? `The following error occurred in ${hookName}: "${cause.message}"`
2695
- : `Something that is not an Error was thrown in ${hookName} (see this Error's "cause" property)`;
2696
- throw error;
2697
- };
2698
- let result;
2699
- try {
2700
- result = fn(...args);
2316
+ let currentPluginManager = null;
2317
+ const _getCurrentPluginManager = () => currentPluginManager;
2318
+ class Plugin {
2319
+ setup() { }
2320
+ }
2321
+ Plugin.id = "";
2322
+ class PluginManager {
2323
+ constructor(parent) {
2324
+ var _a;
2325
+ this.children = [];
2326
+ this.onDestroyCb = [];
2327
+ this.status = 0 /* STATUS.NEW */;
2328
+ this.parent = parent;
2329
+ (_a = this.parent) === null || _a === void 0 ? void 0 : _a.children.push(this);
2330
+ this.plugins = this.parent ? Object.create(this.parent.plugins) : {};
2331
+ }
2332
+ destroy() {
2333
+ for (let children of this.children) {
2334
+ children.destroy();
2701
2335
  }
2702
- catch (cause) {
2703
- onError(cause);
2336
+ const cbs = this.onDestroyCb;
2337
+ while (cbs.length) {
2338
+ cbs.pop()();
2704
2339
  }
2705
- if (!(result instanceof Promise)) {
2706
- return result;
2340
+ this.status = 3 /* STATUS.DESTROYED */;
2341
+ }
2342
+ getPluginById(id) {
2343
+ return this.plugins[id] || null;
2344
+ }
2345
+ getPlugin(pluginType) {
2346
+ return this.getPluginById(pluginType.id);
2347
+ }
2348
+ startPlugins(pluginTypes) {
2349
+ const previousManager = currentPluginManager;
2350
+ currentPluginManager = this;
2351
+ const plugins = [];
2352
+ // instantiate plugins
2353
+ for (const pluginType of pluginTypes) {
2354
+ if (!pluginType.id) {
2355
+ currentPluginManager = previousManager;
2356
+ throw new OwlError(`Plugin "${pluginType.name}" has no id`);
2357
+ }
2358
+ if (this.plugins.hasOwnProperty(pluginType.id)) {
2359
+ continue;
2360
+ }
2361
+ const plugin = new pluginType();
2362
+ this.plugins[pluginType.id] = plugin;
2363
+ plugins.push(plugin);
2707
2364
  }
2708
- const timeout = HOOK_TIMEOUT[hookName];
2709
- if (timeout) {
2710
- const fiber = node.fiber;
2711
- Promise.race([
2712
- result.catch(() => { }),
2713
- new Promise((resolve) => setTimeout(() => resolve(TIMEOUT), timeout)),
2714
- ]).then((res) => {
2715
- if (res === TIMEOUT && node.fiber === fiber && node.status <= 2) {
2716
- timeoutError.message = `${hookName}'s promise hasn't resolved after ${timeout / 1000} seconds`;
2717
- console.log(timeoutError);
2718
- }
2719
- });
2365
+ // setup phase
2366
+ for (let p of plugins) {
2367
+ p.setup();
2720
2368
  }
2721
- return result.catch(onError);
2722
- };
2369
+ currentPluginManager = previousManager;
2370
+ if (!currentPluginManager) {
2371
+ this.status = 1 /* STATUS.MOUNTED */;
2372
+ }
2373
+ return plugins;
2374
+ }
2723
2375
  }
2376
+ function plugin(pluginType) {
2377
+ // getCurrent will throw if we're not in a component
2378
+ const manager = currentPluginManager || getCurrent().pluginManager;
2379
+ let plugin = manager.getPluginById(pluginType.id);
2380
+ if (!plugin) {
2381
+ if (manager === currentPluginManager) {
2382
+ manager.startPlugins([pluginType]);
2383
+ plugin = manager.getPluginById(pluginType.id);
2384
+ }
2385
+ else {
2386
+ throw new OwlError(`Unknown plugin "${pluginType.id}"`);
2387
+ }
2388
+ }
2389
+ return plugin;
2390
+ }
2391
+
2724
2392
  // -----------------------------------------------------------------------------
2725
2393
  // hooks
2726
2394
  // -----------------------------------------------------------------------------
2395
+ function decorate(node, f, hookName) {
2396
+ const result = f.bind(node.component);
2397
+ if (node.app.dev) {
2398
+ const suffix = f.name ? ` <${f.name}>` : "";
2399
+ Reflect.defineProperty(result, "name", {
2400
+ value: hookName + suffix,
2401
+ });
2402
+ }
2403
+ return result;
2404
+ }
2727
2405
  function onWillStart(fn) {
2728
2406
  const node = getCurrent();
2729
- const decorate = node.app.dev ? wrapError : (fn) => fn;
2730
- node.willStart.push(decorate(fn.bind(node.component), "onWillStart"));
2407
+ node.willStart.push(decorate(node, fn, "onWillStart"));
2731
2408
  }
2732
2409
  function onWillUpdateProps(fn) {
2733
2410
  const node = getCurrent();
2734
- const decorate = node.app.dev ? wrapError : (fn) => fn;
2735
- node.willUpdateProps.push(decorate(fn.bind(node.component), "onWillUpdateProps"));
2411
+ node.willUpdateProps.push(decorate(node, fn, "onWillUpdateProps"));
2736
2412
  }
2737
2413
  function onMounted(fn) {
2738
2414
  const node = getCurrent();
2739
- const decorate = node.app.dev ? wrapError : (fn) => fn;
2740
- node.mounted.push(decorate(fn.bind(node.component), "onMounted"));
2415
+ node.mounted.push(decorate(node, fn, "onMounted"));
2741
2416
  }
2742
2417
  function onWillPatch(fn) {
2743
2418
  const node = getCurrent();
2744
- const decorate = node.app.dev ? wrapError : (fn) => fn;
2745
- node.willPatch.unshift(decorate(fn.bind(node.component), "onWillPatch"));
2419
+ node.willPatch.unshift(decorate(node, fn, "onWillPatch"));
2746
2420
  }
2747
2421
  function onPatched(fn) {
2748
2422
  const node = getCurrent();
2749
- const decorate = node.app.dev ? wrapError : (fn) => fn;
2750
- node.patched.push(decorate(fn.bind(node.component), "onPatched"));
2423
+ node.patched.push(decorate(node, fn, "onPatched"));
2751
2424
  }
2752
2425
  function onWillUnmount(fn) {
2753
2426
  const node = getCurrent();
2754
- const decorate = node.app.dev ? wrapError : (fn) => fn;
2755
- node.willUnmount.unshift(decorate(fn.bind(node.component), "onWillUnmount"));
2427
+ node.willUnmount.unshift(decorate(node, fn, "onWillUnmount"));
2756
2428
  }
2757
2429
  function onWillDestroy(fn) {
2758
- const node = getCurrent();
2759
- const decorate = node.app.dev ? wrapError : (fn) => fn;
2760
- node.willDestroy.push(decorate(fn.bind(node.component), "onWillDestroy"));
2761
- }
2762
- function onWillRender(fn) {
2763
- const node = getCurrent();
2764
- const renderFn = node.renderFn;
2765
- const decorate = node.app.dev ? wrapError : (fn) => fn;
2766
- fn = decorate(fn.bind(node.component), "onWillRender");
2767
- node.renderFn = () => {
2768
- fn();
2769
- return renderFn();
2770
- };
2771
- }
2772
- function onRendered(fn) {
2773
- const node = getCurrent();
2774
- const renderFn = node.renderFn;
2775
- const decorate = node.app.dev ? wrapError : (fn) => fn;
2776
- fn = decorate(fn.bind(node.component), "onRendered");
2777
- node.renderFn = () => {
2778
- const result = renderFn();
2779
- fn();
2780
- return result;
2781
- };
2430
+ const pm = _getCurrentPluginManager();
2431
+ if (pm) {
2432
+ pm.onDestroyCb.push(fn);
2433
+ }
2434
+ else {
2435
+ const node = getCurrent();
2436
+ node.willDestroy.unshift(decorate(node, fn, "onWillDestroy"));
2437
+ }
2782
2438
  }
2783
2439
  function onError(callback) {
2784
2440
  const node = getCurrent();
@@ -2791,9 +2447,7 @@ function onError(callback) {
2791
2447
  }
2792
2448
 
2793
2449
  class Component {
2794
- constructor(props, env, node) {
2795
- this.props = props;
2796
- this.env = env;
2450
+ constructor(node) {
2797
2451
  this.__owl__ = node;
2798
2452
  }
2799
2453
  setup() { }
@@ -2803,83 +2457,6 @@ class Component {
2803
2457
  }
2804
2458
  Component.template = "";
2805
2459
 
2806
- const VText = text("").constructor;
2807
- class VPortal extends VText {
2808
- constructor(selector, content) {
2809
- super("");
2810
- this.target = null;
2811
- this.selector = selector;
2812
- this.content = content;
2813
- }
2814
- mount(parent, anchor) {
2815
- super.mount(parent, anchor);
2816
- this.target = document.querySelector(this.selector);
2817
- if (this.target) {
2818
- this.content.mount(this.target, null);
2819
- }
2820
- else {
2821
- this.content.mount(parent, anchor);
2822
- }
2823
- }
2824
- beforeRemove() {
2825
- this.content.beforeRemove();
2826
- }
2827
- remove() {
2828
- if (this.content) {
2829
- super.remove();
2830
- this.content.remove();
2831
- this.content = null;
2832
- }
2833
- }
2834
- patch(other) {
2835
- super.patch(other);
2836
- if (this.content) {
2837
- this.content.patch(other.content, true);
2838
- }
2839
- else {
2840
- this.content = other.content;
2841
- this.content.mount(this.target, null);
2842
- }
2843
- }
2844
- }
2845
- /**
2846
- * kind of similar to <t t-slot="default"/>, but it wraps it around a VPortal
2847
- */
2848
- function portalTemplate(app, bdom, helpers) {
2849
- let { callSlot } = helpers;
2850
- return function template(ctx, node, key = "") {
2851
- return new VPortal(ctx.props.target, callSlot(ctx, node, key, "default", false, null));
2852
- };
2853
- }
2854
- class Portal extends Component {
2855
- setup() {
2856
- const node = this.__owl__;
2857
- onMounted(() => {
2858
- const portal = node.bdom;
2859
- if (!portal.target) {
2860
- const target = document.querySelector(this.props.target);
2861
- if (target) {
2862
- portal.content.moveBeforeDOMNode(target.firstChild, target);
2863
- }
2864
- else {
2865
- throw new OwlError("invalid portal target");
2866
- }
2867
- }
2868
- });
2869
- onWillUnmount(() => {
2870
- const portal = node.bdom;
2871
- portal.remove();
2872
- });
2873
- }
2874
- }
2875
- Portal.template = "__portal__";
2876
- Portal.props = {
2877
- target: {
2878
- type: String,
2879
- },
2880
- slots: true,
2881
- };
2882
-
2883
2460
  // -----------------------------------------------------------------------------
2884
2461
  // helpers
2885
2462
  // -----------------------------------------------------------------------------
@@ -2930,7 +2507,7 @@ function validateSchema(obj, schema) {
2930
2507
  if (Array.isArray(schema)) {
2931
2508
  schema = toSchema(schema);
2932
2509
  }
2933
- obj = toRaw(obj);
2510
+ // obj = toRaw(obj);
2934
2511
  let errors = [];
2935
2512
  // check if each value in obj has correct shape
2936
2513
  for (let key in obj) {
@@ -3003,33 +2580,555 @@ function validateType(key, value, descr) {
3003
2580
  if (typeof value !== "object" || Array.isArray(value)) {
3004
2581
  result = `'${key}' is not an object`;
3005
2582
  }
3006
- else {
3007
- const errors = validateSchema(value, descr.shape);
3008
- if (errors.length) {
3009
- result = `'${key}' doesn't have the correct shape (${errors.join(", ")})`;
3010
- }
2583
+ else {
2584
+ const errors = validateSchema(value, descr.shape);
2585
+ if (errors.length) {
2586
+ result = `'${key}' doesn't have the correct shape (${errors.join(", ")})`;
2587
+ }
2588
+ }
2589
+ }
2590
+ else if ("values" in descr) {
2591
+ if (typeof value !== "object" || Array.isArray(value)) {
2592
+ result = `'${key}' is not an object`;
2593
+ }
2594
+ else {
2595
+ const errors = Object.entries(value)
2596
+ .map(([key, value]) => validateType(key, value, descr.values))
2597
+ .filter(Boolean);
2598
+ if (errors.length) {
2599
+ result = `some of the values in '${key}' are invalid (${errors.join(", ")})`;
2600
+ }
2601
+ }
2602
+ }
2603
+ if ("type" in descr && !result) {
2604
+ result = validateType(key, value, descr.type);
2605
+ }
2606
+ if ("validate" in descr && !result) {
2607
+ result = !descr.validate(value) ? `'${key}' is not valid` : null;
2608
+ }
2609
+ return result;
2610
+ }
2611
+
2612
+ function validateProps(componentName, props, validation, keys) {
2613
+ const propsToValidate = Object.create(null);
2614
+ const errors = [];
2615
+ for (const key of keys) {
2616
+ if (key in props) {
2617
+ propsToValidate[key] = props[key];
2618
+ }
2619
+ if (propsToValidate[key] === undefined &&
2620
+ !Array.isArray(validation) &&
2621
+ validation[key].defaultValue !== undefined) {
2622
+ if (!validation[key].optional) {
2623
+ errors.push(`A default value cannot be defined for the mandatory prop '${key}'`);
2624
+ continue;
2625
+ }
2626
+ propsToValidate[key] = validation[key].defaultValue;
2627
+ }
2628
+ }
2629
+ errors.push(...validateSchema(propsToValidate, validation));
2630
+ if (errors.length) {
2631
+ throw new OwlError(`Invalid props for component '${componentName}': ` + errors.join(", "));
2632
+ // node.app.handleError({
2633
+ // error: new OwlError(`Invalid props for component '${componentName}': ` + errors.join(", ")),
2634
+ // node,
2635
+ // });
2636
+ }
2637
+ }
2638
+ function props(validation) {
2639
+ const node = getCurrent();
2640
+ const isSchemaValidated = validation && !Array.isArray(validation);
2641
+ function getProp(key) {
2642
+ if (isSchemaValidated && node.props[key] === undefined) {
2643
+ return validation[key].defaultValue;
2644
+ }
2645
+ return node.props[key];
2646
+ }
2647
+ const result = Object.create(null);
2648
+ function applyPropGetters(keys) {
2649
+ for (const key of keys) {
2650
+ Reflect.defineProperty(result, key, {
2651
+ enumerable: true,
2652
+ get: getProp.bind(null, key),
2653
+ });
2654
+ }
2655
+ }
2656
+ if (validation) {
2657
+ const keys = (isSchemaValidated ? Object.keys(validation) : validation).map((key) => key.endsWith("?") ? key.slice(0, -1) : key);
2658
+ applyPropGetters(keys);
2659
+ if (node.app.dev) {
2660
+ validateProps(node.name, node.props, validation, keys);
2661
+ node.willUpdateProps.push((np) => {
2662
+ validateProps(node.name, np, validation, keys);
2663
+ });
2664
+ }
2665
+ }
2666
+ else {
2667
+ applyPropGetters(Object.keys(node.props));
2668
+ node.willUpdateProps.push((np) => {
2669
+ for (let key in result) {
2670
+ Reflect.deleteProperty(result, key);
2671
+ }
2672
+ applyPropGetters(Object.keys(np));
2673
+ });
2674
+ }
2675
+ return result;
2676
+ }
2677
+
2678
+ const VText = text("").constructor;
2679
+ class VPortal extends VText {
2680
+ constructor(selector, content) {
2681
+ super("");
2682
+ this.target = null;
2683
+ this.selector = selector;
2684
+ this.content = content;
2685
+ }
2686
+ mount(parent, anchor) {
2687
+ super.mount(parent, anchor);
2688
+ this.target = document.querySelector(this.selector);
2689
+ if (this.target) {
2690
+ this.content.mount(this.target, null);
2691
+ }
2692
+ else {
2693
+ this.content.mount(parent, anchor);
2694
+ }
2695
+ }
2696
+ beforeRemove() {
2697
+ this.content.beforeRemove();
2698
+ }
2699
+ remove() {
2700
+ if (this.content) {
2701
+ super.remove();
2702
+ this.content.remove();
2703
+ this.content = null;
2704
+ }
2705
+ }
2706
+ patch(other) {
2707
+ super.patch(other);
2708
+ if (this.content) {
2709
+ this.content.patch(other.content, true);
2710
+ }
2711
+ else {
2712
+ this.content = other.content;
2713
+ this.content.mount(this.target, null);
2714
+ }
2715
+ }
2716
+ }
2717
+ /**
2718
+ * kind of similar to <t t-slot="default"/>, but it wraps it around a VPortal
2719
+ */
2720
+ function portalTemplate(app, bdom, helpers) {
2721
+ let { callSlot } = helpers;
2722
+ return function template(ctx, node, key = "") {
2723
+ return new VPortal(ctx.this.props.target, callSlot(ctx, node, key, "default", false, null));
2724
+ };
2725
+ }
2726
+ class Portal extends Component {
2727
+ constructor() {
2728
+ super(...arguments);
2729
+ this.props = props({
2730
+ target: String,
2731
+ slots: true,
2732
+ });
2733
+ }
2734
+ setup() {
2735
+ const node = this.__owl__;
2736
+ onMounted(() => {
2737
+ const portal = node.bdom;
2738
+ if (!portal.target) {
2739
+ const target = document.querySelector(node.props.target);
2740
+ if (target) {
2741
+ portal.content.moveBeforeDOMNode(target.firstChild, target);
2742
+ }
2743
+ else {
2744
+ throw new OwlError("invalid portal target");
2745
+ }
2746
+ }
2747
+ });
2748
+ onWillUnmount(() => {
2749
+ const portal = node.bdom;
2750
+ portal.remove();
2751
+ });
2752
+ }
2753
+ }
2754
+ Portal.template = "__portal__";
2755
+
2756
+ // Special key to subscribe to, to be notified of key creation/deletion
2757
+ const KEYCHANGES = Symbol("Key changes");
2758
+ const objectToString = Object.prototype.toString;
2759
+ const objectHasOwnProperty = Object.prototype.hasOwnProperty;
2760
+ // Use arrays because Array.includes is faster than Set.has for small arrays
2761
+ const SUPPORTED_RAW_TYPES = ["Object", "Array", "Set", "Map", "WeakMap"];
2762
+ const COLLECTION_RAW_TYPES = ["Set", "Map", "WeakMap"];
2763
+ /**
2764
+ * extract "RawType" from strings like "[object RawType]" => this lets us ignore
2765
+ * many native objects such as Promise (whose toString is [object Promise])
2766
+ * or Date ([object Date]), while also supporting collections without using
2767
+ * instanceof in a loop
2768
+ *
2769
+ * @param obj the object to check
2770
+ * @returns the raw type of the object
2771
+ */
2772
+ function rawType(obj) {
2773
+ return objectToString.call(toRaw(obj)).slice(8, -1);
2774
+ }
2775
+ /**
2776
+ * Checks whether a given value can be made into a proxy object.
2777
+ *
2778
+ * @param value the value to check
2779
+ * @returns whether the value can be made proxy
2780
+ */
2781
+ function canBeMadeReactive(value) {
2782
+ if (typeof value !== "object") {
2783
+ return false;
2784
+ }
2785
+ return SUPPORTED_RAW_TYPES.includes(rawType(value));
2786
+ }
2787
+ /**
2788
+ * Creates a proxy from the given object/callback if possible and returns it,
2789
+ * returns the original object otherwise.
2790
+ *
2791
+ * @param value the value make proxy
2792
+ * @returns a proxy for the given object when possible, the original otherwise
2793
+ */
2794
+ function possiblyReactive(val) {
2795
+ return canBeMadeReactive(val) ? proxy(val) : val;
2796
+ }
2797
+ const skipped = new WeakSet();
2798
+ /**
2799
+ * Mark an object or array so that it is ignored by the reactivity system
2800
+ *
2801
+ * @param value the value to mark
2802
+ * @returns the object itself
2803
+ */
2804
+ function markRaw(value) {
2805
+ skipped.add(value);
2806
+ return value;
2807
+ }
2808
+ /**
2809
+ * Given a proxy objet, return the raw (non proxy) underlying object
2810
+ *
2811
+ * @param value a proxy value
2812
+ * @returns the underlying value
2813
+ */
2814
+ function toRaw(value) {
2815
+ return targets.has(value) ? targets.get(value) : value;
2816
+ }
2817
+ const targetToKeysToAtomItem = new WeakMap();
2818
+ function getTargetKeyAtom(target, key) {
2819
+ let keyToAtomItem = targetToKeysToAtomItem.get(target);
2820
+ if (!keyToAtomItem) {
2821
+ keyToAtomItem = new Map();
2822
+ targetToKeysToAtomItem.set(target, keyToAtomItem);
2823
+ }
2824
+ let atom = keyToAtomItem.get(key);
2825
+ if (!atom) {
2826
+ atom = {
2827
+ value: undefined,
2828
+ observers: new Set(),
2829
+ };
2830
+ keyToAtomItem.set(key, atom);
2831
+ }
2832
+ return atom;
2833
+ }
2834
+ /**
2835
+ * Observes a given key on a target with an callback. The callback will be
2836
+ * called when the given key changes on the target.
2837
+ *
2838
+ * @param target the target whose key should be observed
2839
+ * @param key the key to observe (or Symbol(KEYCHANGES) for key creation
2840
+ * or deletion)
2841
+ * @param callback the function to call when the key changes
2842
+ */
2843
+ function onReadTargetKey(target, key) {
2844
+ onReadAtom(getTargetKeyAtom(target, key));
2845
+ }
2846
+ /**
2847
+ * Notify Reactives that are observing a given target that a key has changed on
2848
+ * the target.
2849
+ *
2850
+ * @param target target whose Reactives should be notified that the target was
2851
+ * changed.
2852
+ * @param key the key that changed (or Symbol `KEYCHANGES` if a key was created
2853
+ * or deleted)
2854
+ */
2855
+ function onWriteTargetKey(target, key) {
2856
+ const keyToAtomItem = targetToKeysToAtomItem.get(target);
2857
+ if (!keyToAtomItem) {
2858
+ return;
2859
+ }
2860
+ const atom = keyToAtomItem.get(key);
2861
+ if (!atom) {
2862
+ return;
2863
+ }
2864
+ onWriteAtom(atom);
2865
+ }
2866
+ // Maps proxy objects to the underlying target
2867
+ const targets = new WeakMap();
2868
+ const proxyCache = new WeakMap();
2869
+ /**
2870
+ * Creates a reactive proxy for an object. Reading data on the proxy object
2871
+ * subscribes to changes to the data. Writing data on the object will cause the
2872
+ * notify callback to be called if there are suscriptions to that data. Nested
2873
+ * objects and arrays are automatically made reactive as well.
2874
+ *
2875
+ * Whenever you are notified of a change, all subscriptions are cleared, and if
2876
+ * you would like to be notified of any further changes, you should go read
2877
+ * the underlying data again. We assume that if you don't go read it again after
2878
+ * being notified, it means that you are no longer interested in that data.
2879
+ *
2880
+ * Subscriptions:
2881
+ * + Reading a property on an object will subscribe you to changes in the value
2882
+ * of that property.
2883
+ * + Accessing an object's keys (eg with Object.keys or with `for..in`) will
2884
+ * subscribe you to the creation/deletion of keys. Checking the presence of a
2885
+ * key on the object with 'in' has the same effect.
2886
+ * - getOwnPropertyDescriptor does not currently subscribe you to the property.
2887
+ * This is a choice that was made because changing a key's value will trigger
2888
+ * this trap and we do not want to subscribe by writes. This also means that
2889
+ * Object.hasOwnProperty doesn't subscribe as it goes through this trap.
2890
+ *
2891
+ * @param target the object for which to create a proxy proxy
2892
+ * @param callback the function to call when an observed property of the
2893
+ * proxy has changed
2894
+ * @returns a proxy that tracks changes to it
2895
+ */
2896
+ function proxy(target) {
2897
+ if (!canBeMadeReactive(target)) {
2898
+ throw new OwlError(`Cannot make the given value reactive`);
2899
+ }
2900
+ if (skipped.has(target)) {
2901
+ return target;
2902
+ }
2903
+ if (targets.has(target)) {
2904
+ // target is reactive, create a reactive on the underlying object instead
2905
+ return target;
2906
+ }
2907
+ const reactive = proxyCache.get(target);
2908
+ if (reactive)
2909
+ return reactive;
2910
+ const targetRawType = rawType(target);
2911
+ const handler = COLLECTION_RAW_TYPES.includes(targetRawType)
2912
+ ? collectionsProxyHandler(target, targetRawType)
2913
+ : basicProxyHandler();
2914
+ const proxy = new Proxy(target, handler);
2915
+ proxyCache.set(target, proxy);
2916
+ targets.set(proxy, target);
2917
+ return proxy;
2918
+ }
2919
+ /**
2920
+ * Creates a basic proxy handler for regular objects and arrays.
2921
+ *
2922
+ * @param callback @see proxy
2923
+ * @returns a proxy handler object
2924
+ */
2925
+ function basicProxyHandler() {
2926
+ return {
2927
+ get(target, key, receiver) {
2928
+ // non-writable non-configurable properties cannot be made proxy
2929
+ const desc = Object.getOwnPropertyDescriptor(target, key);
2930
+ if (desc && !desc.writable && !desc.configurable) {
2931
+ return Reflect.get(target, key, receiver);
2932
+ }
2933
+ onReadTargetKey(target, key);
2934
+ return possiblyReactive(Reflect.get(target, key, receiver));
2935
+ },
2936
+ set(target, key, value, receiver) {
2937
+ const hadKey = objectHasOwnProperty.call(target, key);
2938
+ const originalValue = Reflect.get(target, key, receiver);
2939
+ const ret = Reflect.set(target, key, toRaw(value), receiver);
2940
+ if (!hadKey && objectHasOwnProperty.call(target, key)) {
2941
+ onWriteTargetKey(target, KEYCHANGES);
2942
+ }
2943
+ // While Array length may trigger the set trap, it's not actually set by this
2944
+ // method but is updated behind the scenes, and the trap is not called with the
2945
+ // new value. We disable the "same-value-optimization" for it because of that.
2946
+ if (originalValue !== Reflect.get(target, key, receiver) ||
2947
+ (key === "length" && Array.isArray(target))) {
2948
+ onWriteTargetKey(target, key);
2949
+ }
2950
+ return ret;
2951
+ },
2952
+ deleteProperty(target, key) {
2953
+ const ret = Reflect.deleteProperty(target, key);
2954
+ // TODO: only notify when something was actually deleted
2955
+ onWriteTargetKey(target, KEYCHANGES);
2956
+ onWriteTargetKey(target, key);
2957
+ return ret;
2958
+ },
2959
+ ownKeys(target) {
2960
+ onReadTargetKey(target, KEYCHANGES);
2961
+ return Reflect.ownKeys(target);
2962
+ },
2963
+ has(target, key) {
2964
+ // TODO: this observes all key changes instead of only the presence of the argument key
2965
+ // observing the key itself would observe value changes instead of presence changes
2966
+ // so we may need a finer grained system to distinguish observing value vs presence.
2967
+ onReadTargetKey(target, KEYCHANGES);
2968
+ return Reflect.has(target, key);
2969
+ },
2970
+ };
2971
+ }
2972
+ /**
2973
+ * Creates a function that will observe the key that is passed to it when called
2974
+ * and delegates to the underlying method.
2975
+ *
2976
+ * @param methodName name of the method to delegate to
2977
+ * @param target @see proxy
2978
+ * @param callback @see proxy
2979
+ */
2980
+ function makeKeyObserver(methodName, target) {
2981
+ return (key) => {
2982
+ key = toRaw(key);
2983
+ onReadTargetKey(target, key);
2984
+ return possiblyReactive(target[methodName](key));
2985
+ };
2986
+ }
2987
+ /**
2988
+ * Creates an iterable that will delegate to the underlying iteration method and
2989
+ * observe keys as necessary.
2990
+ *
2991
+ * @param methodName name of the method to delegate to
2992
+ * @param target @see proxy
2993
+ * @param callback @see proxy
2994
+ */
2995
+ function makeIteratorObserver(methodName, target) {
2996
+ return function* () {
2997
+ onReadTargetKey(target, KEYCHANGES);
2998
+ const keys = target.keys();
2999
+ for (const item of target[methodName]()) {
3000
+ const key = keys.next().value;
3001
+ onReadTargetKey(target, key);
3002
+ yield possiblyReactive(item);
3003
+ }
3004
+ };
3005
+ }
3006
+ /**
3007
+ * Creates a forEach function that will delegate to forEach on the underlying
3008
+ * collection while observing key changes, and keys as they're iterated over,
3009
+ * and making the passed keys/values proxy.
3010
+ *
3011
+ * @param target @see proxy
3012
+ * @param callback @see proxy
3013
+ */
3014
+ function makeForEachObserver(target) {
3015
+ return function forEach(forEachCb, thisArg) {
3016
+ onReadTargetKey(target, KEYCHANGES);
3017
+ target.forEach(function (val, key, targetObj) {
3018
+ onReadTargetKey(target, key);
3019
+ forEachCb.call(thisArg, possiblyReactive(val), possiblyReactive(key), possiblyReactive(targetObj));
3020
+ }, thisArg);
3021
+ };
3022
+ }
3023
+ /**
3024
+ * Creates a function that will delegate to an underlying method, and check if
3025
+ * that method has modified the presence or value of a key, and notify the
3026
+ * proxys appropriately.
3027
+ *
3028
+ * @param setterName name of the method to delegate to
3029
+ * @param getterName name of the method which should be used to retrieve the
3030
+ * value before calling the delegate method for comparison purposes
3031
+ * @param target @see proxy
3032
+ */
3033
+ function delegateAndNotify(setterName, getterName, target) {
3034
+ return (key, value) => {
3035
+ key = toRaw(key);
3036
+ const hadKey = target.has(key);
3037
+ const originalValue = target[getterName](key);
3038
+ const ret = target[setterName](key, value);
3039
+ const hasKey = target.has(key);
3040
+ if (hadKey !== hasKey) {
3041
+ onWriteTargetKey(target, KEYCHANGES);
3042
+ }
3043
+ if (originalValue !== target[getterName](key)) {
3044
+ onWriteTargetKey(target, key);
3011
3045
  }
3012
- }
3013
- else if ("values" in descr) {
3014
- if (typeof value !== "object" || Array.isArray(value)) {
3015
- result = `'${key}' is not an object`;
3046
+ return ret;
3047
+ };
3048
+ }
3049
+ /**
3050
+ * Creates a function that will clear the underlying collection and notify that
3051
+ * the keys of the collection have changed.
3052
+ *
3053
+ * @param target @see proxy
3054
+ */
3055
+ function makeClearNotifier(target) {
3056
+ return () => {
3057
+ const allKeys = [...target.keys()];
3058
+ target.clear();
3059
+ onWriteTargetKey(target, KEYCHANGES);
3060
+ for (const key of allKeys) {
3061
+ onWriteTargetKey(target, key);
3016
3062
  }
3017
- else {
3018
- const errors = Object.entries(value)
3019
- .map(([key, value]) => validateType(key, value, descr.values))
3020
- .filter(Boolean);
3021
- if (errors.length) {
3022
- result = `some of the values in '${key}' are invalid (${errors.join(", ")})`;
3063
+ };
3064
+ }
3065
+ /**
3066
+ * Maps raw type of an object to an object containing functions that can be used
3067
+ * to build an appropritate proxy handler for that raw type. Eg: when making a
3068
+ * proxy set, calling the has method should mark the key that is being
3069
+ * retrieved as observed, and calling the add or delete method should notify the
3070
+ * proxys that the key which is being added or deleted has been modified.
3071
+ */
3072
+ const rawTypeToFuncHandlers = {
3073
+ Set: (target) => ({
3074
+ has: makeKeyObserver("has", target),
3075
+ add: delegateAndNotify("add", "has", target),
3076
+ delete: delegateAndNotify("delete", "has", target),
3077
+ keys: makeIteratorObserver("keys", target),
3078
+ values: makeIteratorObserver("values", target),
3079
+ entries: makeIteratorObserver("entries", target),
3080
+ [Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target),
3081
+ forEach: makeForEachObserver(target),
3082
+ clear: makeClearNotifier(target),
3083
+ get size() {
3084
+ onReadTargetKey(target, KEYCHANGES);
3085
+ return target.size;
3086
+ },
3087
+ }),
3088
+ Map: (target) => ({
3089
+ has: makeKeyObserver("has", target),
3090
+ get: makeKeyObserver("get", target),
3091
+ set: delegateAndNotify("set", "get", target),
3092
+ delete: delegateAndNotify("delete", "has", target),
3093
+ keys: makeIteratorObserver("keys", target),
3094
+ values: makeIteratorObserver("values", target),
3095
+ entries: makeIteratorObserver("entries", target),
3096
+ [Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target),
3097
+ forEach: makeForEachObserver(target),
3098
+ clear: makeClearNotifier(target),
3099
+ get size() {
3100
+ onReadTargetKey(target, KEYCHANGES);
3101
+ return target.size;
3102
+ },
3103
+ }),
3104
+ WeakMap: (target) => ({
3105
+ has: makeKeyObserver("has", target),
3106
+ get: makeKeyObserver("get", target),
3107
+ set: delegateAndNotify("set", "get", target),
3108
+ delete: delegateAndNotify("delete", "has", target),
3109
+ }),
3110
+ };
3111
+ /**
3112
+ * Creates a proxy handler for collections (Set/Map/WeakMap)
3113
+ *
3114
+ * @param callback @see proxy
3115
+ * @param target @see proxy
3116
+ * @returns a proxy handler object
3117
+ */
3118
+ function collectionsProxyHandler(target, targetRawType) {
3119
+ // TODO: if performance is an issue we can create the special handlers lazily when each
3120
+ // property is read.
3121
+ const specialHandlers = rawTypeToFuncHandlers[targetRawType](target);
3122
+ return Object.assign(basicProxyHandler(), {
3123
+ // FIXME: probably broken when part of prototype chain since we ignore the receiver
3124
+ get(target, key) {
3125
+ if (objectHasOwnProperty.call(specialHandlers, key)) {
3126
+ return specialHandlers[key];
3023
3127
  }
3024
- }
3025
- }
3026
- if ("type" in descr && !result) {
3027
- result = validateType(key, value, descr.type);
3028
- }
3029
- if ("validate" in descr && !result) {
3030
- result = !descr.validate(value) ? `'${key}' is not valid` : null;
3031
- }
3032
- return result;
3128
+ onReadTargetKey(target, key);
3129
+ return possiblyReactive(target[key]);
3130
+ },
3131
+ });
3033
3132
  }
3034
3133
 
3035
3134
  const ObjectCreate = Object.create;
@@ -3042,7 +3141,7 @@ function withDefault(value, defaultValue) {
3042
3141
  }
3043
3142
  function callSlot(ctx, parent, key, name, dynamic, extra, defaultContent) {
3044
3143
  key = key + "__slot_" + name;
3045
- const slots = ctx.props.slots || {};
3144
+ const slots = ctx.__owl__.props.slots || {};
3046
3145
  const { __render, __ctx, __scope } = slots[name] || {};
3047
3146
  const slotScope = ObjectCreate(__ctx || {});
3048
3147
  if (__scope) {
@@ -3147,84 +3246,52 @@ function safeOutput(value, defaultValue) {
3147
3246
  }
3148
3247
  let safeKey;
3149
3248
  let block;
3150
- switch (typeof value) {
3151
- case "object":
3152
- if (value instanceof Markup) {
3153
- safeKey = `string_safe`;
3154
- block = html(value);
3155
- }
3156
- else if (value instanceof LazyValue) {
3157
- safeKey = `lazy_value`;
3158
- block = value.evaluate();
3159
- }
3160
- else if (value instanceof String) {
3161
- safeKey = "string_unsafe";
3162
- block = text(value);
3163
- }
3164
- else {
3165
- // Assuming it is a block
3166
- safeKey = "block_safe";
3167
- block = value;
3168
- }
3169
- break;
3170
- case "string":
3171
- safeKey = "string_unsafe";
3172
- block = text(value);
3173
- break;
3174
- default:
3175
- safeKey = "string_unsafe";
3176
- block = text(String(value));
3249
+ if (value instanceof Markup) {
3250
+ safeKey = `string_safe`;
3251
+ block = html(value);
3252
+ }
3253
+ else if (value instanceof LazyValue) {
3254
+ safeKey = `lazy_value`;
3255
+ block = value.evaluate();
3256
+ }
3257
+ else {
3258
+ safeKey = "string_unsafe";
3259
+ block = text(value);
3177
3260
  }
3178
3261
  return toggler(safeKey, block);
3179
3262
  }
3180
- /**
3181
- * Validate the component props (or next props) against the (static) props
3182
- * description. This is potentially an expensive operation: it may needs to
3183
- * visit recursively the props and all the children to check if they are valid.
3184
- * This is why it is only done in 'dev' mode.
3185
- */
3186
- function validateProps(name, props, comp) {
3187
- const ComponentClass = typeof name !== "string"
3188
- ? name
3189
- : comp.constructor.components[name];
3190
- if (!ComponentClass) {
3191
- // this is an error, wrong component. We silently return here instead so the
3192
- // error is triggered by the usual path ('component' function)
3193
- return;
3263
+ function createRef(ref) {
3264
+ if (!ref) {
3265
+ throw new OwlError(`Ref is undefined or null`);
3194
3266
  }
3195
- const schema = ComponentClass.props;
3196
- if (!schema) {
3197
- if (comp.__owl__.app.warnIfNoStaticProps) {
3198
- console.warn(`Component '${ComponentClass.name}' does not have a static props description`);
3199
- }
3200
- return;
3267
+ let add;
3268
+ let remove;
3269
+ if (ref.add && ref.remove) {
3270
+ add = ref.add.bind(ref);
3271
+ remove = ref.remove.bind(ref);
3201
3272
  }
3202
- const defaultProps = ComponentClass.defaultProps;
3203
- if (defaultProps) {
3204
- let isMandatory = (name) => Array.isArray(schema)
3205
- ? schema.includes(name)
3206
- : name in schema && !("*" in schema) && !isOptional(schema[name]);
3207
- for (let p in defaultProps) {
3208
- if (isMandatory(p)) {
3209
- throw new OwlError(`A default value cannot be defined for a mandatory prop (name: '${p}', component: ${ComponentClass.name})`);
3210
- }
3211
- }
3273
+ else if (ref.set) {
3274
+ add = ref.set.bind(ref);
3275
+ remove = () => ref.set(null);
3212
3276
  }
3213
- const errors = validateSchema(props, schema);
3214
- if (errors.length) {
3215
- throw new OwlError(`Invalid props for component '${ComponentClass.name}': ` + errors.join(", "));
3277
+ else {
3278
+ throw new OwlError(`Ref should implement either a 'set' function or 'add' and 'remove' functions`);
3216
3279
  }
3217
- }
3218
- function makeRefWrapper(node) {
3219
- let refNames = new Set();
3220
- return (name, fn) => {
3221
- if (refNames.has(name)) {
3222
- throw new OwlError(`Cannot set the same ref more than once in the same component, ref "${name}" was set multiple times in ${node.name}`);
3280
+ return (el, previousEl) => {
3281
+ if (previousEl) {
3282
+ remove(previousEl);
3283
+ }
3284
+ if (el) {
3285
+ add(el);
3223
3286
  }
3224
- refNames.add(name);
3225
- return fn;
3226
3287
  };
3227
3288
  }
3289
+ function modelExpr(value) {
3290
+ if (!value.set || typeof value !== "function") {
3291
+ throw new OwlError(`Invalid t-model expression: expression should evaluate to a function with a 'set' method defined on it`);
3292
+ }
3293
+ return value;
3294
+ }
3228
3295
  const helpers = {
3229
3296
  withDefault,
3230
3297
  zero: Symbol("zero"),
@@ -3236,13 +3303,13 @@ const helpers = {
3236
3303
  setContextValue,
3237
3304
  shallowEqual,
3238
3305
  toNumber,
3239
- validateProps,
3240
3306
  LazyValue,
3241
3307
  safeOutput,
3242
3308
  createCatcher,
3243
3309
  markRaw,
3244
3310
  OwlError,
3245
- makeRefWrapper,
3311
+ createRef,
3312
+ modelExpr,
3246
3313
  };
3247
3314
 
3248
3315
  /**
@@ -3283,6 +3350,9 @@ function parseXML(xml) {
3283
3350
 
3284
3351
  const bdom = { text, createBlock, list, multi, html, toggler, comment };
3285
3352
  class TemplateSet {
3353
+ static registerTemplate(name, fn) {
3354
+ globalTemplates[name] = fn;
3355
+ }
3286
3356
  constructor(config = {}) {
3287
3357
  this.rawTemplates = Object.create(globalTemplates);
3288
3358
  this.templates = {};
@@ -3305,9 +3375,6 @@ class TemplateSet {
3305
3375
  this.runtimeUtils = { ...helpers, __globals__: config.globalValues || {} };
3306
3376
  this.hasGlobalValues = Boolean(config.globalValues && Object.keys(config.globalValues).length);
3307
3377
  }
3308
- static registerTemplate(name, fn) {
3309
- globalTemplates[name] = fn;
3310
- }
3311
3378
  addTemplate(name, template) {
3312
3379
  if (name in this.rawTemplates) {
3313
3380
  // this check can be expensive, just silently ignore double definitions outside dev mode
@@ -3618,7 +3685,7 @@ function compileExprToArray(expr) {
3618
3685
  stack.pop();
3619
3686
  }
3620
3687
  let isVar = token.type === "SYMBOL" && !RESERVED_WORDS.includes(token.value);
3621
- if (token.type === "SYMBOL" && !RESERVED_WORDS.includes(token.value)) {
3688
+ if (isVar) {
3622
3689
  if (prevToken) {
3623
3690
  // normalize missing tokens: {a} should be equivalent to {a:a}
3624
3691
  if (groupType === "LEFT_BRACE" &&
@@ -3810,7 +3877,6 @@ class CodeTarget {
3810
3877
  this.hasRoot = false;
3811
3878
  this.hasCache = false;
3812
3879
  this.shouldProtectScope = false;
3813
- this.hasRefWrapper = false;
3814
3880
  this.name = name;
3815
3881
  this.on = on || null;
3816
3882
  }
@@ -3830,9 +3896,6 @@ class CodeTarget {
3830
3896
  result.push(` ctx = Object.create(ctx);`);
3831
3897
  result.push(` ctx[isBoundary] = 1`);
3832
3898
  }
3833
- if (this.hasRefWrapper) {
3834
- result.push(` let refWrapper = makeRefWrapper(this.__owl__);`);
3835
- }
3836
3899
  if (this.hasCache) {
3837
3900
  result.push(` let cache = ctx.cache || {};`);
3838
3901
  result.push(` let nextCache = ctx.cache = {};`);
@@ -3899,7 +3962,7 @@ class CodeGenerator {
3899
3962
  }
3900
3963
  generateCode() {
3901
3964
  const ast = this.ast;
3902
- this.isDebug = ast.type === 12 /* TDebug */;
3965
+ this.isDebug = ast.type === 11 /* ASTType.TDebug */;
3903
3966
  BlockDescription.nextBlockId = 1;
3904
3967
  nextDataIds = {};
3905
3968
  this.compileAST(ast, {
@@ -4055,43 +4118,41 @@ class CodeGenerator {
4055
4118
  */
4056
4119
  compileAST(ast, ctx) {
4057
4120
  switch (ast.type) {
4058
- case 1 /* Comment */:
4121
+ case 1 /* ASTType.Comment */:
4059
4122
  return this.compileComment(ast, ctx);
4060
- case 0 /* Text */:
4123
+ case 0 /* ASTType.Text */:
4061
4124
  return this.compileText(ast, ctx);
4062
- case 2 /* DomNode */:
4125
+ case 2 /* ASTType.DomNode */:
4063
4126
  return this.compileTDomNode(ast, ctx);
4064
- case 4 /* TEsc */:
4065
- return this.compileTEsc(ast, ctx);
4066
- case 8 /* TOut */:
4127
+ case 7 /* ASTType.TOut */:
4067
4128
  return this.compileTOut(ast, ctx);
4068
- case 5 /* TIf */:
4129
+ case 4 /* ASTType.TIf */:
4069
4130
  return this.compileTIf(ast, ctx);
4070
- case 9 /* TForEach */:
4131
+ case 8 /* ASTType.TForEach */:
4071
4132
  return this.compileTForeach(ast, ctx);
4072
- case 10 /* TKey */:
4133
+ case 9 /* ASTType.TKey */:
4073
4134
  return this.compileTKey(ast, ctx);
4074
- case 3 /* Multi */:
4135
+ case 3 /* ASTType.Multi */:
4075
4136
  return this.compileMulti(ast, ctx);
4076
- case 7 /* TCall */:
4137
+ case 6 /* ASTType.TCall */:
4077
4138
  return this.compileTCall(ast, ctx);
4078
- case 15 /* TCallBlock */:
4139
+ case 14 /* ASTType.TCallBlock */:
4079
4140
  return this.compileTCallBlock(ast, ctx);
4080
- case 6 /* TSet */:
4141
+ case 5 /* ASTType.TSet */:
4081
4142
  return this.compileTSet(ast, ctx);
4082
- case 11 /* TComponent */:
4143
+ case 10 /* ASTType.TComponent */:
4083
4144
  return this.compileComponent(ast, ctx);
4084
- case 12 /* TDebug */:
4145
+ case 11 /* ASTType.TDebug */:
4085
4146
  return this.compileDebug(ast, ctx);
4086
- case 13 /* TLog */:
4147
+ case 12 /* ASTType.TLog */:
4087
4148
  return this.compileLog(ast, ctx);
4088
- case 14 /* TSlot */:
4089
- return this.compileTSlot(ast, ctx);
4090
- case 16 /* TTranslation */:
4149
+ case 13 /* ASTType.TCallSlot */:
4150
+ return this.compileTCallSlot(ast, ctx);
4151
+ case 15 /* ASTType.TTranslation */:
4091
4152
  return this.compileTTranslation(ast, ctx);
4092
- case 17 /* TTranslationContext */:
4153
+ case 16 /* ASTType.TTranslationContext */:
4093
4154
  return this.compileTTranslationContext(ast, ctx);
4094
- case 18 /* TPortal */:
4155
+ case 17 /* ASTType.TPortal */:
4095
4156
  return this.compileTPortal(ast, ctx);
4096
4157
  }
4097
4158
  }
@@ -4142,7 +4203,7 @@ class CodeGenerator {
4142
4203
  });
4143
4204
  }
4144
4205
  else {
4145
- const createFn = ast.type === 0 /* Text */ ? xmlDoc.createTextNode : xmlDoc.createComment;
4206
+ const createFn = ast.type === 0 /* ASTType.Text */ ? xmlDoc.createTextNode : xmlDoc.createComment;
4146
4207
  block.insert(createFn.call(xmlDoc, value));
4147
4208
  }
4148
4209
  return block.varName;
@@ -4236,14 +4297,11 @@ class CodeGenerator {
4236
4297
  // t-model
4237
4298
  let tModelSelectedExpr;
4238
4299
  if (ast.model) {
4239
- const { hasDynamicChildren, baseExpr, expr, eventType, shouldNumberize, shouldTrim, targetAttr, specialInitTargetAttr, } = ast.model;
4240
- const baseExpression = compileExpr(baseExpr);
4241
- const bExprId = generateId("bExpr");
4242
- this.define(bExprId, baseExpression);
4300
+ const { hasDynamicChildren, expr, eventType, shouldNumberize, shouldTrim, targetAttr, specialInitTargetAttr, } = ast.model;
4243
4301
  const expression = compileExpr(expr);
4244
4302
  const exprId = generateId("expr");
4245
- this.define(exprId, expression);
4246
- const fullExpression = `${bExprId}[${exprId}]`;
4303
+ this.helpers.add("modelExpr");
4304
+ this.define(exprId, `modelExpr(${expression})`);
4247
4305
  let idx;
4248
4306
  if (specialInitTargetAttr) {
4249
4307
  let targetExpr = targetAttr in attrs && `'${attrs[targetAttr]}'`;
@@ -4254,23 +4312,23 @@ class CodeGenerator {
4254
4312
  targetExpr = compileExpr(dynamicTgExpr);
4255
4313
  }
4256
4314
  }
4257
- idx = block.insertData(`${fullExpression} === ${targetExpr}`, "prop");
4315
+ idx = block.insertData(`${exprId}() === ${targetExpr}`, "prop");
4258
4316
  attrs[`block-property-${idx}`] = specialInitTargetAttr;
4259
4317
  }
4260
4318
  else if (hasDynamicChildren) {
4261
4319
  const bValueId = generateId("bValue");
4262
4320
  tModelSelectedExpr = `${bValueId}`;
4263
- this.define(tModelSelectedExpr, fullExpression);
4321
+ this.define(tModelSelectedExpr, `${exprId}()`);
4264
4322
  }
4265
4323
  else {
4266
- idx = block.insertData(`${fullExpression}`, "prop");
4324
+ idx = block.insertData(`${exprId}()`, "prop");
4267
4325
  attrs[`block-property-${idx}`] = targetAttr;
4268
4326
  }
4269
4327
  this.helpers.add("toNumber");
4270
4328
  let valueCode = `ev.target.${targetAttr}`;
4271
4329
  valueCode = shouldTrim ? `${valueCode}.trim()` : valueCode;
4272
4330
  valueCode = shouldNumberize ? `toNumber(${valueCode})` : valueCode;
4273
- const handler = `[(ev) => { ${fullExpression} = ${valueCode}; }]`;
4331
+ const handler = `[(ev) => { ${exprId}.set(${valueCode}); }]`;
4274
4332
  idx = block.insertData(handler, "hdlr");
4275
4333
  attrs[`block-handler-${idx}`] = eventType;
4276
4334
  }
@@ -4282,19 +4340,9 @@ class CodeGenerator {
4282
4340
  }
4283
4341
  // t-ref
4284
4342
  if (ast.ref) {
4285
- if (this.dev) {
4286
- this.helpers.add("makeRefWrapper");
4287
- this.target.hasRefWrapper = true;
4288
- }
4289
- const isDynamic = INTERP_REGEXP.test(ast.ref);
4290
- let name = `\`${ast.ref}\``;
4291
- if (isDynamic) {
4292
- name = replaceDynamicParts(ast.ref, (expr) => this.captureExpression(expr, true));
4293
- }
4294
- let setRefStr = `(el) => this.__owl__.setRef((${name}), el)`;
4295
- if (this.dev) {
4296
- setRefStr = `refWrapper(${name}, ${setRefStr})`;
4297
- }
4343
+ const refExpr = compileExpr(ast.ref);
4344
+ this.helpers.add("createRef");
4345
+ const setRefStr = `createRef(${refExpr})`;
4298
4346
  const idx = block.insertData(setRefStr, "ref");
4299
4347
  attrs["block-ref"] = String(idx);
4300
4348
  }
@@ -4348,32 +4396,6 @@ class CodeGenerator {
4348
4396
  }
4349
4397
  return block.varName;
4350
4398
  }
4351
- compileTEsc(ast, ctx) {
4352
- let { block, forceNewBlock } = ctx;
4353
- let expr;
4354
- if (ast.expr === "0") {
4355
- this.helpers.add("zero");
4356
- expr = `ctx[zero]`;
4357
- }
4358
- else {
4359
- expr = compileExpr(ast.expr);
4360
- if (ast.defaultValue) {
4361
- this.helpers.add("withDefault");
4362
- // FIXME: defaultValue is not translated
4363
- expr = `withDefault(${expr}, ${toStringExpression(ast.defaultValue)})`;
4364
- }
4365
- }
4366
- if (!block || forceNewBlock) {
4367
- block = this.createBlock(block, "text", ctx);
4368
- this.insertBlock(`text(${expr})`, block, { ...ctx, forceNewBlock: forceNewBlock && !block });
4369
- }
4370
- else {
4371
- const idx = block.insertData(expr, "txt");
4372
- const text = xmlDoc.createElement(`block-text-${idx}`);
4373
- block.insert(text);
4374
- }
4375
- return block.varName;
4376
- }
4377
4399
  compileTOut(ast, ctx) {
4378
4400
  let { block } = ctx;
4379
4401
  if (block) {
@@ -4389,7 +4411,7 @@ class CodeGenerator {
4389
4411
  let bodyValue = null;
4390
4412
  bodyValue = BlockDescription.nextBlockId;
4391
4413
  const subCtx = createContext(ctx);
4392
- this.compileAST({ type: 3 /* Multi */, content: ast.body }, subCtx);
4414
+ this.compileAST({ type: 3 /* ASTType.Multi */, content: ast.body }, subCtx);
4393
4415
  this.helpers.add("safeOutput");
4394
4416
  blockStr = `safeOutput(${compileExpr(ast.expr)}, b${bodyValue})`;
4395
4417
  }
@@ -4594,7 +4616,7 @@ class CodeGenerator {
4594
4616
  let ctxVar = ctx.ctxVar || "ctx";
4595
4617
  if (ast.context) {
4596
4618
  ctxVar = generateId("ctx");
4597
- this.addLine(`let ${ctxVar} = ${compileExpr(ast.context)};`);
4619
+ this.addLine(`let ${ctxVar} = {this: ${compileExpr(ast.context)}, __owl__: this.__owl__};`);
4598
4620
  }
4599
4621
  const isDynamic = INTERP_REGEXP.test(ast.name);
4600
4622
  const subTemplate = isDynamic ? interpolate(ast.name) : "`" + ast.name + "`";
@@ -4607,7 +4629,7 @@ class CodeGenerator {
4607
4629
  this.addLine(`${ctxVar}[isBoundary] = 1;`);
4608
4630
  this.helpers.add("isBoundary");
4609
4631
  const subCtx = createContext(ctx, { ctxVar });
4610
- const bl = this.compileMulti({ type: 3 /* Multi */, content: ast.body }, subCtx);
4632
+ const bl = this.compileMulti({ type: 3 /* ASTType.Multi */, content: ast.body }, subCtx);
4611
4633
  if (bl) {
4612
4634
  this.helpers.add("zero");
4613
4635
  this.addLine(`${ctxVar}[zero] = ${bl};`);
@@ -4655,7 +4677,7 @@ class CodeGenerator {
4655
4677
  const expr = ast.value ? compileExpr(ast.value || "") : "null";
4656
4678
  if (ast.body) {
4657
4679
  this.helpers.add("LazyValue");
4658
- const bodyAst = { type: 3 /* Multi */, content: ast.body };
4680
+ const bodyAst = { type: 3 /* ASTType.Multi */, content: ast.body };
4659
4681
  const name = this.compileInNewTarget("value", bodyAst, ctx);
4660
4682
  let key = this.target.currentKey(ctx);
4661
4683
  let value = `new LazyValue(${name}, ctx, this, node, ${key})`;
@@ -4794,9 +4816,6 @@ class CodeGenerator {
4794
4816
  else {
4795
4817
  expr = `\`${ast.name}\``;
4796
4818
  }
4797
- if (this.dev) {
4798
- this.addLine(`helpers.validateProps(${expr}, ${propVar}, this);`);
4799
- }
4800
4819
  if (block && (ctx.forceNewBlock === false || ctx.tKeyExpr)) {
4801
4820
  // todo: check the forcenewblock condition
4802
4821
  this.insertAnchor(block);
@@ -4851,7 +4870,7 @@ class CodeGenerator {
4851
4870
  this.staticDefs.push({ id: name, expr: `createCatcher(${JSON.stringify(spec)})` });
4852
4871
  return `${name}(${expr}, [${handlers.join(",")}])`;
4853
4872
  }
4854
- compileTSlot(ast, ctx) {
4873
+ compileTCallSlot(ast, ctx) {
4855
4874
  this.helpers.add("callSlot");
4856
4875
  let { block } = ctx;
4857
4876
  let blockString;
@@ -4968,7 +4987,7 @@ function parse(xml, customDir) {
4968
4987
  }
4969
4988
  function _parse(xml, ctx) {
4970
4989
  normalizeXML(xml);
4971
- return parseNode(xml, ctx) || { type: 0 /* Text */, value: "" };
4990
+ return parseNode(xml, ctx) || { type: 0 /* ASTType.Text */, value: "" };
4972
4991
  }
4973
4992
  function parseNode(node, ctx) {
4974
4993
  if (!(node instanceof Element)) {
@@ -4984,9 +5003,8 @@ function parseNode(node, ctx) {
4984
5003
  parseTTranslation(node, ctx) ||
4985
5004
  parseTTranslationContext(node, ctx) ||
4986
5005
  parseTKey(node, ctx) ||
4987
- parseTEscNode(node, ctx) ||
4988
5006
  parseTOutNode(node, ctx) ||
4989
- parseTSlot(node, ctx) ||
5007
+ parseTCallSlot(node, ctx) ||
4990
5008
  parseComponent(node, ctx) ||
4991
5009
  parseDOMNode(node, ctx) ||
4992
5010
  parseTSetNode(node, ctx) ||
@@ -5011,10 +5029,10 @@ function parseTextCommentNode(node, ctx) {
5011
5029
  if (!ctx.inPreTag && lineBreakRE.test(value) && !value.trim()) {
5012
5030
  return null;
5013
5031
  }
5014
- return { type: 0 /* Text */, value };
5032
+ return { type: 0 /* ASTType.Text */, value };
5015
5033
  }
5016
5034
  else if (node.nodeType === Node.COMMENT_NODE) {
5017
- return { type: 1 /* Comment */, value: node.textContent || "" };
5035
+ return { type: 1 /* ASTType.Comment */, value: node.textContent || "" };
5018
5036
  }
5019
5037
  return null;
5020
5038
  }
@@ -5055,7 +5073,7 @@ function parseTDebugLog(node, ctx) {
5055
5073
  node.removeAttribute("t-debug");
5056
5074
  const content = parseNode(node, ctx);
5057
5075
  const ast = {
5058
- type: 12 /* TDebug */,
5076
+ type: 11 /* ASTType.TDebug */,
5059
5077
  content,
5060
5078
  };
5061
5079
  if (content === null || content === void 0 ? void 0 : content.hasNoRepresentation) {
@@ -5068,7 +5086,7 @@ function parseTDebugLog(node, ctx) {
5068
5086
  node.removeAttribute("t-log");
5069
5087
  const content = parseNode(node, ctx);
5070
5088
  const ast = {
5071
- type: 13 /* TLog */,
5089
+ type: 12 /* ASTType.TLog */,
5072
5090
  expr,
5073
5091
  content,
5074
5092
  };
@@ -5082,8 +5100,6 @@ function parseTDebugLog(node, ctx) {
5082
5100
  // -----------------------------------------------------------------------------
5083
5101
  // Regular dom node
5084
5102
  // -----------------------------------------------------------------------------
5085
- const hasDotAtTheEnd = /\.[\w_]+\s*$/;
5086
- const hasBracketsAtTheEnd = /\[[^\[]+\]\s*$/;
5087
5103
  const ROOT_SVG_TAGS = new Set(["svg", "g", "path"]);
5088
5104
  function parseDOMNode(node, ctx) {
5089
5105
  const { tagName } = node;
@@ -5120,20 +5136,6 @@ function parseDOMNode(node, ctx) {
5120
5136
  if (!["input", "select", "textarea"].includes(tagName)) {
5121
5137
  throw new OwlError("The t-model directive only works with <input>, <textarea> and <select>");
5122
5138
  }
5123
- let baseExpr, expr;
5124
- if (hasDotAtTheEnd.test(value)) {
5125
- const index = value.lastIndexOf(".");
5126
- baseExpr = value.slice(0, index);
5127
- expr = `'${value.slice(index + 1)}'`;
5128
- }
5129
- else if (hasBracketsAtTheEnd.test(value)) {
5130
- const index = value.lastIndexOf("[");
5131
- baseExpr = value.slice(0, index);
5132
- expr = value.slice(index + 1, -1);
5133
- }
5134
- else {
5135
- throw new OwlError(`Invalid t-model expression: "${value}" (it should be assignable)`);
5136
- }
5137
5139
  const typeAttr = node.getAttribute("type");
5138
5140
  const isInput = tagName === "input";
5139
5141
  const isSelect = tagName === "select";
@@ -5144,8 +5146,7 @@ function parseDOMNode(node, ctx) {
5144
5146
  const hasNumberMod = attr.includes(".number");
5145
5147
  const eventType = isRadioInput ? "click" : isSelect || hasLazyMod ? "change" : "input";
5146
5148
  model = {
5147
- baseExpr,
5148
- expr,
5149
+ expr: value,
5149
5150
  targetAttr: isCheckboxInput ? "checked" : "value",
5150
5151
  specialInitTargetAttr: isRadioInput ? "checked" : null,
5151
5152
  eventType,
@@ -5187,7 +5188,7 @@ function parseDOMNode(node, ctx) {
5187
5188
  }
5188
5189
  const children = parseChildren(node, ctx);
5189
5190
  return {
5190
- type: 2 /* DomNode */,
5191
+ type: 2 /* ASTType.DomNode */,
5191
5192
  tag: tagName,
5192
5193
  dynamicTag,
5193
5194
  attrs,
@@ -5200,55 +5201,26 @@ function parseDOMNode(node, ctx) {
5200
5201
  };
5201
5202
  }
5202
5203
  // -----------------------------------------------------------------------------
5203
- // t-esc
5204
- // -----------------------------------------------------------------------------
5205
- function parseTEscNode(node, ctx) {
5206
- if (!node.hasAttribute("t-esc")) {
5207
- return null;
5208
- }
5209
- const escValue = node.getAttribute("t-esc");
5210
- node.removeAttribute("t-esc");
5211
- const tesc = {
5212
- type: 4 /* TEsc */,
5213
- expr: escValue,
5214
- defaultValue: node.textContent || "",
5215
- };
5216
- let ref = node.getAttribute("t-ref");
5217
- node.removeAttribute("t-ref");
5218
- const ast = parseNode(node, ctx);
5219
- if (!ast) {
5220
- return tesc;
5221
- }
5222
- if (ast.type === 2 /* DomNode */) {
5223
- return {
5224
- ...ast,
5225
- ref,
5226
- content: [tesc],
5227
- };
5228
- }
5229
- return tesc;
5230
- }
5231
- // -----------------------------------------------------------------------------
5232
5204
  // t-out
5233
5205
  // -----------------------------------------------------------------------------
5234
5206
  function parseTOutNode(node, ctx) {
5235
- if (!node.hasAttribute("t-out") && !node.hasAttribute("t-raw")) {
5207
+ if (!node.hasAttribute("t-out") && !node.hasAttribute("t-esc")) {
5236
5208
  return null;
5237
5209
  }
5238
- if (node.hasAttribute("t-raw")) {
5239
- console.warn(`t-raw has been deprecated in favor of t-out. If the value to render is not wrapped by the "markup" function, it will be escaped`);
5210
+ if (node.hasAttribute("t-esc")) {
5211
+ console.warn(`t-esc has been deprecated in favor of t-out. If the value to render is not wrapped by the "markup" function, it will be escaped`);
5240
5212
  }
5241
- const expr = (node.getAttribute("t-out") || node.getAttribute("t-raw"));
5213
+ const expr = (node.getAttribute("t-out") || node.getAttribute("t-esc"));
5242
5214
  node.removeAttribute("t-out");
5243
- node.removeAttribute("t-raw");
5244
- const tOut = { type: 8 /* TOut */, expr, body: null };
5215
+ node.removeAttribute("t-esc");
5216
+ const tOut = { type: 7 /* ASTType.TOut */, expr, body: null };
5245
5217
  const ref = node.getAttribute("t-ref");
5246
5218
  node.removeAttribute("t-ref");
5247
5219
  const ast = parseNode(node, ctx);
5248
5220
  if (!ast) {
5249
5221
  return tOut;
5250
5222
  }
5251
- if (ast.type === 2 /* DomNode */) {
5223
+ if (ast.type === 2 /* ASTType.DomNode */) {
5252
5224
  tOut.body = ast.content.length ? ast.content : null;
5253
5225
  return {
5254
5226
  ...ast,
@@ -5287,7 +5259,7 @@ function parseTForEach(node, ctx) {
5287
5259
  const hasNoIndex = hasNoTCall && !html.includes(`${elem}_index`);
5288
5260
  const hasNoValue = hasNoTCall && !html.includes(`${elem}_value`);
5289
5261
  return {
5290
- type: 9 /* TForEach */,
5262
+ type: 8 /* ASTType.TForEach */,
5291
5263
  collection,
5292
5264
  elem,
5293
5265
  body,
@@ -5310,7 +5282,7 @@ function parseTKey(node, ctx) {
5310
5282
  return null;
5311
5283
  }
5312
5284
  const ast = {
5313
- type: 10 /* TKey */,
5285
+ type: 9 /* ASTType.TKey */,
5314
5286
  expr: key,
5315
5287
  content,
5316
5288
  };
@@ -5332,12 +5304,12 @@ function parseTCall(node, ctx) {
5332
5304
  node.removeAttribute("t-call-context");
5333
5305
  if (node.tagName !== "t") {
5334
5306
  const ast = parseNode(node, ctx);
5335
- const tcall = { type: 7 /* TCall */, name: subTemplate, body: null, context };
5336
- if (ast && ast.type === 2 /* DomNode */) {
5307
+ const tcall = { type: 6 /* ASTType.TCall */, name: subTemplate, body: null, context };
5308
+ if (ast && ast.type === 2 /* ASTType.DomNode */) {
5337
5309
  ast.content = [tcall];
5338
5310
  return ast;
5339
5311
  }
5340
- if (ast && ast.type === 11 /* TComponent */) {
5312
+ if (ast && ast.type === 10 /* ASTType.TComponent */) {
5341
5313
  return {
5342
5314
  ...ast,
5343
5315
  slots: {
@@ -5354,7 +5326,7 @@ function parseTCall(node, ctx) {
5354
5326
  }
5355
5327
  const body = parseChildren(node, ctx);
5356
5328
  return {
5357
- type: 7 /* TCall */,
5329
+ type: 6 /* ASTType.TCall */,
5358
5330
  name: subTemplate,
5359
5331
  body: body.length ? body : null,
5360
5332
  context,
@@ -5369,7 +5341,7 @@ function parseTCallBlock(node, ctx) {
5369
5341
  }
5370
5342
  const name = node.getAttribute("t-call-block");
5371
5343
  return {
5372
- type: 15 /* TCallBlock */,
5344
+ type: 14 /* ASTType.TCallBlock */,
5373
5345
  name,
5374
5346
  };
5375
5347
  }
@@ -5382,7 +5354,7 @@ function parseTIf(node, ctx) {
5382
5354
  }
5383
5355
  const condition = node.getAttribute("t-if");
5384
5356
  node.removeAttribute("t-if");
5385
- const content = parseNode(node, ctx) || { type: 0 /* Text */, value: "" };
5357
+ const content = parseNode(node, ctx) || { type: 0 /* ASTType.Text */, value: "" };
5386
5358
  let nextElement = node.nextElementSibling;
5387
5359
  // t-elifs
5388
5360
  const tElifs = [];
@@ -5405,7 +5377,7 @@ function parseTIf(node, ctx) {
5405
5377
  nextElement.remove();
5406
5378
  }
5407
5379
  return {
5408
- type: 5 /* TIf */,
5380
+ type: 4 /* ASTType.TIf */,
5409
5381
  condition,
5410
5382
  content,
5411
5383
  tElif: tElifs.length ? tElifs : null,
@@ -5426,7 +5398,7 @@ function parseTSetNode(node, ctx) {
5426
5398
  if (node.textContent !== node.innerHTML) {
5427
5399
  body = parseChildren(node, ctx);
5428
5400
  }
5429
- return { type: 6 /* TSet */, name, value, defaultValue, body, hasNoRepresentation: true };
5401
+ return { type: 5 /* ASTType.TSet */, name, value, defaultValue, body, hasNoRepresentation: true };
5430
5402
  }
5431
5403
  // -----------------------------------------------------------------------------
5432
5404
  // Components
@@ -5555,7 +5527,7 @@ function parseComponent(node, ctx) {
5555
5527
  }
5556
5528
  }
5557
5529
  return {
5558
- type: 11 /* TComponent */,
5530
+ type: 10 /* ASTType.TComponent */,
5559
5531
  name,
5560
5532
  isDynamic,
5561
5533
  dynamicProps,
@@ -5568,12 +5540,12 @@ function parseComponent(node, ctx) {
5568
5540
  // -----------------------------------------------------------------------------
5569
5541
  // Slots
5570
5542
  // -----------------------------------------------------------------------------
5571
- function parseTSlot(node, ctx) {
5572
- if (!node.hasAttribute("t-slot")) {
5543
+ function parseTCallSlot(node, ctx) {
5544
+ if (!node.hasAttribute("t-call-slot")) {
5573
5545
  return null;
5574
5546
  }
5575
- const name = node.getAttribute("t-slot");
5576
- node.removeAttribute("t-slot");
5547
+ const name = node.getAttribute("t-call-slot");
5548
+ node.removeAttribute("t-call-slot");
5577
5549
  let attrs = null;
5578
5550
  let attrsTranslationCtx = null;
5579
5551
  let on = null;
@@ -5594,7 +5566,7 @@ function parseTSlot(node, ctx) {
5594
5566
  }
5595
5567
  }
5596
5568
  return {
5597
- type: 14 /* TSlot */,
5569
+ type: 13 /* ASTType.TCallSlot */,
5598
5570
  name,
5599
5571
  attrs,
5600
5572
  attrsTranslationCtx,
@@ -5606,7 +5578,7 @@ function parseTSlot(node, ctx) {
5606
5578
  // Translation
5607
5579
  // -----------------------------------------------------------------------------
5608
5580
  function wrapInTTranslationAST(r) {
5609
- const ast = { type: 16 /* TTranslation */, content: r };
5581
+ const ast = { type: 15 /* ASTType.TTranslation */, content: r };
5610
5582
  if (r === null || r === void 0 ? void 0 : r.hasNoRepresentation) {
5611
5583
  ast.hasNoRepresentation = true;
5612
5584
  }
@@ -5618,7 +5590,7 @@ function parseTTranslation(node, ctx) {
5618
5590
  }
5619
5591
  node.removeAttribute("t-translation");
5620
5592
  const result = parseNode(node, ctx);
5621
- if ((result === null || result === void 0 ? void 0 : result.type) === 3 /* Multi */) {
5593
+ if ((result === null || result === void 0 ? void 0 : result.type) === 3 /* ASTType.Multi */) {
5622
5594
  const children = result.content.map(wrapInTTranslationAST);
5623
5595
  return makeASTMulti(children);
5624
5596
  }
@@ -5629,7 +5601,7 @@ function parseTTranslation(node, ctx) {
5629
5601
  // -----------------------------------------------------------------------------
5630
5602
  function wrapInTTranslationContextAST(r, translationCtx) {
5631
5603
  const ast = {
5632
- type: 17 /* TTranslationContext */,
5604
+ type: 16 /* ASTType.TTranslationContext */,
5633
5605
  content: r,
5634
5606
  translationCtx,
5635
5607
  };
@@ -5645,7 +5617,7 @@ function parseTTranslationContext(node, ctx) {
5645
5617
  }
5646
5618
  node.removeAttribute("t-translation-context");
5647
5619
  const result = parseNode(node, ctx);
5648
- if ((result === null || result === void 0 ? void 0 : result.type) === 3 /* Multi */) {
5620
+ if ((result === null || result === void 0 ? void 0 : result.type) === 3 /* ASTType.Multi */) {
5649
5621
  const children = result.content.map((c) => wrapInTTranslationContextAST(c, translationCtx));
5650
5622
  return makeASTMulti(children);
5651
5623
  }
@@ -5663,12 +5635,12 @@ function parseTPortal(node, ctx) {
5663
5635
  const content = parseNode(node, ctx);
5664
5636
  if (!content) {
5665
5637
  return {
5666
- type: 0 /* Text */,
5638
+ type: 0 /* ASTType.Text */,
5667
5639
  value: "",
5668
5640
  };
5669
5641
  }
5670
5642
  return {
5671
- type: 18 /* TPortal */,
5643
+ type: 17 /* ASTType.TPortal */,
5672
5644
  target,
5673
5645
  content,
5674
5646
  };
@@ -5684,7 +5656,7 @@ function parseChildren(node, ctx) {
5684
5656
  for (let child of node.childNodes) {
5685
5657
  const childAst = parseNode(child, ctx);
5686
5658
  if (childAst) {
5687
- if (childAst.type === 3 /* Multi */) {
5659
+ if (childAst.type === 3 /* ASTType.Multi */) {
5688
5660
  children.push(...childAst.content);
5689
5661
  }
5690
5662
  else {
@@ -5695,7 +5667,7 @@ function parseChildren(node, ctx) {
5695
5667
  return children;
5696
5668
  }
5697
5669
  function makeASTMulti(children) {
5698
- const ast = { type: 3 /* Multi */, content: children };
5670
+ const ast = { type: 3 /* ASTType.Multi */, content: children };
5699
5671
  if (children.every((c) => c.hasNoRepresentation)) {
5700
5672
  ast.hasNoRepresentation = true;
5701
5673
  }
@@ -5756,28 +5728,26 @@ function normalizeTIf(el) {
5756
5728
  }
5757
5729
  }
5758
5730
  /**
5759
- * Normalizes the content of an Element so that t-esc directives on components
5760
- * are removed and instead places a <t t-esc=""> as the default slot of the
5731
+ * Normalizes the content of an Element so that t-out directives on components
5732
+ * are removed and instead places a <t t-out=""> as the default slot of the
5761
5733
  * component. Also throws if the component already has content. This function
5762
5734
  * modifies the Element in place.
5763
5735
  *
5764
5736
  * @param el the element containing the tree that should be normalized
5765
5737
  */
5766
- function normalizeTEscTOut(el) {
5767
- for (const d of ["t-esc", "t-out"]) {
5768
- const elements = [...el.querySelectorAll(`[${d}]`)].filter((el) => el.tagName[0] === el.tagName[0].toUpperCase() || el.hasAttribute("t-component"));
5769
- for (const el of elements) {
5770
- if (el.childNodes.length) {
5771
- throw new OwlError(`Cannot have ${d} on a component that already has content`);
5772
- }
5773
- const value = el.getAttribute(d);
5774
- el.removeAttribute(d);
5775
- const t = el.ownerDocument.createElement("t");
5776
- if (value != null) {
5777
- t.setAttribute(d, value);
5778
- }
5779
- el.appendChild(t);
5738
+ function normalizeTOut(el) {
5739
+ const elements = [...el.querySelectorAll(`[t-out]`)].filter((el) => el.tagName[0] === el.tagName[0].toUpperCase() || el.hasAttribute("t-component"));
5740
+ for (const el of elements) {
5741
+ if (el.childNodes.length) {
5742
+ throw new OwlError(`Cannot have t-out on a component that already has content`);
5743
+ }
5744
+ const value = el.getAttribute("t-out");
5745
+ el.removeAttribute("t-out");
5746
+ const t = el.ownerDocument.createElement("t");
5747
+ if (value != null) {
5748
+ t.setAttribute("t-out", value);
5780
5749
  }
5750
+ el.appendChild(t);
5781
5751
  }
5782
5752
  }
5783
5753
  /**
@@ -5788,7 +5758,7 @@ function normalizeTEscTOut(el) {
5788
5758
  */
5789
5759
  function normalizeXML(el) {
5790
5760
  normalizeTIf(el);
5791
- normalizeTEscTOut(el);
5761
+ normalizeTOut(el);
5792
5762
  }
5793
5763
 
5794
5764
  function compile(template, options = {
@@ -5799,7 +5769,7 @@ function compile(template, options = {
5799
5769
  // some work
5800
5770
  const hasSafeContext = template instanceof Node
5801
5771
  ? !(template instanceof Element) || template.querySelector("[t-set], [t-call]") === null
5802
- : !template.includes("t-set") && !template.includes("t-call");
5772
+ : !template.includes("t-set=") && !template.includes("t-call=");
5803
5773
  // code generation
5804
5774
  const codeGenerator = new CodeGenerator(ast, { ...options, hasSafeContext });
5805
5775
  const code = codeGenerator.generateCode();
@@ -5817,7 +5787,7 @@ function compile(template, options = {
5817
5787
  }
5818
5788
 
5819
5789
  // do not modify manually. This file is generated by the release script.
5820
- const version = "2.8.1";
5790
+ const version = "3.0.0-alpha";
5821
5791
 
5822
5792
  // -----------------------------------------------------------------------------
5823
5793
  // Scheduler
@@ -5849,7 +5819,7 @@ class Scheduler {
5849
5819
  let renders = this.delayedRenders;
5850
5820
  this.delayedRenders = [];
5851
5821
  for (let f of renders) {
5852
- if (f.root && f.node.status !== 3 /* DESTROYED */ && f.node.fiber === f) {
5822
+ if (f.root && f.node.status !== 3 /* STATUS.DESTROYED */ && f.node.fiber === f) {
5853
5823
  f.render();
5854
5824
  }
5855
5825
  }
@@ -5872,7 +5842,7 @@ class Scheduler {
5872
5842
  this.processFiber(task);
5873
5843
  }
5874
5844
  for (let task of this.tasks) {
5875
- if (task.node.status === 3 /* DESTROYED */) {
5845
+ if (task.node.status === 3 /* STATUS.DESTROYED */) {
5876
5846
  this.tasks.delete(task);
5877
5847
  }
5878
5848
  }
@@ -5888,7 +5858,7 @@ class Scheduler {
5888
5858
  this.tasks.delete(fiber);
5889
5859
  return;
5890
5860
  }
5891
- if (fiber.node.status === 3 /* DESTROYED */) {
5861
+ if (fiber.node.status === 3 /* STATUS.DESTROYED */) {
5892
5862
  this.tasks.delete(fiber);
5893
5863
  return;
5894
5864
  }
@@ -5913,105 +5883,94 @@ Scheduler.requestAnimationFrame = window.requestAnimationFrame.bind(window);
5913
5883
 
5914
5884
  let hasBeenLogged = false;
5915
5885
  const apps = new Set();
5916
- window.__OWL_DEVTOOLS__ || (window.__OWL_DEVTOOLS__ = { apps, Fiber, RootFiber, toRaw, reactive });
5886
+ window.__OWL_DEVTOOLS__ || (window.__OWL_DEVTOOLS__ = { apps, Fiber, RootFiber, toRaw, proxy });
5917
5887
  class App extends TemplateSet {
5918
- constructor(Root, config = {}) {
5888
+ constructor(config = {}) {
5919
5889
  super(config);
5920
5890
  this.scheduler = new Scheduler();
5921
- this.subRoots = new Set();
5922
- this.root = null;
5891
+ this.roots = new Set();
5923
5892
  this.name = config.name || "";
5924
- this.Root = Root;
5925
5893
  apps.add(this);
5894
+ this.pluginManager = config.pluginManager || new PluginManager(null);
5895
+ if (config.plugins) {
5896
+ this.pluginManager.startPlugins(config.plugins);
5897
+ }
5926
5898
  if (config.test) {
5927
5899
  this.dev = true;
5928
5900
  }
5929
- this.warnIfNoStaticProps = config.warnIfNoStaticProps || false;
5930
5901
  if (this.dev && !config.test && !hasBeenLogged) {
5931
5902
  console.info(`Owl is running in 'dev' mode.`);
5932
5903
  hasBeenLogged = true;
5933
5904
  }
5934
- const env = config.env || {};
5935
- const descrs = Object.getOwnPropertyDescriptors(env);
5936
- this.env = Object.freeze(Object.create(Object.getPrototypeOf(env), descrs));
5937
- this.props = config.props || {};
5938
- }
5939
- mount(target, options) {
5940
- const root = this.createRoot(this.Root, { props: this.props });
5941
- this.root = root.node;
5942
- this.subRoots.delete(root.node);
5943
- return root.mount(target, options);
5944
5905
  }
5945
5906
  createRoot(Root, config = {}) {
5946
5907
  const props = config.props || {};
5947
- // hack to make sure the sub root get the sub env if necessary. for owl 3,
5948
- // would be nice to rethink the initialization process to make sure that
5949
- // we can create a ComponentNode and give it explicitely the env, instead
5950
- // of looking it up in the app
5951
- const env = this.env;
5952
- if (config.env) {
5953
- this.env = config.env;
5954
- }
5908
+ let resolve;
5909
+ let reject;
5910
+ const promise = new Promise((res, rej) => {
5911
+ resolve = res;
5912
+ reject = rej;
5913
+ });
5955
5914
  const restore = saveCurrent();
5956
- const node = this.makeNode(Root, props);
5957
- restore();
5958
- if (config.env) {
5959
- this.env = env;
5915
+ let node;
5916
+ let error = null;
5917
+ try {
5918
+ node = this.makeNode(Root, props);
5960
5919
  }
5961
- this.subRoots.add(node);
5962
- return {
5963
- node,
5920
+ catch (e) {
5921
+ error = e;
5922
+ reject(e);
5923
+ }
5924
+ finally {
5925
+ restore();
5926
+ }
5927
+ const root = {
5928
+ node: node,
5929
+ promise,
5964
5930
  mount: (target, options) => {
5965
- App.validateTarget(target);
5966
- if (this.dev) {
5967
- validateProps(Root, props, { __owl__: { app: this } });
5931
+ if (error) {
5932
+ return promise;
5968
5933
  }
5969
- const prom = this.mountNode(node, target, options);
5970
- return prom;
5934
+ App.validateTarget(target);
5935
+ this.mountNode(node, target, resolve, reject, options);
5936
+ return promise;
5971
5937
  },
5972
5938
  destroy: () => {
5973
- this.subRoots.delete(node);
5939
+ this.roots.delete(root);
5974
5940
  node.destroy();
5975
5941
  this.scheduler.processTasks();
5976
5942
  },
5977
5943
  };
5944
+ this.roots.add(root);
5945
+ return root;
5978
5946
  }
5979
5947
  makeNode(Component, props) {
5980
5948
  return new ComponentNode(Component, props, this, null, null);
5981
5949
  }
5982
- mountNode(node, target, options) {
5983
- const promise = new Promise((resolve, reject) => {
5984
- let isResolved = false;
5985
- // manually set a onMounted callback.
5986
- // that way, we are independant from the current node.
5987
- node.mounted.push(() => {
5988
- resolve(node.component);
5989
- isResolved = true;
5990
- });
5991
- // Manually add the last resort error handler on the node
5992
- let handlers = nodeErrorHandlers.get(node);
5993
- if (!handlers) {
5994
- handlers = [];
5995
- nodeErrorHandlers.set(node, handlers);
5996
- }
5997
- handlers.unshift((e) => {
5998
- if (!isResolved) {
5999
- reject(e);
6000
- }
6001
- throw e;
6002
- });
5950
+ mountNode(node, target, resolve, reject, options) {
5951
+ // Manually add the last resort error handler on the node
5952
+ let handlers = nodeErrorHandlers.get(node);
5953
+ if (!handlers) {
5954
+ handlers = [];
5955
+ nodeErrorHandlers.set(node, handlers);
5956
+ }
5957
+ handlers.unshift((e, finalize) => {
5958
+ const finalError = finalize();
5959
+ reject(finalError);
5960
+ });
5961
+ // manually set a onMounted callback.
5962
+ // that way, we are independant from the current node.
5963
+ node.mounted.push(() => {
5964
+ resolve(node.component);
5965
+ handlers.shift();
6003
5966
  });
6004
5967
  node.mountComponent(target, options);
6005
- return promise;
6006
5968
  }
6007
5969
  destroy() {
6008
- if (this.root) {
6009
- for (let subroot of this.subRoots) {
6010
- subroot.destroy();
6011
- }
6012
- this.root.destroy();
6013
- this.scheduler.processTasks();
5970
+ for (let root of this.roots) {
5971
+ root.destroy();
6014
5972
  }
5973
+ this.scheduler.processTasks();
6015
5974
  apps.delete(this);
6016
5975
  }
6017
5976
  createComponent(name, isStatic, hasSlotsProp, hasDynamicPropList, propList) {
@@ -6090,7 +6049,9 @@ App.validateTarget = validateTarget;
6090
6049
  App.apps = apps;
6091
6050
  App.version = version;
6092
6051
  async function mount(C, target, config = {}) {
6093
- return new App(C, config).mount(target, config);
6052
+ const app = new App(config);
6053
+ const root = app.createRoot(C, config);
6054
+ return root.mount(target, config);
6094
6055
  }
6095
6056
 
6096
6057
  const mainEventHandler = (data, ev, currentTarget) => {
@@ -6131,72 +6092,227 @@ const mainEventHandler = (data, ev, currentTarget) => {
6131
6092
  throw new OwlError(`Invalid handler (expected a function, received: '${handler}')`);
6132
6093
  }
6133
6094
  let node = data[1] ? data[1].__owl__ : null;
6134
- if (node ? node.status === 1 /* MOUNTED */ : true) {
6095
+ if (node ? node.status === 1 /* STATUS.MOUNTED */ : true) {
6135
6096
  handler.call(node ? node.component : null, ev);
6136
6097
  }
6137
6098
  }
6138
6099
  return stopped;
6139
6100
  };
6140
6101
 
6141
- function status(component) {
6142
- switch (component.__owl__.status) {
6143
- case 0 /* NEW */:
6144
- return "new";
6145
- case 2 /* CANCELLED */:
6146
- return "cancelled";
6147
- case 1 /* MOUNTED */:
6148
- return "mounted";
6149
- case 3 /* DESTROYED */:
6150
- return "destroyed";
6102
+ function computed(fn, opts) {
6103
+ // todo: handle cleanup
6104
+ let derivedComputation;
6105
+ return () => {
6106
+ derivedComputation !== null && derivedComputation !== void 0 ? derivedComputation : (derivedComputation = {
6107
+ state: ComputationState.STALE,
6108
+ sources: new Set(),
6109
+ compute: () => {
6110
+ onWriteAtom(derivedComputation);
6111
+ return fn();
6112
+ },
6113
+ isDerived: true,
6114
+ value: undefined,
6115
+ observers: new Set(),
6116
+ name: opts === null || opts === void 0 ? void 0 : opts.name,
6117
+ });
6118
+ updateComputation(derivedComputation);
6119
+ return derivedComputation.value;
6120
+ };
6121
+ }
6122
+
6123
+ function signal(value, opts) {
6124
+ const atom = {
6125
+ value,
6126
+ observers: new Set(),
6127
+ name: opts === null || opts === void 0 ? void 0 : opts.name,
6128
+ };
6129
+ const read = () => {
6130
+ onReadAtom(atom);
6131
+ return atom.value;
6132
+ };
6133
+ const write = (newValue) => {
6134
+ if (typeof newValue === "function") {
6135
+ newValue = newValue(atom.value);
6136
+ }
6137
+ if (Object.is(atom.value, newValue))
6138
+ return;
6139
+ atom.value = newValue;
6140
+ onWriteAtom(atom);
6141
+ };
6142
+ read.set = write;
6143
+ read.update = (updater) => {
6144
+ if (updater) {
6145
+ write(updater(atom.value));
6146
+ }
6147
+ else {
6148
+ onWriteAtom(atom);
6149
+ }
6150
+ };
6151
+ return read;
6152
+ }
6153
+
6154
+ class Resource {
6155
+ constructor(name, type) {
6156
+ this._items = signal([]);
6157
+ this.items = computed(() => {
6158
+ return this._items()
6159
+ .sort((el1, el2) => el1[0] - el2[0])
6160
+ .map((elem) => elem[1]);
6161
+ });
6162
+ this._name = name || "resource";
6163
+ this._type = type;
6164
+ }
6165
+ add(item, sequence = 50) {
6166
+ if (this._type) {
6167
+ const error = validateType("item", item, this._type);
6168
+ if (error) {
6169
+ throw new Error(`Invalid type: ${error} (resource '${this._name}')`);
6170
+ }
6171
+ }
6172
+ this._items().push([sequence, item]);
6173
+ this._items.update();
6174
+ return this;
6175
+ }
6176
+ remove(item) {
6177
+ const items = this._items().filter(([seq, val]) => val !== item);
6178
+ this._items.set(items);
6179
+ return this;
6180
+ }
6181
+ has(item) {
6182
+ return this._items().some(([s, value]) => value === item);
6183
+ }
6184
+ }
6185
+ function useResource(r, elements) {
6186
+ for (let elem of elements) {
6187
+ r.add(elem);
6151
6188
  }
6189
+ onWillDestroy(() => {
6190
+ for (let elem of elements) {
6191
+ r.remove(elem);
6192
+ }
6193
+ });
6152
6194
  }
6153
6195
 
6154
- // -----------------------------------------------------------------------------
6155
- // useRef
6156
- // -----------------------------------------------------------------------------
6157
- /**
6158
- * The purpose of this hook is to allow components to get a reference to a sub
6159
- * html node or component.
6160
- */
6161
- function useRef(name) {
6162
- const node = getCurrent();
6163
- const refs = node.refs;
6164
- return {
6165
- get el() {
6166
- const el = refs[name];
6167
- return inOwnerDocument(el) ? el : null;
6196
+ class Registry {
6197
+ constructor(name, type) {
6198
+ this._map = signal(Object.create(null));
6199
+ this.entries = computed(() => {
6200
+ const entries = Object.entries(this._map())
6201
+ .sort((el1, el2) => el1[1][0] - el2[1][0])
6202
+ .map(([str, elem]) => [str, elem[1]]);
6203
+ return entries;
6204
+ });
6205
+ this.items = computed(() => this.entries().map((e) => e[1]));
6206
+ this._name = name || "registry";
6207
+ this._type = type;
6208
+ }
6209
+ addById(item, sequence = 50) {
6210
+ if (!item.id) {
6211
+ throw new Error(`Item should have an id key`);
6212
+ }
6213
+ return this.add(item.id, item, sequence);
6214
+ }
6215
+ add(key, value, sequence = 50) {
6216
+ if (this._type) {
6217
+ const error = validateType(key, value, this._type);
6218
+ // todo: move error handling in validation.js
6219
+ if (error) {
6220
+ throw new Error("Invalid type: " + error);
6221
+ }
6222
+ }
6223
+ this._map()[key] = [sequence, value];
6224
+ this._map.update();
6225
+ return this;
6226
+ }
6227
+ get(key, defaultValue) {
6228
+ const hasKey = key in this._map();
6229
+ if (!hasKey && arguments.length < 2) {
6230
+ throw new Error(`KeyNotFoundError: Cannot find key "${key}" in this registry`);
6231
+ }
6232
+ return hasKey ? this._map()[key][1] : defaultValue;
6233
+ }
6234
+ remove(key) {
6235
+ delete this._map()[key];
6236
+ this._map.update();
6237
+ }
6238
+ has(key) {
6239
+ return key in this._map();
6240
+ }
6241
+ }
6242
+
6243
+ function status() {
6244
+ const pm = _getCurrentPluginManager();
6245
+ const node = pm || getCurrent();
6246
+ return () => {
6247
+ switch (node.status) {
6248
+ case 0 /* STATUS.NEW */:
6249
+ return "new";
6250
+ case 2 /* STATUS.CANCELLED */:
6251
+ return "cancelled";
6252
+ case 1 /* STATUS.MOUNTED */:
6253
+ return pm ? "started" : "mounted";
6254
+ case 3 /* STATUS.DESTROYED */:
6255
+ return "destroyed";
6256
+ }
6257
+ };
6258
+ }
6259
+
6260
+ function effect(fn, opts) {
6261
+ var _a, _b, _c;
6262
+ const effectComputation = {
6263
+ state: ComputationState.STALE,
6264
+ value: undefined,
6265
+ compute() {
6266
+ // In case the cleanup read an atom.
6267
+ // todo: test it
6268
+ setComputation(undefined);
6269
+ // CurrentComputation = undefined!;
6270
+ // `removeSources` is made by `runComputation`.
6271
+ unsubscribeEffect(effectComputation);
6272
+ setComputation(effectComputation);
6273
+ // CurrentComputation = effectComputation;
6274
+ return fn();
6168
6275
  },
6276
+ sources: new Set(),
6277
+ childrenEffect: [],
6278
+ name: opts === null || opts === void 0 ? void 0 : opts.name,
6279
+ };
6280
+ (_c = (_b = (_a = getCurrentComputation()) === null || _a === void 0 ? void 0 : _a.childrenEffect) === null || _b === void 0 ? void 0 : _b.push) === null || _c === void 0 ? void 0 : _c.call(_b, effectComputation);
6281
+ updateComputation(effectComputation);
6282
+ // Remove sources and unsubscribe
6283
+ return () => {
6284
+ // In case the cleanup read an atom.
6285
+ // todo: test it
6286
+ const previousComputation = getCurrentComputation();
6287
+ setComputation(undefined);
6288
+ unsubscribeEffect(effectComputation);
6289
+ setComputation(previousComputation);
6169
6290
  };
6170
6291
  }
6292
+ function unsubscribeEffect(effectComputation) {
6293
+ removeSources(effectComputation);
6294
+ cleanupEffect(effectComputation);
6295
+ for (const children of effectComputation.childrenEffect) {
6296
+ // Consider it executed to avoid it's re-execution
6297
+ // todo: make a test for it
6298
+ children.state = ComputationState.EXECUTED;
6299
+ removeSources(children);
6300
+ unsubscribeEffect(children);
6301
+ }
6302
+ effectComputation.childrenEffect.length = 0;
6303
+ }
6304
+ function cleanupEffect(computation) {
6305
+ // the computation.value of an effect is a cleanup function
6306
+ const cleanupFn = computation.value;
6307
+ if (cleanupFn && typeof cleanupFn === "function") {
6308
+ cleanupFn();
6309
+ computation.value = undefined;
6310
+ }
6311
+ }
6312
+
6171
6313
  // -----------------------------------------------------------------------------
6172
- // useEnv and useSubEnv
6314
+ // useEffect
6173
6315
  // -----------------------------------------------------------------------------
6174
- /**
6175
- * This hook is useful as a building block for some customized hooks, that may
6176
- * need a reference to the env of the component calling them.
6177
- */
6178
- function useEnv() {
6179
- return getCurrent().component.env;
6180
- }
6181
- function extendEnv(currentEnv, extension) {
6182
- const env = Object.create(currentEnv);
6183
- const descrs = Object.getOwnPropertyDescriptors(extension);
6184
- return Object.freeze(Object.defineProperties(env, descrs));
6185
- }
6186
- /**
6187
- * This hook is a simple way to let components use a sub environment. Note that
6188
- * like for all hooks, it is important that this is only called in the
6189
- * constructor method.
6190
- */
6191
- function useSubEnv(envExtension) {
6192
- const node = getCurrent();
6193
- node.component.env = extendEnv(node.component.env, envExtension);
6194
- useChildSubEnv(envExtension);
6195
- }
6196
- function useChildSubEnv(envExtension) {
6197
- const node = getCurrent();
6198
- node.childEnv = extendEnv(node.childEnv, envExtension);
6199
- }
6200
6316
  /**
6201
6317
  * This hook will run a callback when a component is mounted and patched, and
6202
6318
  * will run a cleanup function before patching and before unmounting the
@@ -6210,32 +6326,15 @@ function useChildSubEnv(envExtension) {
6210
6326
  * again. The default value returns an array containing only NaN because
6211
6327
  * NaN !== NaN, which will cause the effect to rerun on every patch.
6212
6328
  */
6213
- function useEffect(effect, computeDependencies = () => [NaN]) {
6214
- let cleanup;
6215
- let dependencies;
6216
- onMounted(() => {
6217
- dependencies = computeDependencies();
6218
- cleanup = effect(...dependencies);
6219
- });
6220
- onPatched(() => {
6221
- const newDeps = computeDependencies();
6222
- const shouldReapply = newDeps.some((val, i) => val !== dependencies[i]);
6223
- if (shouldReapply) {
6224
- dependencies = newDeps;
6225
- if (cleanup) {
6226
- cleanup();
6227
- }
6228
- cleanup = effect(...dependencies);
6229
- }
6230
- });
6231
- onWillUnmount(() => cleanup && cleanup());
6329
+ function useEffect(fn) {
6330
+ onWillDestroy(effect(fn));
6232
6331
  }
6233
6332
  // -----------------------------------------------------------------------------
6234
- // useExternalListener
6333
+ // useListener
6235
6334
  // -----------------------------------------------------------------------------
6236
6335
  /**
6237
6336
  * When a component needs to listen to DOM Events on element(s) that are not
6238
- * part of his hierarchy, we can use the `useExternalListener` hook.
6337
+ * part of his hierarchy, we can use the `useListener` hook.
6239
6338
  * It will correctly add and remove the event listener, whenever the
6240
6339
  * component is mounted and unmounted.
6241
6340
  *
@@ -6244,13 +6343,20 @@ function useEffect(effect, computeDependencies = () => [NaN]) {
6244
6343
  *
6245
6344
  * Usage:
6246
6345
  * in the constructor of the OWL component that needs to be notified,
6247
- * `useExternalListener(window, 'click', this._doSomething);`
6346
+ * `useListener(window, 'click', this._doSomething);`
6248
6347
  * */
6249
- function useExternalListener(target, eventName, handler, eventParams) {
6348
+ function useListener(target, eventName, handler, eventParams) {
6250
6349
  const node = getCurrent();
6251
6350
  const boundHandler = handler.bind(node.component);
6252
6351
  onMounted(() => target.addEventListener(eventName, boundHandler, eventParams));
6253
6352
  onWillUnmount(() => target.removeEventListener(eventName, boundHandler, eventParams));
6353
+ }
6354
+ function usePlugins(Plugins) {
6355
+ const node = getCurrent();
6356
+ const manager = new PluginManager(node.pluginManager);
6357
+ node.pluginManager = manager;
6358
+ onWillDestroy(() => manager.destroy());
6359
+ return manager.startPlugins(Plugins);
6254
6360
  }
6255
6361
 
6256
6362
  config.shouldNormalizeDom = false;
@@ -6289,41 +6395,45 @@ exports.App = App;
6289
6395
  exports.Component = Component;
6290
6396
  exports.EventBus = EventBus;
6291
6397
  exports.OwlError = OwlError;
6398
+ exports.Plugin = Plugin;
6399
+ exports.PluginManager = PluginManager;
6400
+ exports.Registry = Registry;
6401
+ exports.Resource = Resource;
6292
6402
  exports.__info__ = __info__;
6293
6403
  exports.batched = batched;
6294
6404
  exports.blockDom = blockDom;
6405
+ exports.computed = computed;
6406
+ exports.effect = effect;
6295
6407
  exports.htmlEscape = htmlEscape;
6296
- exports.loadFile = loadFile;
6297
6408
  exports.markRaw = markRaw;
6298
6409
  exports.markup = markup;
6299
6410
  exports.mount = mount;
6300
6411
  exports.onError = onError;
6301
6412
  exports.onMounted = onMounted;
6302
6413
  exports.onPatched = onPatched;
6303
- exports.onRendered = onRendered;
6304
6414
  exports.onWillDestroy = onWillDestroy;
6305
6415
  exports.onWillPatch = onWillPatch;
6306
- exports.onWillRender = onWillRender;
6307
6416
  exports.onWillStart = onWillStart;
6308
6417
  exports.onWillUnmount = onWillUnmount;
6309
6418
  exports.onWillUpdateProps = onWillUpdateProps;
6310
- exports.reactive = reactive;
6419
+ exports.plugin = plugin;
6420
+ exports.props = props;
6421
+ exports.proxy = proxy;
6422
+ exports.signal = signal;
6311
6423
  exports.status = status;
6312
6424
  exports.toRaw = toRaw;
6313
- exports.useChildSubEnv = useChildSubEnv;
6425
+ exports.untrack = untrack;
6314
6426
  exports.useComponent = useComponent;
6315
6427
  exports.useEffect = useEffect;
6316
- exports.useEnv = useEnv;
6317
- exports.useExternalListener = useExternalListener;
6318
- exports.useRef = useRef;
6319
- exports.useState = useState;
6320
- exports.useSubEnv = useSubEnv;
6428
+ exports.useListener = useListener;
6429
+ exports.usePlugins = usePlugins;
6430
+ exports.useResource = useResource;
6321
6431
  exports.validate = validate;
6322
6432
  exports.validateType = validateType;
6323
6433
  exports.whenReady = whenReady;
6324
6434
  exports.xml = xml;
6325
6435
 
6326
6436
 
6327
- __info__.date = '2025-09-23T07:17:45.055Z';
6328
- __info__.hash = '5211116';
6437
+ __info__.date = '2025-12-22T15:42:39.839Z';
6438
+ __info__.hash = '169a8cc';
6329
6439
  __info__.url = 'https://github.com/odoo/owl';