@odoo/owl 3.0.0-alpha.20 → 3.0.0-alpha.21

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 (61) hide show
  1. package/README.md +100 -110
  2. package/dist/compile_templates.mjs +84 -80
  3. package/dist/compiler.js +2356 -2356
  4. package/dist/owl-devtools.zip +0 -0
  5. package/dist/owl.cjs.js +1596 -1459
  6. package/dist/owl.cjs.runtime.js +4362 -3952
  7. package/dist/owl.es.js +1596 -1459
  8. package/dist/owl.es.runtime.js +4360 -3950
  9. package/dist/owl.iife.js +1596 -1459
  10. package/dist/owl.iife.min.js +1 -1
  11. package/dist/owl.iife.runtime.js +4363 -3953
  12. package/dist/owl.iife.runtime.min.js +1 -1
  13. package/dist/types/common/types.d.ts +1 -1
  14. package/dist/types/compiler/code_generator.d.ts +1 -2
  15. package/dist/types/compiler/inline_expressions.d.ts +7 -24
  16. package/dist/types/compiler/parser.d.ts +3 -8
  17. package/dist/types/owl.d.ts +33 -27
  18. package/dist/types/runtime/blockdom/attributes.d.ts +2 -0
  19. package/dist/types/runtime/component_node.d.ts +2 -0
  20. package/dist/types/runtime/error_handling.d.ts +13 -13
  21. package/dist/types/runtime/fibers.d.ts +37 -37
  22. package/dist/types/runtime/plugin_manager.d.ts +2 -0
  23. package/dist/types/runtime/portal.d.ts +15 -12
  24. package/dist/types/runtime/reactivity/computations.d.ts +1 -0
  25. package/dist/types/runtime/reactivity.d.ts +57 -57
  26. package/dist/types/runtime/rendering/scheduler.d.ts +1 -1
  27. package/dist/types/runtime/rendering/template_helpers.d.ts +2 -2
  28. package/dist/types/runtime/scheduler.d.ts +21 -21
  29. package/dist/types/runtime/types.d.ts +3 -0
  30. package/dist/types/version.d.ts +1 -1
  31. package/package.json +24 -1
  32. package/dist/types/runtime/cancellableContext.d.ts +0 -15
  33. package/dist/types/runtime/cancellablePromise.d.ts +0 -15
  34. package/dist/types/runtime/executionContext.d.ts +0 -0
  35. package/dist/types/runtime/listOperation.d.ts +0 -1
  36. package/dist/types/runtime/model.d.ts +0 -48
  37. package/dist/types/runtime/plugins.d.ts +0 -36
  38. package/dist/types/runtime/reactivity/async_computed.d.ts +0 -1
  39. package/dist/types/runtime/reactivity/derived.d.ts +0 -7
  40. package/dist/types/runtime/reactivity/reactivity.d.ts +0 -46
  41. package/dist/types/runtime/reactivity/signals.d.ts +0 -30
  42. package/dist/types/runtime/reactivity/state.d.ts +0 -48
  43. package/dist/types/runtime/relationalModel/discussModel.d.ts +0 -19
  44. package/dist/types/runtime/relationalModel/discussModelTypes.d.ts +0 -22
  45. package/dist/types/runtime/relationalModel/field.d.ts +0 -20
  46. package/dist/types/runtime/relationalModel/model.d.ts +0 -59
  47. package/dist/types/runtime/relationalModel/modelData.d.ts +0 -18
  48. package/dist/types/runtime/relationalModel/modelRegistry.d.ts +0 -3
  49. package/dist/types/runtime/relationalModel/modelUtils.d.ts +0 -4
  50. package/dist/types/runtime/relationalModel/store.d.ts +0 -16
  51. package/dist/types/runtime/relationalModel/types.d.ts +0 -83
  52. package/dist/types/runtime/relationalModel/util.d.ts +0 -1
  53. package/dist/types/runtime/relationalModel/web/WebDataPoint.d.ts +0 -25
  54. package/dist/types/runtime/relationalModel/web/WebRecord.d.ts +0 -131
  55. package/dist/types/runtime/relationalModel/web/WebStaticList.d.ts +0 -63
  56. package/dist/types/runtime/relationalModel/web/webModel.d.ts +0 -5
  57. package/dist/types/runtime/relationalModel/web/webModelTypes.d.ts +0 -139
  58. package/dist/types/runtime/signals.d.ts +0 -19
  59. package/dist/types/runtime/suspense.d.ts +0 -5
  60. package/dist/types/runtime/task.d.ts +0 -12
  61. package/dist/types/utils/registry.d.ts +0 -15
package/dist/owl.iife.js CHANGED
@@ -130,10 +130,16 @@
130
130
  return new VToggler(key, child);
131
131
  }
132
132
 
133
- const { setAttribute: elemSetAttribute, removeAttribute } = Element.prototype;
134
- const tokenList = DOMTokenList.prototype;
135
- const tokenListAdd = tokenList.add;
136
- const tokenListRemove = tokenList.remove;
133
+ let elemSetAttribute;
134
+ let removeAttribute;
135
+ let tokenListAdd;
136
+ let tokenListRemove;
137
+ if (typeof Element !== "undefined") {
138
+ ({ setAttribute: elemSetAttribute, removeAttribute } = Element.prototype);
139
+ const tokenList = DOMTokenList.prototype;
140
+ tokenListAdd = tokenList.add;
141
+ tokenListRemove = tokenList.remove;
142
+ }
137
143
  const isArray = Array.isArray;
138
144
  const { split, trim } = String.prototype;
139
145
  const wordRegexp = /\s+/;
@@ -165,6 +171,9 @@
165
171
  if (attrs[0] === "class") {
166
172
  setClass.call(this, attrs[1]);
167
173
  }
174
+ else if (attrs[0] === "style") {
175
+ setStyle.call(this, attrs[1]);
176
+ }
168
177
  else {
169
178
  setAttribute.call(this, attrs[0], attrs[1]);
170
179
  }
@@ -174,6 +183,9 @@
174
183
  if (k === "class") {
175
184
  setClass.call(this, attrs[k]);
176
185
  }
186
+ else if (k === "style") {
187
+ setStyle.call(this, attrs[k]);
188
+ }
177
189
  else {
178
190
  setAttribute.call(this, k, attrs[k]);
179
191
  }
@@ -191,6 +203,9 @@
191
203
  if (name === "class") {
192
204
  updateClass.call(this, val, oldAttrs[1]);
193
205
  }
206
+ else if (name === "style") {
207
+ updateStyle.call(this, val, oldAttrs[1]);
208
+ }
194
209
  else {
195
210
  setAttribute.call(this, name, val);
196
211
  }
@@ -206,6 +221,9 @@
206
221
  if (k === "class") {
207
222
  updateClass.call(this, "", oldAttrs[k]);
208
223
  }
224
+ else if (k === "style") {
225
+ updateStyle.call(this, "", oldAttrs[k]);
226
+ }
209
227
  else {
210
228
  removeAttribute.call(this, k);
211
229
  }
@@ -217,6 +235,9 @@
217
235
  if (k === "class") {
218
236
  updateClass.call(this, val, oldAttrs[k]);
219
237
  }
238
+ else if (k === "style") {
239
+ updateStyle.call(this, val, oldAttrs[k]);
240
+ }
220
241
  else {
221
242
  setAttribute.call(this, k, val);
222
243
  }
@@ -289,6 +310,80 @@
289
310
  }
290
311
  }
291
312
  }
313
+ // ---------------------------------------------------------------------------
314
+ // Style
315
+ // ---------------------------------------------------------------------------
316
+ const CSS_PROP_CACHE = {};
317
+ function toKebabCase(prop) {
318
+ if (prop in CSS_PROP_CACHE) {
319
+ return CSS_PROP_CACHE[prop];
320
+ }
321
+ const result = prop.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase());
322
+ CSS_PROP_CACHE[prop] = result;
323
+ return result;
324
+ }
325
+ function toStyleObj(expr) {
326
+ const result = {};
327
+ switch (typeof expr) {
328
+ case "string": {
329
+ const str = trim.call(expr);
330
+ if (!str) {
331
+ return {};
332
+ }
333
+ const parts = str.split(";");
334
+ for (let part of parts) {
335
+ part = trim.call(part);
336
+ if (!part) {
337
+ continue;
338
+ }
339
+ const colonIdx = part.indexOf(":");
340
+ if (colonIdx === -1) {
341
+ continue;
342
+ }
343
+ const prop = trim.call(part.slice(0, colonIdx));
344
+ const value = trim.call(part.slice(colonIdx + 1));
345
+ if (prop && value) {
346
+ result[prop] = value;
347
+ }
348
+ }
349
+ return result;
350
+ }
351
+ case "object":
352
+ for (let prop in expr) {
353
+ const value = expr[prop];
354
+ if (value || value === 0) {
355
+ result[toKebabCase(prop)] = String(value);
356
+ }
357
+ }
358
+ return result;
359
+ default:
360
+ return {};
361
+ }
362
+ }
363
+ function setStyle(val) {
364
+ val = val === "" ? {} : toStyleObj(val);
365
+ const style = this.style;
366
+ for (let prop in val) {
367
+ style.setProperty(prop, val[prop]);
368
+ }
369
+ }
370
+ function updateStyle(val, oldVal) {
371
+ oldVal = oldVal === "" ? {} : toStyleObj(oldVal);
372
+ val = val === "" ? {} : toStyleObj(val);
373
+ const style = this.style;
374
+ // remove old styles
375
+ for (let prop in oldVal) {
376
+ if (!(prop in val)) {
377
+ style.removeProperty(prop);
378
+ }
379
+ }
380
+ // set new/changed styles
381
+ for (let prop in val) {
382
+ if (val[prop] !== oldVal[prop]) {
383
+ style.setProperty(prop, val[prop]);
384
+ }
385
+ }
386
+ }
292
387
 
