@odoo/owl 2.1.3 → 2.2.1

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`);
@@ -179,11 +181,21 @@ function createAttrUpdater(attr) {
179
181
  }
180
182
  function attrsSetter(attrs) {
181
183
  if (isArray(attrs)) {
182
- setAttribute.call(this, attrs[0], attrs[1]);
184
+ if (attrs[0] === "class") {
185
+ setClass.call(this, attrs[1]);
186
+ }
187
+ else {
188
+ setAttribute.call(this, attrs[0], attrs[1]);
189
+ }
183
190
  }
184
191
  else {
185
192
  for (let k in attrs) {
186
- setAttribute.call(this, k, attrs[k]);
193
+ if (k === "class") {
194
+ setClass.call(this, attrs[k]);
195
+ }
196
+ else {
197
+ setAttribute.call(this, k, attrs[k]);
198
+ }
187
199
  }
188
200
  }
189
201
  }
@@ -195,7 +207,12 @@ function attrsUpdater(attrs, oldAttrs) {
195
207
  if (val === oldAttrs[1]) {
196
208
  return;
197
209
  }
198
- setAttribute.call(this, name, val);
210
+ if (name === "class") {
211
+ updateClass.call(this, val, oldAttrs[1]);
212
+ }
213
+ else {
214
+ setAttribute.call(this, name, val);
215
+ }
199
216
  }
200
217
  else {
201
218
  removeAttribute.call(this, oldAttrs[0]);
@@ -205,13 +222,23 @@ function attrsUpdater(attrs, oldAttrs) {
205
222
  else {
206
223
  for (let k in oldAttrs) {
207
224
  if (!(k in attrs)) {
208
- removeAttribute.call(this, k);
225
+ if (k === "class") {
226
+ updateClass.call(this, "", oldAttrs[k]);
227
+ }
228
+ else {
229
+ removeAttribute.call(this, k);
230
+ }
209
231
  }
210
232
  }
211
233
  for (let k in attrs) {
212
234
  const val = attrs[k];
213
235
  if (val !== oldAttrs[k]) {
214
- setAttribute.call(this, k, val);
236
+ if (k === "class") {
237
+ updateClass.call(this, val, oldAttrs[k]);
238
+ }
239
+ else {
240
+ setAttribute.call(this, k, val);
241
+ }
215
242
  }
216
243
  }
217
244
  }
@@ -290,20 +317,13 @@ function updateClass(val, oldVal) {
290
317
  * @returns a batched version of the original callback
291
318
  */
292
319
  function batched(callback) {
293
- let called = false;
294
- return async () => {
295
- // This await blocks all calls to the callback here, then releases them sequentially
296
- // in the next microtick. This line decides the granularity of the batch.
297
- await Promise.resolve();
298
- if (!called) {
299
- called = true;
300
- // wait for all calls in this microtick to fall through before resetting "called"
301
- // so that only the first call to the batched function calls the original callback.
302
- // Schedule this before calling the callback so that calls to the batched function
303
- // within the callback will proceed only after resetting called to false, and have
304
- // a chance to execute the callback again
305
- Promise.resolve().then(() => (called = false));
306
- 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);
307
327
  }
308
328
  };
309
329
  }
@@ -1636,8 +1656,7 @@ function cancelFibers(fibers) {
1636
1656
  let node = fiber.node;
1637
1657
  fiber.render = throwOnRender;
1638
1658
  if (node.status === 0 /* NEW */) {
1639
- node.destroy();
1640
- delete node.parent.children[node.parentKey];
1659
+ node.cancel();
1641
1660
  }
1642
1661
  node.fiber = null;
1643
1662
  if (fiber.bdom) {
@@ -2364,6 +2383,9 @@ class ComponentNode {
2364
2383
  }
2365
2384
  }
2366
2385
  async render(deep) {
2386
+ if (this.status >= 2 /* CANCELLED */) {
2387
+ return;
2388
+ }
2367
2389
  let current = this.fiber;
2368
2390
  if (current && (current.root.locked || current.bdom === true)) {
2369
2391
  await Promise.resolve();
@@ -2389,7 +2411,7 @@ class ComponentNode {
2389
2411
  this.fiber = fiber;
2390
2412
  this.app.scheduler.addFiber(fiber);
2391
2413
  await Promise.resolve();
2392
- if (this.status === 2 /* DESTROYED */) {
2414
+ if (this.status >= 2 /* CANCELLED */) {
2393
2415
  return;
2394
2416
  }
2395
2417
  // We only want to actually render the component if the following two
@@ -2407,6 +2429,18 @@ class ComponentNode {
2407
2429
  fiber.render();
2408
2430
  }
2409
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
+ }
2410
2444
  destroy() {
2411
2445
  let shouldRemove = this.status === 1 /* MOUNTED */;
2412
2446
  this._destroy();
@@ -2434,7 +2468,7 @@ class ComponentNode {
2434
2468
  this.app.handleError({ error: e, node: this });
2435
2469
  }
2436
2470
  }
2437
- this.status = 2 /* DESTROYED */;
2471
+ this.status = 3 /* DESTROYED */;
2438
2472
  }
2439
2473
  async updateAndRender(props, parentFiber) {
2440
2474
  this.nextProps = props;
@@ -2967,12 +3001,22 @@ function prepareList(collection) {
2967
3001
  keys = collection;
2968
3002
  values = collection;
2969
3003
  }
2970
- else if (collection) {
2971
- values = Object.keys(collection);
2972
- 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
+ }
2973
3017
  }
2974
3018
  else {
2975
- throw new OwlError("Invalid loop expression");
3019
+ throw new OwlError(`Invalid loop expression: "${collection}" is not iterable`);
2976
3020
  }
2977
3021
  const n = values.length;
2978
3022
  return [keys, values, n, new Array(n)];
@@ -3879,6 +3923,10 @@ class CodeGenerator {
3879
3923
  })
3880
3924
  .join("");
3881
3925
  }
3926
+ translate(str) {
3927
+ const match = translationRE.exec(str);
3928
+ return match[1] + this.translateFn(match[2]) + match[3];
3929
+ }
3882
3930
  /**
3883
3931
  * @returns the newly created block name, if any
3884
3932
  */
@@ -3956,8 +4004,7 @@ class CodeGenerator {
3956
4004
  let { block, forceNewBlock } = ctx;
3957
4005
  let value = ast.value;
3958
4006
  if (value && ctx.translate !== false) {
3959
- const match = translationRE.exec(value);
3960
- value = match[1] + this.translateFn(match[2]) + match[3];
4007
+ value = this.translate(value);
3961
4008
  }
3962
4009
  if (!ctx.inPreTag) {
3963
4010
  value = value.replace(whitespaceRE, " ");
@@ -4498,11 +4545,12 @@ class CodeGenerator {
4498
4545
  else {
4499
4546
  let value;
4500
4547
  if (ast.defaultValue) {
4548
+ const defaultValue = ctx.translate ? this.translate(ast.defaultValue) : ast.defaultValue;
4501
4549
  if (ast.value) {
4502
- value = `withDefault(${expr}, \`${ast.defaultValue}\`)`;
4550
+ value = `withDefault(${expr}, \`${defaultValue}\`)`;
4503
4551
  }
