@odoo/owl 2.0.1 → 2.0.3
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 +80 -80
- package/dist/owl.es.js +80 -80
- package/dist/owl.iife.js +80 -80
- package/dist/owl.iife.min.js +1 -1
- package/dist/types/owl.d.ts +74 -81
- package/dist/types/runtime/app.d.ts +1 -1
- package/dist/types/runtime/component_node.d.ts +2 -2
- package/dist/types/runtime/index.d.ts +0 -1
- package/dist/types/runtime/reactivity.d.ts +6 -12
- package/dist/types/runtime/template_helpers.d.ts +1 -1
- package/package.json +6 -6
package/dist/owl.cjs.js
CHANGED
|
@@ -1744,10 +1744,6 @@ class MountFiber extends RootFiber {
|
|
|
1744
1744
|
}
|
|
1745
1745
|
}
|
|
1746
1746
|
|
|
1747
|
-
// Allows to get the target of a Reactive (used for making a new Reactive from the underlying object)
|
|
1748
|
-
const TARGET = Symbol("Target");
|
|
1749
|
-
// Escape hatch to prevent reactivity system to turn something into a reactive
|
|
1750
|
-
const SKIP = Symbol("Skip");
|
|
1751
1747
|
// Special key to subscribe to, to be notified of key creation/deletion
|
|
1752
1748
|
const KEYCHANGES = Symbol("Key changes");
|
|
1753
1749
|
const objectToString = Object.prototype.toString;
|
|
@@ -1764,7 +1760,7 @@ const COLLECTION_RAWTYPES = new Set(["Set", "Map", "WeakMap"]);
|
|
|
1764
1760
|
* @returns the raw type of the object
|
|
1765
1761
|
*/
|
|
1766
1762
|
function rawType(obj) {
|
|
1767
|
-
return objectToString.call(obj).slice(8, -1);
|
|
1763
|
+
return objectToString.call(toRaw(obj)).slice(8, -1);
|
|
1768
1764
|
}
|
|
1769
1765
|
/**
|
|
1770
1766
|
* Checks whether a given value can be made into a reactive object.
|
|
@@ -1788,6 +1784,7 @@ function canBeMadeReactive(value) {
|
|
|
1788
1784
|
function possiblyReactive(val, cb) {
|
|
1789
1785
|
return canBeMadeReactive(val) ? reactive(val, cb) : val;
|
|
1790
1786
|
}
|
|
1787
|
+
const skipped = new WeakSet();
|
|
1791
1788
|
/**
|
|
1792
1789
|
* Mark an object or array so that it is ignored by the reactivity system
|
|
1793
1790
|
*
|
|
@@ -1795,7 +1792,7 @@ function possiblyReactive(val, cb) {
|
|
|
1795
1792
|
* @returns the object itself
|
|
1796
1793
|
*/
|
|
1797
1794
|
function markRaw(value) {
|
|
1798
|
-
value
|
|
1795
|
+
skipped.add(value);
|
|
1799
1796
|
return value;
|
|
1800
1797
|
}
|
|
1801
1798
|
/**
|
|
@@ -1805,7 +1802,7 @@ function markRaw(value) {
|
|
|
1805
1802
|
* @returns the underlying value
|
|
1806
1803
|
*/
|
|
1807
1804
|
function toRaw(value) {
|
|
1808
|
-
return value
|
|
1805
|
+
return targets.has(value) ? targets.get(value) : value;
|
|
1809
1806
|
}
|
|
1810
1807
|
const targetToKeysToCallbacks = new WeakMap();
|
|
1811
1808
|
/**
|
|
@@ -1887,6 +1884,8 @@ function getSubscriptions(callback) {
|
|
|
1887
1884
|
};
|
|
1888
1885
|
});
|
|
1889
1886
|
}
|
|
1887
|
+
// Maps reactive objects to the underlying target
|
|
1888
|
+
const targets = new WeakMap();
|
|
1890
1889
|
const reactiveCache = new WeakMap();
|
|
1891
1890
|
/**
|
|
1892
1891
|
* Creates a reactive proxy for an object. Reading data on the reactive object
|
|
@@ -1902,7 +1901,7 @@ const reactiveCache = new WeakMap();
|
|
|
1902
1901
|
* Subscriptions:
|
|
1903
1902
|
* + Reading a property on an object will subscribe you to changes in the value
|
|
1904
1903
|
* of that property.
|
|
1905
|
-
* + Accessing an object keys (eg with Object.keys or with `for..in`) will
|
|
1904
|
+
* + Accessing an object's keys (eg with Object.keys or with `for..in`) will
|
|
1906
1905
|
* subscribe you to the creation/deletion of keys. Checking the presence of a
|
|
1907
1906
|
* key on the object with 'in' has the same effect.
|
|
1908
1907
|
* - getOwnPropertyDescriptor does not currently subscribe you to the property.
|
|
@@ -1919,12 +1918,12 @@ function reactive(target, callback = () => { }) {
|
|
|
1919
1918
|
if (!canBeMadeReactive(target)) {
|
|
1920
1919
|
throw new OwlError(`Cannot make the given value reactive`);
|
|
1921
1920
|
}
|
|
1922
|
-
if (
|
|
1921
|
+
if (skipped.has(target)) {
|
|
1923
1922
|
return target;
|
|
1924
1923
|
}
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
return reactive(
|
|
1924
|
+
if (targets.has(target)) {
|
|
1925
|
+
// target is reactive, create a reactive on the underlying object instead
|
|
1926
|
+
return reactive(targets.get(target), callback);
|
|
1928
1927
|
}
|
|
1929
1928
|
if (!reactiveCache.has(target)) {
|
|
1930
1929
|
reactiveCache.set(target, new WeakMap());
|
|
@@ -1937,6 +1936,7 @@ function reactive(target, callback = () => { }) {
|
|
|
1937
1936
|
: basicProxyHandler(callback);
|
|
1938
1937
|
const proxy = new Proxy(target, handler);
|
|
1939
1938
|
reactivesForTarget.set(callback, proxy);
|
|
1939
|
+
targets.set(proxy, target);
|
|
1940
1940
|
}
|
|
1941
1941
|
return reactivesForTarget.get(callback);
|
|
1942
1942
|
}
|
|
@@ -1948,29 +1948,27 @@ function reactive(target, callback = () => { }) {
|
|
|
1948
1948
|
*/
|
|
1949
1949
|
function basicProxyHandler(callback) {
|
|
1950
1950
|
return {
|
|
1951
|
-
get(target, key,
|
|
1952
|
-
if (key === TARGET) {
|
|
1953
|
-
return target;
|
|
1954
|
-
}
|
|
1951
|
+
get(target, key, receiver) {
|
|
1955
1952
|
// non-writable non-configurable properties cannot be made reactive
|
|
1956
1953
|
const desc = Object.getOwnPropertyDescriptor(target, key);
|
|
1957
1954
|
if (desc && !desc.writable && !desc.configurable) {
|
|
1958
|
-
return Reflect.get(target, key,
|
|
1955
|
+
return Reflect.get(target, key, receiver);
|
|
1959
1956
|
}
|
|
1960
1957
|
observeTargetKey(target, key, callback);
|
|
1961
|
-
return possiblyReactive(Reflect.get(target, key,
|
|
1958
|
+
return possiblyReactive(Reflect.get(target, key, receiver), callback);
|
|
1962
1959
|
},
|
|
1963
|
-
set(target, key, value,
|
|
1964
|
-
const
|
|
1965
|
-
const originalValue = Reflect.get(target, key,
|
|
1966
|
-
const ret = Reflect.set(target, key, value,
|
|
1967
|
-
if (
|
|
1960
|
+
set(target, key, value, receiver) {
|
|
1961
|
+
const hadKey = objectHasOwnProperty.call(target, key);
|
|
1962
|
+
const originalValue = Reflect.get(target, key, receiver);
|
|
1963
|
+
const ret = Reflect.set(target, key, value, receiver);
|
|
1964
|
+
if (!hadKey && objectHasOwnProperty.call(target, key)) {
|
|
1968
1965
|
notifyReactives(target, KEYCHANGES);
|
|
1969
1966
|
}
|
|
1970
1967
|
// While Array length may trigger the set trap, it's not actually set by this
|
|
1971
1968
|
// method but is updated behind the scenes, and the trap is not called with the
|
|
1972
1969
|
// new value. We disable the "same-value-optimization" for it because of that.
|
|
1973
|
-
if (originalValue !==
|
|
1970
|
+
if (originalValue !== Reflect.get(target, key, receiver) ||
|
|
1971
|
+
(key === "length" && Array.isArray(target))) {
|
|
1974
1972
|
notifyReactives(target, key);
|
|
1975
1973
|
}
|
|
1976
1974
|
return ret;
|
|
@@ -2146,10 +2144,8 @@ function collectionsProxyHandler(target, callback, targetRawType) {
|
|
|
2146
2144
|
// property is read.
|
|
2147
2145
|
const specialHandlers = rawTypeToFuncHandlers[targetRawType](target, callback);
|
|
2148
2146
|
return Object.assign(basicProxyHandler(callback), {
|
|
2147
|
+
// FIXME: probably broken when part of prototype chain since we ignore the receiver
|
|
2149
2148
|
get(target, key) {
|
|
2150
|
-
if (key === TARGET) {
|
|
2151
|
-
return target;
|
|
2152
|
-
}
|
|
2153
2149
|
if (objectHasOwnProperty.call(specialHandlers, key)) {
|
|
2154
2150
|
return specialHandlers[key];
|
|
2155
2151
|
}
|
|
@@ -2309,12 +2305,13 @@ class ComponentNode {
|
|
|
2309
2305
|
this.childEnv = env;
|
|
2310
2306
|
for (const key in props) {
|
|
2311
2307
|
const prop = props[key];
|
|
2312
|
-
if (prop && typeof prop === "object" && prop
|
|
2308
|
+
if (prop && typeof prop === "object" && targets.has(prop)) {
|
|
2313
2309
|
props[key] = useState(prop);
|
|
2314
2310
|
}
|
|
2315
2311
|
}
|
|
2316
2312
|
this.component = new C(props, env, this);
|
|
2317
|
-
|
|
2313
|
+
const ctx = Object.assign(Object.create(this.component), { this: this.component });
|
|
2314
|
+
this.renderFn = app.getTemplate(C.template).bind(this.component, ctx, this);
|
|
2318
2315
|
this.component.setup();
|
|
2319
2316
|
currentNode = null;
|
|
2320
2317
|
}
|
|
@@ -2427,7 +2424,7 @@ class ComponentNode {
|
|
|
2427
2424
|
currentNode = this;
|
|
2428
2425
|
for (const key in props) {
|
|
2429
2426
|
const prop = props[key];
|
|
2430
|
-
if (prop && typeof prop === "object" && prop
|
|
2427
|
+
if (prop && typeof prop === "object" && targets.has(prop)) {
|
|
2431
2428
|
props[key] = useState(prop);
|
|
2432
2429
|
}
|
|
2433
2430
|
}
|
|
@@ -2505,6 +2502,7 @@ class ComponentNode {
|
|
|
2505
2502
|
}
|
|
2506
2503
|
_patch() {
|
|
2507
2504
|
let hasChildren = false;
|
|
2505
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2508
2506
|
for (let _k in this.children) {
|
|
2509
2507
|
hasChildren = true;
|
|
2510
2508
|
break;
|
|
@@ -2885,7 +2883,7 @@ function callSlot(ctx, parent, key, name, dynamic, extra, defaultContent) {
|
|
|
2885
2883
|
if (__scope) {
|
|
2886
2884
|
slotScope[__scope] = extra;
|
|
2887
2885
|
}
|
|
2888
|
-
const slotBDom = __render ? __render
|
|
2886
|
+
const slotBDom = __render ? __render(slotScope, parent, key) : null;
|
|
2889
2887
|
if (defaultContent) {
|
|
2890
2888
|
let child1 = undefined;
|
|
2891
2889
|
let child2 = undefined;
|
|
@@ -2893,15 +2891,14 @@ function callSlot(ctx, parent, key, name, dynamic, extra, defaultContent) {
|
|
|
2893
2891
|
child1 = dynamic ? toggler(name, slotBDom) : slotBDom;
|
|
2894
2892
|
}
|
|
2895
2893
|
else {
|
|
2896
|
-
child2 = defaultContent
|
|
2894
|
+
child2 = defaultContent(ctx, parent, key);
|
|
2897
2895
|
}
|
|
2898
2896
|
return multi([child1, child2]);
|
|
2899
2897
|
}
|
|
2900
2898
|
return slotBDom || text("");
|
|
2901
2899
|
}
|
|
2902
2900
|
function capture(ctx) {
|
|
2903
|
-
const
|
|
2904
|
-
const result = ObjectCreate(component);
|
|
2901
|
+
const result = ObjectCreate(ctx);
|
|
2905
2902
|
for (let k in ctx) {
|
|
2906
2903
|
result[k] = ctx[k];
|
|
2907
2904
|
}
|
|
@@ -3010,8 +3007,7 @@ function safeOutput(value, defaultValue) {
|
|
|
3010
3007
|
let boundFunctions = new WeakMap();
|
|
3011
3008
|
const WeakMapGet = WeakMap.prototype.get;
|
|
3012
3009
|
const WeakMapSet = WeakMap.prototype.set;
|
|
3013
|
-
function bind(
|
|
3014
|
-
let component = ctx.__owl__.component;
|
|
3010
|
+
function bind(component, fn) {
|
|
3015
3011
|
let boundFnMap = WeakMapGet.call(boundFunctions, component);
|
|
3016
3012
|
if (!boundFnMap) {
|
|
3017
3013
|
boundFnMap = new WeakMap();
|
|
@@ -3239,7 +3235,7 @@ TemplateSet.registerTemplate("__portal__", portalTemplate);
|
|
|
3239
3235
|
//------------------------------------------------------------------------------
|
|
3240
3236
|
// Misc types, constants and helpers
|
|
3241
3237
|
//------------------------------------------------------------------------------
|
|
3242
|
-
const RESERVED_WORDS = "true,false,NaN,null,undefined,debugger,console,window,in,instanceof,new,function,return,
|
|
3238
|
+
const RESERVED_WORDS = "true,false,NaN,null,undefined,debugger,console,window,in,instanceof,new,function,return,eval,void,Math,RegExp,Array,Object,Date".split(",");
|
|
3243
3239
|
const WORD_REPLACEMENT = Object.assign(Object.create(null), {
|
|
3244
3240
|
and: "&&",
|
|
3245
3241
|
or: "||",
|
|
@@ -3625,7 +3621,7 @@ class CodeTarget {
|
|
|
3625
3621
|
let result = [];
|
|
3626
3622
|
result.push(`function ${this.name}(ctx, node, key = "") {`);
|
|
3627
3623
|
if (this.hasRef) {
|
|
3628
|
-
result.push(` const refs =
|
|
3624
|
+
result.push(` const refs = this.__owl__.refs;`);
|
|
3629
3625
|
for (let name in this.refInfo) {
|
|
3630
3626
|
const [id, expr] = this.refInfo[name];
|
|
3631
3627
|
result.push(` const ${id} = ${expr};`);
|
|
@@ -3717,7 +3713,7 @@ class CodeGenerator {
|
|
|
3717
3713
|
for (let block of this.blocks) {
|
|
3718
3714
|
if (block.dom) {
|
|
3719
3715
|
let xmlString = block.asXmlString();
|
|
3720
|
-
xmlString = xmlString.replace(/`/g, "\\`");
|
|
3716
|
+
xmlString = xmlString.replace(/\\/g, "\\\\").replace(/`/g, "\\`");
|
|
3721
3717
|
if (block.dynamicTagName) {
|
|
3722
3718
|
xmlString = xmlString.replace(/^<\w+/, `<\${tag || '${block.dom.nodeName}'}`);
|
|
3723
3719
|
xmlString = xmlString.replace(/\w+>$/, `\${tag || '${block.dom.nodeName}'}>`);
|
|
@@ -4010,39 +4006,6 @@ class CodeGenerator {
|
|
|
4010
4006
|
attrs[`block-attribute-${selectedId}`] = "selected";
|
|
4011
4007
|
}
|
|
4012
4008
|
}
|
|
4013
|
-
// event handlers
|
|
4014
|
-
for (let ev in ast.on) {
|
|
4015
|
-
const name = this.generateHandlerCode(ev, ast.on[ev]);
|
|
4016
|
-
const idx = block.insertData(name, "hdlr");
|
|
4017
|
-
attrs[`block-handler-${idx}`] = ev;
|
|
4018
|
-
}
|
|
4019
|
-
// t-ref
|
|
4020
|
-
if (ast.ref) {
|
|
4021
|
-
this.target.hasRef = true;
|
|
4022
|
-
const isDynamic = INTERP_REGEXP.test(ast.ref);
|
|
4023
|
-
if (isDynamic) {
|
|
4024
|
-
const str = replaceDynamicParts(ast.ref, (expr) => this.captureExpression(expr, true));
|
|
4025
|
-
const idx = block.insertData(`(el) => refs[${str}] = el`, "ref");
|
|
4026
|
-
attrs["block-ref"] = String(idx);
|
|
4027
|
-
}
|
|
4028
|
-
else {
|
|
4029
|
-
let name = ast.ref;
|
|
4030
|
-
if (name in this.target.refInfo) {
|
|
4031
|
-
// ref has already been defined
|
|
4032
|
-
this.helpers.add("multiRefSetter");
|
|
4033
|
-
const info = this.target.refInfo[name];
|
|
4034
|
-
const index = block.data.push(info[0]) - 1;
|
|
4035
|
-
attrs["block-ref"] = String(index);
|
|
4036
|
-
info[1] = `multiRefSetter(refs, \`${name}\`)`;
|
|
4037
|
-
}
|
|
4038
|
-
else {
|
|
4039
|
-
let id = generateId("ref");
|
|
4040
|
-
this.target.refInfo[name] = [id, `(el) => refs[\`${name}\`] = el`];
|
|
4041
|
-
const index = block.data.push(id) - 1;
|
|
4042
|
-
attrs["block-ref"] = String(index);
|
|
4043
|
-
}
|
|
4044
|
-
}
|
|
4045
|
-
}
|
|
4046
4009
|
// t-model
|
|
4047
4010
|
let tModelSelectedExpr;
|
|
4048
4011
|
if (ast.model) {
|
|
@@ -4076,6 +4039,39 @@ class CodeGenerator {
|
|
|
4076
4039
|
idx = block.insertData(handler, "hdlr");
|
|
4077
4040
|
attrs[`block-handler-${idx}`] = eventType;
|
|
4078
4041
|
}
|
|
4042
|
+
// event handlers
|
|
4043
|
+
for (let ev in ast.on) {
|
|
4044
|
+
const name = this.generateHandlerCode(ev, ast.on[ev]);
|
|
4045
|
+
const idx = block.insertData(name, "hdlr");
|
|
4046
|
+
attrs[`block-handler-${idx}`] = ev;
|
|
4047
|
+
}
|
|
4048
|
+
// t-ref
|
|
4049
|
+
if (ast.ref) {
|
|
4050
|
+
this.target.hasRef = true;
|
|
4051
|
+
const isDynamic = INTERP_REGEXP.test(ast.ref);
|
|
4052
|
+
if (isDynamic) {
|
|
4053
|
+
const str = replaceDynamicParts(ast.ref, (expr) => this.captureExpression(expr, true));
|
|
4054
|
+
const idx = block.insertData(`(el) => refs[${str}] = el`, "ref");
|
|
4055
|
+
attrs["block-ref"] = String(idx);
|
|
4056
|
+
}
|
|
4057
|
+
else {
|
|
4058
|
+
let name = ast.ref;
|
|
4059
|
+
if (name in this.target.refInfo) {
|
|
4060
|
+
// ref has already been defined
|
|
4061
|
+
this.helpers.add("multiRefSetter");
|
|
4062
|
+
const info = this.target.refInfo[name];
|
|
4063
|
+
const index = block.data.push(info[0]) - 1;
|
|
4064
|
+
attrs["block-ref"] = String(index);
|
|
4065
|
+
info[1] = `multiRefSetter(refs, \`${name}\`)`;
|
|
4066
|
+
}
|
|
4067
|
+
else {
|
|
4068
|
+
let id = generateId("ref");
|
|
4069
|
+
this.target.refInfo[name] = [id, `(el) => refs[\`${name}\`] = el`];
|
|
4070
|
+
const index = block.data.push(id) - 1;
|
|
4071
|
+
attrs["block-ref"] = String(index);
|
|
4072
|
+
}
|
|
4073
|
+
}
|
|
4074
|
+
}
|
|
4079
4075
|
const dom = xmlDoc.createElement(ast.tag);
|
|
4080
4076
|
for (const [attr, val] of Object.entries(attrs)) {
|
|
4081
4077
|
if (!(attr === "class" && val === "")) {
|
|
@@ -4484,7 +4480,7 @@ class CodeGenerator {
|
|
|
4484
4480
|
if (suffix === "bind") {
|
|
4485
4481
|
this.helpers.add("bind");
|
|
4486
4482
|
name = _name;
|
|
4487
|
-
value = `bind(
|
|
4483
|
+
value = `bind(this, ${value || undefined})`;
|
|
4488
4484
|
}
|
|
4489
4485
|
else {
|
|
4490
4486
|
throw new OwlError("Invalid prop suffix");
|
|
@@ -4523,7 +4519,7 @@ class CodeGenerator {
|
|
|
4523
4519
|
const params = [];
|
|
4524
4520
|
if (slotAst.content) {
|
|
4525
4521
|
const name = this.compileInNewTarget("slot", slotAst.content, ctx, slotAst.on);
|
|
4526
|
-
params.push(`__render: ${name}, __ctx: ${ctxStr}`);
|
|
4522
|
+
params.push(`__render: ${name}.bind(this), __ctx: ${ctxStr}`);
|
|
4527
4523
|
}
|
|
4528
4524
|
const scope = ast.slots[slotName].scope;
|
|
4529
4525
|
if (scope) {
|
|
@@ -4634,7 +4630,7 @@ class CodeGenerator {
|
|
|
4634
4630
|
const scope = this.getPropString(props, dynProps);
|
|
4635
4631
|
if (ast.defaultContent) {
|
|
4636
4632
|
const name = this.compileInNewTarget("defaultContent", ast.defaultContent, ctx);
|
|
4637
|
-
blockString = `callSlot(ctx, node, ${key}, ${slotName}, ${dynamic}, ${scope}, ${name})`;
|
|
4633
|
+
blockString = `callSlot(ctx, node, ${key}, ${slotName}, ${dynamic}, ${scope}, ${name}.bind(this))`;
|
|
4638
4634
|
}
|
|
4639
4635
|
else {
|
|
4640
4636
|
if (dynamic) {
|
|
@@ -4682,7 +4678,7 @@ class CodeGenerator {
|
|
|
4682
4678
|
expr: `app.createComponent(null, false, true, false, false)`,
|
|
4683
4679
|
});
|
|
4684
4680
|
const target = compileExpr(ast.target);
|
|
4685
|
-
const blockString = `${id}({target: ${target},slots: {'default': {__render: ${name}, __ctx: ${ctxStr}}}}, key + \`${key}\`, node, ctx, Portal)`;
|
|
4681
|
+
const blockString = `${id}({target: ${target},slots: {'default': {__render: ${name}.bind(this), __ctx: ${ctxStr}}}}, key + \`${key}\`, node, ctx, Portal)`;
|
|
4686
4682
|
if (block) {
|
|
4687
4683
|
this.insertAnchor(block);
|
|
4688
4684
|
}
|
|
@@ -5657,7 +5653,11 @@ class App extends TemplateSet {
|
|
|
5657
5653
|
else {
|
|
5658
5654
|
// new component
|
|
5659
5655
|
if (isStatic) {
|
|
5660
|
-
|
|
5656
|
+
const components = parent.constructor.components;
|
|
5657
|
+
if (!components) {
|
|
5658
|
+
throw new OwlError(`Cannot find the definition of component "${name}", missing static components key in parent`);
|
|
5659
|
+
}
|
|
5660
|
+
C = components[name];
|
|
5661
5661
|
if (!C) {
|
|
5662
5662
|
throw new OwlError(`Cannot find the definition of component "${name}"`);
|
|
5663
5663
|
}
|
|
@@ -5857,7 +5857,7 @@ exports.whenReady = whenReady;
|
|
|
5857
5857
|
exports.xml = xml;
|
|
5858
5858
|
|
|
5859
5859
|
|
|
5860
|
-
__info__.version = '2.0.
|
|
5861
|
-
__info__.date = '
|
|
5862
|
-
__info__.hash = '
|
|
5860
|
+
__info__.version = '2.0.3';
|
|
5861
|
+
__info__.date = '2023-01-20T10:27:02.171Z';
|
|
5862
|
+
__info__.hash = '316eb06';
|
|
5863
5863
|
__info__.url = 'https://github.com/odoo/owl';
|