293
388
  /**
294
389
  * Creates a batched version of a callback so that all calls to it in the same
@@ -424,16 +519,17 @@
424
519
  function createEventHandler(rawEvent) {
425
520
  const eventName = rawEvent.split(".")[0];
426
521
  const capture = rawEvent.includes(".capture");
522
+ const passive = rawEvent.includes(".passive");
427
523
  if (rawEvent.includes(".synthetic")) {
428
- return createSyntheticHandler(eventName, capture);
524
+ return createSyntheticHandler(eventName, capture, passive);
429
525
  }
430
526
  else {
431
- return createElementHandler(eventName, capture);
527
+ return createElementHandler(eventName, capture, passive);
432
528
  }
433
529
  }
434
530
  // Native listener
435
531
  let nextNativeEventId = 1;
436
- function createElementHandler(evName, capture = false) {
532
+ function createElementHandler(evName, capture = false, passive = false) {
437
533
  let eventKey = `__event__${evName}_${nextNativeEventId++}`;
438
534
  if (capture) {
439
535
  eventKey = `${eventKey}_capture`;
@@ -447,13 +543,14 @@
447
543
  return;
448
544
  config$1.mainEventHandler(data, ev, currentTarget);
449
545
  }
546
+ const options = { capture, passive };
450
547
  function setup(data) {
451
548
  this[eventKey] = data;
452
- this.addEventListener(evName, listener, { capture });
549
+ this.addEventListener(evName, listener, options);
453
550
  }
454
551
  function remove() {
455
552
  delete this[eventKey];
456
- this.removeEventListener(evName, listener, { capture });
553
+ this.removeEventListener(evName, listener, options);
457
554
  }
458
555
  function update(data) {
459
556
  this[eventKey] = data;
@@ -463,12 +560,12 @@
463
560
  // Synthetic handler: a form of event delegation that allows placing only one
464
561
  // listener per event type.
465
562
  let nextSyntheticEventId = 1;
466
- function createSyntheticHandler(evName, capture = false) {
563
+ function createSyntheticHandler(evName, capture = false, passive = false) {
467
564
  let eventKey = `__event__synthetic_${evName}`;
468
565
  if (capture) {
469
566
  eventKey = `${eventKey}_capture`;
470
567
  }
471
- setupSyntheticEvent(evName, eventKey, capture);
568
+ setupSyntheticEvent(evName, eventKey, capture, passive);
472
569
  const currentId = nextSyntheticEventId++;
473
570
  function setup(data) {
474
571
  const _data = this[eventKey] || {};
@@ -495,21 +592,27 @@
495
592
  }
496
593
  }
497
594
  const CONFIGURED_SYNTHETIC_EVENTS = {};
498
- function setupSyntheticEvent(evName, eventKey, capture = false) {
595
+ function setupSyntheticEvent(evName, eventKey, capture = false, passive = false) {
499
596
  if (CONFIGURED_SYNTHETIC_EVENTS[eventKey]) {
500
597
  return;
501
598
  }
502
599
  document.addEventListener(evName, (event) => nativeToSyntheticEvent(eventKey, event), {
503
600
  capture,
601
+ passive,
504
602
  });
505
603
  CONFIGURED_SYNTHETIC_EVENTS[eventKey] = true;
506
604
  }
507
605
 
508
606
  const getDescriptor$3 = (o, p) => Object.getOwnPropertyDescriptor(o, p);
509
- const nodeProto$4 = Node.prototype;
510
- const nodeInsertBefore$3 = nodeProto$4.insertBefore;
511
- const nodeSetTextContent$1 = getDescriptor$3(nodeProto$4, "textContent").set;
512
- const nodeRemoveChild$3 = nodeProto$4.removeChild;
607
+ let nodeInsertBefore$3;
608
+ let nodeSetTextContent$1;
609
+ let nodeRemoveChild$3;
610
+ if (typeof Node !== "undefined") {
611
+ const nodeProto = Node.prototype;
612
+ nodeInsertBefore$3 = nodeProto.insertBefore;
613
+ nodeSetTextContent$1 = getDescriptor$3(nodeProto, "textContent").set;
614
+ nodeRemoveChild$3 = nodeProto.removeChild;
615
+ }
513
616
  // -----------------------------------------------------------------------------
514
617
  // Multi NODE
515
618
  // -----------------------------------------------------------------------------
@@ -649,11 +752,15 @@
649
752
  }
650
753
 
651
754
  const getDescriptor$2 = (o, p) => Object.getOwnPropertyDescriptor(o, p);
652
- const nodeProto$3 = Node.prototype;
653
- const characterDataProto$1 = CharacterData.prototype;
654
- const nodeInsertBefore$2 = nodeProto$3.insertBefore;
655
- const characterDataSetData$1 = getDescriptor$2(characterDataProto$1, "data").set;
656
- const nodeRemoveChild$2 = nodeProto$3.removeChild;
755
+ let nodeInsertBefore$2;
756
+ let characterDataSetData$1;
757
+ let nodeRemoveChild$2;
758
+ if (typeof Node !== "undefined") {
759
+ const nodeProto = Node.prototype;
760
+ nodeInsertBefore$2 = nodeProto.insertBefore;
761
+ nodeRemoveChild$2 = nodeProto.removeChild;
762
+ characterDataSetData$1 = getDescriptor$2(CharacterData.prototype, "data").set;
763
+ }
657
764
  class VSimpleNode {
658
765
  text;
659
766
  parentEl;
@@ -684,7 +791,7 @@
684
791
  return this.text;
685
792
  }
686
793
  }
687
- let VText$1 = class VText extends VSimpleNode {
794
+ class VText extends VSimpleNode {
688
795
  mount(parent, afterNode) {
689
796
  this.mountNode(document.createTextNode(toText(this.text)), parent, afterNode);
690
797
  }
@@ -695,7 +802,7 @@
695
802
  this.text = text2;
696
803
  }
697
804
  }
698
- };
805
+ }
699
806
  class VComment extends VSimpleNode {
700
807
  mount(parent, afterNode) {
701
808
  this.mountNode(document.createComment(toText(this.text)), parent, afterNode);
@@ -703,7 +810,7 @@
703
810
  patch() { }
704
811
  }
705
812
  function text(str) {
706
- return new VText$1(str);
813
+ return new VText(str);
707
814
  }
708
815
  function comment(str) {
709
816
  return new VComment(str);
@@ -722,12 +829,18 @@
722
829
  }
723
830
 
724
831
  const getDescriptor$1 = (o, p) => Object.getOwnPropertyDescriptor(o, p);
725
- const nodeProto$2 = Node.prototype;
726
- const elementProto = Element.prototype;
727
- const characterDataProto = CharacterData.prototype;
728
- const characterDataSetData = getDescriptor$1(characterDataProto, "data").set;
729
- const nodeGetFirstChild = getDescriptor$1(nodeProto$2, "firstChild").get;
730
- const nodeGetNextSibling = getDescriptor$1(nodeProto$2, "nextSibling").get;
832
+ let nodeProto;
833
+ let elementProto;
834
+ let characterDataSetData;
835
+ let nodeGetFirstChild;
836
+ let nodeGetNextSibling;
837
+ if (typeof Node !== "undefined") {
838
+ nodeProto = Node.prototype;
839
+ elementProto = Element.prototype;
840
+ characterDataSetData = getDescriptor$1(CharacterData.prototype, "data").set;
841
+ nodeGetFirstChild = getDescriptor$1(nodeProto, "firstChild").get;
842
+ nodeGetNextSibling = getDescriptor$1(nodeProto, "nextSibling").get;
843
+ }
731
844
  const NO_OP = () => { };
732
845
  function makePropSetter(name) {
733
846
  return function setProp(value) {
@@ -1022,6 +1135,10 @@
1022
1135
  setter = setClass;
1023
1136
  updater = updateClass;
1024
1137
  }
1138
+ else if (info.name === "style") {
1139
+ setter = setStyle;
1140
+ updater = updateStyle;
1141
+ }
1025
1142
  else {
1026
1143
  setter = createAttrUpdater(info.name);
1027
1144
  updater = setter;
@@ -1107,8 +1224,8 @@
1107
1224
  (((c.afterRefIdx ?? 0) & 0x7fff) << 16));
1108
1225
  // these values are defined here to make them faster to lookup in the class
1109
1226
  // block scope
1110
- const nodeCloneNode = nodeProto$2.cloneNode;
1111
- const nodeInsertBefore = nodeProto$2.insertBefore;
1227
+ const nodeCloneNode = nodeProto.cloneNode;
1228
+ const nodeInsertBefore = nodeProto.insertBefore;
1112
1229
  const elementRemove = elementProto.remove;
1113
1230
  class Block {
1114
1231
  el;
@@ -1164,8 +1281,7 @@
1164
1281
  locSetters[i].call(refs[locRefIdxs[i]], data[i]);
1165
1282
  }
1166
1283
  }
1167
- nodeInsertBefore.call(parent, el, afterNode);
1168
- // preparing all children
1284
+ // preparing all children (off-DOM, before inserting el into the live document)
1169
1285
  if (childN) {
1170
1286
  const children = this.children;
1171
1287
  for (let i = 0; i < childN; i++) {
@@ -1179,6 +1295,7 @@
1179
1295
  }
1180
1296
  }
1181
1297
  }
1298
+ nodeInsertBefore.call(parent, el, afterNode);
1182
1299
  this.el = el;
1183
1300
  this.parentEl = parent;
1184
1301
  if (cbRefs.length) {
@@ -1256,11 +1373,17 @@
1256
1373
  }
1257
1374
 
1258
1375
  const getDescriptor = (o, p) => Object.getOwnPropertyDescriptor(o, p);
1259
- const nodeProto$1 = Node.prototype;
1260
- const nodeInsertBefore$1 = nodeProto$1.insertBefore;
1261
- const nodeAppendChild = nodeProto$1.appendChild;
1262
- const nodeRemoveChild$1 = nodeProto$1.removeChild;
1263
- const nodeSetTextContent = getDescriptor(nodeProto$1, "textContent").set;
1376
+ let nodeInsertBefore$1;
1377
+ let nodeAppendChild;
1378
+ let nodeRemoveChild$1;
1379
+ let nodeSetTextContent;
1380
+ if (typeof Node !== "undefined") {
1381
+ const nodeProto = Node.prototype;
1382
+ nodeInsertBefore$1 = nodeProto.insertBefore;
1383
+ nodeAppendChild = nodeProto.appendChild;
1384
+ nodeRemoveChild$1 = nodeProto.removeChild;
1385
+ nodeSetTextContent = getDescriptor(nodeProto, "textContent").set;
1386
+ }
1264
1387
  // -----------------------------------------------------------------------------
1265
1388
  // List Node
1266
1389
  // -----------------------------------------------------------------------------
@@ -1476,9 +1599,13 @@
1476
1599
  return mapping;
1477
1600
  }
1478
1601
 
1479
- const nodeProto = Node.prototype;
1480
- const nodeInsertBefore = nodeProto.insertBefore;
1481
- const nodeRemoveChild = nodeProto.removeChild;
1602
+ let nodeInsertBefore;
1603
+ let nodeRemoveChild;
1604
+ if (typeof Node !== "undefined") {
1605
+ const nodeProto = Node.prototype;
1606
+ nodeInsertBefore = nodeProto.insertBefore;
1607
+ nodeRemoveChild = nodeProto.removeChild;
1608
+ }
1482
1609
  class VHtml {
1483
1610
  html;
1484
1611
  parentEl;
@@ -1734,127 +1861,6 @@
1734
1861
  setup() { }
1735
1862
  }
1736
1863
 
1737
- // Maps fibers to thrown errors
1738
- const fibersInError = new WeakMap();
1739
- const nodeErrorHandlers = new WeakMap();
1740
- function destroyApp(app, error) {
1741
- try {
1742
- app.destroy();
1743
- }
1744
- catch (e) {
1745
- // mute all errors here because we are in a corrupted state anyway
1746
- }
1747
- const e = Object.assign(new OwlError(`[Owl] Unhandled error. Destroying the root component`), {
1748
- cause: error,
1749
- });
1750
- return e;
1751
- }
1752
- function _handleError(node, error) {
1753
- if (!node) {
1754
- return false;
1755
- }
1756
- const fiber = node.fiber;
1757
- if (fiber) {
1758
- fibersInError.set(fiber, error);
1759
- }
1760
- const errorHandlers = nodeErrorHandlers.get(node);
1761
- if (errorHandlers) {
1762
- let handled = false;
1763
- // execute in the opposite order
1764
- const finalize = () => destroyApp(node.app, error);
1765
- for (let i = errorHandlers.length - 1; i >= 0; i--) {
1766
- try {
1767
- errorHandlers[i](error, finalize);
1768
- handled = true;
1769
- break;
1770
- }
1771
- catch (e) {
1772
- error = e;
1773
- }
1774
- }
1775
- if (handled) {
1776
- return true;
1777
- }
1778
- }
1779
- return _handleError(node.parent, error);
1780
- }
1781
- function handleError(params) {
1782
- let { error } = params;
1783
- const node = "node" in params ? params.node : params.fiber.node;
1784
- const fiber = "fiber" in params ? params.fiber : node.fiber;
1785
- if (fiber) {
1786
- // resets the fibers on components if possible. This is important so that
1787
- // new renderings can be properly included in the initial one, if any.
1788
- let current = fiber;
1789
- do {
1790
- current.node.fiber = current;
1791
- fibersInError.set(current, error);
1792
- current = current.parent;
1793
- } while (current);
1794
- fibersInError.set(fiber.root, error);
1795
- }
1796
- const handled = _handleError(node, error);
1797
- if (!handled) {
1798
- throw destroyApp(node.app, error);
1799
- }
1800
- }
1801
-
1802
- // -----------------------------------------------------------------------------
1803
- // hooks
1804
- // -----------------------------------------------------------------------------
1805
- function decorate(node, f, hookName) {
1806
- const result = f.bind(node.component);
1807
- if (node.app.dev) {
1808
- const suffix = f.name ? ` <${f.name}>` : "";
1809
- Reflect.defineProperty(result, "name", {
1810
- value: hookName + suffix,
1811
- });
1812
- }
1813
- return result;
1814
- }
1815
- function onWillStart(fn) {
1816
- const { node } = getContext("component");
1817
- node.willStart.push(decorate(node, fn, "onWillStart"));
1818
- }
1819
- function onWillUpdateProps(fn) {
1820
- const { node } = getContext("component");
1821
- node.willUpdateProps.push(decorate(node, fn, "onWillUpdateProps"));
1822
- }
1823
- function onMounted(fn) {
1824
- const { node } = getContext("component");
1825
- node.mounted.push(decorate(node, fn, "onMounted"));
1826
- }
1827
- function onWillPatch(fn) {
1828
- const { node } = getContext("component");
1829
- node.willPatch.unshift(decorate(node, fn, "onWillPatch"));
1830
- }
1831
- function onPatched(fn) {
1832
- const { node } = getContext("component");
1833
- node.patched.push(decorate(node, fn, "onPatched"));
1834
- }
1835
- function onWillUnmount(fn) {
1836
- const { node } = getContext("component");
1837
- node.willUnmount.unshift(decorate(node, fn, "onWillUnmount"));
1838
- }
1839
- function onWillDestroy(fn) {
1840
- const context = getContext();
1841
- if (context.type === "component") {
1842
- context.node.willDestroy.unshift(decorate(context.node, fn, "onWillDestroy"));
1843
- }
1844
- else {
1845
- context.manager.onDestroyCb.push(fn);
1846
- }
1847
- }
1848
- function onError(callback) {
1849
- const { node } = getContext("component");
1850
- let handlers = nodeErrorHandlers.get(node);
1851
- if (!handlers) {
1852
- handlers = [];
1853
- nodeErrorHandlers.set(node, handlers);
1854
- }
1855
- handlers.push(callback.bind(node.component));
1856
- }
1857
-
1858
1864
  var ComputationState;
1859
1865
  (function (ComputationState) {
1860
1866
  ComputationState[ComputationState["EXECUTED"] = 0] = "EXECUTED";
@@ -1947,6 +1953,20 @@
1947
1953
  }
1948
1954
  sources.clear();
1949
1955
  }
1956
+ function disposeComputation(computation) {
1957
+ for (const source of computation.sources) {
1958
+ source.observers.delete(computation);
1959
+ // Recursively dispose derived computations that lost all observers
1960
+ if ("compute" in source &&
1961
+ source.isDerived &&
1962
+ source.observers.size === 0) {
1963
+ disposeComputation(source);
1964
+ }
1965
+ }
1966
+ computation.sources.clear();
1967
+ // Mark as stale so it recomputes correctly if ever re-used (shared computed case)
1968
+ computation.state = ComputationState.STALE;
1969
+ }
1950
1970
  function markDownstream(computation) {
1951
1971
  const stack = [computation];
1952
1972
  let current;
@@ -1978,668 +1998,332 @@
1978
1998
  return result;
1979
1999
  }
1980
2000
 
1981
- const anyType = function validateAny() { };
1982
- const booleanType = function validateBoolean(context) {
1983
- if (typeof context.value !== "boolean") {
1984
- context.addIssue({ message: "value is not a boolean" });
1985
- }
1986
- };
1987
- const numberType = function validateNumber(context) {
1988
- if (typeof context.value !== "number") {
1989
- context.addIssue({ message: "value is not a number" });
2001
+ // Maps fibers to thrown errors
2002
+ const fibersInError = new WeakMap();
2003
+ const nodeErrorHandlers = new WeakMap();
2004
+ function destroyApp(app, error) {
2005
+ try {
2006
+ app.destroy();
1990
2007
  }
1991
- };
1992
- const stringType = function validateString(context) {
1993
- if (typeof context.value !== "string") {
1994
- context.addIssue({ message: "value is not a string" });
2008
+ catch (e) {
2009
+ // mute all errors here because we are in a corrupted state anyway
1995
2010
  }
1996
- };
1997
- function arrayType(elementType) {
1998
- return function validateArray(context) {
1999
- if (!Array.isArray(context.value)) {
2000
- context.addIssue({ message: "value is not an array" });
2001
- return;
2002
- }
2003
- if (!elementType) {
2004
- return;
2005
- }
2006
- for (let index = 0; index < context.value.length; index++) {
2007
- context.withKey(index).validate(elementType);
2008
- }
2009
- };
2010
- }
2011
- function constructorType(constructor) {
2012
- return function validateConstructor(context) {
2013
- if (!(typeof context.value === "function") ||
2014
- !(context.value === constructor || context.value.prototype instanceof constructor)) {
2015
- context.addIssue({ message: `value is not '${constructor.name}' or an extension` });
2016
- }
2017
- };
2011
+ const e = Object.assign(new OwlError(`[Owl] Unhandled error. Destroying the root component`), {
2012
+ cause: error,
2013
+ });
2014
+ return e;
2018
2015
  }
2019
- function customValidator(type, validator, errorMessage = "value does not match custom validation") {
2020
- return function validateCustom(context) {
2021
- context.validate(type);
2022
- if (!context.isValid) {
2023
- return;
2016
+ function _handleError(node, error) {
2017
+ if (!node) {
2018
+ return false;
2019
+ }
2020
+ const fiber = node.fiber;
2021
+ if (fiber) {
2022
+ fibersInError.set(fiber, error);
2023
+ }
2024
+ const errorHandlers = nodeErrorHandlers.get(node);
2025
+ if (errorHandlers) {
2026
+ let handled = false;
2027
+ // execute in the opposite order
2028
+ const finalize = () => destroyApp(node.app, error);
2029
+ for (let i = errorHandlers.length - 1; i >= 0; i--) {
2030
+ try {
2031
+ errorHandlers[i](error, finalize);
2032
+ handled = true;
2033
+ break;
2034
+ }
2035
+ catch (e) {
2036
+ error = e;
2037
+ }
2024
2038
  }
2025
- if (!validator(context.value)) {
2026
- context.addIssue({ message: errorMessage });
2039
+ if (handled) {
2040
+ return true;
2027
2041
  }
2028
- };
2042
+ }
2043
+ return _handleError(node.parent, error);
2029
2044
  }
2030
- function functionType(parameters = [], result = undefined) {
2031
- return function validateFunction(context) {
2032
- if (typeof context.value !== "function") {
2033
- context.addIssue({ message: "value is not a function" });
2034
- }
2035
- };
2036
- }
2037
- function instanceType(constructor) {
2038
- return function validateInstanceType(context) {
2039
- if (!(context.value instanceof constructor)) {
2040
- context.addIssue({ message: `value is not an instance of '${constructor.name}'` });
2041
- }
2042
- };
2043
- }
2044
- function intersection(types) {
2045
- return function validateIntersection(context) {
2046
- for (const type of types) {
2047
- context.validate(type);
2048
- }
2049
- };
2050
- }
2051
- function literalType(literal) {
2052
- return function validateLiteral(context) {
2053
- if (context.value !== literal) {
2054
- context.addIssue({
2055
- message: `value is not equal to ${typeof literal === "string" ? `'${literal}'` : literal}`,
2056
- });
2057
- }
2058
- };
2045
+ function handleError(params) {
2046
+ let { error } = params;
2047
+ const node = "node" in params ? params.node : params.fiber.node;
2048
+ const fiber = "fiber" in params ? params.fiber : node.fiber;
2049
+ if (fiber) {
2050
+ // resets the fibers on components if possible. This is important so that
2051
+ // new renderings can be properly included in the initial one, if any.
2052
+ let current = fiber;
2053
+ do {
2054
+ current.node.fiber = current;
2055
+ fibersInError.set(current, error);
2056
+ current = current.parent;
2057
+ } while (current);
2058
+ fibersInError.set(fiber.root, error);
2059
+ }
2060
+ const handled = _handleError(node, error);
2061
+ if (!handled) {
2062
+ throw destroyApp(node.app, error);
2063
+ }
2059
2064
  }
2060
- function literalSelection(literals) {
2061
- return union(literals.map(literalType));
2065
+
2066
+ function makeChildFiber(node, parent) {
2067
+ let current = node.fiber;
2068
+ if (current) {
2069
+ cancelFibers(current.children);
2070
+ current.root = null;
2071
+ }
2072
+ return new Fiber(node, parent);
2062
2073
  }
2063
- function validateObjectShape(context, shape) {
2064
- const missingKeys = [];
2065
- for (const key in shape) {
2066
- const property = key.endsWith("?") ? key.slice(0, -1) : key;
2067
- if (context.value[property] === undefined) {
2068
- if (!key.endsWith("?")) {
2069
- missingKeys.push(property);
2074
+ function makeRootFiber(node) {
2075
+ let current = node.fiber;
2076
+ if (current) {
2077
+ let root = current.root;
2078
+ // lock root fiber because canceling children fibers may destroy components,
2079
+ // which means any arbitrary code can be run in onWillDestroy, which may
2080
+ // trigger new renderings
2081
+ root.locked = true;
2082
+ root.setCounter(root.counter + 1 - cancelFibers(current.children));
2083
+ root.locked = false;
2084
+ current.children = [];
2085
+ current.childrenMap = {};
2086
+ current.bdom = null;
2087
+ if (fibersInError.has(current)) {
2088
+ fibersInError.delete(current);
2089
+ fibersInError.delete(root);
2090
+ current.appliedToDom = false;
2091
+ if (current instanceof RootFiber) {
2092
+ // it is possible that this fiber is a fiber that crashed while being
2093
+ // mounted, so the mounted list is possibly corrupted. We restore it to
2094
+ // its normal initial state (which is empty list or a list with a mount
2095
+ // fiber.
2096
+ current.mounted = current instanceof MountFiber ? [current] : [];
2070
2097
  }
2071
- continue;
2072
2098
  }
2073
- context.withKey(property).validate(shape[key]);
2099
+ return current;
2074
2100
  }
2075
- if (missingKeys.length) {
2076
- context.addIssue({
2077
- message: "object value have missing keys",
2078
- missingKeys,
2079
- });
2101
+ const fiber = new RootFiber(node, null);
2102
+ if (node.willPatch.length) {
2103
+ fiber.willPatch.push(fiber);
2080
2104
  }
2081
- }
2082
- function validateObjectKeys(context, keys) {
2083
- const missingKeys = keys.filter((key) => {
2084
- if (key.endsWith("?")) {
2085
- return false;
2086
- }
2087
- return !(key in context.value);
2088
- });
2089
- if (missingKeys.length) {
2090
- context.addIssue({
2091
- message: "object value have missing keys",
2092
- missingKeys,
2093
- });
2105
+ if (node.patched.length) {
2106
+ fiber.patched.push(fiber);
2094
2107
  }
2108
+ return fiber;
2095
2109
  }
2096
- function objectType(schema = {}) {
2097
- return function validateObject(context) {
2098
- if (typeof context.value !== "object" ||
2099
- Array.isArray(context.value) ||
2100
- context.value === null) {
2101
- context.addIssue({ message: "value is not an object" });
2102
- return;
2103
- }
2104
- if (!schema) {
2105
- return;
2106
- }
2107
- if (Array.isArray(schema)) {
2108
- validateObjectKeys(context, schema);
2109
- }
2110
- else {
2111
- validateObjectShape(context, schema);
2112
- }
2113
- };
2114
- }
2115
- function promiseType(type) {
2116
- return function validatePromise(context) {
2117
- if (!(context.value instanceof Promise)) {
2118
- context.addIssue({ message: "value is not a promise" });
2119
- }
2120
- };
2110
+ function throwOnRender() {
2111
+ throw new OwlError("Attempted to render cancelled fiber");
2121
2112
  }
2122
- function recordType(valueType) {
2123
- return function validateRecord(context) {
2124
- if (typeof context.value !== "object" ||
2125
- Array.isArray(context.value) ||
2126
- context.value === null) {
2127
- context.addIssue({ message: "value is not an object" });
2128
- return;
2113
+ /**
2114
+ * @returns number of not-yet rendered fibers cancelled
2115
+ */
2116
+ function cancelFibers(fibers) {
2117
+ let result = 0;
2118
+ for (let fiber of fibers) {
2119
+ let node = fiber.node;
2120
+ fiber.render = throwOnRender;
2121
+ if (node.status === 0 /* STATUS.NEW */) {
2122
+ node.cancel();
2129
2123
  }
2130
- if (!valueType) {
2131
- return;
2124
+ node.fiber = null;
2125
+ if (fiber.bdom) {
2126
+ // if fiber has been rendered, this means that the component props have
2127
+ // been updated. however, this fiber will not be patched to the dom, so
2128
+ // it could happen that the next render compare the current props with
2129
+ // the same props, and skip the render completely. With the next line,
2130
+ // we kindly request the component code to force a render, so it works as
2131
+ // expected.
2132
+ node.forceNextRender = true;
2132
2133
  }
2133
- for (const key in context.value) {
2134
- context.withKey(key).validate(valueType);
2134
+ else {
2135
+ result++;
2135
2136
  }
2136
- };
2137
+ result += cancelFibers(fiber.children);
2138
+ }
2139
+ return result;
2137
2140
  }
2138
- function tuple(types) {
2139
- return function validateTuple(context) {
2140
- if (!Array.isArray(context.value)) {
2141
- context.addIssue({ message: "value is not an array" });
2142
- return;
2143
- }
2144
- if (context.value.length !== types.length) {
2145
- context.addIssue({ message: "tuple value does not have the correct length" });
2146
- return;
2141
+ class Fiber {
2142
+ node;
2143
+ bdom = null;
2144
+ root; // A Fiber that has been replaced by another has no root
2145
+ parent;
2146
+ children = [];
2147
+ appliedToDom = false;
2148
+ deep = false;
2149
+ childrenMap = {};
2150
+ constructor(node, parent) {
2151
+ this.node = node;
2152
+ this.parent = parent;
2153
+ if (parent) {
2154
+ this.deep = parent.deep;
2155
+ const root = parent.root;
2156
+ root.setCounter(root.counter + 1);
2157
+ this.root = root;
2158
+ parent.children.push(this);
2147
2159
  }
2148
- for (let index = 0; index < types.length; index++) {
2149
- context.withKey(index).validate(types[index]);
2160
+ else {
2161
+ this.root = this;
2150
2162
  }
2151
- };
2152
- }
2153
- function union(types) {
2154
- return function validateUnion(context) {
2155
- let firstIssueIndex = 0;
2156
- const subIssues = [];
2157
- for (const type of types) {
2158
- const subContext = context.withIssues(subIssues);
2159
- subContext.validate(type);
2160
- if (subIssues.length === firstIssueIndex || subContext.issueDepth > 0) {
2161
- context.mergeIssues(subIssues.slice(firstIssueIndex));
2162
- return;
2163
+ }
2164
+ render() {
2165
+ // if some parent has a fiber => register in followup
2166
+ let prev = this.root.node;
2167
+ let scheduler = prev.app.scheduler;
2168
+ let current = prev.parent;
2169
+ while (current) {
2170
+ if (current.fiber) {
2171
+ let root = current.fiber.root;
2172
+ if (root.counter === 0 && prev.parentKey in current.fiber.childrenMap) {
2173
+ current = root.node;
2174
+ }
2175
+ else {
2176
+ scheduler.delayedRenders.push(this);
2177
+ return;
2178
+ }
2163
2179
  }
2164
- firstIssueIndex = subIssues.length;
2180
+ prev = current;
2181
+ current = current.parent;
2165
2182
  }
2166
- context.addIssue({
2167
- message: "value does not match union type",
2168
- subIssues,
2169
- });
2170
- };
2171
- }
2172
- function reactiveValueType(type) {
2173
- return function validateReactiveValue(context) {
2174
- if (typeof context.value !== "function" || !context.value[atomSymbol]) {
2175
- context.addIssue({ message: "value is not a reactive value" });
2183
+ // there are no current rendering from above => we can render
2184
+ this._render();
2185
+ }
2186
+ _render() {
2187
+ const node = this.node;
2188
+ const root = this.root;
2189
+ if (root) {
2190
+ // todo: should use updateComputation somewhere else.
2191
+ const c = getCurrentComputation();
2192
+ removeSources(node.signalComputation);
2193
+ setComputation(node.signalComputation);
2194
+ try {
2195
+ this.bdom = true;
2196
+ this.bdom = node.renderFn();
2197
+ }
2198
+ catch (e) {
2199
+ node.app.handleError({ node, error: e });
2200
+ }
2201
+ setComputation(c);
2202
+ root.setCounter(root.counter - 1);
2176
2203
  }
2177
- };
2204
+ }
2178
2205
  }
2179
- function ref(type) {
2180
- return union([literalType(null), instanceType(type)]);
2181
- }
2182
- const types = {
2183
- and: intersection,
2184
- any: anyType,
2185
- array: arrayType,
2186
- boolean: booleanType,
2187
- constructor: constructorType,
2188
- customValidator: customValidator,
2189
- function: functionType,
2190
- instanceOf: instanceType,
2191
- literal: literalType,
2192
- number: numberType,
2193
- object: objectType,
2194
- or: union,
2195
- promise: promiseType,
2196
- record: recordType,
2197
- ref,
2198
- selection: literalSelection,
2199
- signal: reactiveValueType,
2200
- string: stringType,
2201
- tuple: tuple,
2202
- };
2203
-
2204
- function assertType(value, validation, errorMessage = "Value does not match the type") {
2205
- const issues = validateType(value, validation);
2206
- if (issues.length) {
2207
- const issueStrings = JSON.stringify(issues, (key, value) => {
2208
- if (typeof value === "function") {
2209
- return value.name;
2206
+ class RootFiber extends Fiber {
2207
+ counter = 1;
2208
+ // only add stuff in this if they have registered some hooks
2209
+ willPatch = [];
2210
+ patched = [];
2211
+ mounted = [];
2212
+ // A fiber is typically locked when it is completing and the patch has not, or is being applied.
2213
+ // i.e.: render triggered in onWillUnmount or in willPatch will be delayed
2214
+ locked = false;
2215
+ complete() {
2216
+ const node = this.node;
2217
+ this.locked = true;
2218
+ let current = undefined;
2219
+ let mountedFibers = this.mounted;
2220
+ try {
2221
+ // Step 1: calling all willPatch lifecycle hooks
2222
+ for (current of this.willPatch) {
2223
+ // because of the asynchronous nature of the rendering, some parts of the
2224
+ // UI may have been rendered, then deleted in a followup rendering, and we
2225
+ // do not want to call onWillPatch in that case.
2226
+ let node = current.node;
2227
+ if (node.fiber === current) {
2228
+ const component = node.component;
2229
+ for (let cb of node.willPatch) {
2230
+ cb.call(component);
2231
+ }
2232
+ }
2210
2233
  }
2211
- return value;
2212
- }, 2);
2213
- throw new OwlError(`${errorMessage}\n${issueStrings}`);
2214
- }
2215
- }
2216
- function createContext$1(issues, value, path, parent) {
2217
- return {
2218
- issueDepth: 0,
2219
- path,
2220
- value,
2221
- get isValid() {
2222
- return !issues.length;
2223
- },
2224
- addIssue(issue) {
2225
- issues.push({
2226
- received: this.value,
2227
- path: this.path,
2228
- ...issue,
2229
- });
2230
- },
2231
- mergeIssues(newIssues) {
2232
- issues.push(...newIssues);
2233
- },
2234
- validate(type) {
2235
- type(this);
2236
- if (!this.isValid && parent) {
2237
- parent.issueDepth = this.issueDepth + 1;
2234
+ current = undefined;
2235
+ // Step 2: patching the dom
2236
+ node._patch();
2237
+ this.locked = false;
2238
+ // Step 4: calling all mounted lifecycle hooks
2239
+ while ((current = mountedFibers.pop())) {
2240
+ current = current;
2241
+ if (current.appliedToDom) {
2242
+ for (let cb of current.node.mounted) {
2243
+ cb();
2244
+ }
2245
+ }
2238
2246
  }
2239
- },
2240
- withIssues(issues) {
2241
- return createContext$1(issues, this.value, this.path, this);
2242
- },
2243
- withKey(key) {
2244
- return createContext$1(issues, this.value[key], this.path.concat(key), this);
2245
- },
2246
- };
2247
- }
2248
- function validateType(value, validation) {
2249
- const issues = [];
2250
- validation(createContext$1(issues, value, []));
2251
- return issues;
2252
- }
2253
-
2254
- function validateObjectWithDefaults(schema, defaultValues) {
2255
- const keys = Array.isArray(schema) ? schema : Object.keys(schema);
2256
- const mandatoryDefaultedKeys = keys.filter((key) => !key.endsWith("?") && key in defaultValues);
2257
- return (context) => {
2258
- if (mandatoryDefaultedKeys.length) {
2259
- context.addIssue({
2260
- message: "props have default values on mandatory keys",
2261
- keys: mandatoryDefaultedKeys,
2262
- });
2263
- }
2264
- context.validate(types.object(schema));
2265
- };
2266
- }
2267
- function props(type, defaults) {
2268
- const { node, app, componentName } = getContext("component");
2269
- function getProp(key) {
2270
- if (node.props[key] === undefined && defaults) {
2271
- return defaults[key];
2272
- }
2273
- return node.props[key];
2274
- }
2275
- const result = Object.create(null);
2276
- function applyPropGetters(keys) {
2277
- for (const key of keys) {
2278
- Reflect.defineProperty(result, key, {
2279
- enumerable: true,
2280
- get: getProp.bind(null, key),
2281
- });
2282
- }
2283
- }
2284
- if (type) {
2285
- const keys = (Array.isArray(type) ? type : Object.keys(type)).map((key) => key.endsWith("?") ? key.slice(0, -1) : key);
2286
- applyPropGetters(keys);
2287
- if (app.dev) {
2288
- const validation = defaults ? validateObjectWithDefaults(type, defaults) : types.object(type);
2289
- assertType(node.props, validation, `Invalid component props (${componentName})`);
2290
- node.willUpdateProps.push((np) => {
2291
- assertType(np, validation, `Invalid component props (${componentName})`);
2292
- });
2293
- }
2294
- }
2295
- else {
2296
- applyPropGetters(Object.keys(node.props));
2297
- node.willUpdateProps.push((np) => {
2298
- for (let key in result) {
2299
- Reflect.deleteProperty(result, key);
2247
+ // Step 5: calling all patched hooks
2248
+ let patchedFibers = this.patched;
2249
+ while ((current = patchedFibers.pop())) {
2250
+ current = current;
2251
+ if (current.appliedToDom) {
2252
+ for (let cb of current.node.patched) {
2253
+ cb();
2254
+ }
2255
+ }
2300
2256
  }
2301
- applyPropGetters(Object.keys(np));
2302
- });
2303
- }
2304
- return result;
2305
- }
2306
-
2307
- const VText = text("").constructor;
2308
- class VPortal extends VText {
2309
- content;
2310
- selector;
2311
- target = null;
2312
- constructor(selector, content) {
2313
- super("");
2314
- this.selector = selector;
2315
- this.content = content;
2316
- }
2317
- mount(parent, anchor) {
2318
- super.mount(parent, anchor);
2319
- this.target = document.querySelector(this.selector);
2320
- if (this.target) {
2321
- this.content.mount(this.target, null);
2322
- }
2323
- else {
2324
- this.content.mount(parent, anchor);
2325
2257
  }
2326
- }
2327
- beforeRemove() {
2328
- this.content.beforeRemove();
2329
- }
2330
- remove() {
2331
- if (this.content) {
2332
- super.remove();
2333
- this.content.remove();
2334
- this.content = null;
2258
+ catch (e) {
2259
+ // if mountedFibers is not empty, this means that a crash occured while
2260
+ // calling the mounted hooks of some component. So, there may still be
2261
+ // some component that have been mounted, but for which the mounted hooks
2262
+ // have not been called. Here, we remove the willUnmount hooks for these
2263
+ // specific component to prevent a worse situation (willUnmount being
2264
+ // called even though mounted has not been called)
2265
+ for (let fiber of mountedFibers) {
2266
+ fiber.node.willUnmount = [];
2267
+ }
2268
+ this.locked = false;
2269
+ node.app.handleError({ fiber: current || this, error: e });
2335
2270
  }
2336
2271
  }
2337
- patch(other) {
2338
- super.patch(other);
2339
- if (this.content) {
2340
- this.content.patch(other.content, true);
2341
- }
2342
- else {
2343
- this.content = other.content;
2344
- this.content.mount(this.target, null);
2272
+ setCounter(newValue) {
2273
+ this.counter = newValue;
2274
+ if (newValue === 0) {
2275
+ this.node.app.scheduler.flush();
2345
2276
  }
2346
2277
  }
2347
2278
  }
2348
- /**
2349
- * kind of similar to <t t-slot="default"/>, but it wraps it around a VPortal
2350
- */
2351
- function portalTemplate(app, bdom, helpers) {
2352
- let { callSlot } = helpers;
2353
- return function template(ctx, node, key = "") {
2354
- return new VPortal(ctx.this.props.target, callSlot(ctx, node, key, "default", false, null));
2355
- };
2356
- }
2357
- class Portal extends Component {
2358
- static template = "__portal__";
2359
- props = props({
2360
- target: types.string,
2361
- });
2362
- setup() {
2363
- const node = this.__owl__;
2364
- onMounted(() => {
2365
- const portal = node.bdom;
2366
- if (!portal.target) {
2367
- const target = document.querySelector(node.props.target);
2368
- if (target) {
2369
- portal.content.moveBeforeDOMNode(target.firstChild, target);
2279
+ class MountFiber extends RootFiber {
2280
+ target;
2281
+ position;
2282
+ constructor(node, target, options = {}) {
2283
+ super(node, null);
2284
+ this.target = target;
2285
+ this.position = options.position || "last-child";
2286
+ }
2287
+ complete() {
2288
+ let current = this;
2289
+ try {
2290
+ const node = this.node;
2291
+ node.children = this.childrenMap;
2292
+ node.app.constructor.validateTarget(this.target);
2293
+ if (node.bdom) {
2294
+ // this is a complicated situation: if we mount a fiber with an existing
2295
+ // bdom, this means that this same fiber was already completed, mounted,
2296
+ // but a crash occurred in some mounted hook. Then, it was handled and
2297
+ // the new rendering is being applied.
2298
+ node.updateDom();
2299
+ }
2300
+ else {
2301
+ node.bdom = this.bdom;
2302
+ if (this.position === "last-child" || this.target.childNodes.length === 0) {
2303
+ mount$1(node.bdom, this.target);
2370
2304
  }
2371
2305
  else {
2372
- throw new OwlError("invalid portal target");
2306
+ const firstChild = this.target.childNodes[0];
2307
+ mount$1(node.bdom, this.target, firstChild);
2373
2308
  }
2374
2309
  }
2375
- });
2376
- onWillUnmount(() => {
2377
- const portal = node.bdom;
2378
- portal.remove();
2379
- });
2380
- }
2381
- }
2382
-
2383
- function makeChildFiber(node, parent) {
2384
- let current = node.fiber;
2385
- if (current) {
2386
- cancelFibers(current.children);
2387
- current.root = null;
2388
- }
2389
- return new Fiber(node, parent);
2390
- }
2391
- function makeRootFiber(node) {
2392
- let current = node.fiber;
2393
- if (current) {
2394
- let root = current.root;
2395
- // lock root fiber because canceling children fibers may destroy components,
2396
- // which means any arbitrary code can be run in onWillDestroy, which may
2397
- // trigger new renderings
2398
- root.locked = true;
2399
- root.setCounter(root.counter + 1 - cancelFibers(current.children));
2400
- root.locked = false;
2401
- current.children = [];
2402
- current.childrenMap = {};
2403
- current.bdom = null;
2404
- if (fibersInError.has(current)) {
2405
- fibersInError.delete(current);
2406
- fibersInError.delete(root);
2407
- current.appliedToDom = false;
2408
- if (current instanceof RootFiber) {
2409
- // it is possible that this fiber is a fiber that crashed while being
2410
- // mounted, so the mounted list is possibly corrupted. We restore it to
2411
- // its normal initial state (which is empty list or a list with a mount
2412
- // fiber.
2413
- current.mounted = current instanceof MountFiber ? [current] : [];
2414
- }
2415
- }
2416
- return current;
2417
- }
2418
- const fiber = new RootFiber(node, null);
2419
- if (node.willPatch.length) {
2420
- fiber.willPatch.push(fiber);
2421
- }
2422
- if (node.patched.length) {
2423
- fiber.patched.push(fiber);
2424
- }
2425
- return fiber;
2426
- }
2427
- function throwOnRender() {
2428
- throw new OwlError("Attempted to render cancelled fiber");
2429
- }
2430
- /**
2431
- * @returns number of not-yet rendered fibers cancelled
2432
- */
2433
- function cancelFibers(fibers) {
2434
- let result = 0;
2435
- for (let fiber of fibers) {
2436
- let node = fiber.node;
2437
- fiber.render = throwOnRender;
2438
- if (node.status === 0 /* STATUS.NEW */) {
2439
- node.cancel();
2440
- }
2441
- node.fiber = null;
2442
- if (fiber.bdom) {
2443
- // if fiber has been rendered, this means that the component props have
2444
- // been updated. however, this fiber will not be patched to the dom, so
2445
- // it could happen that the next render compare the current props with
2446
- // the same props, and skip the render completely. With the next line,
2447
- // we kindly request the component code to force a render, so it works as
2448
- // expected.
2449
- node.forceNextRender = true;
2450
- }
2451
- else {
2452
- result++;
2453
- }
2454
- result += cancelFibers(fiber.children);
2455
- }
2456
- return result;
2457
- }
2458
- class Fiber {
2459
- node;
2460
- bdom = null;
2461
- root; // A Fiber that has been replaced by another has no root
2462
- parent;
2463
- children = [];
2464
- appliedToDom = false;
2465
- deep = false;
2466
- childrenMap = {};
2467
- constructor(node, parent) {
2468
- this.node = node;
2469
- this.parent = parent;
2470
- if (parent) {
2471
- this.deep = parent.deep;
2472
- const root = parent.root;
2473
- root.setCounter(root.counter + 1);
2474
- this.root = root;
2475
- parent.children.push(this);
2476
- }
2477
- else {
2478
- this.root = this;
2479
- }
2480
- }
2481
- render() {
2482
- // if some parent has a fiber => register in followup
2483
- let prev = this.root.node;
2484
- let scheduler = prev.app.scheduler;
2485
- let current = prev.parent;
2486
- while (current) {
2487
- if (current.fiber) {
2488
- let root = current.fiber.root;
2489
- if (root.counter === 0 && prev.parentKey in current.fiber.childrenMap) {
2490
- current = root.node;
2491
- }
2492
- else {
2493
- scheduler.delayedRenders.push(this);
2494
- return;
2495
- }
2496
- }
2497
- prev = current;
2498
- current = current.parent;
2499
- }
2500
- // there are no current rendering from above => we can render
2501
- this._render();
2502
- }
2503
- _render() {
2504
- const node = this.node;
2505
- const root = this.root;
2506
- if (root) {
2507
- // todo: should use updateComputation somewhere else.
2508
- const c = getCurrentComputation();
2509
- setComputation(node.signalComputation);
2510
- try {
2511
- this.bdom = true;
2512
- this.bdom = node.renderFn();
2513
- }
2514
- catch (e) {
2515
- node.app.handleError({ node, error: e });
2516
- }
2517
- setComputation(c);
2518
- root.setCounter(root.counter - 1);
2519
- }
2520
- }
2521
- }
2522
- class RootFiber extends Fiber {
2523
- counter = 1;
2524
- // only add stuff in this if they have registered some hooks
2525
- willPatch = [];
2526
- patched = [];
2527
- mounted = [];
2528
- // A fiber is typically locked when it is completing and the patch has not, or is being applied.
2529
- // i.e.: render triggered in onWillUnmount or in willPatch will be delayed
2530
- locked = false;
2531
- complete() {
2532
- const node = this.node;
2533
- this.locked = true;
2534
- let current = undefined;
2535
- let mountedFibers = this.mounted;
2536
- try {
2537
- // Step 1: calling all willPatch lifecycle hooks
2538
- for (current of this.willPatch) {
2539
- // because of the asynchronous nature of the rendering, some parts of the
2540
- // UI may have been rendered, then deleted in a followup rendering, and we
2541
- // do not want to call onWillPatch in that case.
2542
- let node = current.node;
2543
- if (node.fiber === current) {
2544
- const component = node.component;
2545
- for (let cb of node.willPatch) {
2546
- cb.call(component);
2547
- }
2548
- }
2549
- }
2550
- current = undefined;
2551
- // Step 2: patching the dom
2552
- node._patch();
2553
- this.locked = false;
2554
- // Step 4: calling all mounted lifecycle hooks
2555
- while ((current = mountedFibers.pop())) {
2556
- current = current;
2557
- if (current.appliedToDom) {
2558
- for (let cb of current.node.mounted) {
2559
- cb();
2560
- }
2561
- }
2562
- }
2563
- // Step 5: calling all patched hooks
2564
- let patchedFibers = this.patched;
2565
- while ((current = patchedFibers.pop())) {
2566
- current = current;
2567
- if (current.appliedToDom) {
2568
- for (let cb of current.node.patched) {
2569
- cb();
2570
- }
2571
- }
2572
- }
2573
- }
2574
- catch (e) {
2575
- // if mountedFibers is not empty, this means that a crash occured while
2576
- // calling the mounted hooks of some component. So, there may still be
2577
- // some component that have been mounted, but for which the mounted hooks
2578
- // have not been called. Here, we remove the willUnmount hooks for these
2579
- // specific component to prevent a worse situation (willUnmount being
2580
- // called even though mounted has not been called)
2581
- for (let fiber of mountedFibers) {
2582
- fiber.node.willUnmount = [];
2583
- }
2584
- this.locked = false;
2585
- node.app.handleError({ fiber: current || this, error: e });
2586
- }
2587
- }
2588
- setCounter(newValue) {
2589
- this.counter = newValue;
2590
- if (newValue === 0) {
2591
- this.node.app.scheduler.flush();
2592
- }
2593
- }
2594
- }
2595
- class MountFiber extends RootFiber {
2596
- target;
2597
- position;
2598
- constructor(node, target, options = {}) {
2599
- super(node, null);
2600
- this.target = target;
2601
- this.position = options.position || "last-child";
2602
- }
2603
- complete() {
2604
- let current = this;
2605
- try {
2606
- const node = this.node;
2607
- node.children = this.childrenMap;
2608
- node.app.constructor.validateTarget(this.target);
2609
- if (node.bdom) {
2610
- // this is a complicated situation: if we mount a fiber with an existing
2611
- // bdom, this means that this same fiber was already completed, mounted,
2612
- // but a crash occurred in some mounted hook. Then, it was handled and
2613
- // the new rendering is being applied.
2614
- node.updateDom();
2615
- }
2616
- else {
2617
- node.bdom = this.bdom;
2618
- if (this.position === "last-child" || this.target.childNodes.length === 0) {
2619
- mount$1(node.bdom, this.target);
2620
- }
2621
- else {
2622
- const firstChild = this.target.childNodes[0];
2623
- mount$1(node.bdom, this.target, firstChild);
2624
- }
2625
- }
2626
- // unregistering the fiber before mounted since it can do another render
2627
- // and that the current rendering is obviously completed
2628
- node.fiber = null;
2629
- node.status = 1 /* STATUS.MOUNTED */;
2630
- this.appliedToDom = true;
2631
- let mountedFibers = this.mounted;
2632
- while ((current = mountedFibers.pop())) {
2633
- if (current.appliedToDom) {
2634
- for (let cb of current.node.mounted) {
2635
- cb();
2636
- }
2637
- }
2638
- }
2639
- }
2640
- catch (e) {
2641
- this.node.app.handleError({ fiber: current, error: e });
2642
- }
2310
+ // unregistering the fiber before mounted since it can do another render
2311
+ // and that the current rendering is obviously completed
2312
+ node.fiber = null;
2313
+ node.status = 1 /* STATUS.MOUNTED */;
2314
+ this.appliedToDom = true;
2315
+ let mountedFibers = this.mounted;
2316
+ while ((current = mountedFibers.pop())) {
2317
+ if (current.appliedToDom) {
2318
+ for (let cb of current.node.mounted) {
2319
+ cb();
2320
+ }
2321
+ }
2322
+ }
2323
+ }
2324
+ catch (e) {
2325
+ this.node.app.handleError({ fiber: current, error: e });
2326
+ }
2643
2327
  }
2644
2328
  }
2645
2329
 
@@ -2653,6 +2337,7 @@
2653
2337
  forceNextRender = false;
2654
2338
  parentKey;
2655
2339
  props;
2340
+ defaultProps = {};
2656
2341
  renderFn;
2657
2342
  parent;
2658
2343
  children = Object.create(null);
@@ -2664,6 +2349,7 @@
2664
2349
  patched = [];
2665
2350
  willDestroy = [];
2666
2351
  signalComputation;
2352
+ computations = [];
2667
2353
  pluginManager;
2668
2354
  constructor(C, props, app, parent, parentKey) {
2669
2355
  this.app = app;
@@ -2671,7 +2357,7 @@
2671
2357
  this.parentKey = parentKey;
2672
2358
  this.pluginManager = parent ? parent.pluginManager : app.pluginManager;
2673
2359
  this.signalComputation = createComputation(() => this.render(false), false, ComputationState.EXECUTED);
2674
- this.props = Object.assign({}, props);
2360
+ this.props = props;
2675
2361
  contextStack.push({
2676
2362
  type: "component",
2677
2363
  app,
@@ -2807,10 +2493,19 @@
2807
2493
  this.app.handleError({ error: e, node: this });
2808
2494
  }
2809
2495
  }
2496
+ for (const computation of this.computations) {
2497
+ disposeComputation(computation);
2498
+ }
2499
+ disposeComputation(this.signalComputation);
2810
2500
  this.status = 3 /* STATUS.DESTROYED */;
2811
2501
  }
2812
2502
  async updateAndRender(props, parentFiber) {
2813
2503
  props = Object.assign({}, props);
2504
+ for (const key in this.defaultProps) {
2505
+ if (props[key] === undefined) {
2506
+ props[key] = this.defaultProps[key];
2507
+ }
2508
+ }
2814
2509
  // update
2815
2510
  const fiber = makeChildFiber(this, parentFiber);
2816
2511
  this.fiber = fiber;
@@ -3439,6 +3134,13 @@
3439
3134
  }
3440
3135
  };
3441
3136
  }
3137
+ function callHandler(fn, ctx, ev) {
3138
+ if (typeof fn !== "function") {
3139
+ throw new OwlError(`Invalid handler expression: the \`t-on\` expression should evaluate to a function, but got '${typeof fn}'. ` +
3140
+ `Did you mean to use an arrow function? (e.g. \`t-on-click="() => expr"\`)`);
3141
+ }
3142
+ fn.call(ctx["this"], ev);
3143
+ }
3442
3144
  function modelExpr(value) {
3443
3145
  if (typeof value !== "function" || typeof value.set !== "function") {
3444
3146
  throw new OwlError(`Invalid t-model expression: expression should evaluate to a function with a 'set' method defined on it`);
@@ -3533,8 +3235,8 @@
3533
3235
  createRef,
3534
3236
  modelExpr,
3535
3237
  createComponent,
3536
- Portal,
3537
3238
  callTemplate,
3239
+ callHandler,
3538
3240
  };
3539
3241
 
3540
3242
  const bdom = { text, createBlock, list, multi, html, toggler, comment };
@@ -3642,7 +3344,6 @@
3642
3344
  return name;
3643
3345
  }
3644
3346
  xml.nextId = 1;
3645
- TemplateSet.registerTemplate("__portal__", portalTemplate);
3646
3347
 
3647
3348
  /**
3648
3349
  * Owl QWeb Expression Parser
@@ -3854,11 +3555,18 @@
3854
3555
  * the arrow operator, then we add the current (or some previous tokens) token to
3855
3556
  * the list of variables so it does not get replaced by a lookup in the context
3856
3557
  */
3857
- function compileExprToArray(expr) {
3558
+ // Leading spaces are trimmed during tokenization, so they need to be added back for some values
3559
+ const paddedValues = new Map([["in ", " in "]]);
3560
+ /**
3561
+ * Processes a javascript expression: compiles variable lookups and detects
3562
+ * top-level arrow functions with their free variables, all in a single pass.
3563
+ */
3564
+ function processExpr(expr) {
3858
3565
  const localVars = new Set();
3859
3566
  const tokens = tokenize(expr);
3860
3567
  let i = 0;
3861
3568
  let stack = []; // to track last opening (, [ or {
3569
+ let topLevelArrowIndex = -1;
3862
3570
  while (i < tokens.length) {
3863
3571
  let token = tokens[i];
3864
3572
  let prevToken = tokens[i - 1];
@@ -3899,18 +3607,21 @@
3899
3607
  token.value = token.replace((expr) => compileExpr(expr));
3900
3608
  }
3901
3609
  if (nextToken && nextToken.type === "OPERATOR" && nextToken.value === "=>") {
3610
+ if (stack.length === 0) {
3611
+ topLevelArrowIndex = i + 1;
3612
+ }
3902
3613
  if (token.type === "RIGHT_PAREN") {
3903
3614
  let j = i - 1;
3904
3615
  while (j > 0 && tokens[j].type !== "LEFT_PAREN") {
3905
3616
  if (tokens[j].type === "SYMBOL" && tokens[j].originalValue) {
3906
3617
  tokens[j].value = tokens[j].originalValue;
3907
- localVars.add(tokens[j].value); //] = { id: tokens[j].value, expr: tokens[j].value };
3618
+ localVars.add(tokens[j].value);
3908
3619
  }
3909
3620
  j--;
3910
3621
  }
3911
3622
  }
3912
3623
  else {
3913
- localVars.add(token.value); //] = { id: token.value, expr: token.value };
3624
+ localVars.add(token.value);
3914
3625
  }
3915
3626
  }
3916
3627
  if (isVar) {
@@ -3931,14 +3642,24 @@
3931
3642
  token.isLocal = true;
3932
3643
  }
3933
3644
  }
3934
- return tokens;
3645
+ // Collect free variables from arrow function body
3646
+ let freeVariables = null;
3647
+ if (topLevelArrowIndex !== -1) {
3648
+ freeVariables = [];
3649
+ const seen = new Set();
3650
+ for (let i = topLevelArrowIndex + 1; i < tokens.length; i++) {
3651
+ const t = tokens[i];
3652
+ if (t.varName && !t.isLocal && t.varName !== "this" && !seen.has(t.varName)) {
3653
+ seen.add(t.varName);
3654
+ freeVariables.push(t.varName);
3655
+ }
3656
+ }
3657
+ }
3658
+ const compiled = tokens.map((t) => paddedValues.get(t.value) || t.value).join("");
3659
+ return { expr: compiled, freeVariables };
3935
3660
  }
3936
- // Leading spaces are trimmed during tokenization, so they need to be added back for some values
3937
- const paddedValues = new Map([["in ", " in "]]);
3938
3661
  function compileExpr(expr) {
3939
- return compileExprToArray(expr)
3940
- .map((t) => paddedValues.get(t.value) || t.value)
3941
- .join("");
3662
+ return processExpr(expr).expr;
3942
3663
  }
3943
3664
  const INTERP_REGEXP = /\{\{.*?\}\}|\#\{.*?\}/g;
3944
3665
  function replaceDynamicParts(s, replacer) {
@@ -3957,8 +3678,11 @@
3957
3678
  const whitespaceRE = /\s+/g;
3958
3679
  // using a non-html document so that <inner/outer>HTML serializes as XML instead
3959
3680
  // of HTML (as we will parse it as xml later)
3960
- const xmlDoc = document.implementation.createDocument(null, null, null);
3961
- const MODS = new Set(["stop", "capture", "prevent", "self", "synthetic"]);
3681
+ let xmlDoc;
3682
+ if (typeof document !== "undefined") {
3683
+ xmlDoc = document.implementation.createDocument(null, null, null);
3684
+ }
3685
+ const MODS = new Set(["stop", "capture", "prevent", "self", "synthetic", "passive"]);
3962
3686
  let nextDataIds = {};
3963
3687
  function generateId(prefix = "") {
3964
3688
  nextDataIds[prefix] = (nextDataIds[prefix] || 0) + 1;
@@ -4056,7 +3780,7 @@
4056
3780
  return t.innerHTML;
4057
3781
  }
4058
3782
  }
4059
- function createContext(parentCtx, params) {
3783
+ function createContext$1(parentCtx, params) {
4060
3784
  return Object.assign({
4061
3785
  block: null,
4062
3786
  index: 0,
@@ -4224,7 +3948,7 @@
4224
3948
  const target = new CodeTarget(name, on);
4225
3949
  this.targets.push(target);
4226
3950
  this.target = target;
4227
- this.compileAST(ast, createContext(ctx));
3951
+ this.compileAST(ast, createContext$1(ctx));
4228
3952
  this.target = initialTarget;
4229
3953
  return name;
4230
3954
  }
@@ -4318,8 +4042,6 @@
4318
4042
  return this.compileTTranslation(ast, ctx);
4319
4043
  case 16 /* ASTType.TTranslationContext */:
4320
4044
  return this.compileTTranslationContext(ast, ctx);
4321
- case 17 /* ASTType.TPortal */:
4322
- return this.compileTPortal(ast, ctx);
4323
4045
  }
4324
4046
  }
4325
4047
  compileDebug(ast, ctx) {
@@ -4405,7 +4127,8 @@
4405
4127
  hoistedExpr = `(ctx,${bareArrowMatch[1]})=>${rest}`;
4406
4128
  }
4407
4129
  else {
4408
- hoistedExpr = `(ctx, ev) => (${compiled}).call(ctx['this'], ev)`;
4130
+ this.helpers.add("callHandler");
4131
+ hoistedExpr = `(ctx, ev) => callHandler(${compiled}, ctx, ev)`;
4409
4132
  }
4410
4133
  const id = generateId("hdlr_fn");
4411
4134
  this.staticDefs.push({ id, expr: hoistedExpr });
@@ -4483,11 +4206,22 @@
4483
4206
  // t-model
4484
4207
  let tModelSelectedExpr;
4485
4208
  if (ast.model) {
4486
- const { hasDynamicChildren, expr, eventType, shouldNumberize, shouldTrim, targetAttr, specialInitTargetAttr, } = ast.model;
4487
- const expression = compileExpr(expr);
4488
- const exprId = generateId("expr");
4489
- this.helpers.add("modelExpr");
4490
- this.define(exprId, `modelExpr(${expression})`);
4209
+ const { hasDynamicChildren, expr, eventType, shouldNumberize, shouldTrim, targetAttr, specialInitTargetAttr, isProxy, } = ast.model;
4210
+ let readExpr;
4211
+ let writeExpr;
4212
+ if (isProxy) {
4213
+ const expression = compileExpr(expr);
4214
+ readExpr = expression;
4215
+ writeExpr = (value) => `${expression} = ${value}`;
4216
+ }
4217
+ else {
4218
+ const exprId = generateId("expr");
4219
+ const expression = compileExpr(expr);
4220
+ this.helpers.add("modelExpr");
4221
+ this.define(exprId, `modelExpr(${expression})`);
4222
+ readExpr = `${exprId}()`;
4223
+ writeExpr = (value) => `${exprId}.set(${value})`;
4224
+ }
4491
4225
  let idx;
4492
4226
  if (specialInitTargetAttr) {
4493
4227
  let targetExpr = targetAttr in attrs && `'${attrs[targetAttr]}'`;
@@ -4498,23 +4232,23 @@
4498
4232
  targetExpr = compileExpr(dynamicTgExpr);
4499
4233
  }
4500
4234
  }
4501
- idx = block.insertData(`${exprId}() === ${targetExpr}`, "prop");
4235
+ idx = block.insertData(`${readExpr} === ${targetExpr}`, "prop");
4502
4236
  attrs[`block-property-${idx}`] = specialInitTargetAttr;
4503
4237
  }
4504
4238
  else if (hasDynamicChildren) {
4505
4239
  const bValueId = generateId("bValue");
4506
4240
  tModelSelectedExpr = `${bValueId}`;
4507
- this.define(tModelSelectedExpr, `${exprId}()`);
4241
+ this.define(tModelSelectedExpr, readExpr);
4508
4242
  }
4509
4243
  else {
4510
- idx = block.insertData(`${exprId}()`, "prop");
4244
+ idx = block.insertData(readExpr, "prop");
4511
4245
  attrs[`block-property-${idx}`] = targetAttr;
4512
4246
  }
4513
4247
  this.helpers.add("toNumber");
4514
4248
  let valueCode = `ev.target.${targetAttr}`;
4515
4249
  valueCode = shouldTrim ? `${valueCode}.trim()` : valueCode;
4516
4250
  valueCode = shouldNumberize ? `toNumber(${valueCode})` : valueCode;
4517
- const handler = `[(ctx, ev) => { ${exprId}.set(${valueCode}); }, ctx]`;
4251
+ const handler = `[(ctx, ev) => { ${writeExpr(valueCode)}; }, ctx]`;
4518
4252
  idx = block.insertData(handler, "hdlr");
4519
4253
  attrs[`block-handler-${idx}`] = eventType;
4520
4254
  }
@@ -4548,7 +4282,7 @@
4548
4282
  const children = ast.content;
4549
4283
  for (let i = 0; i < children.length; i++) {
4550
4284
  const child = ast.content[i];
4551
- const subCtx = createContext(ctx, {
4285
+ const subCtx = createContext$1(ctx, {
4552
4286
  block,
4553
4287
  index: block.childNumber,
4554
4288
  forceNewBlock: false,
@@ -4604,7 +4338,7 @@
4604
4338
  else if (ast.body) {
4605
4339
  let bodyValue = null;
4606
4340
  bodyValue = BlockDescription.nextBlockId;
4607
- const subCtx = createContext(ctx);
4341
+ const subCtx = createContext$1(ctx);
4608
4342
  this.compileAST({ type: 3 /* ASTType.Multi */, content: ast.body }, subCtx);
4609
4343
  this.helpers.add("safeOutput");
4610
4344
  blockStr = `safeOutput(${compileExpr(ast.expr)}, b${bodyValue})`;
@@ -4619,7 +4353,7 @@
4619
4353
  compileTIfBranch(content, block, ctx) {
4620
4354
  this.target.indentLevel++;
4621
4355
  let childN = block.children.length;
4622
- this.compileAST(content, createContext(ctx, { block, index: ctx.index }));
4356
+ this.compileAST(content, createContext$1(ctx, { block, index: ctx.index }));
4623
4357
  if (block.children.length > childN) {
4624
4358
  // we have some content => need to insert an anchor at correct index
4625
4359
  this.insertAnchor(block, childN);
@@ -4715,7 +4449,7 @@
4715
4449
  this.addLine(`if (keys${block.id}.has(String(key${this.target.loopLevel}))) { throw new OwlError(\`Got duplicate key in t-foreach: \${key${this.target.loopLevel}}\`)}`);
4716
4450
  this.addLine(`keys${block.id}.add(String(key${this.target.loopLevel}));`);
4717
4451
  }
4718
- const subCtx = createContext(ctx, { block, index: loopVar });
4452
+ const subCtx = createContext$1(ctx, { block, index: loopVar });
4719
4453
  this.compileAST(ast.body, subCtx);
4720
4454
  this.target.indentLevel--;
4721
4455
  this.target.loopLevel--;
@@ -4727,7 +4461,7 @@
4727
4461
  compileTKey(ast, ctx) {
4728
4462
  const tKeyExpr = generateId("tKey_");
4729
4463
  this.define(tKeyExpr, compileExpr(ast.expr));
4730
- ctx = createContext(ctx, {
4464
+ ctx = createContext$1(ctx, {
4731
4465
  tKeyExpr,
4732
4466
  block: ctx.block,
4733
4467
  index: ctx.index,
@@ -4754,7 +4488,7 @@
4754
4488
  for (let i = 0, l = ast.content.length; i < l; i++) {
4755
4489
  const child = ast.content[i];
4756
4490
  const forceNewBlock = !child.hasNoRepresentation;
4757
- const subCtx = createContext(ctx, {
4491
+ const subCtx = createContext$1(ctx, {
4758
4492
  block,
4759
4493
  index,
4760
4494
  forceNewBlock,
@@ -4807,12 +4541,11 @@
4807
4541
  if (ast.context) {
4808
4542
  const dynCtxVar = generateId("ctx");
4809
4543
  this.addLine(`const ${dynCtxVar} = ${compileExpr(ast.context)};`);
4810
- if (ast.attrs) {
4811
- ctxExpr = `Object.assign({}, ${dynCtxVar}${attrs.length ? ", " + ctxString : ""})`;
4544
+ if (attrs.length) {
4545
+ ctxExpr = `Object.assign({this: ${dynCtxVar}}, ${ctxString})`;
4812
4546
  }
4813
4547
  else {
4814
- const thisCtx = `{this: ${dynCtxVar}, __owl__: this.__owl__}`;
4815
- ctxExpr = `Object.assign({}, ${dynCtxVar}, ${thisCtx}${attrs.length ? ", " + ctxString : ""})`;
4548
+ ctxExpr = `{this: ${dynCtxVar}}`;
4816
4549
  }
4817
4550
  }
4818
4551
  else {
@@ -4957,9 +4690,29 @@
4957
4690
  let { block } = ctx;
4958
4691
  // props
4959
4692
  const hasSlotsProp = "slots" in (ast.props || {});
4960
- const props = ast.props
4961
- ? this.formatPropObject(ast.props, ast.propsTranslationCtx, ctx.translationCtx)
4962
- : [];
4693
+ const props = [];
4694
+ const propList = [];
4695
+ for (let p in ast.props || {}) {
4696
+ let [name, suffix] = p.split(".");
4697
+ if (suffix) {
4698
+ // .alike, .bind, .translate — delegate to formatProp, no propList entry
4699
+ props.push(this.formatProp(p, ast.props[p], ast.propsTranslationCtx, ctx.translationCtx));
4700
+ continue;
4701
+ }
4702
+ const { expr: compiledValue, freeVariables } = processExpr(ast.props[p]);
4703
+ const propName = /^[a-z_]+$/i.test(name) ? name : `'${name}'`;
4704
+ props.push(`${propName}: ${compiledValue || undefined}`);
4705
+ if (freeVariables) {
4706
+ for (const varName of freeVariables) {
4707
+ const syntheticKey = `\x01${name}.${varName}`;
4708
+ propList.push(`"${syntheticKey}"`);
4709
+ props.push(`"${syntheticKey}": ctx['${varName}']`);
4710
+ }
4711
+ }
4712
+ else {
4713
+ propList.push(`"${name}"`);
4714
+ }
4715
+ }
4963
4716
  // slots
4964
4717
  let slotDef = "";
4965
4718
  if (ast.slots) {
@@ -5016,13 +4769,6 @@
5016
4769
  keyArg = `${ctx.tKeyExpr} + ${keyArg}`;
5017
4770
  }
5018
4771
  let id = generateId("comp");
5019
- const propList = [];
5020
- for (let p in ast.props || {}) {
5021
- let [name, suffix] = p.split(".");
5022
- if (!suffix) {
5023
- propList.push(`"${name}"`);
5024
- }
5025
- }
5026
4772
  this.helpers.add("createComponent");
5027
4773
  this.staticDefs.push({
5028
4774
  id,
@@ -5127,26 +4873,6 @@
5127
4873
  }
5128
4874
  return null;
5129
4875
  }
5130
- compileTPortal(ast, ctx) {
5131
- this.helpers.add("Portal");
5132
- let { block } = ctx;
5133
- const name = this.compileInNewTarget("slot", ast.content, ctx);
5134
- let id = generateId("comp");
5135
- this.helpers.add("createComponent");
5136
- this.staticDefs.push({
5137
- id,
5138
- expr: `createComponent(app, null, false, true, false, false)`,
5139
- });
5140
- const target = compileExpr(ast.target);
5141
- const key = this.generateComponentKey();
5142
- const blockString = `${id}({target: ${target},slots: {'default': {__render: ${name}.bind(this), __ctx: ctx}}}, ${key}, node, ctx, Portal)`;
5143
- if (block) {
5144
- this.insertAnchor(block);
5145
- }
5146
- block = this.createBlock(block, "multi", ctx);
5147
- this.insertBlock(blockString, block, { ...ctx, forceNewBlock: false });
5148
- return block.varName;
5149
- }
5150
4876
  }
5151
4877
 
5152
4878
  // -----------------------------------------------------------------------------
@@ -5182,7 +4908,6 @@
5182
4908
  parseTDebugLog(node, ctx) ||
5183
4909
  parseTForEach(node, ctx) ||
5184
4910
  parseTIf(node, ctx) ||
5185
- parseTPortal(node, ctx) ||
5186
4911
  parseTTranslation(node, ctx) ||
5187
4912
  parseTTranslationContext(node, ctx) ||
5188
4913
  parseTCall(node, ctx) ||
@@ -5329,6 +5054,7 @@
5329
5054
  const hasTrimMod = attr.includes(".trim");
5330
5055
  const hasLazyMod = hasTrimMod || attr.includes(".lazy");
5331
5056
  const hasNumberMod = attr.includes(".number");
5057
+ const hasProxyMod = attr.includes(".proxy");
5332
5058
  const eventType = isRadioInput ? "click" : isSelect || hasLazyMod ? "change" : "input";
5333
5059
  model = {
5334
5060
  expr: value,
@@ -5338,6 +5064,7 @@
5338
5064
  hasDynamicChildren: false,
5339
5065
  shouldTrim: hasTrimMod,
5340
5066
  shouldNumberize: hasNumberMod,
5067
+ isProxy: hasProxyMod,
5341
5068
  };
5342
5069
  if (isSelect) {
5343
5070
  // don't pollute the original ctx
@@ -5809,694 +5536,1104 @@
5809
5536
  return wrapInTTranslationContextAST(result, translationCtx);
5810
5537
  }
5811
5538
  // -----------------------------------------------------------------------------
5812
- // Portal
5539
+ // helpers
5813
5540
  // -----------------------------------------------------------------------------
5814
- function parseTPortal(node, ctx) {
5815
- if (!node.hasAttribute("t-portal")) {
5816
- return null;
5541
+ /**
5542
+ * Parse all the child nodes of a given node and return a list of ast elements
5543
+ */
5544
+ function parseChildren(node, ctx) {
5545
+ const children = [];
5546
+ for (let child of node.childNodes) {
5547
+ const childAst = parseNode(child, ctx);
5548
+ if (childAst) {
5549
+ if (childAst.type === 3 /* ASTType.Multi */) {
5550
+ children.push(...childAst.content);
5551
+ }
5552
+ else {
5553
+ children.push(childAst);
5554
+ }
5555
+ }
5817
5556
  }
5818
- const target = node.getAttribute("t-portal");
5819
- node.removeAttribute("t-portal");
5820
- const content = parseNode(node, ctx);
5821
- if (!content) {
5822
- return {
5823
- type: 0 /* ASTType.Text */,
5824
- value: "",
5825
- };
5557
+ return children;
5558
+ }
5559
+ function makeASTMulti(children) {
5560
+ const ast = { type: 3 /* ASTType.Multi */, content: children };
5561
+ if (children.every((c) => c.hasNoRepresentation)) {
5562
+ ast.hasNoRepresentation = true;
5563
+ }
5564
+ return ast;
5565
+ }
5566
+ /**
5567
+ * Parse all the child nodes of a given node and return an ast if possible.
5568
+ * In the case there are multiple children, they are wrapped in a astmulti.
5569
+ */
5570
+ function parseChildNodes(node, ctx) {
5571
+ const children = parseChildren(node, ctx);
5572
+ switch (children.length) {
5573
+ case 0:
5574
+ return null;
5575
+ case 1:
5576
+ return children[0];
5577
+ default:
5578
+ return makeASTMulti(children);
5579
+ }
5580
+ }
5581
+ /**
5582
+ * Normalizes the content of an Element so that t-if/t-elif/t-else directives
5583
+ * immediately follow one another (by removing empty text nodes or comments).
5584
+ * Throws an error when a conditional branching statement is malformed. This
5585
+ * function modifies the Element in place.
5586
+ *
5587
+ * @param el the element containing the tree that should be normalized
5588
+ */
5589
+ function normalizeTIf(el) {
5590
+ let tbranch = el.querySelectorAll("[t-elif], [t-else]");
5591
+ for (let i = 0, ilen = tbranch.length; i < ilen; i++) {
5592
+ let node = tbranch[i];
5593
+ let prevElem = node.previousElementSibling;
5594
+ let pattr = (name) => prevElem.getAttribute(name);
5595
+ let nattr = (name) => +!!node.getAttribute(name);
5596
+ if (prevElem && (pattr("t-if") || pattr("t-elif"))) {
5597
+ if (pattr("t-foreach")) {
5598
+ throw new OwlError("t-if cannot stay at the same level as t-foreach when using t-elif or t-else");
5599
+ }
5600
+ if (["t-if", "t-elif", "t-else"].map(nattr).reduce(function (a, b) {
5601
+ return a + b;
5602
+ }) > 1) {
5603
+ throw new OwlError("Only one conditional branching directive is allowed per node");
5604
+ }
5605
+ // All text (with only spaces) and comment nodes (nodeType 8) between
5606
+ // branch nodes are removed
5607
+ let textNode;
5608
+ while ((textNode = node.previousSibling) !== prevElem) {
5609
+ if (textNode.nodeValue.trim().length && textNode.nodeType !== 8) {
5610
+ throw new OwlError("text is not allowed between branching directives");
5611
+ }
5612
+ textNode.remove();
5613
+ }
5614
+ }
5615
+ else {
5616
+ throw new OwlError("t-elif and t-else directives must be preceded by a t-if or t-elif directive");
5617
+ }
5618
+ }
5619
+ }
5620
+ /**
5621
+ * Normalizes the content of an Element so that t-out directives on components
5622
+ * are removed and instead places a <t t-out=""> as the default slot of the
5623
+ * component. Also throws if the component already has content. This function
5624
+ * modifies the Element in place.
5625
+ *
5626
+ * @param el the element containing the tree that should be normalized
5627
+ */
5628
+ function normalizeTOut(el) {
5629
+ const elements = [...el.querySelectorAll(`[t-out]`)].filter((el) => el.tagName[0] === el.tagName[0].toUpperCase() || el.hasAttribute("t-component"));
5630
+ for (const el of elements) {
5631
+ if (el.childNodes.length) {
5632
+ throw new OwlError(`Cannot have t-out on a component that already has content`);
5633
+ }
5634
+ const value = el.getAttribute("t-out");
5635
+ el.removeAttribute("t-out");
5636
+ const t = el.ownerDocument.createElement("t");
5637
+ if (value != null) {
5638
+ t.setAttribute("t-out", value);
5639
+ }
5640
+ el.appendChild(t);
5641
+ }
5642
+ }
5643
+ /**
5644
+ * Normalizes the tree inside a given element and do some preliminary validation
5645
+ * on it. This function modifies the Element in place.
5646
+ *
5647
+ * @param el the element containing the tree that should be normalized
5648
+ */
5649
+ function normalizeXML(el) {
5650
+ normalizeTIf(el);
5651
+ normalizeTOut(el);
5652
+ }
5653
+
5654
+ function compile(template, options = {
5655
+ hasGlobalValues: false,
5656
+ }) {
5657
+ // parsing
5658
+ const ast = parse(template, options.customDirectives);
5659
+ // code generation
5660
+ const codeGenerator = new CodeGenerator(ast, options);
5661
+ const code = codeGenerator.generateCode();
5662
+ // template function
5663
+ try {
5664
+ return new Function("app, bdom, helpers", code);
5665
+ }
5666
+ catch (originalError) {
5667
+ const { name } = options;
5668
+ const nameStr = name ? `template "${name}"` : "anonymous template";
5669
+ const err = new OwlError(`Failed to compile ${nameStr}: ${originalError.message}\n\ngenerated code:\nfunction(app, bdom, helpers) {\n${code}\n}`);
5670
+ err.cause = originalError;
5671
+ throw err;
5672
+ }
5673
+ }
5674
+
5675
+ // do not modify manually. This file is generated by the release script.
5676
+ const version = "3.0.0-alpha.21";
5677
+
5678
+ function effect(fn) {
5679
+ const computation = createComputation(() => {
5680
+ // In case the cleanup read an atom.
5681
+ // todo: test it
5682
+ setComputation(undefined);
5683
+ unsubscribeEffect(computation);
5684
+ setComputation(computation);
5685
+ return fn();
5686
+ }, false);
5687
+ getCurrentComputation()?.observers.add(computation);
5688
+ updateComputation(computation);
5689
+ // Remove sources and unsubscribe
5690
+ return function cleanupEffect() {
5691
+ // In case the cleanup read an atom.
5692
+ // todo: test it
5693
+ const previousComputation = getCurrentComputation();
5694
+ setComputation(undefined);
5695
+ unsubscribeEffect(computation);
5696
+ setComputation(previousComputation);
5697
+ };
5698
+ }
5699
+ function unsubscribeEffect(effect) {
5700
+ removeSources(effect);
5701
+ cleanupEffect(effect);
5702
+ for (const childEffect of effect.observers) {
5703
+ // Consider it executed to avoid it's re-execution
5704
+ // todo: make a test for it
5705
+ childEffect.state = ComputationState.EXECUTED;
5706
+ removeSources(childEffect);
5707
+ unsubscribeEffect(childEffect);
5708
+ }
5709
+ effect.observers.clear();
5710
+ }
5711
+ function cleanupEffect(effect) {
5712
+ // the computation.value of an effect is a cleanup function
5713
+ const cleanupFn = effect.value;
5714
+ if (cleanupFn && typeof cleanupFn === "function") {
5715
+ cleanupFn();
5716
+ effect.value = undefined;
5717
+ }
5718
+ }
5719
+
5720
+ class Plugin {
5721
+ static _shadowId;
5722
+ static get id() {
5723
+ return this._shadowId ?? this.name;
5724
+ }
5725
+ static set id(shadowId) {
5726
+ this._shadowId = shadowId;
5727
+ }
5728
+ __owl__;
5729
+ constructor(manager) {
5730
+ this.__owl__ = manager;
5731
+ }
5732
+ setup() { }
5733
+ }
5734
+ class PluginManager {
5735
+ app;
5736
+ config;
5737
+ onDestroyCb = [];
5738
+ computations = [];
5739
+ plugins;
5740
+ status = 0 /* STATUS.NEW */;
5741
+ constructor(app, options = {}) {
5742
+ this.app = app;
5743
+ this.config = options.config ?? {};
5744
+ if (options.parent) {
5745
+ const parent = options.parent;
5746
+ parent.onDestroyCb.push(() => this.destroy());
5747
+ this.plugins = Object.create(parent.plugins);
5748
+ }
5749
+ else {
5750
+ this.plugins = {};
5751
+ }
5752
+ }
5753
+ destroy() {
5754
+ const cbs = this.onDestroyCb;
5755
+ while (cbs.length) {
5756
+ cbs.pop()();
5757
+ }
5758
+ for (const computation of this.computations) {
5759
+ disposeComputation(computation);
5760
+ }
5761
+ this.status = 3 /* STATUS.DESTROYED */;
5762
+ }
5763
+ getPluginById(id) {
5764
+ return this.plugins[id] || null;
5765
+ }
5766
+ getPlugin(pluginConstructor) {
5767
+ return this.getPluginById(pluginConstructor.id);
5768
+ }
5769
+ startPlugin(pluginConstructor) {
5770
+ if (!pluginConstructor.id) {
5771
+ throw new OwlError(`Plugin "${pluginConstructor.name}" has no id`);
5772
+ }
5773
+ if (this.plugins.hasOwnProperty(pluginConstructor.id)) {
5774
+ const existingPluginType = this.getPluginById(pluginConstructor.id).constructor;
5775
+ if (existingPluginType !== pluginConstructor) {
5776
+ throw new OwlError(`Trying to start a plugin with the same id as an other plugin (id: '${pluginConstructor.id}', existing plugin: '${existingPluginType.name}', starting plugin: '${pluginConstructor.name}')`);
5777
+ }
5778
+ return null;
5779
+ }
5780
+ const plugin = new pluginConstructor(this);
5781
+ this.plugins[pluginConstructor.id] = plugin;
5782
+ plugin.setup();
5783
+ return plugin;
5784
+ }
5785
+ startPlugins(pluginConstructors) {
5786
+ contextStack.push({
5787
+ type: "plugin",
5788
+ app: this.app,
5789
+ manager: this,
5790
+ get status() {
5791
+ return this.manager.status;
5792
+ },
5793
+ });
5794
+ try {
5795
+ for (const pluginConstructor of pluginConstructors) {
5796
+ this.startPlugin(pluginConstructor);
5797
+ }
5798
+ }
5799
+ finally {
5800
+ contextStack.pop();
5801
+ }
5802
+ this.status = 1 /* STATUS.MOUNTED */;
5803
+ }
5804
+ }
5805
+ function startPlugins(manager, plugins) {
5806
+ if (Array.isArray(plugins)) {
5807
+ manager.startPlugins(plugins);
5808
+ }
5809
+ else {
5810
+ manager.onDestroyCb.push(effect(() => {
5811
+ const pluginItems = plugins.items();
5812
+ untrack(() => manager.startPlugins(pluginItems));
5813
+ }));
5814
+ }
5815
+ }
5816
+
5817
+ // -----------------------------------------------------------------------------
5818
+ // Scheduler
5819
+ // -----------------------------------------------------------------------------
5820
+ let requestAnimationFrame;
5821
+ if (typeof window !== "undefined") {
5822
+ requestAnimationFrame = window.requestAnimationFrame.bind(window);
5823
+ }
5824
+ class Scheduler {
5825
+ // capture the value of requestAnimationFrame as soon as possible, to avoid
5826
+ // interactions with other code, such as test frameworks that override them
5827
+ static requestAnimationFrame = requestAnimationFrame;
5828
+ tasks = new Set();
5829
+ requestAnimationFrame;
5830
+ frame = 0;
5831
+ delayedRenders = [];
5832
+ cancelledNodes = new Set();
5833
+ processing = false;
5834
+ constructor() {
5835
+ this.requestAnimationFrame = Scheduler.requestAnimationFrame;
5836
+ }
5837
+ addFiber(fiber) {
5838
+ this.tasks.add(fiber.root);
5839
+ }
5840
+ scheduleDestroy(node) {
5841
+ this.cancelledNodes.add(node);
5842
+ if (this.frame === 0) {
5843
+ this.frame = this.requestAnimationFrame(() => this.processTasks());
5844
+ }
5845
+ }
5846
+ /**
5847
+ * Process all current tasks. This only applies to the fibers that are ready.
5848
+ * Other tasks are left unchanged.
5849
+ */
5850
+ flush() {
5851
+ if (this.delayedRenders.length) {
5852
+ let renders = this.delayedRenders;
5853
+ this.delayedRenders = [];
5854
+ for (let f of renders) {
5855
+ if (f.root && f.node.status !== 3 /* STATUS.DESTROYED */ && f.node.fiber === f) {
5856
+ f.render();
5857
+ }
5858
+ }
5859
+ }
5860
+ if (this.frame === 0) {
5861
+ this.frame = this.requestAnimationFrame(() => this.processTasks());
5862
+ }
5863
+ }
5864
+ processTasks() {
5865
+ if (this.processing) {
5866
+ return;
5867
+ }
5868
+ this.processing = true;
5869
+ this.frame = 0;
5870
+ for (let node of this.cancelledNodes) {
5871
+ node._destroy();
5872
+ }
5873
+ this.cancelledNodes.clear();
5874
+ for (let task of this.tasks) {
5875
+ this.processFiber(task);
5876
+ }
5877
+ for (let task of this.tasks) {
5878
+ if (task.node.status === 3 /* STATUS.DESTROYED */) {
5879
+ this.tasks.delete(task);
5880
+ }
5881
+ }
5882
+ this.processing = false;
5883
+ }
5884
+ processFiber(fiber) {
5885
+ if (fiber.root !== fiber) {
5886
+ this.tasks.delete(fiber);
5887
+ return;
5888
+ }
5889
+ const hasError = fibersInError.has(fiber);
5890
+ if (hasError && fiber.counter !== 0) {
5891
+ this.tasks.delete(fiber);
5892
+ return;
5893
+ }
5894
+ if (fiber.node.status === 3 /* STATUS.DESTROYED */) {
5895
+ this.tasks.delete(fiber);
5896
+ return;
5897
+ }
5898
+ if (fiber.counter === 0) {
5899
+ if (!hasError) {
5900
+ fiber.complete();
5901
+ }
5902
+ // at this point, the fiber should have been applied to the DOM, so we can
5903
+ // remove it from the task list. If it is not the case, it means that there
5904
+ // was an error and an error handler triggered a new rendering that recycled
5905
+ // the fiber, so in that case, we actually want to keep the fiber around,
5906
+ // otherwise it will just be ignored.
5907
+ if (fiber.appliedToDom) {
5908
+ this.tasks.delete(fiber);
5909
+ }
5910
+ }
5911
+ }
5912
+ }
5913
+
5914
+ let hasBeenLogged = false;
5915
+ const apps = new Set();
5916
+ if (typeof window !== "undefined") {
5917
+ window.__OWL_DEVTOOLS__ ||= { apps, Fiber, RootFiber, toRaw, proxy };
5918
+ }
5919
+ class App extends TemplateSet {
5920
+ static validateTarget = validateTarget;
5921
+ static apps = apps;
5922
+ static version = version;
5923
+ name;
5924
+ scheduler = new Scheduler();
5925
+ roots = new Set();
5926
+ pluginManager;
5927
+ constructor(config = {}) {
5928
+ super(config);
5929
+ this.name = config.name || "";
5930
+ apps.add(this);
5931
+ this.pluginManager = new PluginManager(this, { config: config.config });
5932
+ if (config.plugins) {
5933
+ startPlugins(this.pluginManager, config.plugins);
5934
+ }
5935
+ if (config.test) {
5936
+ this.dev = true;
5937
+ }
5938
+ if (this.dev && !config.test && !hasBeenLogged) {
5939
+ console.info(`Owl is running in 'dev' mode.`);
5940
+ hasBeenLogged = true;
5941
+ }
5942
+ }
5943
+ createRoot(Root, config = {}) {
5944
+ const props = config.props || {};
5945
+ let resolve;
5946
+ let reject;
5947
+ const promise = new Promise((res, rej) => {
5948
+ resolve = res;
5949
+ reject = rej;
5950
+ });
5951
+ const restore = saveContext();
5952
+ let node;
5953
+ let error = null;
5954
+ try {
5955
+ node = new ComponentNode(Root, props, this, null, null);
5956
+ }
5957
+ catch (e) {
5958
+ error = e;
5959
+ reject(e);
5960
+ }
5961
+ finally {
5962
+ restore();
5963
+ }
5964
+ const root = {
5965
+ node: node,
5966
+ promise,
5967
+ mount: (target, options) => {
5968
+ if (error) {
5969
+ return promise;
5970
+ }
5971
+ App.validateTarget(target);
5972
+ this.mountNode(node, target, resolve, reject, options);
5973
+ return promise;
5974
+ },
5975
+ destroy: () => {
5976
+ this.roots.delete(root);
5977
+ node.destroy();
5978
+ this.scheduler.processTasks();
5979
+ },
5980
+ };
5981
+ this.roots.add(root);
5982
+ return root;
5983
+ }
5984
+ mountNode(node, target, resolve, reject, options) {
5985
+ // Manually add the last resort error handler on the node
5986
+ let handlers = nodeErrorHandlers.get(node);
5987
+ if (!handlers) {
5988
+ handlers = [];
5989
+ nodeErrorHandlers.set(node, handlers);
5990
+ }
5991
+ handlers.unshift((e, finalize) => {
5992
+ const finalError = finalize();
5993
+ reject(finalError);
5994
+ });
5995
+ // manually set a onMounted callback.
5996
+ // that way, we are independant from the current node.
5997
+ node.mounted.push(() => {
5998
+ resolve(node.component);
5999
+ handlers.shift();
6000
+ });
6001
+ node.mountComponent(target, options);
6002
+ }
6003
+ destroy() {
6004
+ for (let root of this.roots) {
6005
+ root.destroy();
6006
+ }
6007
+ this.pluginManager.destroy();
6008
+ this.scheduler.processTasks();
6009
+ apps.delete(this);
6010
+ }
6011
+ handleError(...args) {
6012
+ return handleError(...args);
6013
+ }
6014
+ }
6015
+ async function mount(C, target, config = {}) {
6016
+ const app = new App(config);
6017
+ const root = app.createRoot(C, config);
6018
+ return root.mount(target, config);
6019
+ }
6020
+
6021
+ const mainEventHandler = (data, ev, currentTarget) => {
6022
+ const { data: _data, modifiers } = filterOutModifiersFromData(data);
6023
+ data = _data;
6024
+ let stopped = false;
6025
+ if (modifiers.length) {
6026
+ let selfMode = false;
6027
+ const isSelf = ev.target === currentTarget;
6028
+ for (const mod of modifiers) {
6029
+ switch (mod) {
6030
+ case "self":
6031
+ selfMode = true;
6032
+ if (isSelf) {
6033
+ continue;
6034
+ }
6035
+ else {
6036
+ return stopped;
6037
+ }
6038
+ case "prevent":
6039
+ if ((selfMode && isSelf) || !selfMode)
6040
+ ev.preventDefault();
6041
+ continue;
6042
+ case "stop":
6043
+ if ((selfMode && isSelf) || !selfMode)
6044
+ ev.stopPropagation();
6045
+ stopped = true;
6046
+ continue;
6047
+ }
6048
+ }
6049
+ }
6050
+ // If handler is empty, the array slot 0 will also be empty, and data will not have the property 0
6051
+ // We check this rather than data[0] being truthy (or typeof function) so that it crashes
6052
+ // as expected when there is a handler expression that evaluates to a falsy value
6053
+ if (Object.hasOwnProperty.call(data, 0)) {
6054
+ const handler = data[0];
6055
+ if (typeof handler !== "function") {
6056
+ throw new OwlError(`Invalid handler (expected a function, received: '${handler}')`);
6057
+ }
6058
+ let node = data[1] ? data[1].__owl__ : null;
6059
+ if (node ? node.status === 1 /* STATUS.MOUNTED */ : true) {
6060
+ handler(data[1], ev);
6061
+ }
5826
6062
  }
5827
- return {
5828
- type: 17 /* ASTType.TPortal */,
5829
- target,
5830
- content,
5831
- };
5832
- }
6063
+ return stopped;
6064
+ };
6065
+
5833
6066
  // -----------------------------------------------------------------------------
5834
- // helpers
6067
+ // hooks
5835
6068
  // -----------------------------------------------------------------------------
5836
- /**
5837
- * Parse all the child nodes of a given node and return a list of ast elements
5838
- */
5839
- function parseChildren(node, ctx) {
5840
- const children = [];
5841
- for (let child of node.childNodes) {
5842
- const childAst = parseNode(child, ctx);
5843
- if (childAst) {
5844
- if (childAst.type === 3 /* ASTType.Multi */) {
5845
- children.push(...childAst.content);
5846
- }
5847
- else {
5848
- children.push(childAst);
5849
- }
5850
- }
6069
+ function decorate(node, f, hookName) {
6070
+ const result = f.bind(node.component);
6071
+ if (node.app.dev) {
6072
+ const suffix = f.name ? ` <${f.name}>` : "";
6073
+ Reflect.defineProperty(result, "name", {
6074
+ value: hookName + suffix,
6075
+ });
5851
6076
  }
5852
- return children;
6077
+ return result;
5853
6078
  }
5854
- function makeASTMulti(children) {
5855
- const ast = { type: 3 /* ASTType.Multi */, content: children };
5856
- if (children.every((c) => c.hasNoRepresentation)) {
5857
- ast.hasNoRepresentation = true;
6079
+ function onWillStart(fn) {
6080
+ const { node } = getContext("component");
6081
+ node.willStart.push(decorate(node, fn, "onWillStart"));
6082
+ }
6083
+ function onWillUpdateProps(fn) {
6084
+ const { node } = getContext("component");
6085
+ node.willUpdateProps.push(decorate(node, fn, "onWillUpdateProps"));
6086
+ }
6087
+ function onMounted(fn) {
6088
+ const { node } = getContext("component");
6089
+ node.mounted.push(decorate(node, fn, "onMounted"));
6090
+ }
6091
+ function onWillPatch(fn) {
6092
+ const { node } = getContext("component");
6093
+ node.willPatch.unshift(decorate(node, fn, "onWillPatch"));
6094
+ }
6095
+ function onPatched(fn) {
6096
+ const { node } = getContext("component");
6097
+ node.patched.push(decorate(node, fn, "onPatched"));
6098
+ }
6099
+ function onWillUnmount(fn) {
6100
+ const { node } = getContext("component");
6101
+ node.willUnmount.unshift(decorate(node, fn, "onWillUnmount"));
6102
+ }
6103
+ function onWillDestroy(fn) {
6104
+ const context = getContext();
6105
+ if (context.type === "component") {
6106
+ context.node.willDestroy.unshift(decorate(context.node, fn, "onWillDestroy"));
6107
+ }
6108
+ else {
6109
+ context.manager.onDestroyCb.push(fn);
5858
6110
  }
5859
- return ast;
5860
6111
  }
5861
- /**
5862
- * Parse all the child nodes of a given node and return an ast if possible.
5863
- * In the case there are multiple children, they are wrapped in a astmulti.
5864
- */
5865
- function parseChildNodes(node, ctx) {
5866
- const children = parseChildren(node, ctx);
5867
- switch (children.length) {
5868
- case 0:
5869
- return null;
5870
- case 1:
5871
- return children[0];
5872
- default:
5873
- return makeASTMulti(children);
6112
+ function onError(callback) {
6113
+ const { node } = getContext("component");
6114
+ let handlers = nodeErrorHandlers.get(node);
6115
+ if (!handlers) {
6116
+ handlers = [];
6117
+ nodeErrorHandlers.set(node, handlers);
5874
6118
  }
6119
+ handlers.push(callback.bind(node.component));
5875
6120
  }
5876
- /**
5877
- * Normalizes the content of an Element so that t-if/t-elif/t-else directives
5878
- * immediately follow one another (by removing empty text nodes or comments).
5879
- * Throws an error when a conditional branching statement is malformed. This
5880
- * function modifies the Element in place.
5881
- *
5882
- * @param el the element containing the tree that should be normalized
5883
- */
5884
- function normalizeTIf(el) {
5885
- let tbranch = el.querySelectorAll("[t-elif], [t-else]");
5886
- for (let i = 0, ilen = tbranch.length; i < ilen; i++) {
5887
- let node = tbranch[i];
5888
- let prevElem = node.previousElementSibling;
5889
- let pattr = (name) => prevElem.getAttribute(name);
5890
- let nattr = (name) => +!!node.getAttribute(name);
5891
- if (prevElem && (pattr("t-if") || pattr("t-elif"))) {
5892
- if (pattr("t-foreach")) {
5893
- throw new OwlError("t-if cannot stay at the same level as t-foreach when using t-elif or t-else");
5894
- }
5895
- if (["t-if", "t-elif", "t-else"].map(nattr).reduce(function (a, b) {
5896
- return a + b;
5897
- }) > 1) {
5898
- throw new OwlError("Only one conditional branching directive is allowed per node");
5899
- }
5900
- // All text (with only spaces) and comment nodes (nodeType 8) between
5901
- // branch nodes are removed
5902
- let textNode;
5903
- while ((textNode = node.previousSibling) !== prevElem) {
5904
- if (textNode.nodeValue.trim().length && textNode.nodeType !== 8) {
5905
- throw new OwlError("text is not allowed between branching directives");
5906
- }
5907
- textNode.remove();
5908
- }
6121
+
6122
+ function computed(getter, options = {}) {
6123
+ const computation = createComputation(() => {
6124
+ const newValue = getter();
6125
+ if (!Object.is(computation.value, newValue)) {
6126
+ onWriteAtom(computation);
5909
6127
  }
5910
- else {
5911
- throw new OwlError("t-elif and t-else directives must be preceded by a t-if or t-elif directive");
6128
+ return newValue;
6129
+ }, true);
6130
+ function readComputed() {
6131
+ if (computation.state !== ComputationState.EXECUTED) {
6132
+ updateComputation(computation);
5912
6133
  }
6134
+ onReadAtom(computation);
6135
+ return computation.value;
5913
6136
  }
5914
- }
5915
- /**
5916
- * Normalizes the content of an Element so that t-out directives on components
5917
- * are removed and instead places a <t t-out=""> as the default slot of the
5918
- * component. Also throws if the component already has content. This function
5919
- * modifies the Element in place.
5920
- *
5921
- * @param el the element containing the tree that should be normalized
5922
- */
5923
- function normalizeTOut(el) {
5924
- const elements = [...el.querySelectorAll(`[t-out]`)].filter((el) => el.tagName[0] === el.tagName[0].toUpperCase() || el.hasAttribute("t-component"));
5925
- for (const el of elements) {
5926
- if (el.childNodes.length) {
5927
- throw new OwlError(`Cannot have t-out on a component that already has content`);
6137
+ readComputed[atomSymbol] = computation;
6138
+ readComputed.set = options.set ?? (() => { });
6139
+ const context = contextStack.at(-1);
6140
+ if (context) {
6141
+ if (context.type === "component") {
6142
+ context.node.computations.push(computation);
5928
6143
  }
5929
- const value = el.getAttribute("t-out");
5930
- el.removeAttribute("t-out");
5931
- const t = el.ownerDocument.createElement("t");
5932
- if (value != null) {
5933
- t.setAttribute("t-out", value);
6144
+ else if (context.type === "plugin") {
6145
+ context.manager.computations.push(computation);
5934
6146
  }
5935
- el.appendChild(t);
5936
- }
5937
- }
5938
- /**
5939
- * Normalizes the tree inside a given element and do some preliminary validation
5940
- * on it. This function modifies the Element in place.
5941
- *
5942
- * @param el the element containing the tree that should be normalized
5943
- */
5944
- function normalizeXML(el) {
5945
- normalizeTIf(el);
5946
- normalizeTOut(el);
5947
- }
5948
-
5949
- function compile(template, options = {
5950
- hasGlobalValues: false,
5951
- }) {
5952
- // parsing
5953
- const ast = parse(template, options.customDirectives);
5954
- // code generation
5955
- const codeGenerator = new CodeGenerator(ast, options);
5956
- const code = codeGenerator.generateCode();
5957
- // template function
5958
- try {
5959
- return new Function("app, bdom, helpers", code);
5960
- }
5961
- catch (originalError) {
5962
- const { name } = options;
5963
- const nameStr = name ? `template "${name}"` : "anonymous template";
5964
- const err = new OwlError(`Failed to compile ${nameStr}: ${originalError.message}\n\ngenerated code:\nfunction(app, bdom, helpers) {\n${code}\n}`);
5965
- err.cause = originalError;
5966
- throw err;
5967
6147
  }
6148
+ return readComputed;
5968
6149
  }
5969
6150
 
5970
- // do not modify manually. This file is generated by the release script.
5971
- const version = "3.0.0-alpha";
5972
-
5973
- function effect(fn) {
5974
- const computation = createComputation(() => {
5975
- // In case the cleanup read an atom.
5976
- // todo: test it
5977
- setComputation(undefined);
5978
- unsubscribeEffect(computation);
5979
- setComputation(computation);
5980
- return fn();
5981
- }, false);
5982
- getCurrentComputation()?.observers.add(computation);
5983
- updateComputation(computation);
5984
- // Remove sources and unsubscribe
5985
- return function cleanupEffect() {
5986
- // In case the cleanup read an atom.
5987
- // todo: test it
5988
- const previousComputation = getCurrentComputation();
5989
- setComputation(undefined);
5990
- unsubscribeEffect(computation);
5991
- setComputation(previousComputation);
6151
+ function buildSignal(value, set) {
6152
+ const atom = {
6153
+ type: "signal",
6154
+ value,
6155
+ observers: new Set(),
6156
+ };
6157
+ let readValue = set(atom);
6158
+ const readSignal = () => {
6159
+ onReadAtom(atom);
6160
+ return readValue;
6161
+ };
6162
+ readSignal[atomSymbol] = atom;
6163
+ readSignal.set = function writeSignal(newValue) {
6164
+ if (Object.is(atom.value, newValue)) {
6165
+ return;
6166
+ }
6167
+ atom.value = newValue;
6168
+ readValue = set(atom);
6169
+ onWriteAtom(atom);
5992
6170
  };
6171
+ return readSignal;
5993
6172
  }
5994
- function unsubscribeEffect(effect) {
5995
- removeSources(effect);
5996
- cleanupEffect(effect);
5997
- for (const childEffect of effect.observers) {
5998
- // Consider it executed to avoid it's re-execution
5999
- // todo: make a test for it
6000
- childEffect.state = ComputationState.EXECUTED;
6001
- removeSources(childEffect);
6002
- unsubscribeEffect(childEffect);
6173
+ function invalidateSignal(signal) {
6174
+ if (typeof signal !== "function" || signal[atomSymbol]?.type !== "signal") {
6175
+ throw new OwlError(`Value is not a signal (${signal})`);
6003
6176
  }
6004
- effect.observers.clear();
6177
+ onWriteAtom(signal[atomSymbol]);
6178
+ }
6179
+ function signalArray(initialValue) {
6180
+ return buildSignal(initialValue, (atom) => proxifyTarget(atom.value, atom));
6181
+ }
6182
+ function signalObject(initialValue) {
6183
+ return buildSignal(initialValue, (atom) => proxifyTarget(atom.value, atom));
6184
+ }
6185
+ function signalMap(initialValue) {
6186
+ return buildSignal(initialValue, (atom) => proxifyTarget(atom.value, atom));
6005
6187
  }
6006
- function cleanupEffect(effect) {
6007
- // the computation.value of an effect is a cleanup function
6008
- const cleanupFn = effect.value;
6009
- if (cleanupFn && typeof cleanupFn === "function") {
6010
- cleanupFn();
6011
- effect.value = undefined;
6012
- }
6188
+ function signalSet(initialValue) {
6189
+ return buildSignal(initialValue, (atom) => proxifyTarget(atom.value, atom));
6190
+ }
6191
+ function signal(value) {
6192
+ return buildSignal(value, (atom) => atom.value);
6013
6193
  }
6194
+ signal.invalidate = invalidateSignal;
6195
+ signal.Array = signalArray;
6196
+ signal.Map = signalMap;
6197
+ signal.Object = signalObject;
6198
+ signal.Set = signalSet;
6014
6199
 
6015
- class Plugin {
6016
- static _shadowId;
6017
- static get id() {
6018
- return this._shadowId ?? this.name;
6019
- }
6020
- static set id(shadowId) {
6021
- this._shadowId = shadowId;
6022
- }
6023
- __owl__;
6024
- constructor(manager) {
6025
- this.__owl__ = manager;
6200
+ function assertType(value, validation, errorMessage = "Value does not match the type") {
6201
+ const issues = validateType(value, validation);
6202
+ if (issues.length) {
6203
+ const issueStrings = JSON.stringify(issues, (key, value) => {
6204
+ if (typeof value === "function") {
6205
+ return value.name;
6206
+ }
6207
+ return value;
6208
+ }, 2);
6209
+ throw new OwlError(`${errorMessage}\n${issueStrings}`);
6026
6210
  }
6027
- setup() { }
6028
6211
  }
6029
- class PluginManager {
6030
- app;
6031
- config;
6032
- onDestroyCb = [];
6033
- plugins;
6034
- status = 0 /* STATUS.NEW */;
6035
- constructor(app, options = {}) {
6036
- this.app = app;
6037
- this.config = options.config ?? {};
6038
- if (options.parent) {
6039
- const parent = options.parent;
6040
- parent.onDestroyCb.push(() => this.destroy());
6041
- this.plugins = Object.create(parent.plugins);
6042
- }
6043
- else {
6044
- this.plugins = {};
6045
- }
6212
+ function createContext(issues, value, path, parent) {
6213
+ return {
6214
+ issueDepth: 0,
6215
+ path,
6216
+ value,
6217
+ get isValid() {
6218
+ return !issues.length;
6219
+ },
6220
+ addIssue(issue) {
6221
+ issues.push({
6222
+ received: this.value,
6223
+ path: this.path,
6224
+ ...issue,
6225
+ });
6226
+ },
6227
+ mergeIssues(newIssues) {
6228
+ issues.push(...newIssues);
6229
+ },
6230
+ validate(type) {
6231
+ type(this);
6232
+ if (!this.isValid && parent) {
6233
+ parent.issueDepth = this.issueDepth + 1;
6234
+ }
6235
+ },
6236
+ withIssues(issues) {
6237
+ return createContext(issues, this.value, this.path, this);
6238
+ },
6239
+ withKey(key) {
6240
+ return createContext(issues, this.value[key], this.path.concat(key), this);
6241
+ },
6242
+ };
6243
+ }
6244
+ function validateType(value, validation) {
6245
+ const issues = [];
6246
+ validation(createContext(issues, value, []));
6247
+ return issues;
6248
+ }
6249
+
6250
+ class Resource {
6251
+ _items = signal.Array([]);
6252
+ _name;
6253
+ _validation;
6254
+ constructor(options = {}) {
6255
+ this._name = options.name;
6256
+ this._validation = options.validation;
6046
6257
  }
6047
- destroy() {
6048
- const cbs = this.onDestroyCb;
6049
- while (cbs.length) {
6050
- cbs.pop()();
6258
+ items = computed(() => {
6259
+ return this._items()
6260
+ .sort((el1, el2) => el1[0] - el2[0])
6261
+ .map((elem) => elem[1]);
6262
+ });
6263
+ add(item, options = {}) {
6264
+ if (this._validation) {
6265
+ const info = this._name ? ` (resource '${this._name}')` : "";
6266
+ assertType(item, this._validation, `Resource item does not match the type${info}`);
6051
6267
  }
6052
- this.status = 3 /* STATUS.DESTROYED */;
6053
- }
6054
- getPluginById(id) {
6055
- return this.plugins[id] || null;
6056
- }
6057
- getPlugin(pluginConstructor) {
6058
- return this.getPluginById(pluginConstructor.id);
6268
+ this._items().push([options.sequence ?? 50, item]);
6269
+ return this;
6059
6270
  }
6060
- startPlugin(pluginConstructor) {
6061
- if (!pluginConstructor.id) {
6062
- throw new OwlError(`Plugin "${pluginConstructor.name}" has no id`);
6063
- }
6064
- if (this.plugins.hasOwnProperty(pluginConstructor.id)) {
6065
- const existingPluginType = this.getPluginById(pluginConstructor.id).constructor;
6066
- if (existingPluginType !== pluginConstructor) {
6067
- throw new OwlError(`Trying to start a plugin with the same id as an other plugin (id: '${pluginConstructor.id}', existing plugin: '${existingPluginType.name}', starting plugin: '${pluginConstructor.name}')`);
6068
- }
6069
- return null;
6070
- }
6071
- const plugin = new pluginConstructor(this);
6072
- this.plugins[pluginConstructor.id] = plugin;
6073
- plugin.setup();
6074
- return plugin;
6271
+ delete(item) {
6272
+ const items = this._items().filter(([seq, val]) => val !== item);
6273
+ this._items.set(items);
6274
+ return this;
6075
6275
  }
6076
- startPlugins(pluginConstructors) {
6077
- contextStack.push({
6078
- type: "plugin",
6079
- app: this.app,
6080
- manager: this,
6081
- get status() {
6082
- return this.manager.status;
6083
- },
6084
- });
6085
- try {
6086
- for (const pluginConstructor of pluginConstructors) {
6087
- this.startPlugin(pluginConstructor);
6088
- }
6089
- }
6090
- finally {
6091
- contextStack.pop();
6092
- }
6093
- this.status = 1 /* STATUS.MOUNTED */;
6276
+ has(item) {
6277
+ return this._items().some(([s, value]) => value === item);
6094
6278
  }
6095
6279
  }
6096
- function startPlugins(manager, plugins) {
6097
- if (Array.isArray(plugins)) {
6098
- manager.startPlugins(plugins);
6099
- }
6100
- else {
6101
- manager.onDestroyCb.push(effect(() => {
6102
- const pluginItems = plugins.items();
6103
- untrack(() => manager.startPlugins(pluginItems));
6104
- }));
6280
+ function useResource(r, elements) {
6281
+ for (let elem of elements) {
6282
+ r.add(elem);
6105
6283
  }
6284
+ onWillDestroy(() => {
6285
+ for (let elem of elements) {
6286
+ r.delete(elem);
6287
+ }
6288
+ });
6106
6289
  }
6107
6290
 
6108
- // -----------------------------------------------------------------------------
6109
- // Scheduler
6110
- // -----------------------------------------------------------------------------
6111
- class Scheduler {
6112
- // capture the value of requestAnimationFrame as soon as possible, to avoid
6113
- // interactions with other code, such as test frameworks that override them
6114
- static requestAnimationFrame = window.requestAnimationFrame.bind(window);
6115
- tasks = new Set();
6116
- requestAnimationFrame;
6117
- frame = 0;
6118
- delayedRenders = [];
6119
- cancelledNodes = new Set();
6120
- processing = false;
6121
- constructor() {
6122
- this.requestAnimationFrame = Scheduler.requestAnimationFrame;
6123
- }
6124
- addFiber(fiber) {
6125
- this.tasks.add(fiber.root);
6291
+ class Registry {
6292
+ _map = signal.Object(Object.create(null));
6293
+ _name;
6294
+ _validation;
6295
+ constructor(options = {}) {
6296
+ this._name = options.name || "registry";
6297
+ this._validation = options.validation;
6126
6298
  }
6127
- scheduleDestroy(node) {
6128
- this.cancelledNodes.add(node);
6129
- if (this.frame === 0) {
6130
- this.frame = this.requestAnimationFrame(() => this.processTasks());
6299
+ entries = computed(() => {
6300
+ const entries = Object.entries(this._map())
6301
+ .sort((el1, el2) => el1[1][0] - el2[1][0])
6302
+ .map(([str, elem]) => [str, elem[1]]);
6303
+ return entries;
6304
+ });
6305
+ items = computed(() => this.entries().map((e) => e[1]));
6306
+ addById(item, options = {}) {
6307
+ if (!item.id) {
6308
+ throw new OwlError(`Item should have an id key (registry '${this._name}')`);
6131
6309
  }
6310
+ return this.add(item.id, item, { sequence: options.sequence ?? 50 });
6132
6311
  }
6133
- /**
6134
- * Process all current tasks. This only applies to the fibers that are ready.
6135
- * Other tasks are left unchanged.
6136
- */
6137
- flush() {
6138
- if (this.delayedRenders.length) {
6139
- let renders = this.delayedRenders;
6140
- this.delayedRenders = [];
6141
- for (let f of renders) {
6142
- if (f.root && f.node.status !== 3 /* STATUS.DESTROYED */ && f.node.fiber === f) {
6143
- f.render();
6144
- }
6145
- }
6146
- }
6147
- if (this.frame === 0) {
6148
- this.frame = this.requestAnimationFrame(() => this.processTasks());
6312
+ add(key, value, options = {}) {
6313
+ if (this._validation) {
6314
+ const info = this._name ? ` (registry '${this._name}', key: '${key}')` : ` (key: '${key}')`;
6315
+ assertType(value, this._validation, `Registry entry does not match the type${info}`);
6149
6316
  }
6317
+ this._map()[key] = [options.sequence ?? 50, value];
6318
+ return this;
6150
6319
  }
6151
- processTasks() {
6152
- if (this.processing) {
6153
- return;
6154
- }
6155
- this.processing = true;
6156
- this.frame = 0;
6157
- for (let node of this.cancelledNodes) {
6158
- node._destroy();
6159
- }
6160
- this.cancelledNodes.clear();
6161
- for (let task of this.tasks) {
6162
- this.processFiber(task);
6163
- }
6164
- for (let task of this.tasks) {
6165
- if (task.node.status === 3 /* STATUS.DESTROYED */) {
6166
- this.tasks.delete(task);
6167
- }
6320
+ get(key, defaultValue) {
6321
+ const hasKey = key in this._map();
6322
+ if (!hasKey && arguments.length < 2) {
6323
+ throw new Error(`KeyNotFoundError: Cannot find key "${key}" (registry '${this._name}')`);
6168
6324
  }
6169
- this.processing = false;
6325
+ return hasKey ? this._map()[key][1] : defaultValue;
6170
6326
  }
6171
- processFiber(fiber) {
6172
- if (fiber.root !== fiber) {
6173
- this.tasks.delete(fiber);
6327
+ delete(key) {
6328
+ delete this._map()[key];
6329
+ }
6330
+ has(key) {
6331
+ return key in this._map();
6332
+ }
6333
+ }
6334
+
6335
+ const anyType = function validateAny() { };
6336
+ const booleanType = function validateBoolean(context) {
6337
+ if (typeof context.value !== "boolean") {
6338
+ context.addIssue({ message: "value is not a boolean" });
6339
+ }
6340
+ };
6341
+ const numberType = function validateNumber(context) {
6342
+ if (typeof context.value !== "number") {
6343
+ context.addIssue({ message: "value is not a number" });
6344
+ }
6345
+ };
6346
+ const stringType = function validateString(context) {
6347
+ if (typeof context.value !== "string") {
6348
+ context.addIssue({ message: "value is not a string" });
6349
+ }
6350
+ };
6351
+ function arrayType(elementType) {
6352
+ return function validateArray(context) {
6353
+ if (!Array.isArray(context.value)) {
6354
+ context.addIssue({ message: "value is not an array" });
6174
6355
  return;
6175
6356
  }
6176
- const hasError = fibersInError.has(fiber);
6177
- if (hasError && fiber.counter !== 0) {
6178
- this.tasks.delete(fiber);
6357
+ if (!elementType) {
6179
6358
  return;
6180
6359
  }
6181
- if (fiber.node.status === 3 /* STATUS.DESTROYED */) {
6182
- this.tasks.delete(fiber);
6183
- return;
6360
+ for (let index = 0; index < context.value.length; index++) {
6361
+ context.withKey(index).validate(elementType);
6184
6362
  }
6185
- if (fiber.counter === 0) {
6186
- if (!hasError) {
6187
- fiber.complete();
6188
- }
6189
- // at this point, the fiber should have been applied to the DOM, so we can
6190
- // remove it from the task list. If it is not the case, it means that there
6191
- // was an error and an error handler triggered a new rendering that recycled
6192
- // the fiber, so in that case, we actually want to keep the fiber around,
6193
- // otherwise it will just be ignored.
6194
- if (fiber.appliedToDom) {
6195
- this.tasks.delete(fiber);
6196
- }
6363
+ };
6364
+ }
6365
+ function constructorType(constructor) {
6366
+ return function validateConstructor(context) {
6367
+ if (!(typeof context.value === "function") ||
6368
+ !(context.value === constructor || context.value.prototype instanceof constructor)) {
6369
+ context.addIssue({ message: `value is not '${constructor.name}' or an extension` });
6197
6370
  }
6198
- }
6371
+ };
6199
6372
  }
6200
-
6201
- let hasBeenLogged = false;
6202
- const apps = new Set();
6203
- window.__OWL_DEVTOOLS__ ||= { apps, Fiber, RootFiber, toRaw, proxy };
6204
- class App extends TemplateSet {
6205
- static validateTarget = validateTarget;
6206
- static apps = apps;
6207
- static version = version;
6208
- name;
6209
- scheduler = new Scheduler();
6210
- roots = new Set();
6211
- pluginManager;
6212
- constructor(config = {}) {
6213
- super(config);
6214
- this.name = config.name || "";
6215
- apps.add(this);
6216
- this.pluginManager = new PluginManager(this, { config: config.config });
6217
- if (config.plugins) {
6218
- startPlugins(this.pluginManager, config.plugins);
6373
+ function customValidator(type, validator, errorMessage = "value does not match custom validation") {
6374
+ return function validateCustom(context) {
6375
+ context.validate(type);
6376
+ if (!context.isValid) {
6377
+ return;
6219
6378
  }
6220
- if (config.test) {
6221
- this.dev = true;
6379
+ if (!validator(context.value)) {
6380
+ context.addIssue({ message: errorMessage });
6222
6381
  }
6223
- if (this.dev && !config.test && !hasBeenLogged) {
6224
- console.info(`Owl is running in 'dev' mode.`);
6225
- hasBeenLogged = true;
6382
+ };
6383
+ }
6384
+ function functionType(parameters = [], result = undefined) {
6385
+ return function validateFunction(context) {
6386
+ if (typeof context.value !== "function") {
6387
+ context.addIssue({ message: "value is not a function" });
6226
6388
  }
6227
- }
6228
- createRoot(Root, config = {}) {
6229
- const props = config.props || {};
6230
- let resolve;
6231
- let reject;
6232
- const promise = new Promise((res, rej) => {
6233
- resolve = res;
6234
- reject = rej;
6235
- });
6236
- const restore = saveContext();
6237
- let node;
6238
- let error = null;
6239
- try {
6240
- node = new ComponentNode(Root, props, this, null, null);
6389
+ };
6390
+ }
6391
+ function instanceType(constructor) {
6392
+ return function validateInstanceType(context) {
6393
+ if (!(context.value instanceof constructor)) {
6394
+ context.addIssue({ message: `value is not an instance of '${constructor.name}'` });
6241
6395
  }
6242
- catch (e) {
6243
- error = e;
6244
- reject(e);
6396
+ };
6397
+ }
6398
+ function intersection(types) {
6399
+ return function validateIntersection(context) {
6400
+ for (const type of types) {
6401
+ context.validate(type);
6245
6402
  }
6246
- finally {
6247
- restore();
6403
+ };
6404
+ }
6405
+ function literalType(literal) {
6406
+ return function validateLiteral(context) {
6407
+ if (context.value !== literal) {
6408
+ context.addIssue({
6409
+ message: `value is not equal to ${typeof literal === "string" ? `'${literal}'` : literal}`,
6410
+ });
6248
6411
  }
6249
- const root = {
6250
- node: node,
6251
- promise,
6252
- mount: (target, options) => {
6253
- if (error) {
6254
- return promise;
6255
- }
6256
- App.validateTarget(target);
6257
- this.mountNode(node, target, resolve, reject, options);
6258
- return promise;
6259
- },
6260
- destroy: () => {
6261
- this.roots.delete(root);
6262
- node.destroy();
6263
- this.scheduler.processTasks();
6264
- },
6265
- };
6266
- this.roots.add(root);
6267
- return root;
6412
+ };
6413
+ }
6414
+ function literalSelection(literals) {
6415
+ return union(literals.map(literalType));
6416
+ }
6417
+ function validateObject(context, schema, isStrict) {
6418
+ if (typeof context.value !== "object" || Array.isArray(context.value) || context.value === null) {
6419
+ context.addIssue({ message: "value is not an object" });
6420
+ return;
6268
6421
  }
6269
- mountNode(node, target, resolve, reject, options) {
6270
- // Manually add the last resort error handler on the node
6271
- let handlers = nodeErrorHandlers.get(node);
6272
- if (!handlers) {
6273
- handlers = [];
6274
- nodeErrorHandlers.set(node, handlers);
6275
- }
6276
- handlers.unshift((e, finalize) => {
6277
- const finalError = finalize();
6278
- reject(finalError);
6279
- });
6280
- // manually set a onMounted callback.
6281
- // that way, we are independant from the current node.
6282
- node.mounted.push(() => {
6283
- resolve(node.component);
6284
- handlers.shift();
6285
- });
6286
- node.mountComponent(target, options);
6422
+ if (!schema) {
6423
+ return;
6287
6424
  }
6288
- destroy() {
6289
- for (let root of this.roots) {
6290
- root.destroy();
6425
+ const isShape = !Array.isArray(schema);
6426
+ let shape = schema;
6427
+ if (Array.isArray(schema)) {
6428
+ shape = {};
6429
+ for (const key of schema) {
6430
+ shape[key] = null;
6291
6431
  }
6292
- this.pluginManager.destroy();
6293
- this.scheduler.processTasks();
6294
- apps.delete(this);
6295
- }
6296
- handleError(...args) {
6297
- return handleError(...args);
6298
6432
  }
6299
- }
6300
- async function mount(C, target, config = {}) {
6301
- const app = new App(config);
6302
- const root = app.createRoot(C, config);
6303
- return root.mount(target, config);
6304
- }
6305
-
6306
- const mainEventHandler = (data, ev, currentTarget) => {
6307
- const { data: _data, modifiers } = filterOutModifiersFromData(data);
6308
- data = _data;
6309
- let stopped = false;
6310
- if (modifiers.length) {
6311
- let selfMode = false;
6312
- const isSelf = ev.target === currentTarget;
6313
- for (const mod of modifiers) {
6314
- switch (mod) {
6315
- case "self":
6316
- selfMode = true;
6317
- if (isSelf) {
6318
- continue;
6319
- }
6320
- else {
6321
- return stopped;
6322
- }
6323
- case "prevent":
6324
- if ((selfMode && isSelf) || !selfMode)
6325
- ev.preventDefault();
6326
- continue;
6327
- case "stop":
6328
- if ((selfMode && isSelf) || !selfMode)
6329
- ev.stopPropagation();
6330
- stopped = true;
6331
- continue;
6433
+ const missingKeys = [];
6434
+ for (const key in shape) {
6435
+ const property = key.endsWith("?") ? key.slice(0, -1) : key;
6436
+ if (context.value[property] === undefined) {
6437
+ if (!key.endsWith("?")) {
6438
+ missingKeys.push(property);
6332
6439
  }
6440
+ continue;
6333
6441
  }
6334
- }
6335
- // If handler is empty, the array slot 0 will also be empty, and data will not have the property 0
6336
- // We check this rather than data[0] being truthy (or typeof function) so that it crashes
6337
- // as expected when there is a handler expression that evaluates to a falsy value
6338
- if (Object.hasOwnProperty.call(data, 0)) {
6339
- const handler = data[0];
6340
- if (typeof handler !== "function") {
6341
- throw new OwlError(`Invalid handler (expected a function, received: '${handler}')`);
6342
- }
6343
- let node = data[1] ? data[1].__owl__ : null;
6344
- if (node ? node.status === 1 /* STATUS.MOUNTED */ : true) {
6345
- handler(data[1], ev);
6442
+ if (isShape) {
6443
+ context.withKey(property).validate(shape[key]);
6346
6444
  }
6347
6445
  }
6348
- return stopped;
6349
- };
6350
-
6351
- function computed(getter, options = {}) {
6352
- const computation = createComputation(() => {
6353
- onWriteAtom(computation);
6354
- return getter();
6355
- }, true);
6356
- function readComputed() {
6357
- if (computation.state !== ComputationState.EXECUTED) {
6358
- updateComputation(computation);
6446
+ if (missingKeys.length) {
6447
+ context.addIssue({
6448
+ message: "object value has missing keys",
6449
+ missingKeys,
6450
+ });
6451
+ }
6452
+ if (isStrict) {
6453
+ const unknownKeys = [];
6454
+ for (const key in context.value) {
6455
+ if (!(key in shape) && !(`${key}?` in shape)) {
6456
+ unknownKeys.push(key);
6457
+ }
6458
+ }
6459
+ if (unknownKeys.length) {
6460
+ context.addIssue({
6461
+ message: "object value has unknown keys",
6462
+ unknownKeys,
6463
+ });
6359
6464
  }
6360
- onReadAtom(computation);
6361
- return computation.value;
6362
6465
  }
6363
- readComputed[atomSymbol] = computation;
6364
- readComputed.set = options.set ?? (() => { });
6365
- return readComputed;
6366
6466
  }
6367
-
6368
- function buildSignal(value, set) {
6369
- const atom = {
6370
- type: "signal",
6371
- value,
6372
- observers: new Set(),
6467
+ function objectType(schema = {}) {
6468
+ return function validateLooseObject(context) {
6469
+ validateObject(context, schema, false);
6373
6470
  };
6374
- let readValue = set(atom);
6375
- const readSignal = () => {
6376
- onReadAtom(atom);
6377
- return readValue;
6471
+ }
6472
+ function strictObjectType(schema) {
6473
+ return function validateStrictObject(context) {
6474
+ validateObject(context, schema, true);
6378
6475
  };
6379
- readSignal[atomSymbol] = atom;
6380
- readSignal.set = function writeSignal(newValue) {
6381
- if (Object.is(atom.value, newValue)) {
6382
- return;
6476
+ }
6477
+ function promiseType(type) {
6478
+ return function validatePromise(context) {
6479
+ if (!(context.value instanceof Promise)) {
6480
+ context.addIssue({ message: "value is not a promise" });
6383
6481
  }
6384
- atom.value = newValue;
6385
- readValue = set(atom);
6386
- onWriteAtom(atom);
6387
6482
  };
6388
- return readSignal;
6389
- }
6390
- function invalidateSignal(signal) {
6391
- if (typeof signal !== "function" || signal[atomSymbol]?.type !== "signal") {
6392
- throw new OwlError(`Value is not a signal (${signal})`);
6393
- }
6394
- onWriteAtom(signal[atomSymbol]);
6395
6483
  }
6396
- function signalArray(initialValue) {
6397
- return buildSignal(initialValue, (atom) => proxifyTarget(atom.value, atom));
6484
+ function recordType(valueType) {
6485
+ return function validateRecord(context) {
6486
+ if (typeof context.value !== "object" ||
6487
+ Array.isArray(context.value) ||
6488
+ context.value === null) {
6489
+ context.addIssue({ message: "value is not an object" });
6490
+ return;
6491
+ }
6492
+ if (!valueType) {
6493
+ return;
6494
+ }
6495
+ for (const key in context.value) {
6496
+ context.withKey(key).validate(valueType);
6497
+ }
6498
+ };
6398
6499
  }
6399
- function signalObject(initialValue) {
6400
- return buildSignal(initialValue, (atom) => proxifyTarget(atom.value, atom));
6500
+ function tuple(types) {
6501
+ return function validateTuple(context) {
6502
+ if (!Array.isArray(context.value)) {
6503
+ context.addIssue({ message: "value is not an array" });
6504
+ return;
6505
+ }
6506
+ if (context.value.length !== types.length) {
6507
+ context.addIssue({ message: "tuple value does not have the correct length" });
6508
+ return;
6509
+ }
6510
+ for (let index = 0; index < types.length; index++) {
6511
+ context.withKey(index).validate(types[index]);
6512
+ }
6513
+ };
6401
6514
  }
6402
- function signalMap(initialValue) {
6403
- return buildSignal(initialValue, (atom) => proxifyTarget(atom.value, atom));
6515
+ function union(types) {
6516
+ return function validateUnion(context) {
6517
+ let firstIssueIndex = 0;
6518
+ const subIssues = [];
6519
+ for (const type of types) {
6520
+ const subContext = context.withIssues(subIssues);
6521
+ subContext.validate(type);
6522
+ if (subIssues.length === firstIssueIndex || subContext.issueDepth > 0) {
6523
+ context.mergeIssues(subIssues.slice(firstIssueIndex));
6524
+ return;
6525
+ }
6526
+ firstIssueIndex = subIssues.length;
6527
+ }
6528
+ context.addIssue({
6529
+ message: "value does not match union type",
6530
+ subIssues,
6531
+ });
6532
+ };
6404
6533
  }
6405
- function signalSet(initialValue) {
6406
- return buildSignal(initialValue, (atom) => proxifyTarget(atom.value, atom));
6534
+ function reactiveValueType(type) {
6535
+ return function validateReactiveValue(context) {
6536
+ if (typeof context.value !== "function" || !context.value[atomSymbol]) {
6537
+ context.addIssue({ message: "value is not a reactive value" });
6538
+ }
6539
+ };
6407
6540
  }
6408
- function signal(value) {
6409
- return buildSignal(value, (atom) => atom.value);
6541
+ function ref(type) {
6542
+ return union([literalType(null), instanceType(type)]);
6410
6543
  }
6411
- signal.invalidate = invalidateSignal;
6412
- signal.Array = signalArray;
6413
- signal.Map = signalMap;
6414
- signal.Object = signalObject;
6415
- signal.Set = signalSet;
6544
+ const types = {
6545
+ and: intersection,
6546
+ any: anyType,
6547
+ array: arrayType,
6548
+ boolean: booleanType,
6549
+ constructor: constructorType,
6550
+ customValidator: customValidator,
6551
+ function: functionType,
6552
+ instanceOf: instanceType,
6553
+ literal: literalType,
6554
+ number: numberType,
6555
+ object: objectType,
6556
+ or: union,
6557
+ promise: promiseType,
6558
+ record: recordType,
6559
+ ref,
6560
+ selection: literalSelection,
6561
+ signal: reactiveValueType,
6562
+ strictObject: strictObjectType,
6563
+ string: stringType,
6564
+ tuple: tuple,
6565
+ };
6416
6566
 
6417
- class Resource {
6418
- _items = signal.Array([]);
6419
- _name;
6420
- _validation;
6421
- constructor(options = {}) {
6422
- this._name = options.name;
6423
- this._validation = options.validation;
6424
- }
6425
- items = computed(() => {
6426
- return this._items()
6427
- .sort((el1, el2) => el1[0] - el2[0])
6428
- .map((elem) => elem[1]);
6429
- });
6430
- add(item, options = {}) {
6431
- if (this._validation) {
6432
- const info = this._name ? ` (resource '${this._name}')` : "";
6433
- assertType(item, this._validation, `Resource item does not match the type${info}`);
6434
- }
6435
- this._items().push([options.sequence ?? 50, item]);
6436
- return this;
6437
- }
6438
- delete(item) {
6439
- const items = this._items().filter(([seq, val]) => val !== item);
6440
- this._items.set(items);
6441
- return this;
6442
- }
6443
- has(item) {
6444
- return this._items().some(([s, value]) => value === item);
6445
- }
6446
- }
6447
- function useResource(r, elements) {
6448
- for (let elem of elements) {
6449
- r.add(elem);
6450
- }
6451
- onWillDestroy(() => {
6452
- for (let elem of elements) {
6453
- r.delete(elem);
6567
+ function validateObjectWithDefaults(schema, defaultValues) {
6568
+ const keys = Array.isArray(schema) ? schema : Object.keys(schema);
6569
+ const mandatoryDefaultedKeys = keys.filter((key) => !key.endsWith("?") && key in defaultValues);
6570
+ return (context) => {
6571
+ if (mandatoryDefaultedKeys.length) {
6572
+ context.addIssue({
6573
+ message: "props have default values on mandatory keys",
6574
+ keys: mandatoryDefaultedKeys,
6575
+ });
6454
6576
  }
6455
- });
6577
+ context.validate(types.object(schema));
6578
+ };
6456
6579
  }
6457
-
6458
- class Registry {
6459
- _map = signal.Object(Object.create(null));
6460
- _name;
6461
- _validation;
6462
- constructor(options = {}) {
6463
- this._name = options.name || "registry";
6464
- this._validation = options.validation;
6465
- }
6466
- entries = computed(() => {
6467
- const entries = Object.entries(this._map())
6468
- .sort((el1, el2) => el1[1][0] - el2[1][0])
6469
- .map(([str, elem]) => [str, elem[1]]);
6470
- return entries;
6471
- });
6472
- items = computed(() => this.entries().map((e) => e[1]));
6473
- addById(item, options = {}) {
6474
- if (!item.id) {
6475
- throw new OwlError(`Item should have an id key (registry '${this._name}')`);
6580
+ function props(type, defaults) {
6581
+ const { node, app, componentName } = getContext("component");
6582
+ Object.assign(node.defaultProps, defaults);
6583
+ function getProp(key) {
6584
+ if (node.props[key] === undefined && defaults) {
6585
+ return defaults[key];
6476
6586
  }
6477
- return this.add(item.id, item, { sequence: options.sequence ?? 50 });
6587
+ return node.props[key];
6478
6588
  }
6479
- add(key, value, options = {}) {
6480
- if (this._validation) {
6481
- const info = this._name ? ` (registry '${this._name}', key: '${key}')` : ` (key: '${key}')`;
6482
- assertType(value, this._validation, `Registry entry does not match the type${info}`);
6589
+ const result = Object.create(null);
6590
+ function applyPropGetters(keys) {
6591
+ for (const key of keys) {
6592
+ Reflect.defineProperty(result, key, {
6593
+ enumerable: true,
6594
+ get: getProp.bind(null, key),
6595
+ });
6483
6596
  }
6484
- this._map()[key] = [options.sequence ?? 50, value];
6485
- return this;
6486
6597
  }
6487
- get(key, defaultValue) {
6488
- const hasKey = key in this._map();
6489
- if (!hasKey && arguments.length < 2) {
6490
- throw new Error(`KeyNotFoundError: Cannot find key "${key}" (registry '${this._name}')`);
6598
+ if (type) {
6599
+ const keys = (Array.isArray(type) ? type : Object.keys(type)).map((key) => key.endsWith("?") ? key.slice(0, -1) : key);
6600
+ applyPropGetters(keys);
6601
+ if (app.dev) {
6602
+ const validation = defaults ? validateObjectWithDefaults(type, defaults) : types.object(type);
6603
+ assertType(node.props, validation, `Invalid component props (${componentName})`);
6604
+ node.willUpdateProps.push((np) => {
6605
+ assertType(np, validation, `Invalid component props (${componentName})`);
6606
+ });
6491
6607
  }
6492
- return hasKey ? this._map()[key][1] : defaultValue;
6493
- }
6494
- delete(key) {
6495
- delete this._map()[key];
6496
6608
  }
6497
- has(key) {
6498
- return key in this._map();
6609
+ else {
6610
+ const getKeys = (props) => {
6611
+ const keys = [];
6612
+ for (const k in props) {
6613
+ if (k.charCodeAt(0) !== 1) {
6614
+ keys.push(k);
6615
+ }
6616
+ }
6617
+ if (defaults) {
6618
+ for (const k in defaults) {
6619
+ if (!(k in props)) {
6620
+ keys.push(k);
6621
+ }
6622
+ }
6623
+ }
6624
+ return keys;
6625
+ };
6626
+ let keys = getKeys(node.props);
6627
+ applyPropGetters(keys);
6628
+ node.willUpdateProps.push((np) => {
6629
+ for (const key of keys) {
6630
+ Reflect.deleteProperty(result, key);
6631
+ }
6632
+ keys = getKeys(np);
6633
+ applyPropGetters(keys);
6634
+ });
6499
6635
  }
6636
+ return result;
6500
6637
  }
6501
6638
 
6502
6639
  function status(entity) {
@@ -6677,8 +6814,8 @@
6677
6814
  exports.xml = xml;
6678
6815
 
6679
6816
 
6680
- __info__.date = '2026-02-20T08:37:22.309Z';
6681
- __info__.hash = 'b348815';
6817
+ __info__.date = '2026-04-02T11:14:58.432Z';
6818
+ __info__.hash = '9ca35d8';
6682
6819
  __info__.url = 'https://github.com/odoo/owl';
6683
6820
 
6684
6821