4504
4552
  else {
4505
- value = `\`${ast.defaultValue}\``;
4553
+ value = `\`${defaultValue}\``;
4506
4554
  }
4507
4555
  }
4508
4556
  else {
@@ -4795,10 +4843,10 @@ function parseNode(node, ctx) {
4795
4843
  parseTCall(node, ctx) ||
4796
4844
  parseTCallBlock(node) ||
4797
4845
  parseTEscNode(node, ctx) ||
4846
+ parseTOutNode(node, ctx) ||
4798
4847
  parseTKey(node, ctx) ||
4799
4848
  parseTTranslation(node, ctx) ||
4800
4849
  parseTSlot(node, ctx) ||
4801
- parseTOutNode(node, ctx) ||
4802
4850
  parseComponent(node, ctx) ||
4803
4851
  parseDOMNode(node, ctx) ||
4804
4852
  parseTSetNode(node, ctx) ||
@@ -4883,10 +4931,10 @@ function parseDOMNode(node, ctx) {
4883
4931
  let model = null;
4884
4932
  for (let attr of nodeAttrsNames) {
4885
4933
  const value = node.getAttribute(attr);
4886
- if (attr.startsWith("t-on")) {
4887
- if (attr === "t-on") {
4888
- throw new OwlError("Missing event name with t-on directive");
4889
- }
4934
+ if (attr === "t-on" || attr === "t-on-") {
4935
+ throw new OwlError("Missing event name with t-on directive");
4936
+ }
4937
+ if (attr.startsWith("t-on-")) {
4890
4938
  on = on || {};
4891
4939
  on[attr.slice(5)] = value;
4892
4940
  }
@@ -4911,10 +4959,8 @@ function parseDOMNode(node, ctx) {
4911
4959
  const typeAttr = node.getAttribute("type");
4912
4960
  const isInput = tagName === "input";
4913
4961
  const isSelect = tagName === "select";
4914
- const isTextarea = tagName === "textarea";
4915
4962
  const isCheckboxInput = isInput && typeAttr === "checkbox";
4916
4963
  const isRadioInput = isInput && typeAttr === "radio";
4917
- const isOtherInput = isInput && !isCheckboxInput && !isRadioInput;
4918
4964
  const hasLazyMod = attr.includes(".lazy");
4919
4965
  const hasNumberMod = attr.includes(".number");
4920
4966
  const hasTrimMod = attr.includes(".trim");
@@ -4926,8 +4972,8 @@ function parseDOMNode(node, ctx) {
4926
4972
  specialInitTargetAttr: isRadioInput ? "checked" : null,
4927
4973
  eventType,
4928
4974
  hasDynamicChildren: false,
4929
- shouldTrim: hasTrimMod && (isOtherInput || isTextarea),
4930
- shouldNumberize: hasNumberMod && (isOtherInput || isTextarea),
4975
+ shouldTrim: hasTrimMod,
4976
+ shouldNumberize: hasNumberMod,
4931
4977
  };
4932
4978
  if (isSelect) {
4933
4979
  // don't pollute the original ctx
@@ -4990,9 +5036,6 @@ function parseTEscNode(node, ctx) {
4990
5036
  content: [tesc],
4991
5037
  };
4992
5038
  }
4993
- if (ast.type === 11 /* TComponent */) {
4994
- throw new OwlError("t-esc is not supported on Component nodes");
4995
- }
4996
5039
  return tesc;
4997
5040
  }
4998
5041
  // -----------------------------------------------------------------------------
@@ -5434,19 +5477,21 @@ function normalizeTIf(el) {
5434
5477
  *
5435
5478
  * @param el the element containing the tree that should be normalized
5436
5479
  */
5437
- function normalizeTEsc(el) {
5438
- const elements = [...el.querySelectorAll("[t-esc]")].filter((el) => el.tagName[0] === el.tagName[0].toUpperCase() || el.hasAttribute("t-component"));
5439
- for (const el of elements) {
5440
- if (el.childNodes.length) {
5441
- throw new OwlError("Cannot have t-esc on a component that already has content");
5442
- }
5443
- const value = el.getAttribute("t-esc");
5444
- el.removeAttribute("t-esc");
5445
- const t = el.ownerDocument.createElement("t");
5446
- if (value != null) {
5447
- 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);
5448
5494
  }
5449
- el.appendChild(t);
5450
5495
  }
5451
5496
  }
5452
5497
  /**
@@ -5457,7 +5502,7 @@ function normalizeTEsc(el) {
5457
5502
  */
5458
5503
  function normalizeXML(el) {
5459
5504
  normalizeTIf(el);
5460
- normalizeTEsc(el);
5505
+ normalizeTEscTOut(el);
5461
5506
  }
5462
5507
  /**
5463
5508
  * Parses an XML string into an XML document, throwing errors on parser errors
@@ -5510,7 +5555,7 @@ function compile(template, options = {}) {
5510
5555
  }
5511
5556
 
5512
5557
  // do not modify manually. This file is generated by the release script.
5513
- const version = "2.1.2";
5558
+ const version = "2.2";
5514
5559
 
5515
5560
  // -----------------------------------------------------------------------------
5516
5561
  // Scheduler
@@ -5520,11 +5565,18 @@ class Scheduler {
5520
5565
  this.tasks = new Set();
5521
5566
  this.frame = 0;
5522
5567
  this.delayedRenders = [];
5568
+ this.cancelledNodes = new Set();
5523
5569
  this.requestAnimationFrame = Scheduler.requestAnimationFrame;
5524
5570
  }
5525
5571
  addFiber(fiber) {
5526
5572
  this.tasks.add(fiber.root);
5527
5573
  }
5574
+ scheduleDestroy(node) {
5575
+ this.cancelledNodes.add(node);
5576
+ if (this.frame === 0) {
5577
+ this.frame = this.requestAnimationFrame(() => this.processTasks());
5578
+ }
5579
+ }
5528
5580
  /**
5529
5581
  * Process all current tasks. This only applies to the fibers that are ready.
5530
5582
  * Other tasks are left unchanged.
@@ -5534,21 +5586,28 @@ class Scheduler {
5534
5586
  let renders = this.delayedRenders;
5535
5587
  this.delayedRenders = [];
5536
5588
  for (let f of renders) {
5537
- 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) {
5538
5590
  f.render();
5539
5591
  }
5540
5592
  }
5541
5593
  }
5542
5594
  if (this.frame === 0) {
5543
- this.frame = this.requestAnimationFrame(() => {
5544
- this.frame = 0;
5545
- this.tasks.forEach((fiber) => this.processFiber(fiber));
5546
- for (let task of this.tasks) {
5547
- if (task.node.status === 2 /* DESTROYED */) {
5548
- this.tasks.delete(task);
5549
- }
5550
- }
5551
- });
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
+ }
5552
5611
  }
