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