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

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