5553
5612
  }
5554
5613
  processFiber(fiber) {
@@ -5561,7 +5620,7 @@ class Scheduler {
5561
5620
  this.tasks.delete(fiber);
5562
5621
  return;
5563
5622
  }
5564
- if (fiber.node.status === 2 /* DESTROYED */) {
5623
+ if (fiber.node.status === 3 /* DESTROYED */) {
5565
5624
  this.tasks.delete(fiber);
5566
5625
  return;
5567
5626
  }
@@ -5589,6 +5648,8 @@ window.__OWL_DEVTOOLS__ || (window.__OWL_DEVTOOLS__ = {
5589
5648
  apps: new Set(),
5590
5649
  Fiber: Fiber,
5591
5650
  RootFiber: RootFiber,
5651
+ toRaw: toRaw,
5652
+ reactive: reactive,
5592
5653
  });
5593
5654
  class App extends TemplateSet {
5594
5655
  constructor(Root, config = {}) {
@@ -5783,9 +5844,11 @@ function status(component) {
5783
5844
  switch (component.__owl__.status) {
5784
5845
  case 0 /* NEW */:
5785
5846
  return "new";
5847
+ case 2 /* CANCELLED */:
5848
+ return "cancelled";
5786
5849
  case 1 /* MOUNTED */:
5787
5850
  return "mounted";
5788
- case 2 /* DESTROYED */:
5851
+ case 3 /* DESTROYED */:
5789
5852
  return "destroyed";
5790
5853
  }
5791
5854
  }
@@ -5841,8 +5904,9 @@ function useChildSubEnv(envExtension) {
5841
5904
  * will run a cleanup function before patching and before unmounting the
5842
5905
  * the component.
5843
5906
  *
5844
- * @param {Effect} effect the effect to run on component mount and/or patch
5845
- * @param {()=>any[]} [computeDependencies=()=>[NaN]] a callback to compute
5907
+ * @template T
5908
+ * @param {Effect<T>} effect the effect to run on component mount and/or patch
5909
+ * @param {()=>T} [computeDependencies=()=>[NaN]] a callback to compute
5846
5910
  * dependencies that will decide if the effect needs to be cleaned up and
5847
5911
  * run again. If the dependencies did not change, the effect will not run
5848
5912
  * again. The default value returns an array containing only NaN because
@@ -5958,6 +6022,6 @@ exports.whenReady = whenReady;
5958
6022
  exports.xml = xml;
5959
6023
 
5960
6024
 
5961
- __info__.date = '2023-04-29T07:45:54.333Z';
5962
- __info__.hash = 'aabb755';
6025
+ __info__.date = '2023-07-19T13:22:24.480Z';
6026
+ __info__.hash = '2e07799';
5963
6027
  __info__.url = 'https://github.com/odoo/owl';