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