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