@odoo/owl 2.1.4 → 2.2.2

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.
Binary file
package/dist/owl.cjs.js CHANGED
@@ -126,14 +126,16 @@ function handleError(params) {
126
126
  }
127
127
  const node = "node" in params ? params.node : params.fiber.node;
128
128
  const fiber = "fiber" in params ? params.fiber : node.fiber;
129
- // resets the fibers on components if possible. This is important so that
130
- // new renderings can be properly included in the initial one, if any.
131
- let current = fiber;
132
- do {
133
- current.node.fiber = current;
134
- current = current.parent;
135
- } while (current);
136
- fibersInError.set(fiber.root, error);
129
+ if (fiber) {
130
+ // resets the fibers on components if possible. This is important so that
131
+ // new renderings can be properly included in the initial one, if any.
132
+ let current = fiber;
133
+ do {
134
+ current.node.fiber = current;
135
+ current = current.parent;
136
+ } while (current);
137
+ fibersInError.set(fiber.root, error);
138
+ }
137
139
  const handled = _handleError(node, error);
138
140
  if (!handled) {
139
141
  console.warn(`[Owl] Unhandled error. Destroying the root component`);
@@ -315,20 +317,13 @@ function updateClass(val, oldVal) {
315
317
  * @returns a batched version of the original callback
316
318
  */
317
319
  function batched(callback) {
318
- let called = false;
319
- return async () => {
320
- // This await blocks all calls to the callback here, then releases them sequentially
321
- // in the next microtick. This line decides the granularity of the batch.
322
- await Promise.resolve();
323
- if (!called) {
324
- called = true;
325
- // wait for all calls in this microtick to fall through before resetting "called"
326
- // so that only the first call to the batched function calls the original callback.
327
- // Schedule this before calling the callback so that calls to the batched function
328
- // within the callback will proceed only after resetting called to false, and have
329
- // a chance to execute the callback again
330
- Promise.resolve().then(() => (called = false));
331
- callback();
320
+ let scheduled = false;
321
+ return async (...args) => {
322
+ if (!scheduled) {
323
+ scheduled = true;
324
+ await Promise.resolve();
325
+ scheduled = false;
326
+ callback(...args);
332
327
  }
333
328
  };
334
329
  }
@@ -1661,8 +1656,7 @@ function cancelFibers(fibers) {
1661
1656
  let node = fiber.node;
1662
1657
  fiber.render = throwOnRender;
1663
1658
  if (node.status === 0 /* NEW */) {
1664
- node.destroy();
1665
- delete node.parent.children[node.parentKey];
1659
+ node.cancel();
1666
1660
  }
1667
1661
  node.fiber = null;
1668
1662
  if (fiber.bdom) {
@@ -2389,6 +2383,9 @@ class ComponentNode {
2389
2383
  }
2390
2384
  }
2391
2385
  async render(deep) {
2386
+ if (this.status >= 2 /* CANCELLED */) {
2387
+ return;
2388
+ }
2392
2389
  let current = this.fiber;
2393
2390
  if (current && (current.root.locked || current.bdom === true)) {
2394
2391
  await Promise.resolve();
@@ -2414,7 +2411,7 @@ class ComponentNode {
2414
2411
  this.fiber = fiber;
2415
2412
  this.app.scheduler.addFiber(fiber);
2416
2413
  await Promise.resolve();
2417
- if (this.status === 2 /* DESTROYED */) {
2414
+ if (this.status >= 2 /* CANCELLED */) {
2418
2415
  return;
2419
2416
  }
2420
2417
  // We only want to actually render the component if the following two
@@ -2432,6 +2429,18 @@ class ComponentNode {
2432
2429
  fiber.render();
2433
2430
  }
2434
2431
  }
2432
+ cancel() {
2433
+ this._cancel();
2434
+ delete this.parent.children[this.parentKey];
2435
+ this.app.scheduler.scheduleDestroy(this);
2436
+ }
2437
+ _cancel() {
2438
+ this.status = 2 /* CANCELLED */;
2439
+ const children = this.children;
2440
+ for (let childKey in children) {
2441
+ children[childKey]._cancel();
2442
+ }
2443
+ }
2435
2444
  destroy() {
2436
2445
  let shouldRemove = this.status === 1 /* MOUNTED */;
2437
2446
  this._destroy();
@@ -2459,7 +2468,7 @@ class ComponentNode {
2459
2468
  this.app.handleError({ error: e, node: this });
2460
2469
  }
2461
2470
  }
2462
- this.status = 2 /* DESTROYED */;
2471
+ this.status = 3 /* DESTROYED */;
2463
2472
  }
2464
2473
  async updateAndRender(props, parentFiber) {
2465
2474
  this.nextProps = props;
@@ -2992,12 +3001,22 @@ function prepareList(collection) {
2992
3001
  keys = collection;
2993
3002
  values = collection;
2994
3003
  }
2995
- else if (collection) {
2996
- values = Object.keys(collection);
2997
- keys = Object.values(collection);
3004
+ else if (collection instanceof Map) {
3005
+ keys = [...collection.keys()];
3006
+ values = [...collection.values()];
3007
+ }
3008
+ else if (collection && typeof collection === "object") {
3009
+ if (Symbol.iterator in collection) {
3010
+ keys = [...collection];
3011
+ values = keys;
3012
+ }
3013
+ else {
3014
+ values = Object.keys(collection);
3015
+ keys = Object.values(collection);
3016
+ }
2998
3017
  }
2999
3018
  else {
3000
- throw new OwlError("Invalid loop expression");
3019
+ throw new OwlError(`Invalid loop expression: "${collection}" is not iterable`);
3001
3020
  }
3002
3021
  const n = values.length;
3003
3022
  return [keys, values, n, new Array(n)];
@@ -4824,10 +4843,10 @@ function parseNode(node, ctx) {
4824
4843
  parseTCall(node, ctx) ||
4825
4844
  parseTCallBlock(node) ||
4826
4845
  parseTEscNode(node, ctx) ||
4846
+ parseTOutNode(node, ctx) ||
4827
4847
  parseTKey(node, ctx) ||
4828
4848
  parseTTranslation(node, ctx) ||
4829
4849
  parseTSlot(node, ctx) ||
4830
- parseTOutNode(node, ctx) ||
4831
4850
  parseComponent(node, ctx) ||
4832
4851
  parseDOMNode(node, ctx) ||
4833
4852
  parseTSetNode(node, ctx) ||
@@ -4940,10 +4959,8 @@ function parseDOMNode(node, ctx) {
4940
4959
  const typeAttr = node.getAttribute("type");
4941
4960
  const isInput = tagName === "input";
4942
4961
  const isSelect = tagName === "select";
4943
- const isTextarea = tagName === "textarea";
4944
4962
  const isCheckboxInput = isInput && typeAttr === "checkbox";
4945
4963
  const isRadioInput = isInput && typeAttr === "radio";
4946
- const isOtherInput = isInput && !isCheckboxInput && !isRadioInput;
4947
4964
  const hasLazyMod = attr.includes(".lazy");
4948
4965
  const hasNumberMod = attr.includes(".number");
4949
4966
  const hasTrimMod = attr.includes(".trim");
@@ -4955,8 +4972,8 @@ function parseDOMNode(node, ctx) {
4955
4972
  specialInitTargetAttr: isRadioInput ? "checked" : null,
4956
4973
  eventType,
4957
4974
  hasDynamicChildren: false,
4958
- shouldTrim: hasTrimMod && (isOtherInput || isTextarea),
4959
- shouldNumberize: hasNumberMod && (isOtherInput || isTextarea),
4975
+ shouldTrim: hasTrimMod,
4976
+ shouldNumberize: hasNumberMod,
4960
4977
  };
4961
4978
  if (isSelect) {
4962
4979
  // don't pollute the original ctx
@@ -5019,9 +5036,6 @@ function parseTEscNode(node, ctx) {
5019
5036
  content: [tesc],
5020
5037
  };
5021
5038
  }
5022
- if (ast.type === 11 /* TComponent */) {
5023
- throw new OwlError("t-esc is not supported on Component nodes");
5024
- }
5025
5039
  return tesc;
5026
5040
  }
5027
5041
  // -----------------------------------------------------------------------------
@@ -5463,19 +5477,21 @@ function normalizeTIf(el) {
5463
5477
  *
5464
5478
  * @param el the element containing the tree that should be normalized
5465
5479
  */
5466
- function normalizeTEsc(el) {
5467
- const elements = [...el.querySelectorAll("[t-esc]")].filter((el) => el.tagName[0] === el.tagName[0].toUpperCase() || el.hasAttribute("t-component"));
5468
- for (const el of elements) {
5469
- if (el.childNodes.length) {
5470
- throw new OwlError("Cannot have t-esc on a component that already has content");
5471
- }
5472
- const value = el.getAttribute("t-esc");
5473
- el.removeAttribute("t-esc");
5474
- const t = el.ownerDocument.createElement("t");
5475
- if (value != null) {
5476
- t.setAttribute("t-esc", value);
5480
+ function normalizeTEscTOut(el) {
5481
+ for (const d of ["t-esc", "t-out"]) {
5482
+ const elements = [...el.querySelectorAll(`[${d}]`)].filter((el) => el.tagName[0] === el.tagName[0].toUpperCase() || el.hasAttribute("t-component"));
5483
+ for (const el of elements) {
5484
+ if (el.childNodes.length) {
5485
+ throw new OwlError(`Cannot have ${d} on a component that already has content`);
5486
+ }
5487
+ const value = el.getAttribute(d);
5488
+ el.removeAttribute(d);
5489
+ const t = el.ownerDocument.createElement("t");
5490
+ if (value != null) {
5491
+ t.setAttribute(d, value);
5492
+ }
5493
+ el.appendChild(t);
5477
5494
  }
5478
- el.appendChild(t);
5479
5495
  }
5480
5496
  }
5481
5497
  /**
@@ -5486,7 +5502,7 @@ function normalizeTEsc(el) {
5486
5502
  */
5487
5503
  function normalizeXML(el) {
5488
5504
  normalizeTIf(el);
5489
- normalizeTEsc(el);
5505
+ normalizeTEscTOut(el);
5490
5506
  }
5491
5507
  /**
5492
5508
  * Parses an XML string into an XML document, throwing errors on parser errors
@@ -5539,7 +5555,7 @@ function compile(template, options = {}) {
5539
5555
  }
5540
5556
 
5541
5557
  // do not modify manually. This file is generated by the release script.
5542
- const version = "2.1.3";
5558
+ const version = "2.2.2";
5543
5559
 
5544
5560
  // -----------------------------------------------------------------------------
5545
5561
  // Scheduler
@@ -5549,11 +5565,18 @@ class Scheduler {
5549
5565
  this.tasks = new Set();
5550
5566
  this.frame = 0;
5551
5567
  this.delayedRenders = [];
5568
+ this.cancelledNodes = new Set();
5552
5569
  this.requestAnimationFrame = Scheduler.requestAnimationFrame;
5553
5570
  }
5554
5571
  addFiber(fiber) {
5555
5572
  this.tasks.add(fiber.root);
5556
5573
  }
5574
+ scheduleDestroy(node) {
5575
+ this.cancelledNodes.add(node);
5576
+ if (this.frame === 0) {
5577
+ this.frame = this.requestAnimationFrame(() => this.processTasks());
5578
+ }
5579
+ }
5557
5580
  /**
5558
5581
  * Process all current tasks. This only applies to the fibers that are ready.
5559
5582
  * Other tasks are left unchanged.
@@ -5563,21 +5586,28 @@ class Scheduler {
5563
5586
  let renders = this.delayedRenders;
5564
5587
  this.delayedRenders = [];
5565
5588
  for (let f of renders) {
5566
- if (f.root && f.node.status !== 2 /* DESTROYED */ && f.node.fiber === f) {
5589
+ if (f.root && f.node.status !== 3 /* DESTROYED */ && f.node.fiber === f) {
5567
5590
  f.render();
5568
5591
  }
5569
5592
  }
5570
5593
  }
5571
5594
  if (this.frame === 0) {
5572
- this.frame = this.requestAnimationFrame(() => {
5573
- this.frame = 0;
5574
- this.tasks.forEach((fiber) => this.processFiber(fiber));
5575
- for (let task of this.tasks) {
5576
- if (task.node.status === 2 /* DESTROYED */) {
5577
- this.tasks.delete(task);
5578
- }
5579
- }
5580
- });
5595
+ this.frame = this.requestAnimationFrame(() => this.processTasks());
5596
+ }
5597
+ }
5598
+ processTasks() {
5599
+ this.frame = 0;
5600
+ for (let node of this.cancelledNodes) {
5601
+ node._destroy();
5602
+ }
5603
+ this.cancelledNodes.clear();
5604
+ for (let task of this.tasks) {
5605
+ this.processFiber(task);
5606
+ }
5607
+ for (let task of this.tasks) {
5608
+ if (task.node.status === 3 /* DESTROYED */) {
5609
+ this.tasks.delete(task);
5610
+ }
5581
5611
  }
5582
5612
  }
5583
5613
  processFiber(fiber) {
@@ -5590,7 +5620,7 @@ class Scheduler {
5590
5620
  this.tasks.delete(fiber);
5591
5621
  return;
5592
5622
  }
5593
- if (fiber.node.status === 2 /* DESTROYED */) {
5623
+ if (fiber.node.status === 3 /* DESTROYED */) {
5594
5624
  this.tasks.delete(fiber);
5595
5625
  return;
5596
5626
  }
@@ -5814,9 +5844,11 @@ function status(component) {
5814
5844
  switch (component.__owl__.status) {
5815
5845
  case 0 /* NEW */:
5816
5846
  return "new";
5847
+ case 2 /* CANCELLED */:
5848
+ return "cancelled";
5817
5849
  case 1 /* MOUNTED */:
5818
5850
  return "mounted";
5819
- case 2 /* DESTROYED */:
5851
+ case 3 /* DESTROYED */:
5820
5852
  return "destroyed";
5821
5853
  }
5822
5854
  }
@@ -5990,6 +6022,6 @@ exports.whenReady = whenReady;
5990
6022
  exports.xml = xml;
5991
6023
 
5992
6024
 
5993
- __info__.date = '2023-06-28T09:17:13.630Z';
5994
- __info__.hash = '432ff44';
6025
+ __info__.date = '2023-07-19T13:36:58.239Z';
6026
+ __info__.hash = 'ea9f533';
5995
6027
  __info__.url = 'https://github.com/odoo/owl';
package/dist/owl.es.js CHANGED
@@ -122,14 +122,16 @@ function handleError(params) {
122
122
  }
123
123
  const node = "node" in params ? params.node : params.fiber.node;
124
124
  const fiber = "fiber" in params ? params.fiber : node.fiber;
125
- // resets the fibers on components if possible. This is important so that
126
- // new renderings can be properly included in the initial one, if any.
127
- let current = fiber;
128
- do {
129
- current.node.fiber = current;
130
- current = current.parent;
131
- } while (current);
132
- fibersInError.set(fiber.root, error);
125
+ if (fiber) {
126
+ // resets the fibers on components if possible. This is important so that
127
+ // new renderings can be properly included in the initial one, if any.
128
+ let current = fiber;
129
+ do {
130
+ current.node.fiber = current;
131
+ current = current.parent;
132
+ } while (current);
133
+ fibersInError.set(fiber.root, error);
134
+ }
133
135
  const handled = _handleError(node, error);
134
136
  if (!handled) {
135
137
  console.warn(`[Owl] Unhandled error. Destroying the root component`);
@@ -311,20 +313,13 @@ function updateClass(val, oldVal) {
311
313
  * @returns a batched version of the original callback
312
314
  */
313
315
  function batched(callback) {
314
- let called = false;
315
- return async () => {
316
- // This await blocks all calls to the callback here, then releases them sequentially
317
- // in the next microtick. This line decides the granularity of the batch.
318
- await Promise.resolve();
319
- if (!called) {
320
- called = true;
321
- // wait for all calls in this microtick to fall through before resetting "called"
322
- // so that only the first call to the batched function calls the original callback.
323
- // Schedule this before calling the callback so that calls to the batched function
324
- // within the callback will proceed only after resetting called to false, and have
325
- // a chance to execute the callback again
326
- Promise.resolve().then(() => (called = false));
327
- callback();
316
+ let scheduled = false;
317
+ return async (...args) => {
318
+ if (!scheduled) {
319
+ scheduled = true;
320
+ await Promise.resolve();
321
+ scheduled = false;
322
+ callback(...args);
328
323
  }
329
324
  };
330
325
  }
@@ -1657,8 +1652,7 @@ function cancelFibers(fibers) {
1657
1652
  let node = fiber.node;
1658
1653
  fiber.render = throwOnRender;
1659
1654
  if (node.status === 0 /* NEW */) {
1660
- node.destroy();
1661
- delete node.parent.children[node.parentKey];
1655
+ node.cancel();
1662
1656
  }
1663
1657
  node.fiber = null;
1664
1658
  if (fiber.bdom) {
@@ -2385,6 +2379,9 @@ class ComponentNode {
2385
2379
  }
2386
2380
  }
2387
2381
  async render(deep) {
2382
+ if (this.status >= 2 /* CANCELLED */) {
2383
+ return;
2384
+ }
2388
2385
  let current = this.fiber;
2389
2386
  if (current && (current.root.locked || current.bdom === true)) {
2390
2387
  await Promise.resolve();
@@ -2410,7 +2407,7 @@ class ComponentNode {
2410
2407
  this.fiber = fiber;
2411
2408
  this.app.scheduler.addFiber(fiber);
2412
2409
  await Promise.resolve();
2413
- if (this.status === 2 /* DESTROYED */) {
2410
+ if (this.status >= 2 /* CANCELLED */) {
2414
2411
  return;
2415
2412
  }
2416
2413
  // We only want to actually render the component if the following two
@@ -2428,6 +2425,18 @@ class ComponentNode {
2428
2425
  fiber.render();
2429
2426
  }
2430
2427
  }
2428
+ cancel() {
2429
+ this._cancel();
2430
+ delete this.parent.children[this.parentKey];
2431
+ this.app.scheduler.scheduleDestroy(this);
2432
+ }
2433
+ _cancel() {
2434
+ this.status = 2 /* CANCELLED */;
2435
+ const children = this.children;
2436
+ for (let childKey in children) {
2437
+ children[childKey]._cancel();
2438
+ }
2439
+ }
2431
2440
  destroy() {
2432
2441
  let shouldRemove = this.status === 1 /* MOUNTED */;
2433
2442
  this._destroy();
@@ -2455,7 +2464,7 @@ class ComponentNode {
2455
2464
  this.app.handleError({ error: e, node: this });
2456
2465
  }
2457
2466
  }
2458
- this.status = 2 /* DESTROYED */;
2467
+ this.status = 3 /* DESTROYED */;
2459
2468
  }
2460
2469
  async updateAndRender(props, parentFiber) {
2461
2470
  this.nextProps = props;
@@ -2988,12 +2997,22 @@ function prepareList(collection) {
2988
2997
  keys = collection;
2989
2998
  values = collection;
2990
2999
  }
2991
- else if (collection) {
2992
- values = Object.keys(collection);
2993
- keys = Object.values(collection);
3000
+ else if (collection instanceof Map) {
3001
+ keys = [...collection.keys()];
3002
+ values = [...collection.values()];
3003
+ }
3004
+ else if (collection && typeof collection === "object") {
3005
+ if (Symbol.iterator in collection) {
3006
+ keys = [...collection];
3007
+ values = keys;
3008
+ }
3009
+ else {
3010
+ values = Object.keys(collection);
3011
+ keys = Object.values(collection);
3012
+ }
2994
3013
  }
2995
3014
  else {
2996
- throw new OwlError("Invalid loop expression");
3015
+ throw new OwlError(`Invalid loop expression: "${collection}" is not iterable`);
2997
3016
  }
2998
3017
  const n = values.length;
2999
3018
  return [keys, values, n, new Array(n)];
@@ -4820,10 +4839,10 @@ function parseNode(node, ctx) {
4820
4839
  parseTCall(node, ctx) ||
4821
4840
  parseTCallBlock(node) ||
4822
4841
  parseTEscNode(node, ctx) ||
4842
+ parseTOutNode(node, ctx) ||
4823
4843
  parseTKey(node, ctx) ||
4824
4844
  parseTTranslation(node, ctx) ||
4825
4845
  parseTSlot(node, ctx) ||
4826
- parseTOutNode(node, ctx) ||
4827
4846
  parseComponent(node, ctx) ||
4828
4847
  parseDOMNode(node, ctx) ||
4829
4848
  parseTSetNode(node, ctx) ||
@@ -4936,10 +4955,8 @@ function parseDOMNode(node, ctx) {
4936
4955
  const typeAttr = node.getAttribute("type");
4937
4956
  const isInput = tagName === "input";
4938
4957
  const isSelect = tagName === "select";
4939
- const isTextarea = tagName === "textarea";
4940
4958
  const isCheckboxInput = isInput && typeAttr === "checkbox";
4941
4959
  const isRadioInput = isInput && typeAttr === "radio";
4942
- const isOtherInput = isInput && !isCheckboxInput && !isRadioInput;
4943
4960
  const hasLazyMod = attr.includes(".lazy");
4944
4961
  const hasNumberMod = attr.includes(".number");
4945
4962
  const hasTrimMod = attr.includes(".trim");
@@ -4951,8 +4968,8 @@ function parseDOMNode(node, ctx) {
4951
4968
  specialInitTargetAttr: isRadioInput ? "checked" : null,
4952
4969
  eventType,
4953
4970
  hasDynamicChildren: false,
4954
- shouldTrim: hasTrimMod && (isOtherInput || isTextarea),
4955
- shouldNumberize: hasNumberMod && (isOtherInput || isTextarea),
4971
+ shouldTrim: hasTrimMod,
4972
+ shouldNumberize: hasNumberMod,
4956
4973
  };
4957
4974
  if (isSelect) {
4958
4975
  // don't pollute the original ctx
@@ -5015,9 +5032,6 @@ function parseTEscNode(node, ctx) {
5015
5032
  content: [tesc],
5016
5033
  };
5017
5034
  }
5018
- if (ast.type === 11 /* TComponent */) {
5019
- throw new OwlError("t-esc is not supported on Component nodes");
5020
- }
5021
5035
  return tesc;
5022
5036
  }
5023
5037
  // -----------------------------------------------------------------------------
@@ -5459,19 +5473,21 @@ function normalizeTIf(el) {
5459
5473
  *
5460
5474
  * @param el the element containing the tree that should be normalized
5461
5475
  */
5462
- function normalizeTEsc(el) {
5463
- const elements = [...el.querySelectorAll("[t-esc]")].filter((el) => el.tagName[0] === el.tagName[0].toUpperCase() || el.hasAttribute("t-component"));
5464
- for (const el of elements) {
5465
- if (el.childNodes.length) {
5466
- throw new OwlError("Cannot have t-esc on a component that already has content");
5467
- }
5468
- const value = el.getAttribute("t-esc");
5469
- el.removeAttribute("t-esc");
5470
- const t = el.ownerDocument.createElement("t");
5471
- if (value != null) {
5472
- t.setAttribute("t-esc", value);
5476
+ function normalizeTEscTOut(el) {
5477
+ for (const d of ["t-esc", "t-out"]) {
5478
+ const elements = [...el.querySelectorAll(`[${d}]`)].filter((el) => el.tagName[0] === el.tagName[0].toUpperCase() || el.hasAttribute("t-component"));
5479
+ for (const el of elements) {
5480
+ if (el.childNodes.length) {
5481
+ throw new OwlError(`Cannot have ${d} on a component that already has content`);
5482
+ }
5483
+ const value = el.getAttribute(d);
5484
+ el.removeAttribute(d);
5485
+ const t = el.ownerDocument.createElement("t");
5486
+ if (value != null) {
5487
+ t.setAttribute(d, value);
5488
+ }
5489
+ el.appendChild(t);
5473
5490
  }
5474
- el.appendChild(t);
5475
5491
  }
5476
5492
  }
5477
5493
  /**
@@ -5482,7 +5498,7 @@ function normalizeTEsc(el) {
5482
5498
  */
5483
5499
  function normalizeXML(el) {
5484
5500
  normalizeTIf(el);
5485
- normalizeTEsc(el);
5501
+ normalizeTEscTOut(el);
5486
5502
  }
5487
5503
  /**
5488
5504
  * Parses an XML string into an XML document, throwing errors on parser errors
@@ -5535,7 +5551,7 @@ function compile(template, options = {}) {
5535
5551
  }
5536
5552
 
5537
5553
  // do not modify manually. This file is generated by the release script.
5538
- const version = "2.1.3";
5554
+ const version = "2.2.2";
5539
5555
 
5540
5556
  // -----------------------------------------------------------------------------
5541
5557
  // Scheduler
@@ -5545,11 +5561,18 @@ class Scheduler {
5545
5561
  this.tasks = new Set();
5546
5562
  this.frame = 0;
5547
5563
  this.delayedRenders = [];
5564
+ this.cancelledNodes = new Set();
5548
5565
  this.requestAnimationFrame = Scheduler.requestAnimationFrame;
5549
5566
  }
5550
5567
  addFiber(fiber) {
5551
5568
  this.tasks.add(fiber.root);
5552
5569
  }
5570
+ scheduleDestroy(node) {
5571
+ this.cancelledNodes.add(node);
5572
+ if (this.frame === 0) {
5573
+ this.frame = this.requestAnimationFrame(() => this.processTasks());
5574
+ }
5575
+ }
5553
5576
  /**
5554
5577
  * Process all current tasks. This only applies to the fibers that are ready.
5555
5578
  * Other tasks are left unchanged.
@@ -5559,21 +5582,28 @@ class Scheduler {
5559
5582
  let renders = this.delayedRenders;
5560
5583
  this.delayedRenders = [];
5561
5584
  for (let f of renders) {
5562
- if (f.root && f.node.status !== 2 /* DESTROYED */ && f.node.fiber === f) {
5585
+ if (f.root && f.node.status !== 3 /* DESTROYED */ && f.node.fiber === f) {
5563
5586
  f.render();
5564
5587
  }
5565
5588
  }
5566
5589
  }
5567
5590
  if (this.frame === 0) {
5568
- this.frame = this.requestAnimationFrame(() => {
5569
- this.frame = 0;
5570
- this.tasks.forEach((fiber) => this.processFiber(fiber));
5571
- for (let task of this.tasks) {
5572
- if (task.node.status === 2 /* DESTROYED */) {
5573
- this.tasks.delete(task);
5574
- }
5575
- }
5576
- });
5591
+ this.frame = this.requestAnimationFrame(() => this.processTasks());
5592
+ }
5593
+ }
5594
+ processTasks() {
5595
+ this.frame = 0;
5596
+ for (let node of this.cancelledNodes) {
5597
+ node._destroy();
5598
+ }
5599
+ this.cancelledNodes.clear();
5600
+ for (let task of this.tasks) {
5601
+ this.processFiber(task);
5602
+ }
5603
+ for (let task of this.tasks) {
5604
+ if (task.node.status === 3 /* DESTROYED */) {
5605
+ this.tasks.delete(task);
5606
+ }
5577
5607
  }
5578
5608
  }
5579
5609
  processFiber(fiber) {
@@ -5586,7 +5616,7 @@ class Scheduler {
5586
5616
  this.tasks.delete(fiber);
5587
5617
  return;
5588
5618
  }
5589
- if (fiber.node.status === 2 /* DESTROYED */) {
5619
+ if (fiber.node.status === 3 /* DESTROYED */) {
5590
5620
  this.tasks.delete(fiber);
5591
5621
  return;
5592
5622
  }
@@ -5810,9 +5840,11 @@ function status(component) {
5810
5840
  switch (component.__owl__.status) {
5811
5841
  case 0 /* NEW */:
5812
5842
  return "new";
5843
+ case 2 /* CANCELLED */:
5844
+ return "cancelled";
5813
5845
  case 1 /* MOUNTED */:
5814
5846
  return "mounted";
5815
- case 2 /* DESTROYED */:
5847
+ case 3 /* DESTROYED */:
5816
5848
  return "destroyed";
5817
5849
  }
5818
5850
  }
@@ -5952,6 +5984,6 @@ TemplateSet.prototype._compileTemplate = function _compileTemplate(name, templat
5952
5984
  export { App, Component, EventBus, OwlError, __info__, blockDom, loadFile, markRaw, markup, mount, onError, onMounted, onPatched, onRendered, onWillDestroy, onWillPatch, onWillRender, onWillStart, onWillUnmount, onWillUpdateProps, reactive, status, toRaw, useChildSubEnv, useComponent, useEffect, useEnv, useExternalListener, useRef, useState, useSubEnv, validate, validateType, whenReady, xml };
5953
5985
 
5954
5986
 
5955
- __info__.date = '2023-06-28T09:17:13.630Z';
5956
- __info__.hash = '432ff44';
5987
+ __info__.date = '2023-07-19T13:36:58.239Z';
5988
+ __info__.hash = 'ea9f533';
5957
5989
  __info__.url = 'https://github.com/odoo/owl';