@odoo/owl 2.0.0-beta-4 → 2.0.0-beta-7

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.
package/dist/owl.cjs.js CHANGED
@@ -1916,7 +1916,7 @@ xml.nextId = 1;
1916
1916
  // Maps fibers to thrown errors
1917
1917
  const fibersInError = new WeakMap();
1918
1918
  const nodeErrorHandlers = new WeakMap();
1919
- function _handleError(node, error, isFirstRound = false) {
1919
+ function _handleError(node, error) {
1920
1920
  if (!node) {
1921
1921
  return false;
1922
1922
  }
@@ -1926,23 +1926,19 @@ function _handleError(node, error, isFirstRound = false) {
1926
1926
  }
1927
1927
  const errorHandlers = nodeErrorHandlers.get(node);
1928
1928
  if (errorHandlers) {
1929
- let stopped = false;
1929
+ let handled = false;
1930
1930
  // execute in the opposite order
1931
1931
  for (let i = errorHandlers.length - 1; i >= 0; i--) {
1932
1932
  try {
1933
1933
  errorHandlers[i](error);
1934
- stopped = true;
1934
+ handled = true;
1935
1935
  break;
1936
1936
  }
1937
1937
  catch (e) {
1938
1938
  error = e;
1939
1939
  }
1940
1940
  }
1941
- if (stopped) {
1942
- if (isFirstRound && fiber && fiber.node.fiber) {
1943
- const root = fiber.root;
1944
- root.setCounter(root.counter - 1);
1945
- }
1941
+ if (handled) {
1946
1942
  return true;
1947
1943
  }
1948
1944
  }
@@ -1960,7 +1956,7 @@ function handleError(params) {
1960
1956
  current = current.parent;
1961
1957
  } while (current);
1962
1958
  fibersInError.set(fiber.root, error);
1963
- const handled = _handleError(node, error, true);
1959
+ const handled = _handleError(node, error);
1964
1960
  if (!handled) {
1965
1961
  console.warn(`[Owl] Unhandled error. Destroying the root component`);
1966
1962
  try {
@@ -1977,10 +1973,6 @@ function makeChildFiber(node, parent) {
1977
1973
  if (current) {
1978
1974
  cancelFibers(current.children);
1979
1975
  current.root = null;
1980
- if (current instanceof RootFiber && current.delayedRenders.length) {
1981
- let root = parent.root;
1982
- root.delayedRenders = root.delayedRenders.concat(current.delayedRenders);
1983
- }
1984
1976
  }
1985
1977
  return new Fiber(node, parent);
1986
1978
  }
@@ -1988,12 +1980,15 @@ function makeRootFiber(node) {
1988
1980
  let current = node.fiber;
1989
1981
  if (current) {
1990
1982
  let root = current.root;
1983
+ // lock root fiber because canceling children fibers may destroy components,
1984
+ // which means any arbitrary code can be run in onWillDestroy, which may
1985
+ // trigger new renderings
1986
+ root.locked = true;
1991
1987
  root.setCounter(root.counter + 1 - cancelFibers(current.children));
1988
+ root.locked = false;
1992
1989
  current.children = [];
1990
+ current.childrenMap = {};
1993
1991
  current.bdom = null;
1994
- if (current === root) {
1995
- root.reachedChildren = new WeakSet();
1996
- }
1997
1992
  if (fibersInError.has(current)) {
1998
1993
  fibersInError.delete(current);
1999
1994
  fibersInError.delete(root);
@@ -2010,13 +2005,21 @@ function makeRootFiber(node) {
2010
2005
  }
2011
2006
  return fiber;
2012
2007
  }
2008
+ function throwOnRender() {
2009
+ throw new Error("Attempted to render cancelled fiber");
2010
+ }
2013
2011
  /**
2014
2012
  * @returns number of not-yet rendered fibers cancelled
2015
2013
  */
2016
2014
  function cancelFibers(fibers) {
2017
2015
  let result = 0;
2018
2016
  for (let fiber of fibers) {
2019
- fiber.node.fiber = null;
2017
+ let node = fiber.node;
2018
+ fiber.render = throwOnRender;
2019
+ if (node.status === 0 /* NEW */) {
2020
+ node.destroy();
2021
+ }
2022
+ node.fiber = null;
2020
2023
  if (fiber.bdom) {
2021
2024
  // if fiber has been rendered, this means that the component props have
2022
2025
  // been updated. however, this fiber will not be patched to the dom, so
@@ -2024,7 +2027,7 @@ function cancelFibers(fibers) {
2024
2027
  // the same props, and skip the render completely. With the next line,
2025
2028
  // we kindly request the component code to force a render, so it works as
2026
2029
  // expected.
2027
- fiber.node.forceNextRender = true;
2030
+ node.forceNextRender = true;
2028
2031
  }
2029
2032
  else {
2030
2033
  result++;
@@ -2039,6 +2042,7 @@ class Fiber {
2039
2042
  this.children = [];
2040
2043
  this.appliedToDom = false;
2041
2044
  this.deep = false;
2045
+ this.childrenMap = {};
2042
2046
  this.node = node;
2043
2047
  this.parent = parent;
2044
2048
  if (parent) {
@@ -2055,21 +2059,17 @@ class Fiber {
2055
2059
  render() {
2056
2060
  // if some parent has a fiber => register in followup
2057
2061
  let prev = this.root.node;
2062
+ let scheduler = prev.app.scheduler;
2058
2063
  let current = prev.parent;
2059
2064
  while (current) {
2060
2065
  if (current.fiber) {
2061
2066
  let root = current.fiber.root;
2062
- if (root.counter) {
2063
- root.delayedRenders.push(this);
2064
- return;
2067
+ if (root.counter === 0 && prev.parentKey in current.fiber.childrenMap) {
2068
+ current = root.node;
2065
2069
  }
2066
2070
  else {
2067
- if (!root.reachedChildren.has(prev)) {
2068
- // is dead
2069
- this.node.app.scheduler.shouldClear = true;
2070
- return;
2071
- }
2072
- current = root.node;
2071
+ scheduler.delayedRenders.push(this);
2072
+ return;
2073
2073
  }
2074
2074
  }
2075
2075
  prev = current;
@@ -2083,12 +2083,13 @@ class Fiber {
2083
2083
  const root = this.root;
2084
2084
  if (root) {
2085
2085
  try {
2086
+ this.bdom = true;
2086
2087
  this.bdom = node.renderFn();
2087
- root.setCounter(root.counter - 1);
2088
2088
  }
2089
2089
  catch (e) {
2090
2090
  handleError({ node, error: e });
2091
2091
  }
2092
+ root.setCounter(root.counter - 1);
2092
2093
  }
2093
2094
  }
2094
2095
  }
@@ -2103,8 +2104,6 @@ class RootFiber extends Fiber {
2103
2104
  // A fiber is typically locked when it is completing and the patch has not, or is being applied.
2104
2105
  // i.e.: render triggered in onWillUnmount or in willPatch will be delayed
2105
2106
  this.locked = false;
2106
- this.delayedRenders = [];
2107
- this.reachedChildren = new WeakSet();
2108
2107
  }
2109
2108
  complete() {
2110
2109
  const node = this.node;
@@ -2157,14 +2156,6 @@ class RootFiber extends Fiber {
2157
2156
  setCounter(newValue) {
2158
2157
  this.counter = newValue;
2159
2158
  if (newValue === 0) {
2160
- if (this.delayedRenders.length) {
2161
- for (let f of this.delayedRenders) {
2162
- if (f.root) {
2163
- f.render();
2164
- }
2165
- }
2166
- this.delayedRenders = [];
2167
- }
2168
2159
  this.node.app.scheduler.flush();
2169
2160
  }
2170
2161
  }
@@ -2179,6 +2170,7 @@ class MountFiber extends RootFiber {
2179
2170
  let current = this;
2180
2171
  try {
2181
2172
  const node = this.node;
2173
+ node.children = this.childrenMap;
2182
2174
  node.app.constructor.validateTarget(this.target);
2183
2175
  if (node.bdom) {
2184
2176
  // this is a complicated situation: if we mount a fiber with an existing
@@ -2384,7 +2376,7 @@ function useState(state) {
2384
2376
  const node = getCurrent();
2385
2377
  let render = batchedRenderFunctions.get(node);
2386
2378
  if (!render) {
2387
- render = batched(node.render.bind(node));
2379
+ render = batched(node.render.bind(node, false));
2388
2380
  batchedRenderFunctions.set(node, render);
2389
2381
  // manual implementation of onWillDestroy to break cyclic dependency
2390
2382
  node.willDestroy.push(clearReactivesForCallback.bind(null, render));
@@ -2402,14 +2394,8 @@ function arePropsDifferent(props1, props2) {
2402
2394
  function component(name, props, key, ctx, parent) {
2403
2395
  let node = ctx.children[key];
2404
2396
  let isDynamic = typeof name !== "string";
2405
- if (node) {
2406
- if (node.status < 1 /* MOUNTED */) {
2407
- node.destroy();
2408
- node = undefined;
2409
- }
2410
- else if (node.status === 2 /* DESTROYED */) {
2411
- node = undefined;
2412
- }
2397
+ if (node && node.status === 2 /* DESTROYED */) {
2398
+ node = undefined;
2413
2399
  }
2414
2400
  if (isDynamic && node && node.component.constructor !== name) {
2415
2401
  node = undefined;
@@ -2440,15 +2426,15 @@ function component(name, props, key, ctx, parent) {
2440
2426
  throw new Error(`Cannot find the definition of component "${name}"`);
2441
2427
  }
2442
2428
  }
2443
- node = new ComponentNode(C, props, ctx.app, ctx);
2429
+ node = new ComponentNode(C, props, ctx.app, ctx, key);
2444
2430
  ctx.children[key] = node;
2445
2431
  node.initiateRender(new Fiber(node, parentFiber));
2446
2432
  }
2447
- parentFiber.root.reachedChildren.add(node);
2433
+ parentFiber.childrenMap[key] = node;
2448
2434
  return node;
2449
2435
  }
2450
2436
  class ComponentNode {
2451
- constructor(C, props, app, parent) {
2437
+ constructor(C, props, app, parent, parentKey) {
2452
2438
  this.fiber = null;
2453
2439
  this.bdom = null;
2454
2440
  this.status = 0 /* NEW */;
@@ -2464,7 +2450,8 @@ class ComponentNode {
2464
2450
  this.willDestroy = [];
2465
2451
  currentNode = this;
2466
2452
  this.app = app;
2467
- this.parent = parent || null;
2453
+ this.parent = parent;
2454
+ this.parentKey = parentKey;
2468
2455
  this.level = parent ? parent.level + 1 : 0;
2469
2456
  applyDefaultProps(props, C);
2470
2457
  const env = (parent && parent.childEnv) || app.env;
@@ -2497,9 +2484,9 @@ class ComponentNode {
2497
2484
  fiber.render();
2498
2485
  }
2499
2486
  }
2500
- async render(deep = false) {
2487
+ async render(deep) {
2501
2488
  let current = this.fiber;
2502
- if (current && current.root.locked) {
2489
+ if (current && (current.root.locked || current.bdom === true)) {
2503
2490
  await Promise.resolve();
2504
2491
  // situation may have changed after the microtask tick
2505
2492
  current = this.fiber;
@@ -2558,8 +2545,15 @@ class ComponentNode {
2558
2545
  for (let child of Object.values(this.children)) {
2559
2546
  child._destroy();
2560
2547
  }
2561
- for (let cb of this.willDestroy) {
2562
- cb.call(component);
2548
+ if (this.willDestroy.length) {
2549
+ try {
2550
+ for (let cb of this.willDestroy) {
2551
+ cb.call(component);
2552
+ }
2553
+ }
2554
+ catch (e) {
2555
+ handleError({ error: e, node: this });
2556
+ }
2563
2557
  }
2564
2558
  this.status = 2 /* DESTROYED */;
2565
2559
  }
@@ -2625,6 +2619,7 @@ class ComponentNode {
2625
2619
  bdom.mount(parent, anchor);
2626
2620
  this.status = 1 /* MOUNTED */;
2627
2621
  this.fiber.appliedToDom = true;
2622
+ this.children = this.fiber.childrenMap;
2628
2623
  this.fiber = null;
2629
2624
  }
2630
2625
  moveBefore(other, afterNode) {
@@ -2640,10 +2635,8 @@ class ComponentNode {
2640
2635
  }
2641
2636
  _patch() {
2642
2637
  const hasChildren = Object.keys(this.children).length > 0;
2638
+ this.children = this.fiber.childrenMap;
2643
2639
  this.bdom.patch(this.fiber.bdom, hasChildren);
2644
- if (hasChildren) {
2645
- this.cleanOutdatedChildren();
2646
- }
2647
2640
  this.fiber.appliedToDom = true;
2648
2641
  this.fiber = null;
2649
2642
  }
@@ -2653,19 +2646,6 @@ class ComponentNode {
2653
2646
  remove() {
2654
2647
  this.bdom.remove();
2655
2648
  }
2656
- cleanOutdatedChildren() {
2657
- const children = this.children;
2658
- for (const key in children) {
2659
- const node = children[key];
2660
- const status = node.status;
2661
- if (status !== 1 /* MOUNTED */) {
2662
- delete children[key];
2663
- if (status !== 2 /* DESTROYED */) {
2664
- node.destroy();
2665
- }
2666
- }
2667
- }
2668
- }
2669
2649
  // ---------------------------------------------------------------------------
2670
2650
  // Some debug helpers
2671
2651
  // ---------------------------------------------------------------------------
@@ -2685,7 +2665,7 @@ class Scheduler {
2685
2665
  constructor() {
2686
2666
  this.tasks = new Set();
2687
2667
  this.frame = 0;
2688
- this.shouldClear = false;
2668
+ this.delayedRenders = [];
2689
2669
  this.requestAnimationFrame = Scheduler.requestAnimationFrame;
2690
2670
  }
2691
2671
  addFiber(fiber) {
@@ -2696,16 +2676,22 @@ class Scheduler {
2696
2676
  * Other tasks are left unchanged.
2697
2677
  */
2698
2678
  flush() {
2679
+ if (this.delayedRenders.length) {
2680
+ let renders = this.delayedRenders;
2681
+ this.delayedRenders = [];
2682
+ for (let f of renders) {
2683
+ if (f.root && f.node.status !== 2 /* DESTROYED */ && f.node.fiber === f) {
2684
+ f.render();
2685
+ }
2686
+ }
2687
+ }
2699
2688
  if (this.frame === 0) {
2700
2689
  this.frame = this.requestAnimationFrame(() => {
2701
2690
  this.frame = 0;
2702
2691
  this.tasks.forEach((fiber) => this.processFiber(fiber));
2703
- if (this.shouldClear) {
2704
- this.shouldClear = false;
2705
- for (let task of this.tasks) {
2706
- if (task.node.status === 2 /* DESTROYED */) {
2707
- this.tasks.delete(task);
2708
- }
2692
+ for (let task of this.tasks) {
2693
+ if (task.node.status === 2 /* DESTROYED */) {
2694
+ this.tasks.delete(task);
2709
2695
  }
2710
2696
  }
2711
2697
  });
@@ -4019,8 +4005,11 @@ class CodeGenerator {
4019
4005
  let slotStr = [];
4020
4006
  for (let slotName in ast.slots) {
4021
4007
  const slotAst = ast.slots[slotName];
4022
- const name = this.compileInNewTarget("slot", slotAst.content, ctx, slotAst.on);
4023
- const params = [`__render: ${name}, __ctx: ${ctxStr}`];
4008
+ const params = [];
4009
+ if (slotAst.content) {
4010
+ const name = this.compileInNewTarget("slot", slotAst.content, ctx, slotAst.on);
4011
+ params.push(`__render: ${name}, __ctx: ${ctxStr}`);
4012
+ }
4024
4013
  const scope = ast.slots[slotName].scope;
4025
4014
  if (scope) {
4026
4015
  params.push(`__scope: "${scope}"`);
@@ -4122,7 +4111,7 @@ class CodeGenerator {
4122
4111
  if (dynamic) {
4123
4112
  let name = this.generateId("slot");
4124
4113
  this.define(name, slotName);
4125
- blockString = `toggler(${name}, callSlot(ctx, node, key, ${name}), ${dynamic}, ${scope})`;
4114
+ blockString = `toggler(${name}, callSlot(ctx, node, key, ${name}, ${dynamic}, ${scope}))`;
4126
4115
  }
4127
4116
  else {
4128
4117
  blockString = `callSlot(ctx, node, key, ${slotName}, ${dynamic}, ${scope})`;
@@ -4663,28 +4652,26 @@ function parseComponent(node, ctx) {
4663
4652
  slotNode.removeAttribute("t-set-slot");
4664
4653
  slotNode.remove();
4665
4654
  const slotAst = parseNode(slotNode, ctx);
4666
- if (slotAst) {
4667
- let on = null;
4668
- let attrs = null;
4669
- let scope = null;
4670
- for (let attributeName of slotNode.getAttributeNames()) {
4671
- const value = slotNode.getAttribute(attributeName);
4672
- if (attributeName === "t-slot-scope") {
4673
- scope = value;
4674
- continue;
4675
- }
4676
- else if (attributeName.startsWith("t-on-")) {
4677
- on = on || {};
4678
- on[attributeName.slice(5)] = value;
4679
- }
4680
- else {
4681
- attrs = attrs || {};
4682
- attrs[attributeName] = value;
4683
- }
4655
+ let on = null;
4656
+ let attrs = null;
4657
+ let scope = null;
4658
+ for (let attributeName of slotNode.getAttributeNames()) {
4659
+ const value = slotNode.getAttribute(attributeName);
4660
+ if (attributeName === "t-slot-scope") {
4661
+ scope = value;
4662
+ continue;
4663
+ }
4664
+ else if (attributeName.startsWith("t-on-")) {
4665
+ on = on || {};
4666
+ on[attributeName.slice(5)] = value;
4667
+ }
4668
+ else {
4669
+ attrs = attrs || {};
4670
+ attrs[attributeName] = value;
4684
4671
  }
4685
- slots = slots || {};
4686
- slots[name] = { content: slotAst, on, attrs, scope };
4687
4672
  }
4673
+ slots = slots || {};
4674
+ slots[name] = { content: slotAst, on, attrs, scope };
4688
4675
  }
4689
4676
  // default slot
4690
4677
  const defaultContent = parseChildNodes(clone, ctx);
@@ -5030,7 +5017,7 @@ class Component {
5030
5017
  }
5031
5018
  setup() { }
5032
5019
  render(deep = false) {
5033
- this.__owl__.render(deep);
5020
+ this.__owl__.render(deep === true);
5034
5021
  }
5035
5022
  }
5036
5023
  Component.template = "";
@@ -5327,13 +5314,19 @@ class TemplateSet {
5327
5314
  }
5328
5315
  this.helpers = makeHelpers(this.getTemplate.bind(this));
5329
5316
  }
5330
- addTemplate(name, template, options = {}) {
5331
- if (name in this.rawTemplates && !options.allowDuplicate) {
5332
- throw new Error(`Template ${name} already defined`);
5317
+ addTemplate(name, template) {
5318
+ if (name in this.rawTemplates) {
5319
+ const rawTemplate = this.rawTemplates[name];
5320
+ const currentAsString = typeof rawTemplate === "string" ? rawTemplate : rawTemplate.outerHTML;
5321
+ const newAsString = typeof template === "string" ? template : template.outerHTML;
5322
+ if (currentAsString === newAsString) {
5323
+ return;
5324
+ }
5325
+ throw new Error(`Template ${name} already defined with different content`);
5333
5326
  }
5334
5327
  this.rawTemplates[name] = template;
5335
5328
  }
5336
- addTemplates(xml, options = {}) {
5329
+ addTemplates(xml) {
5337
5330
  if (!xml) {
5338
5331
  // empty string
5339
5332
  return;
@@ -5341,7 +5334,7 @@ class TemplateSet {
5341
5334
  xml = xml instanceof Document ? xml : parseXML(xml);
5342
5335
  for (const template of xml.querySelectorAll("[t-name]")) {
5343
5336
  const name = template.getAttribute("t-name");
5344
- this.addTemplate(name, template, options);
5337
+ this.addTemplate(name, template);
5345
5338
  }
5346
5339
  }
5347
5340
  getTemplate(name) {
@@ -5412,7 +5405,7 @@ class App extends TemplateSet {
5412
5405
  return prom;
5413
5406
  }
5414
5407
  makeNode(Component, props) {
5415
- return new ComponentNode(Component, props, this);
5408
+ return new ComponentNode(Component, props, this, null, null);
5416
5409
  }
5417
5410
  mountNode(node, target, options) {
5418
5411
  const promise = new Promise((resolve, reject) => {
@@ -5618,7 +5611,7 @@ exports.whenReady = whenReady;
5618
5611
  exports.xml = xml;
5619
5612
 
5620
5613
 
5621
- __info__.version = '2.0.0-beta-4';
5622
- __info__.date = '2022-03-29T13:50:04.545Z';
5623
- __info__.hash = '55dbc01';
5614
+ __info__.version = '2.0.0-beta-7';
5615
+ __info__.date = '2022-04-27T09:08:40.703Z';
5616
+ __info__.hash = '0cd66c8';
5624
5617
  __info__.url = 'https://github.com/odoo/owl';