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