@odoo/owl 2.0.0-alpha.1 → 2.0.0-beta-4
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/README.md +3 -1
- package/dist/owl.cjs.js +1440 -949
- package/dist/owl.cjs.min.js +1 -1
- package/dist/owl.es.js +1441 -949
- package/dist/owl.es.min.js +1 -1
- package/dist/owl.iife.js +1440 -949
- package/dist/owl.iife.min.js +1 -1
- package/dist/types/app/app.d.ts +3 -3
- package/dist/types/app/template_helpers.d.ts +3 -2
- package/dist/types/app/template_set.d.ts +2 -9
- package/dist/types/blockdom/event_catcher.d.ts +7 -0
- package/dist/types/blockdom/events.d.ts +1 -0
- package/dist/types/blockdom/index.d.ts +1 -0
- package/dist/types/blockdom/text.d.ts +4 -4
- package/dist/types/compiler/code_generator.d.ts +9 -6
- package/dist/types/compiler/parser.d.ts +25 -23
- package/dist/types/component/component.d.ts +1 -1
- package/dist/types/component/component_node.d.ts +20 -5
- package/dist/types/component/fibers.d.ts +6 -0
- package/dist/types/component/scheduler.d.ts +3 -4
- package/dist/types/index.d.ts +3 -5
- package/dist/types/reactivity.d.ts +12 -13
- package/dist/types/utils.d.ts +7 -0
- package/package.json +1 -1
- package/dist/types/memo.d.ts +0 -6
package/dist/owl.es.js
CHANGED
|
@@ -209,7 +209,8 @@ function updateClass(val, oldVal) {
|
|
|
209
209
|
}
|
|
210
210
|
function makePropSetter(name) {
|
|
211
211
|
return function setProp(value) {
|
|
212
|
-
|
|
212
|
+
// support 0, fallback to empty string for other falsy values
|
|
213
|
+
this[name] = value === 0 ? 0 : value || "";
|
|
213
214
|
};
|
|
214
215
|
}
|
|
215
216
|
function isProp(tag, key) {
|
|
@@ -263,10 +264,14 @@ function createElementHandler(evName, capture = false) {
|
|
|
263
264
|
this[eventKey] = data;
|
|
264
265
|
this.addEventListener(evName, listener, { capture });
|
|
265
266
|
}
|
|
267
|
+
function remove() {
|
|
268
|
+
delete this[eventKey];
|
|
269
|
+
this.removeEventListener(evName, listener, { capture });
|
|
270
|
+
}
|
|
266
271
|
function update(data) {
|
|
267
272
|
this[eventKey] = data;
|
|
268
273
|
}
|
|
269
|
-
return { setup, update };
|
|
274
|
+
return { setup, update, remove };
|
|
270
275
|
}
|
|
271
276
|
// Synthetic handler: a form of event delegation that allows placing only one
|
|
272
277
|
// listener per event type.
|
|
@@ -283,7 +288,10 @@ function createSyntheticHandler(evName, capture = false) {
|
|
|
283
288
|
_data[currentId] = data;
|
|
284
289
|
this[eventKey] = _data;
|
|
285
290
|
}
|
|
286
|
-
|
|
291
|
+
function remove() {
|
|
292
|
+
delete this[eventKey];
|
|
293
|
+
}
|
|
294
|
+
return { setup, update: setup, remove };
|
|
287
295
|
}
|
|
288
296
|
function nativeToSyntheticEvent(eventKey, event) {
|
|
289
297
|
let dom = event.target;
|
|
@@ -508,7 +516,7 @@ const characterDataProto = CharacterData.prototype;
|
|
|
508
516
|
const characterDataSetData = getDescriptor$1(characterDataProto, "data").set;
|
|
509
517
|
const nodeGetFirstChild = getDescriptor$1(nodeProto$2, "firstChild").get;
|
|
510
518
|
const nodeGetNextSibling = getDescriptor$1(nodeProto$2, "nextSibling").get;
|
|
511
|
-
const NO_OP
|
|
519
|
+
const NO_OP = () => { };
|
|
512
520
|
const cache$1 = {};
|
|
513
521
|
/**
|
|
514
522
|
* Compiling blocks is a multi-step process:
|
|
@@ -807,13 +815,12 @@ function updateCtx(ctx, tree) {
|
|
|
807
815
|
break;
|
|
808
816
|
}
|
|
809
817
|
case "ref":
|
|
810
|
-
const index = ctx.
|
|
811
|
-
ctx.cbRefs.push(info.idx);
|
|
818
|
+
const index = ctx.cbRefs.push(info.idx) - 1;
|
|
812
819
|
ctx.locations.push({
|
|
813
820
|
idx: info.idx,
|
|
814
821
|
refIdx: info.refIdx,
|
|
815
822
|
setData: makeRefSetter(index, ctx.refList),
|
|
816
|
-
updateData: NO_OP
|
|
823
|
+
updateData: NO_OP,
|
|
817
824
|
});
|
|
818
825
|
}
|
|
819
826
|
}
|
|
@@ -826,10 +833,12 @@ function buildBlock(template, ctx) {
|
|
|
826
833
|
if (ctx.cbRefs.length) {
|
|
827
834
|
const cbRefs = ctx.cbRefs;
|
|
828
835
|
const refList = ctx.refList;
|
|
836
|
+
let cbRefsNumber = cbRefs.length;
|
|
829
837
|
B = class extends B {
|
|
830
838
|
mount(parent, afterNode) {
|
|
839
|
+
refList.push(new Array(cbRefsNumber));
|
|
831
840
|
super.mount(parent, afterNode);
|
|
832
|
-
for (let cbRef of refList) {
|
|
841
|
+
for (let cbRef of refList.pop()) {
|
|
833
842
|
cbRef();
|
|
834
843
|
}
|
|
835
844
|
}
|
|
@@ -991,7 +1000,7 @@ function setText(value) {
|
|
|
991
1000
|
}
|
|
992
1001
|
function makeRefSetter(index, refs) {
|
|
993
1002
|
return function setRef(fn) {
|
|
994
|
-
refs[index] = () => fn(this);
|
|
1003
|
+
refs[refs.length - 1][index] = () => fn(this);
|
|
995
1004
|
};
|
|
996
1005
|
}
|
|
997
1006
|
|
|
@@ -1277,6 +1286,75 @@ function html(str) {
|
|
|
1277
1286
|
return new VHtml(str);
|
|
1278
1287
|
}
|
|
1279
1288
|
|
|
1289
|
+
function createCatcher(eventsSpec) {
|
|
1290
|
+
let setupFns = [];
|
|
1291
|
+
let removeFns = [];
|
|
1292
|
+
for (let name in eventsSpec) {
|
|
1293
|
+
let index = eventsSpec[name];
|
|
1294
|
+
let { setup, remove } = createEventHandler(name);
|
|
1295
|
+
setupFns[index] = setup;
|
|
1296
|
+
removeFns[index] = remove;
|
|
1297
|
+
}
|
|
1298
|
+
let n = setupFns.length;
|
|
1299
|
+
class VCatcher {
|
|
1300
|
+
constructor(child, handlers) {
|
|
1301
|
+
this.afterNode = null;
|
|
1302
|
+
this.child = child;
|
|
1303
|
+
this.handlers = handlers;
|
|
1304
|
+
}
|
|
1305
|
+
mount(parent, afterNode) {
|
|
1306
|
+
this.parentEl = parent;
|
|
1307
|
+
this.afterNode = afterNode;
|
|
1308
|
+
this.child.mount(parent, afterNode);
|
|
1309
|
+
for (let i = 0; i < n; i++) {
|
|
1310
|
+
let origFn = this.handlers[i][0];
|
|
1311
|
+
const self = this;
|
|
1312
|
+
this.handlers[i][0] = function (ev) {
|
|
1313
|
+
const target = ev.target;
|
|
1314
|
+
let currentNode = self.child.firstNode();
|
|
1315
|
+
const afterNode = self.afterNode;
|
|
1316
|
+
while (currentNode !== afterNode) {
|
|
1317
|
+
if (currentNode.contains(target)) {
|
|
1318
|
+
return origFn.call(this, ev);
|
|
1319
|
+
}
|
|
1320
|
+
currentNode = currentNode.nextSibling;
|
|
1321
|
+
}
|
|
1322
|
+
};
|
|
1323
|
+
setupFns[i].call(parent, this.handlers[i]);
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
moveBefore(other, afterNode) {
|
|
1327
|
+
this.afterNode = null;
|
|
1328
|
+
this.child.moveBefore(other ? other.child : null, afterNode);
|
|
1329
|
+
}
|
|
1330
|
+
patch(other, withBeforeRemove) {
|
|
1331
|
+
if (this === other) {
|
|
1332
|
+
return;
|
|
1333
|
+
}
|
|
1334
|
+
this.handlers = other.handlers;
|
|
1335
|
+
this.child.patch(other.child, withBeforeRemove);
|
|
1336
|
+
}
|
|
1337
|
+
beforeRemove() {
|
|
1338
|
+
this.child.beforeRemove();
|
|
1339
|
+
}
|
|
1340
|
+
remove() {
|
|
1341
|
+
for (let i = 0; i < n; i++) {
|
|
1342
|
+
removeFns[i].call(this.parentEl);
|
|
1343
|
+
}
|
|
1344
|
+
this.child.remove();
|
|
1345
|
+
}
|
|
1346
|
+
firstNode() {
|
|
1347
|
+
return this.child.firstNode();
|
|
1348
|
+
}
|
|
1349
|
+
toString() {
|
|
1350
|
+
return this.child.toString();
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
return function (child, handlers) {
|
|
1354
|
+
return new VCatcher(child, handlers);
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1280
1358
|
function mount$1(vnode, fixture, afterNode = null) {
|
|
1281
1359
|
vnode.mount(fixture, afterNode);
|
|
1282
1360
|
}
|
|
@@ -1290,143 +1368,464 @@ function remove(vnode, withBeforeRemove = false) {
|
|
|
1290
1368
|
vnode.remove();
|
|
1291
1369
|
}
|
|
1292
1370
|
|
|
1371
|
+
const mainEventHandler = (data, ev, currentTarget) => {
|
|
1372
|
+
const { data: _data, modifiers } = filterOutModifiersFromData(data);
|
|
1373
|
+
data = _data;
|
|
1374
|
+
let stopped = false;
|
|
1375
|
+
if (modifiers.length) {
|
|
1376
|
+
let selfMode = false;
|
|
1377
|
+
const isSelf = ev.target === currentTarget;
|
|
1378
|
+
for (const mod of modifiers) {
|
|
1379
|
+
switch (mod) {
|
|
1380
|
+
case "self":
|
|
1381
|
+
selfMode = true;
|
|
1382
|
+
if (isSelf) {
|
|
1383
|
+
continue;
|
|
1384
|
+
}
|
|
1385
|
+
else {
|
|
1386
|
+
return stopped;
|
|
1387
|
+
}
|
|
1388
|
+
case "prevent":
|
|
1389
|
+
if ((selfMode && isSelf) || !selfMode)
|
|
1390
|
+
ev.preventDefault();
|
|
1391
|
+
continue;
|
|
1392
|
+
case "stop":
|
|
1393
|
+
if ((selfMode && isSelf) || !selfMode)
|
|
1394
|
+
ev.stopPropagation();
|
|
1395
|
+
stopped = true;
|
|
1396
|
+
continue;
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
// If handler is empty, the array slot 0 will also be empty, and data will not have the property 0
|
|
1401
|
+
// We check this rather than data[0] being truthy (or typeof function) so that it crashes
|
|
1402
|
+
// as expected when there is a handler expression that evaluates to a falsy value
|
|
1403
|
+
if (Object.hasOwnProperty.call(data, 0)) {
|
|
1404
|
+
const handler = data[0];
|
|
1405
|
+
if (typeof handler !== "function") {
|
|
1406
|
+
throw new Error(`Invalid handler (expected a function, received: '${handler}')`);
|
|
1407
|
+
}
|
|
1408
|
+
let node = data[1] ? data[1].__owl__ : null;
|
|
1409
|
+
if (node ? node.status === 1 /* MOUNTED */ : true) {
|
|
1410
|
+
handler.call(node ? node.component : null, ev);
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
return stopped;
|
|
1414
|
+
};
|
|
1415
|
+
|
|
1416
|
+
// Allows to get the target of a Reactive (used for making a new Reactive from the underlying object)
|
|
1417
|
+
const TARGET = Symbol("Target");
|
|
1418
|
+
// Escape hatch to prevent reactivity system to turn something into a reactive
|
|
1419
|
+
const SKIP = Symbol("Skip");
|
|
1420
|
+
// Special key to subscribe to, to be notified of key creation/deletion
|
|
1421
|
+
const KEYCHANGES = Symbol("Key changes");
|
|
1422
|
+
const objectToString = Object.prototype.toString;
|
|
1423
|
+
const objectHasOwnProperty = Object.prototype.hasOwnProperty;
|
|
1424
|
+
const SUPPORTED_RAW_TYPES = new Set(["Object", "Array", "Set", "Map", "WeakMap"]);
|
|
1425
|
+
const COLLECTION_RAWTYPES = new Set(["Set", "Map", "WeakMap"]);
|
|
1293
1426
|
/**
|
|
1294
|
-
*
|
|
1427
|
+
* extract "RawType" from strings like "[object RawType]" => this lets us ignore
|
|
1428
|
+
* many native objects such as Promise (whose toString is [object Promise])
|
|
1429
|
+
* or Date ([object Date]), while also supporting collections without using
|
|
1430
|
+
* instanceof in a loop
|
|
1295
1431
|
*
|
|
1296
|
-
*
|
|
1432
|
+
* @param obj the object to check
|
|
1433
|
+
* @returns the raw type of the object
|
|
1297
1434
|
*/
|
|
1298
|
-
function
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1435
|
+
function rawType(obj) {
|
|
1436
|
+
return objectToString.call(obj).slice(8, -1);
|
|
1437
|
+
}
|
|
1438
|
+
/**
|
|
1439
|
+
* Checks whether a given value can be made into a reactive object.
|
|
1440
|
+
*
|
|
1441
|
+
* @param value the value to check
|
|
1442
|
+
* @returns whether the value can be made reactive
|
|
1443
|
+
*/
|
|
1444
|
+
function canBeMadeReactive(value) {
|
|
1445
|
+
if (typeof value !== "object") {
|
|
1446
|
+
return false;
|
|
1306
1447
|
}
|
|
1448
|
+
return SUPPORTED_RAW_TYPES.has(rawType(value));
|
|
1307
1449
|
}
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1450
|
+
/**
|
|
1451
|
+
* Creates a reactive from the given object/callback if possible and returns it,
|
|
1452
|
+
* returns the original object otherwise.
|
|
1453
|
+
*
|
|
1454
|
+
* @param value the value make reactive
|
|
1455
|
+
* @returns a reactive for the given object when possible, the original otherwise
|
|
1456
|
+
*/
|
|
1457
|
+
function possiblyReactive(val, cb) {
|
|
1458
|
+
return canBeMadeReactive(val) ? reactive(val, cb) : val;
|
|
1459
|
+
}
|
|
1460
|
+
/**
|
|
1461
|
+
* Mark an object or array so that it is ignored by the reactivity system
|
|
1462
|
+
*
|
|
1463
|
+
* @param value the value to mark
|
|
1464
|
+
* @returns the object itself
|
|
1465
|
+
*/
|
|
1466
|
+
function markRaw(value) {
|
|
1467
|
+
value[SKIP] = true;
|
|
1468
|
+
return value;
|
|
1469
|
+
}
|
|
1470
|
+
/**
|
|
1471
|
+
* Given a reactive objet, return the raw (non reactive) underlying object
|
|
1472
|
+
*
|
|
1473
|
+
* @param value a reactive value
|
|
1474
|
+
* @returns the underlying value
|
|
1475
|
+
*/
|
|
1476
|
+
function toRaw(value) {
|
|
1477
|
+
return value[TARGET] || value;
|
|
1478
|
+
}
|
|
1479
|
+
const targetToKeysToCallbacks = new WeakMap();
|
|
1480
|
+
/**
|
|
1481
|
+
* Observes a given key on a target with an callback. The callback will be
|
|
1482
|
+
* called when the given key changes on the target.
|
|
1483
|
+
*
|
|
1484
|
+
* @param target the target whose key should be observed
|
|
1485
|
+
* @param key the key to observe (or Symbol(KEYCHANGES) for key creation
|
|
1486
|
+
* or deletion)
|
|
1487
|
+
* @param callback the function to call when the key changes
|
|
1488
|
+
*/
|
|
1489
|
+
function observeTargetKey(target, key, callback) {
|
|
1490
|
+
if (!targetToKeysToCallbacks.get(target)) {
|
|
1491
|
+
targetToKeysToCallbacks.set(target, new Map());
|
|
1314
1492
|
}
|
|
1315
|
-
|
|
1493
|
+
const keyToCallbacks = targetToKeysToCallbacks.get(target);
|
|
1494
|
+
if (!keyToCallbacks.get(key)) {
|
|
1495
|
+
keyToCallbacks.set(key, new Set());
|
|
1496
|
+
}
|
|
1497
|
+
keyToCallbacks.get(key).add(callback);
|
|
1498
|
+
if (!callbacksToTargets.has(callback)) {
|
|
1499
|
+
callbacksToTargets.set(callback, new Set());
|
|
1500
|
+
}
|
|
1501
|
+
callbacksToTargets.get(callback).add(target);
|
|
1316
1502
|
}
|
|
1317
1503
|
/**
|
|
1318
|
-
*
|
|
1319
|
-
*
|
|
1320
|
-
*
|
|
1321
|
-
*
|
|
1504
|
+
* Notify Reactives that are observing a given target that a key has changed on
|
|
1505
|
+
* the target.
|
|
1506
|
+
*
|
|
1507
|
+
* @param target target whose Reactives should be notified that the target was
|
|
1508
|
+
* changed.
|
|
1509
|
+
* @param key the key that changed (or Symbol `KEYCHANGES` if a key was created
|
|
1510
|
+
* or deleted)
|
|
1322
1511
|
*/
|
|
1323
|
-
function
|
|
1324
|
-
const
|
|
1325
|
-
|
|
1326
|
-
: parent.constructor.components[name];
|
|
1327
|
-
if (!ComponentClass) {
|
|
1328
|
-
// this is an error, wrong component. We silently return here instead so the
|
|
1329
|
-
// error is triggered by the usual path ('component' function)
|
|
1512
|
+
function notifyReactives(target, key) {
|
|
1513
|
+
const keyToCallbacks = targetToKeysToCallbacks.get(target);
|
|
1514
|
+
if (!keyToCallbacks) {
|
|
1330
1515
|
return;
|
|
1331
1516
|
}
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
const allowAdditionalProps = "*" in propsDef;
|
|
1336
|
-
for (let propName in propsDef) {
|
|
1337
|
-
if (propName === "*") {
|
|
1338
|
-
continue;
|
|
1339
|
-
}
|
|
1340
|
-
const propDef = propsDef[propName];
|
|
1341
|
-
let isMandatory = !!propDef;
|
|
1342
|
-
if (typeof propDef === "object" && "optional" in propDef) {
|
|
1343
|
-
isMandatory = !propDef.optional;
|
|
1344
|
-
}
|
|
1345
|
-
if (isMandatory && propName in defaultProps) {
|
|
1346
|
-
throw new Error(`A default value cannot be defined for a mandatory prop (name: '${propName}', component: ${ComponentClass.name})`);
|
|
1347
|
-
}
|
|
1348
|
-
if (props[propName] === undefined) {
|
|
1349
|
-
if (isMandatory) {
|
|
1350
|
-
throw new Error(`Missing props '${propName}' (component '${ComponentClass.name}')`);
|
|
1351
|
-
}
|
|
1352
|
-
else {
|
|
1353
|
-
continue;
|
|
1354
|
-
}
|
|
1355
|
-
}
|
|
1356
|
-
let isValid;
|
|
1357
|
-
try {
|
|
1358
|
-
isValid = isValidProp(props[propName], propDef);
|
|
1359
|
-
}
|
|
1360
|
-
catch (e) {
|
|
1361
|
-
e.message = `Invalid prop '${propName}' in component ${ComponentClass.name} (${e.message})`;
|
|
1362
|
-
throw e;
|
|
1363
|
-
}
|
|
1364
|
-
if (!isValid) {
|
|
1365
|
-
throw new Error(`Invalid Prop '${propName}' in component '${ComponentClass.name}'`);
|
|
1366
|
-
}
|
|
1517
|
+
const callbacks = keyToCallbacks.get(key);
|
|
1518
|
+
if (!callbacks) {
|
|
1519
|
+
return;
|
|
1367
1520
|
}
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
}
|
|
1373
|
-
}
|
|
1521
|
+
// Loop on copy because clearReactivesForCallback will modify the set in place
|
|
1522
|
+
for (const callback of [...callbacks]) {
|
|
1523
|
+
clearReactivesForCallback(callback);
|
|
1524
|
+
callback();
|
|
1374
1525
|
}
|
|
1375
1526
|
}
|
|
1527
|
+
const callbacksToTargets = new WeakMap();
|
|
1376
1528
|
/**
|
|
1377
|
-
*
|
|
1529
|
+
* Clears all subscriptions of the Reactives associated with a given callback.
|
|
1530
|
+
*
|
|
1531
|
+
* @param callback the callback for which the reactives need to be cleared
|
|
1378
1532
|
*/
|
|
1379
|
-
function
|
|
1380
|
-
|
|
1381
|
-
|
|
1533
|
+
function clearReactivesForCallback(callback) {
|
|
1534
|
+
const targetsToClear = callbacksToTargets.get(callback);
|
|
1535
|
+
if (!targetsToClear) {
|
|
1536
|
+
return;
|
|
1382
1537
|
}
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
// So, even though 1 is not an instance of Number, we want to consider that
|
|
1388
|
-
// it is valid.
|
|
1389
|
-
if (typeof prop === "object") {
|
|
1390
|
-
return prop instanceof propDef;
|
|
1538
|
+
for (const target of targetsToClear) {
|
|
1539
|
+
const observedKeys = targetToKeysToCallbacks.get(target);
|
|
1540
|
+
if (!observedKeys) {
|
|
1541
|
+
continue;
|
|
1391
1542
|
}
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
else if (propDef instanceof Array) {
|
|
1395
|
-
// If this code is executed, this means that we want to check if a prop
|
|
1396
|
-
// matches at least one of its descriptor.
|
|
1397
|
-
let result = false;
|
|
1398
|
-
for (let i = 0, iLen = propDef.length; i < iLen; i++) {
|
|
1399
|
-
result = result || isValidProp(prop, propDef[i]);
|
|
1543
|
+
for (const callbacks of observedKeys.values()) {
|
|
1544
|
+
callbacks.delete(callback);
|
|
1400
1545
|
}
|
|
1401
|
-
return result;
|
|
1402
1546
|
}
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1547
|
+
targetsToClear.clear();
|
|
1548
|
+
}
|
|
1549
|
+
function getSubscriptions(callback) {
|
|
1550
|
+
const targets = callbacksToTargets.get(callback) || [];
|
|
1551
|
+
return [...targets].map((target) => {
|
|
1552
|
+
const keysToCallbacks = targetToKeysToCallbacks.get(target);
|
|
1553
|
+
return {
|
|
1554
|
+
target,
|
|
1555
|
+
keys: keysToCallbacks ? [...keysToCallbacks.keys()] : [],
|
|
1556
|
+
};
|
|
1557
|
+
});
|
|
1558
|
+
}
|
|
1559
|
+
const reactiveCache = new WeakMap();
|
|
1560
|
+
/**
|
|
1561
|
+
* Creates a reactive proxy for an object. Reading data on the reactive object
|
|
1562
|
+
* subscribes to changes to the data. Writing data on the object will cause the
|
|
1563
|
+
* notify callback to be called if there are suscriptions to that data. Nested
|
|
1564
|
+
* objects and arrays are automatically made reactive as well.
|
|
1565
|
+
*
|
|
1566
|
+
* Whenever you are notified of a change, all subscriptions are cleared, and if
|
|
1567
|
+
* you would like to be notified of any further changes, you should go read
|
|
1568
|
+
* the underlying data again. We assume that if you don't go read it again after
|
|
1569
|
+
* being notified, it means that you are no longer interested in that data.
|
|
1570
|
+
*
|
|
1571
|
+
* Subscriptions:
|
|
1572
|
+
* + Reading a property on an object will subscribe you to changes in the value
|
|
1573
|
+
* of that property.
|
|
1574
|
+
* + Accessing an object keys (eg with Object.keys or with `for..in`) will
|
|
1575
|
+
* subscribe you to the creation/deletion of keys. Checking the presence of a
|
|
1576
|
+
* key on the object with 'in' has the same effect.
|
|
1577
|
+
* - getOwnPropertyDescriptor does not currently subscribe you to the property.
|
|
1578
|
+
* This is a choice that was made because changing a key's value will trigger
|
|
1579
|
+
* this trap and we do not want to subscribe by writes. This also means that
|
|
1580
|
+
* Object.hasOwnProperty doesn't subscribe as it goes through this trap.
|
|
1581
|
+
*
|
|
1582
|
+
* @param target the object for which to create a reactive proxy
|
|
1583
|
+
* @param callback the function to call when an observed property of the
|
|
1584
|
+
* reactive has changed
|
|
1585
|
+
* @returns a proxy that tracks changes to it
|
|
1586
|
+
*/
|
|
1587
|
+
function reactive(target, callback = () => { }) {
|
|
1588
|
+
if (!canBeMadeReactive(target)) {
|
|
1589
|
+
throw new Error(`Cannot make the given value reactive`);
|
|
1406
1590
|
}
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
result = result && propDef.validate(prop);
|
|
1591
|
+
if (SKIP in target) {
|
|
1592
|
+
return target;
|
|
1410
1593
|
}
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
}
|
|
1594
|
+
const originalTarget = target[TARGET];
|
|
1595
|
+
if (originalTarget) {
|
|
1596
|
+
return reactive(originalTarget, callback);
|
|
1415
1597
|
}
|
|
1416
|
-
if (
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1598
|
+
if (!reactiveCache.has(target)) {
|
|
1599
|
+
reactiveCache.set(target, new Map());
|
|
1600
|
+
}
|
|
1601
|
+
const reactivesForTarget = reactiveCache.get(target);
|
|
1602
|
+
if (!reactivesForTarget.has(callback)) {
|
|
1603
|
+
const targetRawType = rawType(target);
|
|
1604
|
+
const handler = COLLECTION_RAWTYPES.has(targetRawType)
|
|
1605
|
+
? collectionsProxyHandler(target, callback, targetRawType)
|
|
1606
|
+
: basicProxyHandler(callback);
|
|
1607
|
+
const proxy = new Proxy(target, handler);
|
|
1608
|
+
reactivesForTarget.set(callback, proxy);
|
|
1609
|
+
}
|
|
1610
|
+
return reactivesForTarget.get(callback);
|
|
1611
|
+
}
|
|
1612
|
+
/**
|
|
1613
|
+
* Creates a basic proxy handler for regular objects and arrays.
|
|
1614
|
+
*
|
|
1615
|
+
* @param callback @see reactive
|
|
1616
|
+
* @returns a proxy handler object
|
|
1617
|
+
*/
|
|
1618
|
+
function basicProxyHandler(callback) {
|
|
1619
|
+
return {
|
|
1620
|
+
get(target, key, proxy) {
|
|
1621
|
+
if (key === TARGET) {
|
|
1622
|
+
return target;
|
|
1623
|
+
}
|
|
1624
|
+
// non-writable non-configurable properties cannot be made reactive
|
|
1625
|
+
const desc = Object.getOwnPropertyDescriptor(target, key);
|
|
1626
|
+
if (desc && !desc.writable && !desc.configurable) {
|
|
1627
|
+
return Reflect.get(target, key, proxy);
|
|
1628
|
+
}
|
|
1629
|
+
observeTargetKey(target, key, callback);
|
|
1630
|
+
return possiblyReactive(Reflect.get(target, key, proxy), callback);
|
|
1631
|
+
},
|
|
1632
|
+
set(target, key, value, proxy) {
|
|
1633
|
+
const isNewKey = !objectHasOwnProperty.call(target, key);
|
|
1634
|
+
const originalValue = Reflect.get(target, key, proxy);
|
|
1635
|
+
const ret = Reflect.set(target, key, value, proxy);
|
|
1636
|
+
if (isNewKey) {
|
|
1637
|
+
notifyReactives(target, KEYCHANGES);
|
|
1638
|
+
}
|
|
1639
|
+
// While Array length may trigger the set trap, it's not actually set by this
|
|
1640
|
+
// method but is updated behind the scenes, and the trap is not called with the
|
|
1641
|
+
// new value. We disable the "same-value-optimization" for it because of that.
|
|
1642
|
+
if (originalValue !== value || (Array.isArray(target) && key === "length")) {
|
|
1643
|
+
notifyReactives(target, key);
|
|
1426
1644
|
}
|
|
1645
|
+
return ret;
|
|
1646
|
+
},
|
|
1647
|
+
deleteProperty(target, key) {
|
|
1648
|
+
const ret = Reflect.deleteProperty(target, key);
|
|
1649
|
+
// TODO: only notify when something was actually deleted
|
|
1650
|
+
notifyReactives(target, KEYCHANGES);
|
|
1651
|
+
notifyReactives(target, key);
|
|
1652
|
+
return ret;
|
|
1653
|
+
},
|
|
1654
|
+
ownKeys(target) {
|
|
1655
|
+
observeTargetKey(target, KEYCHANGES, callback);
|
|
1656
|
+
return Reflect.ownKeys(target);
|
|
1657
|
+
},
|
|
1658
|
+
has(target, key) {
|
|
1659
|
+
// TODO: this observes all key changes instead of only the presence of the argument key
|
|
1660
|
+
// observing the key itself would observe value changes instead of presence changes
|
|
1661
|
+
// so we may need a finer grained system to distinguish observing value vs presence.
|
|
1662
|
+
observeTargetKey(target, KEYCHANGES, callback);
|
|
1663
|
+
return Reflect.has(target, key);
|
|
1664
|
+
},
|
|
1665
|
+
};
|
|
1666
|
+
}
|
|
1667
|
+
/**
|
|
1668
|
+
* Creates a function that will observe the key that is passed to it when called
|
|
1669
|
+
* and delegates to the underlying method.
|
|
1670
|
+
*
|
|
1671
|
+
* @param methodName name of the method to delegate to
|
|
1672
|
+
* @param target @see reactive
|
|
1673
|
+
* @param callback @see reactive
|
|
1674
|
+
*/
|
|
1675
|
+
function makeKeyObserver(methodName, target, callback) {
|
|
1676
|
+
return (key) => {
|
|
1677
|
+
key = toRaw(key);
|
|
1678
|
+
observeTargetKey(target, key, callback);
|
|
1679
|
+
return possiblyReactive(target[methodName](key), callback);
|
|
1680
|
+
};
|
|
1681
|
+
}
|
|
1682
|
+
/**
|
|
1683
|
+
* Creates an iterable that will delegate to the underlying iteration method and
|
|
1684
|
+
* observe keys as necessary.
|
|
1685
|
+
*
|
|
1686
|
+
* @param methodName name of the method to delegate to
|
|
1687
|
+
* @param target @see reactive
|
|
1688
|
+
* @param callback @see reactive
|
|
1689
|
+
*/
|
|
1690
|
+
function makeIteratorObserver(methodName, target, callback) {
|
|
1691
|
+
return function* () {
|
|
1692
|
+
observeTargetKey(target, KEYCHANGES, callback);
|
|
1693
|
+
const keys = target.keys();
|
|
1694
|
+
for (const item of target[methodName]()) {
|
|
1695
|
+
const key = keys.next().value;
|
|
1696
|
+
observeTargetKey(target, key, callback);
|
|
1697
|
+
yield possiblyReactive(item, callback);
|
|
1427
1698
|
}
|
|
1428
|
-
}
|
|
1429
|
-
|
|
1699
|
+
};
|
|
1700
|
+
}
|
|
1701
|
+
/**
|
|
1702
|
+
* Creates a forEach function that will delegate to forEach on the underlying
|
|
1703
|
+
* collection while observing key changes, and keys as they're iterated over,
|
|
1704
|
+
* and making the passed keys/values reactive.
|
|
1705
|
+
*
|
|
1706
|
+
* @param target @see reactive
|
|
1707
|
+
* @param callback @see reactive
|
|
1708
|
+
*/
|
|
1709
|
+
function makeForEachObserver(target, callback) {
|
|
1710
|
+
return function forEach(forEachCb, thisArg) {
|
|
1711
|
+
observeTargetKey(target, KEYCHANGES, callback);
|
|
1712
|
+
target.forEach(function (val, key, targetObj) {
|
|
1713
|
+
observeTargetKey(target, key, callback);
|
|
1714
|
+
forEachCb.call(thisArg, possiblyReactive(val, callback), possiblyReactive(key, callback), possiblyReactive(targetObj, callback));
|
|
1715
|
+
}, thisArg);
|
|
1716
|
+
};
|
|
1717
|
+
}
|
|
1718
|
+
/**
|
|
1719
|
+
* Creates a function that will delegate to an underlying method, and check if
|
|
1720
|
+
* that method has modified the presence or value of a key, and notify the
|
|
1721
|
+
* reactives appropriately.
|
|
1722
|
+
*
|
|
1723
|
+
* @param setterName name of the method to delegate to
|
|
1724
|
+
* @param getterName name of the method which should be used to retrieve the
|
|
1725
|
+
* value before calling the delegate method for comparison purposes
|
|
1726
|
+
* @param target @see reactive
|
|
1727
|
+
*/
|
|
1728
|
+
function delegateAndNotify(setterName, getterName, target) {
|
|
1729
|
+
return (key, value) => {
|
|
1730
|
+
key = toRaw(key);
|
|
1731
|
+
const hadKey = target.has(key);
|
|
1732
|
+
const originalValue = target[getterName](key);
|
|
1733
|
+
const ret = target[setterName](key, value);
|
|
1734
|
+
const hasKey = target.has(key);
|
|
1735
|
+
if (hadKey !== hasKey) {
|
|
1736
|
+
notifyReactives(target, KEYCHANGES);
|
|
1737
|
+
}
|
|
1738
|
+
if (originalValue !== value) {
|
|
1739
|
+
notifyReactives(target, key);
|
|
1740
|
+
}
|
|
1741
|
+
return ret;
|
|
1742
|
+
};
|
|
1743
|
+
}
|
|
1744
|
+
/**
|
|
1745
|
+
* Creates a function that will clear the underlying collection and notify that
|
|
1746
|
+
* the keys of the collection have changed.
|
|
1747
|
+
*
|
|
1748
|
+
* @param target @see reactive
|
|
1749
|
+
*/
|
|
1750
|
+
function makeClearNotifier(target) {
|
|
1751
|
+
return () => {
|
|
1752
|
+
const allKeys = [...target.keys()];
|
|
1753
|
+
target.clear();
|
|
1754
|
+
notifyReactives(target, KEYCHANGES);
|
|
1755
|
+
for (const key of allKeys) {
|
|
1756
|
+
notifyReactives(target, key);
|
|
1757
|
+
}
|
|
1758
|
+
};
|
|
1759
|
+
}
|
|
1760
|
+
/**
|
|
1761
|
+
* Maps raw type of an object to an object containing functions that can be used
|
|
1762
|
+
* to build an appropritate proxy handler for that raw type. Eg: when making a
|
|
1763
|
+
* reactive set, calling the has method should mark the key that is being
|
|
1764
|
+
* retrieved as observed, and calling the add or delete method should notify the
|
|
1765
|
+
* reactives that the key which is being added or deleted has been modified.
|
|
1766
|
+
*/
|
|
1767
|
+
const rawTypeToFuncHandlers = {
|
|
1768
|
+
Set: (target, callback) => ({
|
|
1769
|
+
has: makeKeyObserver("has", target, callback),
|
|
1770
|
+
add: delegateAndNotify("add", "has", target),
|
|
1771
|
+
delete: delegateAndNotify("delete", "has", target),
|
|
1772
|
+
keys: makeIteratorObserver("keys", target, callback),
|
|
1773
|
+
values: makeIteratorObserver("values", target, callback),
|
|
1774
|
+
entries: makeIteratorObserver("entries", target, callback),
|
|
1775
|
+
[Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
|
|
1776
|
+
forEach: makeForEachObserver(target, callback),
|
|
1777
|
+
clear: makeClearNotifier(target),
|
|
1778
|
+
get size() {
|
|
1779
|
+
observeTargetKey(target, KEYCHANGES, callback);
|
|
1780
|
+
return target.size;
|
|
1781
|
+
},
|
|
1782
|
+
}),
|
|
1783
|
+
Map: (target, callback) => ({
|
|
1784
|
+
has: makeKeyObserver("has", target, callback),
|
|
1785
|
+
get: makeKeyObserver("get", target, callback),
|
|
1786
|
+
set: delegateAndNotify("set", "get", target),
|
|
1787
|
+
delete: delegateAndNotify("delete", "has", target),
|
|
1788
|
+
keys: makeIteratorObserver("keys", target, callback),
|
|
1789
|
+
values: makeIteratorObserver("values", target, callback),
|
|
1790
|
+
entries: makeIteratorObserver("entries", target, callback),
|
|
1791
|
+
[Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
|
|
1792
|
+
forEach: makeForEachObserver(target, callback),
|
|
1793
|
+
clear: makeClearNotifier(target),
|
|
1794
|
+
get size() {
|
|
1795
|
+
observeTargetKey(target, KEYCHANGES, callback);
|
|
1796
|
+
return target.size;
|
|
1797
|
+
},
|
|
1798
|
+
}),
|
|
1799
|
+
WeakMap: (target, callback) => ({
|
|
1800
|
+
has: makeKeyObserver("has", target, callback),
|
|
1801
|
+
get: makeKeyObserver("get", target, callback),
|
|
1802
|
+
set: delegateAndNotify("set", "get", target),
|
|
1803
|
+
delete: delegateAndNotify("delete", "has", target),
|
|
1804
|
+
}),
|
|
1805
|
+
};
|
|
1806
|
+
/**
|
|
1807
|
+
* Creates a proxy handler for collections (Set/Map/WeakMap)
|
|
1808
|
+
*
|
|
1809
|
+
* @param callback @see reactive
|
|
1810
|
+
* @param target @see reactive
|
|
1811
|
+
* @returns a proxy handler object
|
|
1812
|
+
*/
|
|
1813
|
+
function collectionsProxyHandler(target, callback, targetRawType) {
|
|
1814
|
+
// TODO: if performance is an issue we can create the special handlers lazily when each
|
|
1815
|
+
// property is read.
|
|
1816
|
+
const specialHandlers = rawTypeToFuncHandlers[targetRawType](target, callback);
|
|
1817
|
+
return Object.assign(basicProxyHandler(callback), {
|
|
1818
|
+
get(target, key) {
|
|
1819
|
+
if (key === TARGET) {
|
|
1820
|
+
return target;
|
|
1821
|
+
}
|
|
1822
|
+
if (objectHasOwnProperty.call(specialHandlers, key)) {
|
|
1823
|
+
return specialHandlers[key];
|
|
1824
|
+
}
|
|
1825
|
+
observeTargetKey(target, key, callback);
|
|
1826
|
+
return possiblyReactive(target[key], callback);
|
|
1827
|
+
},
|
|
1828
|
+
});
|
|
1430
1829
|
}
|
|
1431
1830
|
|
|
1432
1831
|
/**
|
|
@@ -1444,11 +1843,13 @@ function batched(callback) {
|
|
|
1444
1843
|
await Promise.resolve();
|
|
1445
1844
|
if (!called) {
|
|
1446
1845
|
called = true;
|
|
1447
|
-
callback();
|
|
1448
1846
|
// wait for all calls in this microtick to fall through before resetting "called"
|
|
1449
|
-
// so that only the first call to the batched function calls the original callback
|
|
1450
|
-
|
|
1451
|
-
called
|
|
1847
|
+
// so that only the first call to the batched function calls the original callback.
|
|
1848
|
+
// Schedule this before calling the callback so that calls to the batched function
|
|
1849
|
+
// within the callback will proceed only after resetting called to false, and have
|
|
1850
|
+
// a chance to execute the callback again
|
|
1851
|
+
Promise.resolve().then(() => (called = false));
|
|
1852
|
+
callback();
|
|
1452
1853
|
}
|
|
1453
1854
|
};
|
|
1454
1855
|
}
|
|
@@ -1495,223 +1896,18 @@ class Markup extends String {
|
|
|
1495
1896
|
*/
|
|
1496
1897
|
function markup(value) {
|
|
1497
1898
|
return new Markup(value);
|
|
1498
|
-
}
|
|
1499
|
-
|
|
1500
|
-
/**
|
|
1501
|
-
* This file contains utility functions that will be injected in each template,
|
|
1502
|
-
* to perform various useful tasks in the compiled code.
|
|
1503
|
-
*/
|
|
1504
|
-
function withDefault(value, defaultValue) {
|
|
1505
|
-
return value === undefined || value === null || value === false ? defaultValue : value;
|
|
1506
|
-
}
|
|
1507
|
-
function callSlot(ctx, parent, key, name, dynamic, extra, defaultContent) {
|
|
1508
|
-
key = key + "__slot_" + name;
|
|
1509
|
-
const slots = (ctx.props && ctx.props.slots) || {};
|
|
1510
|
-
const { __render, __ctx, __scope } = slots[name] || {};
|
|
1511
|
-
const slotScope = Object.create(__ctx || {});
|
|
1512
|
-
if (__scope) {
|
|
1513
|
-
slotScope[__scope] = extra || {};
|
|
1514
|
-
}
|
|
1515
|
-
const slotBDom = __render ? __render.call(__ctx.__owl__.component, slotScope, parent, key) : null;
|
|
1516
|
-
if (defaultContent) {
|
|
1517
|
-
let child1 = undefined;
|
|
1518
|
-
let child2 = undefined;
|
|
1519
|
-
if (slotBDom) {
|
|
1520
|
-
child1 = dynamic ? toggler(name, slotBDom) : slotBDom;
|
|
1521
|
-
}
|
|
1522
|
-
else {
|
|
1523
|
-
child2 = defaultContent.call(ctx.__owl__.component, ctx, parent, key);
|
|
1524
|
-
}
|
|
1525
|
-
return multi([child1, child2]);
|
|
1526
|
-
}
|
|
1527
|
-
return slotBDom || text("");
|
|
1528
|
-
}
|
|
1529
|
-
function capture(ctx) {
|
|
1530
|
-
const component = ctx.__owl__.component;
|
|
1531
|
-
const result = Object.create(component);
|
|
1532
|
-
for (let k in ctx) {
|
|
1533
|
-
result[k] = ctx[k];
|
|
1534
|
-
}
|
|
1535
|
-
return result;
|
|
1536
|
-
}
|
|
1537
|
-
function withKey(elem, k) {
|
|
1538
|
-
elem.key = k;
|
|
1539
|
-
return elem;
|
|
1540
|
-
}
|
|
1541
|
-
function prepareList(collection) {
|
|
1542
|
-
let keys;
|
|
1543
|
-
let values;
|
|
1544
|
-
if (Array.isArray(collection)) {
|
|
1545
|
-
keys = collection;
|
|
1546
|
-
values = collection;
|
|
1547
|
-
}
|
|
1548
|
-
else if (collection) {
|
|
1549
|
-
values = Object.keys(collection);
|
|
1550
|
-
keys = Object.values(collection);
|
|
1551
|
-
}
|
|
1552
|
-
else {
|
|
1553
|
-
throw new Error("Invalid loop expression");
|
|
1554
|
-
}
|
|
1555
|
-
const n = values.length;
|
|
1556
|
-
return [keys, values, n, new Array(n)];
|
|
1557
1899
|
}
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
ctx = newCtx;
|
|
1568
|
-
}
|
|
1569
|
-
ctx[key] = value;
|
|
1570
|
-
}
|
|
1571
|
-
function toNumber(val) {
|
|
1572
|
-
const n = parseFloat(val);
|
|
1573
|
-
return isNaN(n) ? val : n;
|
|
1574
|
-
}
|
|
1575
|
-
function shallowEqual$1(l1, l2) {
|
|
1576
|
-
for (let i = 0, l = l1.length; i < l; i++) {
|
|
1577
|
-
if (l1[i] !== l2[i]) {
|
|
1578
|
-
return false;
|
|
1579
|
-
}
|
|
1580
|
-
}
|
|
1581
|
-
return true;
|
|
1582
|
-
}
|
|
1583
|
-
class LazyValue {
|
|
1584
|
-
constructor(fn, ctx, node) {
|
|
1585
|
-
this.fn = fn;
|
|
1586
|
-
this.ctx = capture(ctx);
|
|
1587
|
-
this.node = node;
|
|
1588
|
-
}
|
|
1589
|
-
evaluate() {
|
|
1590
|
-
return this.fn(this.ctx, this.node);
|
|
1591
|
-
}
|
|
1592
|
-
toString() {
|
|
1593
|
-
return this.evaluate().toString();
|
|
1594
|
-
}
|
|
1595
|
-
}
|
|
1596
|
-
/*
|
|
1597
|
-
* Safely outputs `value` as a block depending on the nature of `value`
|
|
1598
|
-
*/
|
|
1599
|
-
function safeOutput(value) {
|
|
1600
|
-
if (!value) {
|
|
1601
|
-
return value;
|
|
1602
|
-
}
|
|
1603
|
-
let safeKey;
|
|
1604
|
-
let block;
|
|
1605
|
-
if (value instanceof Markup) {
|
|
1606
|
-
safeKey = `string_safe`;
|
|
1607
|
-
block = html(value);
|
|
1608
|
-
}
|
|
1609
|
-
else if (value instanceof LazyValue) {
|
|
1610
|
-
safeKey = `lazy_value`;
|
|
1611
|
-
block = value.evaluate();
|
|
1612
|
-
}
|
|
1613
|
-
else if (typeof value === "string") {
|
|
1614
|
-
safeKey = "string_unsafe";
|
|
1615
|
-
block = text(value);
|
|
1616
|
-
}
|
|
1617
|
-
else {
|
|
1618
|
-
// Assuming it is a block
|
|
1619
|
-
safeKey = "block_safe";
|
|
1620
|
-
block = value;
|
|
1621
|
-
}
|
|
1622
|
-
return toggler(safeKey, block);
|
|
1623
|
-
}
|
|
1624
|
-
let boundFunctions = new WeakMap();
|
|
1625
|
-
function bind(ctx, fn) {
|
|
1626
|
-
let component = ctx.__owl__.component;
|
|
1627
|
-
let boundFnMap = boundFunctions.get(component);
|
|
1628
|
-
if (!boundFnMap) {
|
|
1629
|
-
boundFnMap = new WeakMap();
|
|
1630
|
-
boundFunctions.set(component, boundFnMap);
|
|
1631
|
-
}
|
|
1632
|
-
let boundFn = boundFnMap.get(fn);
|
|
1633
|
-
if (!boundFn) {
|
|
1634
|
-
boundFn = fn.bind(component);
|
|
1635
|
-
boundFnMap.set(fn, boundFn);
|
|
1636
|
-
}
|
|
1637
|
-
return boundFn;
|
|
1638
|
-
}
|
|
1639
|
-
function multiRefSetter(refs, name) {
|
|
1640
|
-
let count = 0;
|
|
1641
|
-
return (el) => {
|
|
1642
|
-
if (el) {
|
|
1643
|
-
count++;
|
|
1644
|
-
if (count > 1) {
|
|
1645
|
-
throw new Error("Cannot have 2 elements with same ref name at the same time");
|
|
1646
|
-
}
|
|
1647
|
-
}
|
|
1648
|
-
if (count === 0 || el) {
|
|
1649
|
-
refs[name] = el;
|
|
1650
|
-
}
|
|
1651
|
-
};
|
|
1900
|
+
// -----------------------------------------------------------------------------
|
|
1901
|
+
// xml tag helper
|
|
1902
|
+
// -----------------------------------------------------------------------------
|
|
1903
|
+
const globalTemplates = {};
|
|
1904
|
+
function xml(...args) {
|
|
1905
|
+
const name = `__template__${xml.nextId++}`;
|
|
1906
|
+
const value = String.raw(...args);
|
|
1907
|
+
globalTemplates[name] = value;
|
|
1908
|
+
return name;
|
|
1652
1909
|
}
|
|
1653
|
-
|
|
1654
|
-
withDefault,
|
|
1655
|
-
zero: Symbol("zero"),
|
|
1656
|
-
isBoundary,
|
|
1657
|
-
callSlot,
|
|
1658
|
-
capture,
|
|
1659
|
-
withKey,
|
|
1660
|
-
prepareList,
|
|
1661
|
-
setContextValue,
|
|
1662
|
-
multiRefSetter,
|
|
1663
|
-
shallowEqual: shallowEqual$1,
|
|
1664
|
-
toNumber,
|
|
1665
|
-
validateProps,
|
|
1666
|
-
LazyValue,
|
|
1667
|
-
safeOutput,
|
|
1668
|
-
bind,
|
|
1669
|
-
};
|
|
1670
|
-
|
|
1671
|
-
const mainEventHandler = (data, ev, currentTarget) => {
|
|
1672
|
-
const { data: _data, modifiers } = filterOutModifiersFromData(data);
|
|
1673
|
-
data = _data;
|
|
1674
|
-
let stopped = false;
|
|
1675
|
-
if (modifiers.length) {
|
|
1676
|
-
let selfMode = false;
|
|
1677
|
-
const isSelf = ev.target === currentTarget;
|
|
1678
|
-
for (const mod of modifiers) {
|
|
1679
|
-
switch (mod) {
|
|
1680
|
-
case "self":
|
|
1681
|
-
selfMode = true;
|
|
1682
|
-
if (isSelf) {
|
|
1683
|
-
continue;
|
|
1684
|
-
}
|
|
1685
|
-
else {
|
|
1686
|
-
return stopped;
|
|
1687
|
-
}
|
|
1688
|
-
case "prevent":
|
|
1689
|
-
if ((selfMode && isSelf) || !selfMode)
|
|
1690
|
-
ev.preventDefault();
|
|
1691
|
-
continue;
|
|
1692
|
-
case "stop":
|
|
1693
|
-
if ((selfMode && isSelf) || !selfMode)
|
|
1694
|
-
ev.stopPropagation();
|
|
1695
|
-
stopped = true;
|
|
1696
|
-
continue;
|
|
1697
|
-
}
|
|
1698
|
-
}
|
|
1699
|
-
}
|
|
1700
|
-
// If handler is empty, the array slot 0 will also be empty, and data will not have the property 0
|
|
1701
|
-
// We check this rather than data[0] being truthy (or typeof function) so that it crashes
|
|
1702
|
-
// as expected when there is a handler expression that evaluates to a falsy value
|
|
1703
|
-
if (Object.hasOwnProperty.call(data, 0)) {
|
|
1704
|
-
const handler = data[0];
|
|
1705
|
-
if (typeof handler !== "function") {
|
|
1706
|
-
throw new Error(`Invalid handler (expected a function, received: '${handler}')`);
|
|
1707
|
-
}
|
|
1708
|
-
let node = data[1] ? data[1].__owl__ : null;
|
|
1709
|
-
if (node ? node.status === 1 /* MOUNTED */ : true) {
|
|
1710
|
-
handler.call(node ? node.component : null, ev);
|
|
1711
|
-
}
|
|
1712
|
-
}
|
|
1713
|
-
return stopped;
|
|
1714
|
-
};
|
|
1910
|
+
xml.nextId = 1;
|
|
1715
1911
|
|
|
1716
1912
|
// Maps fibers to thrown errors
|
|
1717
1913
|
const fibersInError = new WeakMap();
|
|
@@ -1740,7 +1936,8 @@ function _handleError(node, error, isFirstRound = false) {
|
|
|
1740
1936
|
}
|
|
1741
1937
|
if (stopped) {
|
|
1742
1938
|
if (isFirstRound && fiber && fiber.node.fiber) {
|
|
1743
|
-
fiber.root
|
|
1939
|
+
const root = fiber.root;
|
|
1940
|
+
root.setCounter(root.counter - 1);
|
|
1744
1941
|
}
|
|
1745
1942
|
return true;
|
|
1746
1943
|
}
|
|
@@ -1774,9 +1971,12 @@ function handleError(params) {
|
|
|
1774
1971
|
function makeChildFiber(node, parent) {
|
|
1775
1972
|
let current = node.fiber;
|
|
1776
1973
|
if (current) {
|
|
1777
|
-
|
|
1778
|
-
cancelFibers(root, current.children);
|
|
1974
|
+
cancelFibers(current.children);
|
|
1779
1975
|
current.root = null;
|
|
1976
|
+
if (current instanceof RootFiber && current.delayedRenders.length) {
|
|
1977
|
+
let root = parent.root;
|
|
1978
|
+
root.delayedRenders = root.delayedRenders.concat(current.delayedRenders);
|
|
1979
|
+
}
|
|
1780
1980
|
}
|
|
1781
1981
|
return new Fiber(node, parent);
|
|
1782
1982
|
}
|
|
@@ -1784,10 +1984,12 @@ function makeRootFiber(node) {
|
|
|
1784
1984
|
let current = node.fiber;
|
|
1785
1985
|
if (current) {
|
|
1786
1986
|
let root = current.root;
|
|
1787
|
-
root.counter
|
|
1987
|
+
root.setCounter(root.counter + 1 - cancelFibers(current.children));
|
|
1788
1988
|
current.children = [];
|
|
1789
|
-
root.counter++;
|
|
1790
1989
|
current.bdom = null;
|
|
1990
|
+
if (current === root) {
|
|
1991
|
+
root.reachedChildren = new WeakSet();
|
|
1992
|
+
}
|
|
1791
1993
|
if (fibersInError.has(current)) {
|
|
1792
1994
|
fibersInError.delete(current);
|
|
1793
1995
|
fibersInError.delete(root);
|
|
@@ -1807,15 +2009,23 @@ function makeRootFiber(node) {
|
|
|
1807
2009
|
/**
|
|
1808
2010
|
* @returns number of not-yet rendered fibers cancelled
|
|
1809
2011
|
*/
|
|
1810
|
-
function cancelFibers(
|
|
2012
|
+
function cancelFibers(fibers) {
|
|
1811
2013
|
let result = 0;
|
|
1812
2014
|
for (let fiber of fibers) {
|
|
1813
2015
|
fiber.node.fiber = null;
|
|
1814
|
-
fiber.
|
|
1815
|
-
|
|
2016
|
+
if (fiber.bdom) {
|
|
2017
|
+
// if fiber has been rendered, this means that the component props have
|
|
2018
|
+
// been updated. however, this fiber will not be patched to the dom, so
|
|
2019
|
+
// it could happen that the next render compare the current props with
|
|
2020
|
+
// the same props, and skip the render completely. With the next line,
|
|
2021
|
+
// we kindly request the component code to force a render, so it works as
|
|
2022
|
+
// expected.
|
|
2023
|
+
fiber.node.forceNextRender = true;
|
|
2024
|
+
}
|
|
2025
|
+
else {
|
|
1816
2026
|
result++;
|
|
1817
2027
|
}
|
|
1818
|
-
result += cancelFibers(
|
|
2028
|
+
result += cancelFibers(fiber.children);
|
|
1819
2029
|
}
|
|
1820
2030
|
return result;
|
|
1821
2031
|
}
|
|
@@ -1824,11 +2034,13 @@ class Fiber {
|
|
|
1824
2034
|
this.bdom = null;
|
|
1825
2035
|
this.children = [];
|
|
1826
2036
|
this.appliedToDom = false;
|
|
2037
|
+
this.deep = false;
|
|
1827
2038
|
this.node = node;
|
|
1828
2039
|
this.parent = parent;
|
|
1829
2040
|
if (parent) {
|
|
2041
|
+
this.deep = parent.deep;
|
|
1830
2042
|
const root = parent.root;
|
|
1831
|
-
root.counter
|
|
2043
|
+
root.setCounter(root.counter + 1);
|
|
1832
2044
|
this.root = root;
|
|
1833
2045
|
parent.children.push(this);
|
|
1834
2046
|
}
|
|
@@ -1836,6 +2048,45 @@ class Fiber {
|
|
|
1836
2048
|
this.root = this;
|
|
1837
2049
|
}
|
|
1838
2050
|
}
|
|
2051
|
+
render() {
|
|
2052
|
+
// if some parent has a fiber => register in followup
|
|
2053
|
+
let prev = this.root.node;
|
|
2054
|
+
let current = prev.parent;
|
|
2055
|
+
while (current) {
|
|
2056
|
+
if (current.fiber) {
|
|
2057
|
+
let root = current.fiber.root;
|
|
2058
|
+
if (root.counter) {
|
|
2059
|
+
root.delayedRenders.push(this);
|
|
2060
|
+
return;
|
|
2061
|
+
}
|
|
2062
|
+
else {
|
|
2063
|
+
if (!root.reachedChildren.has(prev)) {
|
|
2064
|
+
// is dead
|
|
2065
|
+
this.node.app.scheduler.shouldClear = true;
|
|
2066
|
+
return;
|
|
2067
|
+
}
|
|
2068
|
+
current = root.node;
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
prev = current;
|
|
2072
|
+
current = current.parent;
|
|
2073
|
+
}
|
|
2074
|
+
// there are no current rendering from above => we can render
|
|
2075
|
+
this._render();
|
|
2076
|
+
}
|
|
2077
|
+
_render() {
|
|
2078
|
+
const node = this.node;
|
|
2079
|
+
const root = this.root;
|
|
2080
|
+
if (root) {
|
|
2081
|
+
try {
|
|
2082
|
+
this.bdom = node.renderFn();
|
|
2083
|
+
root.setCounter(root.counter - 1);
|
|
2084
|
+
}
|
|
2085
|
+
catch (e) {
|
|
2086
|
+
handleError({ node, error: e });
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
1839
2090
|
}
|
|
1840
2091
|
class RootFiber extends Fiber {
|
|
1841
2092
|
constructor() {
|
|
@@ -1848,6 +2099,8 @@ class RootFiber extends Fiber {
|
|
|
1848
2099
|
// A fiber is typically locked when it is completing and the patch has not, or is being applied.
|
|
1849
2100
|
// i.e.: render triggered in onWillUnmount or in willPatch will be delayed
|
|
1850
2101
|
this.locked = false;
|
|
2102
|
+
this.delayedRenders = [];
|
|
2103
|
+
this.reachedChildren = new WeakSet();
|
|
1851
2104
|
}
|
|
1852
2105
|
complete() {
|
|
1853
2106
|
const node = this.node;
|
|
@@ -1869,7 +2122,7 @@ class RootFiber extends Fiber {
|
|
|
1869
2122
|
}
|
|
1870
2123
|
current = undefined;
|
|
1871
2124
|
// Step 2: patching the dom
|
|
1872
|
-
node.
|
|
2125
|
+
node._patch();
|
|
1873
2126
|
this.locked = false;
|
|
1874
2127
|
// Step 4: calling all mounted lifecycle hooks
|
|
1875
2128
|
let mountedFibers = this.mounted;
|
|
@@ -1897,6 +2150,20 @@ class RootFiber extends Fiber {
|
|
|
1897
2150
|
handleError({ fiber: current || this, error: e });
|
|
1898
2151
|
}
|
|
1899
2152
|
}
|
|
2153
|
+
setCounter(newValue) {
|
|
2154
|
+
this.counter = newValue;
|
|
2155
|
+
if (newValue === 0) {
|
|
2156
|
+
if (this.delayedRenders.length) {
|
|
2157
|
+
for (let f of this.delayedRenders) {
|
|
2158
|
+
if (f.root) {
|
|
2159
|
+
f.render();
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
this.delayedRenders = [];
|
|
2163
|
+
}
|
|
2164
|
+
this.node.app.scheduler.flush();
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
1900
2167
|
}
|
|
1901
2168
|
class MountFiber extends RootFiber {
|
|
1902
2169
|
constructor(node, target, options = {}) {
|
|
@@ -1940,10 +2207,149 @@ class MountFiber extends RootFiber {
|
|
|
1940
2207
|
}
|
|
1941
2208
|
}
|
|
1942
2209
|
}
|
|
1943
|
-
catch (e) {
|
|
1944
|
-
handleError({ fiber: current, error: e });
|
|
1945
|
-
}
|
|
2210
|
+
catch (e) {
|
|
2211
|
+
handleError({ fiber: current, error: e });
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
|
|
2216
|
+
/**
|
|
2217
|
+
* Apply default props (only top level).
|
|
2218
|
+
*
|
|
2219
|
+
* Note that this method does modify in place the props
|
|
2220
|
+
*/
|
|
2221
|
+
function applyDefaultProps(props, ComponentClass) {
|
|
2222
|
+
const defaultProps = ComponentClass.defaultProps;
|
|
2223
|
+
if (defaultProps) {
|
|
2224
|
+
for (let propName in defaultProps) {
|
|
2225
|
+
if (props[propName] === undefined) {
|
|
2226
|
+
props[propName] = defaultProps[propName];
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
//------------------------------------------------------------------------------
|
|
2232
|
+
// Prop validation helper
|
|
2233
|
+
//------------------------------------------------------------------------------
|
|
2234
|
+
function getPropDescription(staticProps) {
|
|
2235
|
+
if (staticProps instanceof Array) {
|
|
2236
|
+
return Object.fromEntries(staticProps.map((p) => (p.endsWith("?") ? [p.slice(0, -1), false] : [p, true])));
|
|
2237
|
+
}
|
|
2238
|
+
return staticProps || { "*": true };
|
|
2239
|
+
}
|
|
2240
|
+
/**
|
|
2241
|
+
* Validate the component props (or next props) against the (static) props
|
|
2242
|
+
* description. This is potentially an expensive operation: it may needs to
|
|
2243
|
+
* visit recursively the props and all the children to check if they are valid.
|
|
2244
|
+
* This is why it is only done in 'dev' mode.
|
|
2245
|
+
*/
|
|
2246
|
+
function validateProps(name, props, parent) {
|
|
2247
|
+
const ComponentClass = typeof name !== "string"
|
|
2248
|
+
? name
|
|
2249
|
+
: parent.constructor.components[name];
|
|
2250
|
+
if (!ComponentClass) {
|
|
2251
|
+
// this is an error, wrong component. We silently return here instead so the
|
|
2252
|
+
// error is triggered by the usual path ('component' function)
|
|
2253
|
+
return;
|
|
2254
|
+
}
|
|
2255
|
+
applyDefaultProps(props, ComponentClass);
|
|
2256
|
+
const defaultProps = ComponentClass.defaultProps || {};
|
|
2257
|
+
let propsDef = getPropDescription(ComponentClass.props);
|
|
2258
|
+
const allowAdditionalProps = "*" in propsDef;
|
|
2259
|
+
for (let propName in propsDef) {
|
|
2260
|
+
if (propName === "*") {
|
|
2261
|
+
continue;
|
|
2262
|
+
}
|
|
2263
|
+
const propDef = propsDef[propName];
|
|
2264
|
+
let isMandatory = !!propDef;
|
|
2265
|
+
if (typeof propDef === "object" && "optional" in propDef) {
|
|
2266
|
+
isMandatory = !propDef.optional;
|
|
2267
|
+
}
|
|
2268
|
+
if (isMandatory && propName in defaultProps) {
|
|
2269
|
+
throw new Error(`A default value cannot be defined for a mandatory prop (name: '${propName}', component: ${ComponentClass.name})`);
|
|
2270
|
+
}
|
|
2271
|
+
if (props[propName] === undefined) {
|
|
2272
|
+
if (isMandatory) {
|
|
2273
|
+
throw new Error(`Missing props '${propName}' (component '${ComponentClass.name}')`);
|
|
2274
|
+
}
|
|
2275
|
+
else {
|
|
2276
|
+
continue;
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
let isValid;
|
|
2280
|
+
try {
|
|
2281
|
+
isValid = isValidProp(props[propName], propDef);
|
|
2282
|
+
}
|
|
2283
|
+
catch (e) {
|
|
2284
|
+
e.message = `Invalid prop '${propName}' in component ${ComponentClass.name} (${e.message})`;
|
|
2285
|
+
throw e;
|
|
2286
|
+
}
|
|
2287
|
+
if (!isValid) {
|
|
2288
|
+
throw new Error(`Invalid Prop '${propName}' in component '${ComponentClass.name}'`);
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
if (!allowAdditionalProps) {
|
|
2292
|
+
for (let propName in props) {
|
|
2293
|
+
if (!(propName in propsDef)) {
|
|
2294
|
+
throw new Error(`Unknown prop '${propName}' given to component '${ComponentClass.name}'`);
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
/**
|
|
2300
|
+
* Check if an invidual prop value matches its (static) prop definition
|
|
2301
|
+
*/
|
|
2302
|
+
function isValidProp(prop, propDef) {
|
|
2303
|
+
if (propDef === true) {
|
|
2304
|
+
return true;
|
|
2305
|
+
}
|
|
2306
|
+
if (typeof propDef === "function") {
|
|
2307
|
+
// Check if a value is constructed by some Constructor. Note that there is a
|
|
2308
|
+
// slight abuse of language: we want to consider primitive values as well.
|
|
2309
|
+
//
|
|
2310
|
+
// So, even though 1 is not an instance of Number, we want to consider that
|
|
2311
|
+
// it is valid.
|
|
2312
|
+
if (typeof prop === "object") {
|
|
2313
|
+
return prop instanceof propDef;
|
|
2314
|
+
}
|
|
2315
|
+
return typeof prop === propDef.name.toLowerCase();
|
|
2316
|
+
}
|
|
2317
|
+
else if (propDef instanceof Array) {
|
|
2318
|
+
// If this code is executed, this means that we want to check if a prop
|
|
2319
|
+
// matches at least one of its descriptor.
|
|
2320
|
+
let result = false;
|
|
2321
|
+
for (let i = 0, iLen = propDef.length; i < iLen; i++) {
|
|
2322
|
+
result = result || isValidProp(prop, propDef[i]);
|
|
2323
|
+
}
|
|
2324
|
+
return result;
|
|
2325
|
+
}
|
|
2326
|
+
// propsDef is an object
|
|
2327
|
+
if (propDef.optional && prop === undefined) {
|
|
2328
|
+
return true;
|
|
2329
|
+
}
|
|
2330
|
+
let result = propDef.type ? isValidProp(prop, propDef.type) : true;
|
|
2331
|
+
if (propDef.validate) {
|
|
2332
|
+
result = result && propDef.validate(prop);
|
|
2333
|
+
}
|
|
2334
|
+
if (propDef.type === Array && propDef.element) {
|
|
2335
|
+
for (let i = 0, iLen = prop.length; i < iLen; i++) {
|
|
2336
|
+
result = result && isValidProp(prop[i], propDef.element);
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
if (propDef.type === Object && propDef.shape) {
|
|
2340
|
+
const shape = propDef.shape;
|
|
2341
|
+
for (let key in shape) {
|
|
2342
|
+
result = result && isValidProp(prop[key], shape[key]);
|
|
2343
|
+
}
|
|
2344
|
+
if (result) {
|
|
2345
|
+
for (let propName in prop) {
|
|
2346
|
+
if (!(propName in shape)) {
|
|
2347
|
+
throw new Error(`unknown prop '${propName}'`);
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
}
|
|
1946
2351
|
}
|
|
2352
|
+
return result;
|
|
1947
2353
|
}
|
|
1948
2354
|
|
|
1949
2355
|
let currentNode = null;
|
|
@@ -1956,6 +2362,39 @@ function getCurrent() {
|
|
|
1956
2362
|
function useComponent() {
|
|
1957
2363
|
return currentNode.component;
|
|
1958
2364
|
}
|
|
2365
|
+
// -----------------------------------------------------------------------------
|
|
2366
|
+
// Integration with reactivity system (useState)
|
|
2367
|
+
// -----------------------------------------------------------------------------
|
|
2368
|
+
const batchedRenderFunctions = new WeakMap();
|
|
2369
|
+
/**
|
|
2370
|
+
* Creates a reactive object that will be observed by the current component.
|
|
2371
|
+
* Reading data from the returned object (eg during rendering) will cause the
|
|
2372
|
+
* component to subscribe to that data and be rerendered when it changes.
|
|
2373
|
+
*
|
|
2374
|
+
* @param state the state to observe
|
|
2375
|
+
* @returns a reactive object that will cause the component to re-render on
|
|
2376
|
+
* relevant changes
|
|
2377
|
+
* @see reactive
|
|
2378
|
+
*/
|
|
2379
|
+
function useState(state) {
|
|
2380
|
+
const node = getCurrent();
|
|
2381
|
+
let render = batchedRenderFunctions.get(node);
|
|
2382
|
+
if (!render) {
|
|
2383
|
+
render = batched(node.render.bind(node));
|
|
2384
|
+
batchedRenderFunctions.set(node, render);
|
|
2385
|
+
// manual implementation of onWillDestroy to break cyclic dependency
|
|
2386
|
+
node.willDestroy.push(clearReactivesForCallback.bind(null, render));
|
|
2387
|
+
}
|
|
2388
|
+
return reactive(state, render);
|
|
2389
|
+
}
|
|
2390
|
+
function arePropsDifferent(props1, props2) {
|
|
2391
|
+
for (let k in props1) {
|
|
2392
|
+
if (props1[k] !== props2[k]) {
|
|
2393
|
+
return true;
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
return Object.keys(props1).length !== Object.keys(props2).length;
|
|
2397
|
+
}
|
|
1959
2398
|
function component(name, props, key, ctx, parent) {
|
|
1960
2399
|
let node = ctx.children[key];
|
|
1961
2400
|
let isDynamic = typeof name !== "string";
|
|
@@ -1973,7 +2412,17 @@ function component(name, props, key, ctx, parent) {
|
|
|
1973
2412
|
}
|
|
1974
2413
|
const parentFiber = ctx.fiber;
|
|
1975
2414
|
if (node) {
|
|
1976
|
-
node.
|
|
2415
|
+
let shouldRender = node.forceNextRender;
|
|
2416
|
+
if (shouldRender) {
|
|
2417
|
+
node.forceNextRender = false;
|
|
2418
|
+
}
|
|
2419
|
+
else {
|
|
2420
|
+
const currentProps = node.component.props[TARGET];
|
|
2421
|
+
shouldRender = parentFiber.deep || arePropsDifferent(currentProps, props);
|
|
2422
|
+
}
|
|
2423
|
+
if (shouldRender) {
|
|
2424
|
+
node.updateAndRender(props, parentFiber);
|
|
2425
|
+
}
|
|
1977
2426
|
}
|
|
1978
2427
|
else {
|
|
1979
2428
|
// new component
|
|
@@ -1989,9 +2438,9 @@ function component(name, props, key, ctx, parent) {
|
|
|
1989
2438
|
}
|
|
1990
2439
|
node = new ComponentNode(C, props, ctx.app, ctx);
|
|
1991
2440
|
ctx.children[key] = node;
|
|
1992
|
-
|
|
1993
|
-
node.initiateRender(fiber);
|
|
2441
|
+
node.initiateRender(new Fiber(node, parentFiber));
|
|
1994
2442
|
}
|
|
2443
|
+
parentFiber.root.reachedChildren.add(node);
|
|
1995
2444
|
return node;
|
|
1996
2445
|
}
|
|
1997
2446
|
class ComponentNode {
|
|
@@ -1999,6 +2448,7 @@ class ComponentNode {
|
|
|
1999
2448
|
this.fiber = null;
|
|
2000
2449
|
this.bdom = null;
|
|
2001
2450
|
this.status = 0 /* NEW */;
|
|
2451
|
+
this.forceNextRender = false;
|
|
2002
2452
|
this.children = Object.create(null);
|
|
2003
2453
|
this.refs = {};
|
|
2004
2454
|
this.willStart = [];
|
|
@@ -2015,6 +2465,7 @@ class ComponentNode {
|
|
|
2015
2465
|
applyDefaultProps(props, C);
|
|
2016
2466
|
const env = (parent && parent.childEnv) || app.env;
|
|
2017
2467
|
this.childEnv = env;
|
|
2468
|
+
props = useState(props);
|
|
2018
2469
|
this.component = new C(props, env, this);
|
|
2019
2470
|
this.renderFn = app.getTemplate(C.template).bind(this.component, this.component, this);
|
|
2020
2471
|
this.component.setup();
|
|
@@ -2039,23 +2490,32 @@ class ComponentNode {
|
|
|
2039
2490
|
return;
|
|
2040
2491
|
}
|
|
2041
2492
|
if (this.status === 0 /* NEW */ && this.fiber === fiber) {
|
|
2042
|
-
|
|
2493
|
+
fiber.render();
|
|
2043
2494
|
}
|
|
2044
2495
|
}
|
|
2045
|
-
async render() {
|
|
2496
|
+
async render(deep = false) {
|
|
2046
2497
|
let current = this.fiber;
|
|
2047
2498
|
if (current && current.root.locked) {
|
|
2048
2499
|
await Promise.resolve();
|
|
2049
2500
|
// situation may have changed after the microtask tick
|
|
2050
2501
|
current = this.fiber;
|
|
2051
2502
|
}
|
|
2052
|
-
if (current
|
|
2053
|
-
|
|
2503
|
+
if (current) {
|
|
2504
|
+
if (!current.bdom && !fibersInError.has(current)) {
|
|
2505
|
+
if (deep) {
|
|
2506
|
+
// we want the render from this point on to be with deep=true
|
|
2507
|
+
current.deep = deep;
|
|
2508
|
+
}
|
|
2509
|
+
return;
|
|
2510
|
+
}
|
|
2511
|
+
// if current rendering was with deep=true, we want this one to be the same
|
|
2512
|
+
deep = deep || current.deep;
|
|
2054
2513
|
}
|
|
2055
|
-
if (!this.bdom
|
|
2514
|
+
else if (!this.bdom) {
|
|
2056
2515
|
return;
|
|
2057
2516
|
}
|
|
2058
2517
|
const fiber = makeRootFiber(this);
|
|
2518
|
+
fiber.deep = deep;
|
|
2059
2519
|
this.fiber = fiber;
|
|
2060
2520
|
this.app.scheduler.addFiber(fiber);
|
|
2061
2521
|
await Promise.resolve();
|
|
@@ -2074,16 +2534,7 @@ class ComponentNode {
|
|
|
2074
2534
|
// embedded in a rendering coming from above, so the fiber will be rendered
|
|
2075
2535
|
// in the next microtick anyway, so we should not render it again.
|
|
2076
2536
|
if (this.fiber === fiber && (current || !fiber.parent)) {
|
|
2077
|
-
|
|
2078
|
-
}
|
|
2079
|
-
}
|
|
2080
|
-
_render(fiber) {
|
|
2081
|
-
try {
|
|
2082
|
-
fiber.bdom = this.renderFn();
|
|
2083
|
-
fiber.root.counter--;
|
|
2084
|
-
}
|
|
2085
|
-
catch (e) {
|
|
2086
|
-
handleError({ node: this, error: e });
|
|
2537
|
+
fiber.render();
|
|
2087
2538
|
}
|
|
2088
2539
|
}
|
|
2089
2540
|
destroy() {
|
|
@@ -2114,13 +2565,16 @@ class ComponentNode {
|
|
|
2114
2565
|
this.fiber = fiber;
|
|
2115
2566
|
const component = this.component;
|
|
2116
2567
|
applyDefaultProps(props, component.constructor);
|
|
2568
|
+
currentNode = this;
|
|
2569
|
+
props = useState(props);
|
|
2570
|
+
currentNode = null;
|
|
2117
2571
|
const prom = Promise.all(this.willUpdateProps.map((f) => f.call(component, props)));
|
|
2118
2572
|
await prom;
|
|
2119
2573
|
if (fiber !== this.fiber) {
|
|
2120
2574
|
return;
|
|
2121
2575
|
}
|
|
2122
2576
|
component.props = props;
|
|
2123
|
-
|
|
2577
|
+
fiber.render();
|
|
2124
2578
|
const parentRoot = parentFiber.root;
|
|
2125
2579
|
if (this.willPatch.length) {
|
|
2126
2580
|
parentRoot.willPatch.push(fiber);
|
|
@@ -2173,6 +2627,14 @@ class ComponentNode {
|
|
|
2173
2627
|
this.bdom.moveBefore(other ? other.bdom : null, afterNode);
|
|
2174
2628
|
}
|
|
2175
2629
|
patch() {
|
|
2630
|
+
if (this.fiber && this.fiber.parent) {
|
|
2631
|
+
// we only patch here renderings coming from above. renderings initiated
|
|
2632
|
+
// by the component will be patched independently in the appropriate
|
|
2633
|
+
// fiber.complete
|
|
2634
|
+
this._patch();
|
|
2635
|
+
}
|
|
2636
|
+
}
|
|
2637
|
+
_patch() {
|
|
2176
2638
|
const hasChildren = Object.keys(this.children).length > 0;
|
|
2177
2639
|
this.bdom.patch(this.fiber.bdom, hasChildren);
|
|
2178
2640
|
if (hasChildren) {
|
|
@@ -2200,65 +2662,76 @@ class ComponentNode {
|
|
|
2200
2662
|
}
|
|
2201
2663
|
}
|
|
2202
2664
|
}
|
|
2665
|
+
// ---------------------------------------------------------------------------
|
|
2666
|
+
// Some debug helpers
|
|
2667
|
+
// ---------------------------------------------------------------------------
|
|
2668
|
+
get name() {
|
|
2669
|
+
return this.component.constructor.name;
|
|
2670
|
+
}
|
|
2671
|
+
get subscriptions() {
|
|
2672
|
+
const render = batchedRenderFunctions.get(this);
|
|
2673
|
+
return render ? getSubscriptions(render) : [];
|
|
2674
|
+
}
|
|
2203
2675
|
}
|
|
2204
2676
|
|
|
2205
2677
|
// -----------------------------------------------------------------------------
|
|
2206
|
-
//
|
|
2678
|
+
// Scheduler
|
|
2207
2679
|
// -----------------------------------------------------------------------------
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
node.willUpdateProps.push(fn.bind(node.component));
|
|
2215
|
-
}
|
|
2216
|
-
function onMounted(fn) {
|
|
2217
|
-
const node = getCurrent();
|
|
2218
|
-
node.mounted.push(fn.bind(node.component));
|
|
2219
|
-
}
|
|
2220
|
-
function onWillPatch(fn) {
|
|
2221
|
-
const node = getCurrent();
|
|
2222
|
-
node.willPatch.unshift(fn.bind(node.component));
|
|
2223
|
-
}
|
|
2224
|
-
function onPatched(fn) {
|
|
2225
|
-
const node = getCurrent();
|
|
2226
|
-
node.patched.push(fn.bind(node.component));
|
|
2227
|
-
}
|
|
2228
|
-
function onWillUnmount(fn) {
|
|
2229
|
-
const node = getCurrent();
|
|
2230
|
-
node.willUnmount.unshift(fn.bind(node.component));
|
|
2231
|
-
}
|
|
2232
|
-
function onWillDestroy(fn) {
|
|
2233
|
-
const node = getCurrent();
|
|
2234
|
-
node.willDestroy.push(fn.bind(node.component));
|
|
2235
|
-
}
|
|
2236
|
-
function onWillRender(fn) {
|
|
2237
|
-
const node = getCurrent();
|
|
2238
|
-
const renderFn = node.renderFn;
|
|
2239
|
-
node.renderFn = () => {
|
|
2240
|
-
fn.call(node.component);
|
|
2241
|
-
return renderFn();
|
|
2242
|
-
};
|
|
2243
|
-
}
|
|
2244
|
-
function onRendered(fn) {
|
|
2245
|
-
const node = getCurrent();
|
|
2246
|
-
const renderFn = node.renderFn;
|
|
2247
|
-
node.renderFn = () => {
|
|
2248
|
-
const result = renderFn();
|
|
2249
|
-
fn.call(node.component);
|
|
2250
|
-
return result;
|
|
2251
|
-
};
|
|
2252
|
-
}
|
|
2253
|
-
function onError(callback) {
|
|
2254
|
-
const node = getCurrent();
|
|
2255
|
-
let handlers = nodeErrorHandlers.get(node);
|
|
2256
|
-
if (!handlers) {
|
|
2257
|
-
handlers = [];
|
|
2258
|
-
nodeErrorHandlers.set(node, handlers);
|
|
2680
|
+
class Scheduler {
|
|
2681
|
+
constructor() {
|
|
2682
|
+
this.tasks = new Set();
|
|
2683
|
+
this.frame = 0;
|
|
2684
|
+
this.shouldClear = false;
|
|
2685
|
+
this.requestAnimationFrame = Scheduler.requestAnimationFrame;
|
|
2259
2686
|
}
|
|
2260
|
-
|
|
2261
|
-
|
|
2687
|
+
addFiber(fiber) {
|
|
2688
|
+
this.tasks.add(fiber.root);
|
|
2689
|
+
}
|
|
2690
|
+
/**
|
|
2691
|
+
* Process all current tasks. This only applies to the fibers that are ready.
|
|
2692
|
+
* Other tasks are left unchanged.
|
|
2693
|
+
*/
|
|
2694
|
+
flush() {
|
|
2695
|
+
if (this.frame === 0) {
|
|
2696
|
+
this.frame = this.requestAnimationFrame(() => {
|
|
2697
|
+
this.frame = 0;
|
|
2698
|
+
this.tasks.forEach((fiber) => this.processFiber(fiber));
|
|
2699
|
+
if (this.shouldClear) {
|
|
2700
|
+
this.shouldClear = false;
|
|
2701
|
+
for (let task of this.tasks) {
|
|
2702
|
+
if (task.node.status === 2 /* DESTROYED */) {
|
|
2703
|
+
this.tasks.delete(task);
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
});
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2710
|
+
processFiber(fiber) {
|
|
2711
|
+
if (fiber.root !== fiber) {
|
|
2712
|
+
this.tasks.delete(fiber);
|
|
2713
|
+
return;
|
|
2714
|
+
}
|
|
2715
|
+
const hasError = fibersInError.has(fiber);
|
|
2716
|
+
if (hasError && fiber.counter !== 0) {
|
|
2717
|
+
this.tasks.delete(fiber);
|
|
2718
|
+
return;
|
|
2719
|
+
}
|
|
2720
|
+
if (fiber.node.status === 2 /* DESTROYED */) {
|
|
2721
|
+
this.tasks.delete(fiber);
|
|
2722
|
+
return;
|
|
2723
|
+
}
|
|
2724
|
+
if (fiber.counter === 0) {
|
|
2725
|
+
if (!hasError) {
|
|
2726
|
+
fiber.complete();
|
|
2727
|
+
}
|
|
2728
|
+
this.tasks.delete(fiber);
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2731
|
+
}
|
|
2732
|
+
// capture the value of requestAnimationFrame as soon as possible, to avoid
|
|
2733
|
+
// interactions with other code, such as test frameworks that override them
|
|
2734
|
+
Scheduler.requestAnimationFrame = window.requestAnimationFrame.bind(window);
|
|
2262
2735
|
|
|
2263
2736
|
/**
|
|
2264
2737
|
* Owl QWeb Expression Parser
|
|
@@ -2412,24 +2885,31 @@ const TOKENIZERS = [
|
|
|
2412
2885
|
function tokenize(expr) {
|
|
2413
2886
|
const result = [];
|
|
2414
2887
|
let token = true;
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2888
|
+
let error;
|
|
2889
|
+
let current = expr;
|
|
2890
|
+
try {
|
|
2891
|
+
while (token) {
|
|
2892
|
+
current = current.trim();
|
|
2893
|
+
if (current) {
|
|
2894
|
+
for (let tokenizer of TOKENIZERS) {
|
|
2895
|
+
token = tokenizer(current);
|
|
2896
|
+
if (token) {
|
|
2897
|
+
result.push(token);
|
|
2898
|
+
current = current.slice(token.size || token.value.length);
|
|
2899
|
+
break;
|
|
2900
|
+
}
|
|
2424
2901
|
}
|
|
2425
2902
|
}
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2903
|
+
else {
|
|
2904
|
+
token = false;
|
|
2905
|
+
}
|
|
2429
2906
|
}
|
|
2430
2907
|
}
|
|
2431
|
-
|
|
2432
|
-
|
|
2908
|
+
catch (e) {
|
|
2909
|
+
error = e; // Silence all errors and throw a generic error below
|
|
2910
|
+
}
|
|
2911
|
+
if (current.length || error) {
|
|
2912
|
+
throw new Error(`Tokenizer error: could not tokenize \`${expr}\``);
|
|
2433
2913
|
}
|
|
2434
2914
|
return result;
|
|
2435
2915
|
}
|
|
@@ -2634,7 +3114,7 @@ function createContext(parentCtx, params) {
|
|
|
2634
3114
|
}, params);
|
|
2635
3115
|
}
|
|
2636
3116
|
class CodeTarget {
|
|
2637
|
-
constructor(name) {
|
|
3117
|
+
constructor(name, on) {
|
|
2638
3118
|
this.indentLevel = 0;
|
|
2639
3119
|
this.loopLevel = 0;
|
|
2640
3120
|
this.code = [];
|
|
@@ -2645,6 +3125,7 @@ class CodeTarget {
|
|
|
2645
3125
|
this.refInfo = {};
|
|
2646
3126
|
this.shouldProtectScope = false;
|
|
2647
3127
|
this.name = name;
|
|
3128
|
+
this.on = on || null;
|
|
2648
3129
|
}
|
|
2649
3130
|
addLine(line, idx) {
|
|
2650
3131
|
const prefix = new Array(this.indentLevel + 2).join(" ");
|
|
@@ -2694,7 +3175,7 @@ class CodeGenerator {
|
|
|
2694
3175
|
this.targets = [];
|
|
2695
3176
|
this.target = new CodeTarget("template");
|
|
2696
3177
|
this.translatableAttributes = TRANSLATABLE_ATTRS;
|
|
2697
|
-
this.
|
|
3178
|
+
this.staticDefs = [];
|
|
2698
3179
|
this.helpers = new Set();
|
|
2699
3180
|
this.translateFn = options.translateFn || ((s) => s);
|
|
2700
3181
|
if (options.translatableAttributes) {
|
|
@@ -2737,8 +3218,8 @@ class CodeGenerator {
|
|
|
2737
3218
|
if (this.templateName) {
|
|
2738
3219
|
mainCode.push(`// Template name: "${this.templateName}"`);
|
|
2739
3220
|
}
|
|
2740
|
-
for (let { id,
|
|
2741
|
-
mainCode.push(`const ${id} =
|
|
3221
|
+
for (let { id, expr } of this.staticDefs) {
|
|
3222
|
+
mainCode.push(`const ${id} = ${expr};`);
|
|
2742
3223
|
}
|
|
2743
3224
|
// define all blocks
|
|
2744
3225
|
if (this.blocks.length) {
|
|
@@ -2774,19 +3255,21 @@ class CodeGenerator {
|
|
|
2774
3255
|
}
|
|
2775
3256
|
return code;
|
|
2776
3257
|
}
|
|
2777
|
-
compileInNewTarget(prefix, ast, ctx) {
|
|
3258
|
+
compileInNewTarget(prefix, ast, ctx, on) {
|
|
2778
3259
|
const name = this.generateId(prefix);
|
|
2779
3260
|
const initialTarget = this.target;
|
|
2780
|
-
const target = new CodeTarget(name);
|
|
3261
|
+
const target = new CodeTarget(name, on);
|
|
2781
3262
|
this.targets.push(target);
|
|
2782
3263
|
this.target = target;
|
|
2783
|
-
|
|
2784
|
-
this.compileAST(ast, subCtx);
|
|
3264
|
+
this.compileAST(ast, createContext(ctx));
|
|
2785
3265
|
this.target = initialTarget;
|
|
2786
3266
|
return name;
|
|
2787
3267
|
}
|
|
2788
|
-
addLine(line) {
|
|
2789
|
-
this.target.addLine(line);
|
|
3268
|
+
addLine(line, idx) {
|
|
3269
|
+
this.target.addLine(line, idx);
|
|
3270
|
+
}
|
|
3271
|
+
define(varName, expr) {
|
|
3272
|
+
this.addLine(`const ${varName} = ${expr};`);
|
|
2790
3273
|
}
|
|
2791
3274
|
generateId(prefix = "") {
|
|
2792
3275
|
this.ids[prefix] = (this.ids[prefix] || 0) + 1;
|
|
@@ -2828,10 +3311,13 @@ class CodeGenerator {
|
|
|
2828
3311
|
blockExpr = `toggler(${tKeyExpr}, ${blockExpr})`;
|
|
2829
3312
|
}
|
|
2830
3313
|
if (block.isRoot && !ctx.preventRoot) {
|
|
3314
|
+
if (this.target.on) {
|
|
3315
|
+
blockExpr = this.wrapWithEventCatcher(blockExpr, this.target.on);
|
|
3316
|
+
}
|
|
2831
3317
|
this.addLine(`return ${blockExpr};`);
|
|
2832
3318
|
}
|
|
2833
3319
|
else {
|
|
2834
|
-
this.
|
|
3320
|
+
this.define(block.varName, blockExpr);
|
|
2835
3321
|
}
|
|
2836
3322
|
}
|
|
2837
3323
|
/**
|
|
@@ -2859,7 +3345,7 @@ class CodeGenerator {
|
|
|
2859
3345
|
if (!mapping.has(tok.varName)) {
|
|
2860
3346
|
const varId = this.generateId("v");
|
|
2861
3347
|
mapping.set(tok.varName, varId);
|
|
2862
|
-
this.
|
|
3348
|
+
this.define(varId, tok.value);
|
|
2863
3349
|
}
|
|
2864
3350
|
tok.value = mapping.get(tok.varName);
|
|
2865
3351
|
}
|
|
@@ -2998,7 +3484,7 @@ class CodeGenerator {
|
|
|
2998
3484
|
this.blocks.push(block);
|
|
2999
3485
|
if (ast.dynamicTag) {
|
|
3000
3486
|
const tagExpr = this.generateId("tag");
|
|
3001
|
-
this.
|
|
3487
|
+
this.define(tagExpr, compileExpr(ast.dynamicTag));
|
|
3002
3488
|
block.dynamicTagName = tagExpr;
|
|
3003
3489
|
}
|
|
3004
3490
|
}
|
|
@@ -3080,10 +3566,10 @@ class CodeGenerator {
|
|
|
3080
3566
|
const { hasDynamicChildren, baseExpr, expr, eventType, shouldNumberize, shouldTrim, targetAttr, specialInitTargetAttr, } = ast.model;
|
|
3081
3567
|
const baseExpression = compileExpr(baseExpr);
|
|
3082
3568
|
const bExprId = this.generateId("bExpr");
|
|
3083
|
-
this.
|
|
3569
|
+
this.define(bExprId, baseExpression);
|
|
3084
3570
|
const expression = compileExpr(expr);
|
|
3085
3571
|
const exprId = this.generateId("expr");
|
|
3086
|
-
this.
|
|
3572
|
+
this.define(exprId, expression);
|
|
3087
3573
|
const fullExpression = `${bExprId}[${exprId}]`;
|
|
3088
3574
|
let idx;
|
|
3089
3575
|
if (specialInitTargetAttr) {
|
|
@@ -3093,7 +3579,7 @@ class CodeGenerator {
|
|
|
3093
3579
|
else if (hasDynamicChildren) {
|
|
3094
3580
|
const bValueId = this.generateId("bValue");
|
|
3095
3581
|
tModelSelectedExpr = `${bValueId}`;
|
|
3096
|
-
this.
|
|
3582
|
+
this.define(tModelSelectedExpr, fullExpression);
|
|
3097
3583
|
}
|
|
3098
3584
|
else {
|
|
3099
3585
|
idx = block.insertData(`${fullExpression}`, "attr");
|
|
@@ -3141,14 +3627,14 @@ class CodeGenerator {
|
|
|
3141
3627
|
const children = block.children.slice();
|
|
3142
3628
|
let current = children.shift();
|
|
3143
3629
|
for (let i = codeIdx; i < code.length; i++) {
|
|
3144
|
-
if (code[i].trimStart().startsWith(`
|
|
3145
|
-
code[i] = code[i].replace(`
|
|
3630
|
+
if (code[i].trimStart().startsWith(`const ${current.varName} `)) {
|
|
3631
|
+
code[i] = code[i].replace(`const ${current.varName}`, current.varName);
|
|
3146
3632
|
current = children.shift();
|
|
3147
3633
|
if (!current)
|
|
3148
3634
|
break;
|
|
3149
3635
|
}
|
|
3150
3636
|
}
|
|
3151
|
-
this.
|
|
3637
|
+
this.addLine(`let ${block.children.map((c) => c.varName)};`, codeIdx);
|
|
3152
3638
|
}
|
|
3153
3639
|
}
|
|
3154
3640
|
}
|
|
@@ -3236,14 +3722,14 @@ class CodeGenerator {
|
|
|
3236
3722
|
const children = block.children.slice();
|
|
3237
3723
|
let current = children.shift();
|
|
3238
3724
|
for (let i = codeIdx; i < code.length; i++) {
|
|
3239
|
-
if (code[i].trimStart().startsWith(`
|
|
3240
|
-
code[i] = code[i].replace(`
|
|
3725
|
+
if (code[i].trimStart().startsWith(`const ${current.varName} `)) {
|
|
3726
|
+
code[i] = code[i].replace(`const ${current.varName}`, current.varName);
|
|
3241
3727
|
current = children.shift();
|
|
3242
3728
|
if (!current)
|
|
3243
3729
|
break;
|
|
3244
3730
|
}
|
|
3245
3731
|
}
|
|
3246
|
-
this.
|
|
3732
|
+
this.addLine(`let ${block.children.map((c) => c.varName)};`, codeIdx);
|
|
3247
3733
|
}
|
|
3248
3734
|
// note: this part is duplicated from end of compilemulti:
|
|
3249
3735
|
const args = block.children.map((c) => c.varName).join(", ");
|
|
@@ -3264,10 +3750,10 @@ class CodeGenerator {
|
|
|
3264
3750
|
const l = `l_block${block.id}`;
|
|
3265
3751
|
const c = `c_block${block.id}`;
|
|
3266
3752
|
this.helpers.add("prepareList");
|
|
3267
|
-
this.
|
|
3753
|
+
this.define(`[${keys}, ${vals}, ${l}, ${c}]`, `prepareList(${compileExpr(ast.collection)});`);
|
|
3268
3754
|
// Throw errors on duplicate keys in dev mode
|
|
3269
3755
|
if (this.dev) {
|
|
3270
|
-
this.
|
|
3756
|
+
this.define(`keys${block.id}`, `new Set()`);
|
|
3271
3757
|
}
|
|
3272
3758
|
this.addLine(`for (let ${loopVar} = 0; ${loopVar} < ${l}; ${loopVar}++) {`);
|
|
3273
3759
|
this.target.indentLevel++;
|
|
@@ -3284,7 +3770,7 @@ class CodeGenerator {
|
|
|
3284
3770
|
if (!ast.hasNoValue) {
|
|
3285
3771
|
this.addLine(`ctx[\`${ast.elem}_value\`] = ${keys}[${loopVar}];`);
|
|
3286
3772
|
}
|
|
3287
|
-
this.
|
|
3773
|
+
this.define(`key${this.target.loopLevel}`, ast.key ? compileExpr(ast.key) : loopVar);
|
|
3288
3774
|
if (this.dev) {
|
|
3289
3775
|
// Throw error on duplicate keys in dev mode
|
|
3290
3776
|
this.addLine(`if (keys${block.id}.has(key${this.target.loopLevel})) { throw new Error(\`Got duplicate key in t-foreach: \${key${this.target.loopLevel}}\`)}`);
|
|
@@ -3294,8 +3780,8 @@ class CodeGenerator {
|
|
|
3294
3780
|
if (ast.memo) {
|
|
3295
3781
|
this.target.hasCache = true;
|
|
3296
3782
|
id = this.generateId();
|
|
3297
|
-
this.
|
|
3298
|
-
this.
|
|
3783
|
+
this.define(`memo${id}`, compileExpr(ast.memo));
|
|
3784
|
+
this.define(`vnode${id}`, `cache[key${this.target.loopLevel}];`);
|
|
3299
3785
|
this.addLine(`if (vnode${id}) {`);
|
|
3300
3786
|
this.target.indentLevel++;
|
|
3301
3787
|
this.addLine(`if (shallowEqual(vnode${id}.memo, memo${id})) {`);
|
|
@@ -3323,7 +3809,7 @@ class CodeGenerator {
|
|
|
3323
3809
|
}
|
|
3324
3810
|
compileTKey(ast, ctx) {
|
|
3325
3811
|
const tKeyExpr = this.generateId("tKey_");
|
|
3326
|
-
this.
|
|
3812
|
+
this.define(tKeyExpr, compileExpr(ast.expr));
|
|
3327
3813
|
ctx = createContext(ctx, {
|
|
3328
3814
|
tKeyExpr,
|
|
3329
3815
|
block: ctx.block,
|
|
@@ -3368,14 +3854,14 @@ class CodeGenerator {
|
|
|
3368
3854
|
const children = block.children.slice();
|
|
3369
3855
|
let current = children.shift();
|
|
3370
3856
|
for (let i = codeIdx; i < code.length; i++) {
|
|
3371
|
-
if (code[i].trimStart().startsWith(`
|
|
3372
|
-
code[i] = code[i].replace(`
|
|
3857
|
+
if (code[i].trimStart().startsWith(`const ${current.varName} `)) {
|
|
3858
|
+
code[i] = code[i].replace(`const ${current.varName}`, current.varName);
|
|
3373
3859
|
current = children.shift();
|
|
3374
3860
|
if (!current)
|
|
3375
3861
|
break;
|
|
3376
3862
|
}
|
|
3377
3863
|
}
|
|
3378
|
-
this.
|
|
3864
|
+
this.addLine(`let ${block.children.map((c) => c.varName)};`, codeIdx);
|
|
3379
3865
|
}
|
|
3380
3866
|
}
|
|
3381
3867
|
const args = block.children.map((c) => c.varName).join(", ");
|
|
@@ -3406,7 +3892,7 @@ class CodeGenerator {
|
|
|
3406
3892
|
const key = `key + \`${this.generateComponentKey()}\``;
|
|
3407
3893
|
if (isDynamic) {
|
|
3408
3894
|
const templateVar = this.generateId("template");
|
|
3409
|
-
this.
|
|
3895
|
+
this.define(templateVar, subTemplate);
|
|
3410
3896
|
block = this.createBlock(block, "multi", ctx);
|
|
3411
3897
|
this.helpers.add("call");
|
|
3412
3898
|
this.insertBlock(`call(this, ${templateVar}, ctx, node, ${key})`, block, {
|
|
@@ -3417,7 +3903,7 @@ class CodeGenerator {
|
|
|
3417
3903
|
else {
|
|
3418
3904
|
const id = this.generateId(`callTemplate_`);
|
|
3419
3905
|
this.helpers.add("getTemplate");
|
|
3420
|
-
this.
|
|
3906
|
+
this.staticDefs.push({ id, expr: `getTemplate(${subTemplate})` });
|
|
3421
3907
|
block = this.createBlock(block, "multi", ctx);
|
|
3422
3908
|
this.insertBlock(`${id}.call(this, ctx, node, ${key})`, block, {
|
|
3423
3909
|
...ctx,
|
|
@@ -3511,26 +3997,25 @@ class CodeGenerator {
|
|
|
3511
3997
|
compileComponent(ast, ctx) {
|
|
3512
3998
|
let { block } = ctx;
|
|
3513
3999
|
// props
|
|
3514
|
-
const hasSlotsProp = "slots" in ast.props;
|
|
4000
|
+
const hasSlotsProp = "slots" in (ast.props || {});
|
|
3515
4001
|
const props = [];
|
|
3516
|
-
const propExpr = this.formatPropObject(ast.props);
|
|
4002
|
+
const propExpr = this.formatPropObject(ast.props || {});
|
|
3517
4003
|
if (propExpr) {
|
|
3518
4004
|
props.push(propExpr);
|
|
3519
4005
|
}
|
|
3520
4006
|
// slots
|
|
3521
|
-
const hasSlot = !!Object.keys(ast.slots).length;
|
|
3522
4007
|
let slotDef = "";
|
|
3523
|
-
if (
|
|
4008
|
+
if (ast.slots) {
|
|
3524
4009
|
let ctxStr = "ctx";
|
|
3525
4010
|
if (this.target.loopLevel || !this.hasSafeContext) {
|
|
3526
4011
|
ctxStr = this.generateId("ctx");
|
|
3527
4012
|
this.helpers.add("capture");
|
|
3528
|
-
this.
|
|
4013
|
+
this.define(ctxStr, `capture(ctx)`);
|
|
3529
4014
|
}
|
|
3530
4015
|
let slotStr = [];
|
|
3531
4016
|
for (let slotName in ast.slots) {
|
|
3532
|
-
const slotAst = ast.slots[slotName]
|
|
3533
|
-
const name = this.compileInNewTarget("slot", slotAst, ctx);
|
|
4017
|
+
const slotAst = ast.slots[slotName];
|
|
4018
|
+
const name = this.compileInNewTarget("slot", slotAst.content, ctx, slotAst.on);
|
|
3534
4019
|
const params = [`__render: ${name}, __ctx: ${ctxStr}`];
|
|
3535
4020
|
const scope = ast.slots[slotName].scope;
|
|
3536
4021
|
if (scope) {
|
|
@@ -3545,33 +4030,30 @@ class CodeGenerator {
|
|
|
3545
4030
|
slotDef = `{${slotStr.join(", ")}}`;
|
|
3546
4031
|
}
|
|
3547
4032
|
if (slotDef && !(ast.dynamicProps || hasSlotsProp)) {
|
|
3548
|
-
|
|
4033
|
+
this.helpers.add("markRaw");
|
|
4034
|
+
props.push(`slots: markRaw(${slotDef})`);
|
|
3549
4035
|
}
|
|
3550
4036
|
const propStr = `{${props.join(",")}}`;
|
|
3551
4037
|
let propString = propStr;
|
|
3552
4038
|
if (ast.dynamicProps) {
|
|
3553
|
-
|
|
3554
|
-
propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)})`;
|
|
3555
|
-
}
|
|
3556
|
-
else {
|
|
3557
|
-
propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)}, ${propStr})`;
|
|
3558
|
-
}
|
|
4039
|
+
propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)}${props.length ? ", " + propStr : ""})`;
|
|
3559
4040
|
}
|
|
3560
4041
|
let propVar;
|
|
3561
4042
|
if ((slotDef && (ast.dynamicProps || hasSlotsProp)) || this.dev) {
|
|
3562
4043
|
propVar = this.generateId("props");
|
|
3563
|
-
this.
|
|
4044
|
+
this.define(propVar, propString);
|
|
3564
4045
|
propString = propVar;
|
|
3565
4046
|
}
|
|
3566
4047
|
if (slotDef && (ast.dynamicProps || hasSlotsProp)) {
|
|
3567
|
-
this.
|
|
4048
|
+
this.helpers.add("markRaw");
|
|
4049
|
+
this.addLine(`${propVar}.slots = markRaw(Object.assign(${slotDef}, ${propVar}.slots))`);
|
|
3568
4050
|
}
|
|
3569
4051
|
// cmap key
|
|
3570
4052
|
const key = this.generateComponentKey();
|
|
3571
4053
|
let expr;
|
|
3572
4054
|
if (ast.isDynamic) {
|
|
3573
4055
|
expr = this.generateId("Comp");
|
|
3574
|
-
this.
|
|
4056
|
+
this.define(expr, compileExpr(ast.name));
|
|
3575
4057
|
}
|
|
3576
4058
|
else {
|
|
3577
4059
|
expr = `\`${ast.name}\``;
|
|
@@ -3592,9 +4074,28 @@ class CodeGenerator {
|
|
|
3592
4074
|
if (ast.isDynamic) {
|
|
3593
4075
|
blockExpr = `toggler(${expr}, ${blockExpr})`;
|
|
3594
4076
|
}
|
|
4077
|
+
// event handling
|
|
4078
|
+
if (ast.on) {
|
|
4079
|
+
blockExpr = this.wrapWithEventCatcher(blockExpr, ast.on);
|
|
4080
|
+
}
|
|
3595
4081
|
block = this.createBlock(block, "multi", ctx);
|
|
3596
4082
|
this.insertBlock(blockExpr, block, ctx);
|
|
3597
4083
|
}
|
|
4084
|
+
wrapWithEventCatcher(expr, on) {
|
|
4085
|
+
this.helpers.add("createCatcher");
|
|
4086
|
+
let name = this.generateId("catcher");
|
|
4087
|
+
let spec = {};
|
|
4088
|
+
let handlers = [];
|
|
4089
|
+
for (let ev in on) {
|
|
4090
|
+
let handlerId = this.generateId("hdlr");
|
|
4091
|
+
let idx = handlers.push(handlerId) - 1;
|
|
4092
|
+
spec[ev] = idx;
|
|
4093
|
+
const handler = this.generateHandlerCode(ev, on[ev]);
|
|
4094
|
+
this.define(handlerId, handler);
|
|
4095
|
+
}
|
|
4096
|
+
this.staticDefs.push({ id: name, expr: `createCatcher(${JSON.stringify(spec)})` });
|
|
4097
|
+
return `${name}(${expr}, [${handlers.join(",")}])`;
|
|
4098
|
+
}
|
|
3598
4099
|
compileTSlot(ast, ctx) {
|
|
3599
4100
|
this.helpers.add("callSlot");
|
|
3600
4101
|
let { block } = ctx;
|
|
@@ -3616,13 +4117,17 @@ class CodeGenerator {
|
|
|
3616
4117
|
else {
|
|
3617
4118
|
if (dynamic) {
|
|
3618
4119
|
let name = this.generateId("slot");
|
|
3619
|
-
this.
|
|
4120
|
+
this.define(name, slotName);
|
|
3620
4121
|
blockString = `toggler(${name}, callSlot(ctx, node, key, ${name}), ${dynamic}, ${scope})`;
|
|
3621
4122
|
}
|
|
3622
4123
|
else {
|
|
3623
4124
|
blockString = `callSlot(ctx, node, key, ${slotName}, ${dynamic}, ${scope})`;
|
|
3624
4125
|
}
|
|
3625
4126
|
}
|
|
4127
|
+
// event handling
|
|
4128
|
+
if (ast.on) {
|
|
4129
|
+
blockString = this.wrapWithEventCatcher(blockString, ast.on);
|
|
4130
|
+
}
|
|
3626
4131
|
if (block) {
|
|
3627
4132
|
this.insertAnchor(block);
|
|
3628
4133
|
}
|
|
@@ -3643,7 +4148,7 @@ class CodeGenerator {
|
|
|
3643
4148
|
if (this.target.loopLevel || !this.hasSafeContext) {
|
|
3644
4149
|
ctxStr = this.generateId("ctx");
|
|
3645
4150
|
this.helpers.add("capture");
|
|
3646
|
-
this.
|
|
4151
|
+
this.define(ctxStr, `capture(ctx);`);
|
|
3647
4152
|
}
|
|
3648
4153
|
const blockString = `component(Portal, {target: ${ast.target},slots: {'default': {__render: ${name}, __ctx: ${ctxStr}}}}, key + \`${key}\`, node, ctx)`;
|
|
3649
4154
|
if (block) {
|
|
@@ -3764,6 +4269,9 @@ function parseDOMNode(node, ctx) {
|
|
|
3764
4269
|
if (tagName === "t" && !dynamicTag) {
|
|
3765
4270
|
return null;
|
|
3766
4271
|
}
|
|
4272
|
+
if (tagName.startsWith("block-")) {
|
|
4273
|
+
throw new Error(`Invalid tag name: '${tagName}'`);
|
|
4274
|
+
}
|
|
3767
4275
|
ctx = Object.assign({}, ctx);
|
|
3768
4276
|
if (tagName === "pre") {
|
|
3769
4277
|
ctx.inPreTag = true;
|
|
@@ -3774,8 +4282,8 @@ function parseDOMNode(node, ctx) {
|
|
|
3774
4282
|
const ref = node.getAttribute("t-ref");
|
|
3775
4283
|
node.removeAttribute("t-ref");
|
|
3776
4284
|
const nodeAttrsNames = node.getAttributeNames();
|
|
3777
|
-
|
|
3778
|
-
|
|
4285
|
+
let attrs = null;
|
|
4286
|
+
let on = null;
|
|
3779
4287
|
let model = null;
|
|
3780
4288
|
for (let attr of nodeAttrsNames) {
|
|
3781
4289
|
const value = node.getAttribute(attr);
|
|
@@ -3783,6 +4291,7 @@ function parseDOMNode(node, ctx) {
|
|
|
3783
4291
|
if (attr === "t-on") {
|
|
3784
4292
|
throw new Error("Missing event name with t-on directive");
|
|
3785
4293
|
}
|
|
4294
|
+
on = on || {};
|
|
3786
4295
|
on[attr.slice(5)] = value;
|
|
3787
4296
|
}
|
|
3788
4297
|
else if (attr.startsWith("t-model")) {
|
|
@@ -3820,6 +4329,7 @@ function parseDOMNode(node, ctx) {
|
|
|
3820
4329
|
targetAttr: isCheckboxInput ? "checked" : "value",
|
|
3821
4330
|
specialInitTargetAttr: isRadioInput ? "checked" : null,
|
|
3822
4331
|
eventType,
|
|
4332
|
+
hasDynamicChildren: false,
|
|
3823
4333
|
shouldTrim: hasTrimMod && (isOtherInput || isTextarea),
|
|
3824
4334
|
shouldNumberize: hasNumberMod && (isOtherInput || isTextarea),
|
|
3825
4335
|
};
|
|
@@ -3829,6 +4339,9 @@ function parseDOMNode(node, ctx) {
|
|
|
3829
4339
|
ctx.tModelInfo = model;
|
|
3830
4340
|
}
|
|
3831
4341
|
}
|
|
4342
|
+
else if (attr.startsWith("block-")) {
|
|
4343
|
+
throw new Error(`Invalid attribute: '${attr}'`);
|
|
4344
|
+
}
|
|
3832
4345
|
else if (attr !== "t-name") {
|
|
3833
4346
|
if (attr.startsWith("t-") && !attr.startsWith("t-att")) {
|
|
3834
4347
|
throw new Error(`Unknown QWeb directive: '${attr}'`);
|
|
@@ -3837,6 +4350,7 @@ function parseDOMNode(node, ctx) {
|
|
|
3837
4350
|
if (tModel && ["t-att-value", "t-attf-value"].includes(attr)) {
|
|
3838
4351
|
tModel.hasDynamicChildren = true;
|
|
3839
4352
|
}
|
|
4353
|
+
attrs = attrs || {};
|
|
3840
4354
|
attrs[attr] = value;
|
|
3841
4355
|
}
|
|
3842
4356
|
}
|
|
@@ -3987,7 +4501,7 @@ function parseTCall(node, ctx) {
|
|
|
3987
4501
|
if (ast && ast.type === 11 /* TComponent */) {
|
|
3988
4502
|
return {
|
|
3989
4503
|
...ast,
|
|
3990
|
-
slots: { default: { content: tcall } },
|
|
4504
|
+
slots: { default: { content: tcall, scope: null, on: null, attrs: null } },
|
|
3991
4505
|
};
|
|
3992
4506
|
}
|
|
3993
4507
|
}
|
|
@@ -4071,7 +4585,6 @@ function parseTSetNode(node, ctx) {
|
|
|
4071
4585
|
// -----------------------------------------------------------------------------
|
|
4072
4586
|
// Error messages when trying to use an unsupported directive on a component
|
|
4073
4587
|
const directiveErrorMap = new Map([
|
|
4074
|
-
["t-on", "t-on is no longer supported on components. Consider passing a callback in props."],
|
|
4075
4588
|
[
|
|
4076
4589
|
"t-ref",
|
|
4077
4590
|
"t-ref is no longer supported on components. Consider exposing only the public part of the component's API through a callback prop.",
|
|
@@ -4100,18 +4613,26 @@ function parseComponent(node, ctx) {
|
|
|
4100
4613
|
node.removeAttribute("t-props");
|
|
4101
4614
|
const defaultSlotScope = node.getAttribute("t-slot-scope");
|
|
4102
4615
|
node.removeAttribute("t-slot-scope");
|
|
4103
|
-
|
|
4616
|
+
let on = null;
|
|
4617
|
+
let props = null;
|
|
4104
4618
|
for (let name of node.getAttributeNames()) {
|
|
4105
4619
|
const value = node.getAttribute(name);
|
|
4106
4620
|
if (name.startsWith("t-")) {
|
|
4107
|
-
|
|
4108
|
-
|
|
4621
|
+
if (name.startsWith("t-on-")) {
|
|
4622
|
+
on = on || {};
|
|
4623
|
+
on[name.slice(5)] = value;
|
|
4624
|
+
}
|
|
4625
|
+
else {
|
|
4626
|
+
const message = directiveErrorMap.get(name.split("-").slice(0, 2).join("-"));
|
|
4627
|
+
throw new Error(message || `unsupported directive on Component: ${name}`);
|
|
4628
|
+
}
|
|
4109
4629
|
}
|
|
4110
4630
|
else {
|
|
4631
|
+
props = props || {};
|
|
4111
4632
|
props[name] = value;
|
|
4112
4633
|
}
|
|
4113
4634
|
}
|
|
4114
|
-
|
|
4635
|
+
let slots = null;
|
|
4115
4636
|
if (node.hasChildNodes()) {
|
|
4116
4637
|
const clone = node.cloneNode(true);
|
|
4117
4638
|
// named slots
|
|
@@ -4139,32 +4660,36 @@ function parseComponent(node, ctx) {
|
|
|
4139
4660
|
slotNode.remove();
|
|
4140
4661
|
const slotAst = parseNode(slotNode, ctx);
|
|
4141
4662
|
if (slotAst) {
|
|
4142
|
-
|
|
4143
|
-
|
|
4663
|
+
let on = null;
|
|
4664
|
+
let attrs = null;
|
|
4665
|
+
let scope = null;
|
|
4144
4666
|
for (let attributeName of slotNode.getAttributeNames()) {
|
|
4145
4667
|
const value = slotNode.getAttribute(attributeName);
|
|
4146
4668
|
if (attributeName === "t-slot-scope") {
|
|
4147
|
-
|
|
4669
|
+
scope = value;
|
|
4148
4670
|
continue;
|
|
4149
4671
|
}
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4672
|
+
else if (attributeName.startsWith("t-on-")) {
|
|
4673
|
+
on = on || {};
|
|
4674
|
+
on[attributeName.slice(5)] = value;
|
|
4675
|
+
}
|
|
4676
|
+
else {
|
|
4677
|
+
attrs = attrs || {};
|
|
4678
|
+
attrs[attributeName] = value;
|
|
4679
|
+
}
|
|
4154
4680
|
}
|
|
4155
|
-
slots
|
|
4681
|
+
slots = slots || {};
|
|
4682
|
+
slots[name] = { content: slotAst, on, attrs, scope };
|
|
4156
4683
|
}
|
|
4157
4684
|
}
|
|
4158
4685
|
// default slot
|
|
4159
4686
|
const defaultContent = parseChildNodes(clone, ctx);
|
|
4160
4687
|
if (defaultContent) {
|
|
4161
|
-
slots
|
|
4162
|
-
|
|
4163
|
-
slots.default.scope = defaultSlotScope;
|
|
4164
|
-
}
|
|
4688
|
+
slots = slots || {};
|
|
4689
|
+
slots.default = { content: defaultContent, on, attrs: null, scope: defaultSlotScope };
|
|
4165
4690
|
}
|
|
4166
4691
|
}
|
|
4167
|
-
return { type: 11 /* TComponent */, name, isDynamic, dynamicProps, props, slots };
|
|
4692
|
+
return { type: 11 /* TComponent */, name, isDynamic, dynamicProps, props, slots, on };
|
|
4168
4693
|
}
|
|
4169
4694
|
// -----------------------------------------------------------------------------
|
|
4170
4695
|
// Slots
|
|
@@ -4175,15 +4700,24 @@ function parseTSlot(node, ctx) {
|
|
|
4175
4700
|
}
|
|
4176
4701
|
const name = node.getAttribute("t-slot");
|
|
4177
4702
|
node.removeAttribute("t-slot");
|
|
4178
|
-
|
|
4703
|
+
let attrs = null;
|
|
4704
|
+
let on = null;
|
|
4179
4705
|
for (let attributeName of node.getAttributeNames()) {
|
|
4180
4706
|
const value = node.getAttribute(attributeName);
|
|
4181
|
-
|
|
4707
|
+
if (attributeName.startsWith("t-on-")) {
|
|
4708
|
+
on = on || {};
|
|
4709
|
+
on[attributeName.slice(5)] = value;
|
|
4710
|
+
}
|
|
4711
|
+
else {
|
|
4712
|
+
attrs = attrs || {};
|
|
4713
|
+
attrs[attributeName] = value;
|
|
4714
|
+
}
|
|
4182
4715
|
}
|
|
4183
4716
|
return {
|
|
4184
4717
|
type: 14 /* TSlot */,
|
|
4185
4718
|
name,
|
|
4186
4719
|
attrs,
|
|
4720
|
+
on,
|
|
4187
4721
|
defaultContent: parseChildNodes(node, ctx),
|
|
4188
4722
|
};
|
|
4189
4723
|
}
|
|
@@ -4377,108 +4911,112 @@ function compile(template, options = {}) {
|
|
|
4377
4911
|
return new Function("bdom, helpers", code);
|
|
4378
4912
|
}
|
|
4379
4913
|
|
|
4380
|
-
const
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
const
|
|
4384
|
-
const
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
msg +=
|
|
4400
|
-
`\nThe error might be located at xml line ${lineNumber} column ${columnIndex}\n` +
|
|
4401
|
-
`${line}\n${"-".repeat(columnIndex - 1)}^`;
|
|
4402
|
-
}
|
|
4914
|
+
const TIMEOUT = Symbol("timeout");
|
|
4915
|
+
function wrapError(fn, hookName) {
|
|
4916
|
+
const error = new Error(`The following error occurred in ${hookName}: `);
|
|
4917
|
+
const timeoutError = new Error(`${hookName}'s promise hasn't resolved after 3 seconds`);
|
|
4918
|
+
const node = getCurrent();
|
|
4919
|
+
return (...args) => {
|
|
4920
|
+
try {
|
|
4921
|
+
const result = fn(...args);
|
|
4922
|
+
if (result instanceof Promise) {
|
|
4923
|
+
if (hookName === "onWillStart" || hookName === "onWillUpdateProps") {
|
|
4924
|
+
const fiber = node.fiber;
|
|
4925
|
+
Promise.race([
|
|
4926
|
+
result,
|
|
4927
|
+
new Promise((resolve) => setTimeout(() => resolve(TIMEOUT), 3000)),
|
|
4928
|
+
]).then((res) => {
|
|
4929
|
+
if (res === TIMEOUT && node.fiber === fiber) {
|
|
4930
|
+
console.warn(timeoutError);
|
|
4931
|
+
}
|
|
4932
|
+
});
|
|
4403
4933
|
}
|
|
4934
|
+
return result.catch((cause) => {
|
|
4935
|
+
error.cause = cause;
|
|
4936
|
+
if (cause instanceof Error) {
|
|
4937
|
+
error.message += `"${cause.message}"`;
|
|
4938
|
+
}
|
|
4939
|
+
throw error;
|
|
4940
|
+
});
|
|
4404
4941
|
}
|
|
4942
|
+
return result;
|
|
4405
4943
|
}
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
}
|
|
4410
|
-
class TemplateSet {
|
|
4411
|
-
constructor(config = {}) {
|
|
4412
|
-
this.rawTemplates = Object.create(globalTemplates);
|
|
4413
|
-
this.templates = {};
|
|
4414
|
-
this.utils = Object.assign({}, UTILS, {
|
|
4415
|
-
call: (owner, subTemplate, ctx, parent, key) => {
|
|
4416
|
-
const template = this.getTemplate(subTemplate);
|
|
4417
|
-
return toggler(subTemplate, template.call(owner, ctx, parent, key));
|
|
4418
|
-
},
|
|
4419
|
-
getTemplate: (name) => this.getTemplate(name),
|
|
4420
|
-
});
|
|
4421
|
-
this.dev = config.dev || false;
|
|
4422
|
-
this.translateFn = config.translateFn;
|
|
4423
|
-
this.translatableAttributes = config.translatableAttributes;
|
|
4424
|
-
if (config.templates) {
|
|
4425
|
-
this.addTemplates(config.templates);
|
|
4426
|
-
}
|
|
4427
|
-
}
|
|
4428
|
-
addTemplate(name, template, options = {}) {
|
|
4429
|
-
if (name in this.rawTemplates && !options.allowDuplicate) {
|
|
4430
|
-
throw new Error(`Template ${name} already defined`);
|
|
4431
|
-
}
|
|
4432
|
-
this.rawTemplates[name] = template;
|
|
4433
|
-
}
|
|
4434
|
-
addTemplates(xml, options = {}) {
|
|
4435
|
-
if (!xml) {
|
|
4436
|
-
// empty string
|
|
4437
|
-
return;
|
|
4438
|
-
}
|
|
4439
|
-
xml = xml instanceof Document ? xml : parseXML(xml);
|
|
4440
|
-
for (const template of xml.querySelectorAll("[t-name]")) {
|
|
4441
|
-
const name = template.getAttribute("t-name");
|
|
4442
|
-
this.addTemplate(name, template, options);
|
|
4443
|
-
}
|
|
4444
|
-
}
|
|
4445
|
-
getTemplate(name) {
|
|
4446
|
-
if (!(name in this.templates)) {
|
|
4447
|
-
const rawTemplate = this.rawTemplates[name];
|
|
4448
|
-
if (rawTemplate === undefined) {
|
|
4449
|
-
throw new Error(`Missing template: "${name}"`);
|
|
4944
|
+
catch (cause) {
|
|
4945
|
+
if (cause instanceof Error) {
|
|
4946
|
+
error.message += `"${cause.message}"`;
|
|
4450
4947
|
}
|
|
4451
|
-
|
|
4452
|
-
// first add a function to lazily get the template, in case there is a
|
|
4453
|
-
// recursive call to the template name
|
|
4454
|
-
const templates = this.templates;
|
|
4455
|
-
this.templates[name] = function (context, parent) {
|
|
4456
|
-
return templates[name].call(this, context, parent);
|
|
4457
|
-
};
|
|
4458
|
-
const template = templateFn(bdom, this.utils);
|
|
4459
|
-
this.templates[name] = template;
|
|
4948
|
+
throw error;
|
|
4460
4949
|
}
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4950
|
+
};
|
|
4951
|
+
}
|
|
4952
|
+
// -----------------------------------------------------------------------------
|
|
4953
|
+
// hooks
|
|
4954
|
+
// -----------------------------------------------------------------------------
|
|
4955
|
+
function onWillStart(fn) {
|
|
4956
|
+
const node = getCurrent();
|
|
4957
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4958
|
+
node.willStart.push(decorate(fn.bind(node.component), "onWillStart"));
|
|
4959
|
+
}
|
|
4960
|
+
function onWillUpdateProps(fn) {
|
|
4961
|
+
const node = getCurrent();
|
|
4962
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4963
|
+
node.willUpdateProps.push(decorate(fn.bind(node.component), "onWillUpdateProps"));
|
|
4964
|
+
}
|
|
4965
|
+
function onMounted(fn) {
|
|
4966
|
+
const node = getCurrent();
|
|
4967
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4968
|
+
node.mounted.push(decorate(fn.bind(node.component), "onMounted"));
|
|
4969
|
+
}
|
|
4970
|
+
function onWillPatch(fn) {
|
|
4971
|
+
const node = getCurrent();
|
|
4972
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4973
|
+
node.willPatch.unshift(decorate(fn.bind(node.component), "onWillPatch"));
|
|
4471
4974
|
}
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
const name = `__template__${xml.nextId++}`;
|
|
4477
|
-
const value = String.raw(...args);
|
|
4478
|
-
globalTemplates[name] = value;
|
|
4479
|
-
return name;
|
|
4975
|
+
function onPatched(fn) {
|
|
4976
|
+
const node = getCurrent();
|
|
4977
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4978
|
+
node.patched.push(decorate(fn.bind(node.component), "onPatched"));
|
|
4480
4979
|
}
|
|
4481
|
-
|
|
4980
|
+
function onWillUnmount(fn) {
|
|
4981
|
+
const node = getCurrent();
|
|
4982
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4983
|
+
node.willUnmount.unshift(decorate(fn.bind(node.component), "onWillUnmount"));
|
|
4984
|
+
}
|
|
4985
|
+
function onWillDestroy(fn) {
|
|
4986
|
+
const node = getCurrent();
|
|
4987
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4988
|
+
node.willDestroy.push(decorate(fn.bind(node.component), "onWillDestroy"));
|
|
4989
|
+
}
|
|
4990
|
+
function onWillRender(fn) {
|
|
4991
|
+
const node = getCurrent();
|
|
4992
|
+
const renderFn = node.renderFn;
|
|
4993
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4994
|
+
fn = decorate(fn.bind(node.component), "onWillRender");
|
|
4995
|
+
node.renderFn = () => {
|
|
4996
|
+
fn();
|
|
4997
|
+
return renderFn();
|
|
4998
|
+
};
|
|
4999
|
+
}
|
|
5000
|
+
function onRendered(fn) {
|
|
5001
|
+
const node = getCurrent();
|
|
5002
|
+
const renderFn = node.renderFn;
|
|
5003
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
5004
|
+
fn = decorate(fn.bind(node.component), "onRendered");
|
|
5005
|
+
node.renderFn = () => {
|
|
5006
|
+
const result = renderFn();
|
|
5007
|
+
fn();
|
|
5008
|
+
return result;
|
|
5009
|
+
};
|
|
5010
|
+
}
|
|
5011
|
+
function onError(callback) {
|
|
5012
|
+
const node = getCurrent();
|
|
5013
|
+
let handlers = nodeErrorHandlers.get(node);
|
|
5014
|
+
if (!handlers) {
|
|
5015
|
+
handlers = [];
|
|
5016
|
+
nodeErrorHandlers.set(node, handlers);
|
|
5017
|
+
}
|
|
5018
|
+
handlers.push(callback.bind(node.component));
|
|
5019
|
+
}
|
|
4482
5020
|
|
|
4483
5021
|
class Component {
|
|
4484
5022
|
constructor(props, env, node) {
|
|
@@ -4487,8 +5025,8 @@ class Component {
|
|
|
4487
5025
|
this.__owl__ = node;
|
|
4488
5026
|
}
|
|
4489
5027
|
setup() { }
|
|
4490
|
-
render() {
|
|
4491
|
-
this.__owl__.render();
|
|
5028
|
+
render(deep = false) {
|
|
5029
|
+
this.__owl__.render(deep);
|
|
4492
5030
|
}
|
|
4493
5031
|
}
|
|
4494
5032
|
Component.template = "";
|
|
@@ -4557,75 +5095,293 @@ Portal.props = {
|
|
|
4557
5095
|
slots: true,
|
|
4558
5096
|
};
|
|
4559
5097
|
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
5098
|
+
/**
|
|
5099
|
+
* This file contains utility functions that will be injected in each template,
|
|
5100
|
+
* to perform various useful tasks in the compiled code.
|
|
5101
|
+
*/
|
|
5102
|
+
function withDefault(value, defaultValue) {
|
|
5103
|
+
return value === undefined || value === null || value === false ? defaultValue : value;
|
|
5104
|
+
}
|
|
5105
|
+
function callSlot(ctx, parent, key, name, dynamic, extra, defaultContent) {
|
|
5106
|
+
key = key + "__slot_" + name;
|
|
5107
|
+
const slots = ctx.props[TARGET].slots || {};
|
|
5108
|
+
const { __render, __ctx, __scope } = slots[name] || {};
|
|
5109
|
+
const slotScope = Object.create(__ctx || {});
|
|
5110
|
+
if (__scope) {
|
|
5111
|
+
slotScope[__scope] = extra;
|
|
5112
|
+
}
|
|
5113
|
+
const slotBDom = __render ? __render.call(__ctx.__owl__.component, slotScope, parent, key) : null;
|
|
5114
|
+
if (defaultContent) {
|
|
5115
|
+
let child1 = undefined;
|
|
5116
|
+
let child2 = undefined;
|
|
5117
|
+
if (slotBDom) {
|
|
5118
|
+
child1 = dynamic ? toggler(name, slotBDom) : slotBDom;
|
|
5119
|
+
}
|
|
5120
|
+
else {
|
|
5121
|
+
child2 = defaultContent.call(ctx.__owl__.component, ctx, parent, key);
|
|
5122
|
+
}
|
|
5123
|
+
return multi([child1, child2]);
|
|
5124
|
+
}
|
|
5125
|
+
return slotBDom || text("");
|
|
5126
|
+
}
|
|
5127
|
+
function capture(ctx) {
|
|
5128
|
+
const component = ctx.__owl__.component;
|
|
5129
|
+
const result = Object.create(component);
|
|
5130
|
+
for (let k in ctx) {
|
|
5131
|
+
result[k] = ctx[k];
|
|
5132
|
+
}
|
|
5133
|
+
return result;
|
|
5134
|
+
}
|
|
5135
|
+
function withKey(elem, k) {
|
|
5136
|
+
elem.key = k;
|
|
5137
|
+
return elem;
|
|
5138
|
+
}
|
|
5139
|
+
function prepareList(collection) {
|
|
5140
|
+
let keys;
|
|
5141
|
+
let values;
|
|
5142
|
+
if (Array.isArray(collection)) {
|
|
5143
|
+
keys = collection;
|
|
5144
|
+
values = collection;
|
|
5145
|
+
}
|
|
5146
|
+
else if (collection) {
|
|
5147
|
+
values = Object.keys(collection);
|
|
5148
|
+
keys = Object.values(collection);
|
|
5149
|
+
}
|
|
5150
|
+
else {
|
|
5151
|
+
throw new Error("Invalid loop expression");
|
|
5152
|
+
}
|
|
5153
|
+
const n = values.length;
|
|
5154
|
+
return [keys, values, n, new Array(n)];
|
|
5155
|
+
}
|
|
5156
|
+
const isBoundary = Symbol("isBoundary");
|
|
5157
|
+
function setContextValue(ctx, key, value) {
|
|
5158
|
+
const ctx0 = ctx;
|
|
5159
|
+
while (!ctx.hasOwnProperty(key) && !ctx.hasOwnProperty(isBoundary)) {
|
|
5160
|
+
const newCtx = ctx.__proto__;
|
|
5161
|
+
if (!newCtx) {
|
|
5162
|
+
ctx = ctx0;
|
|
5163
|
+
break;
|
|
5164
|
+
}
|
|
5165
|
+
ctx = newCtx;
|
|
5166
|
+
}
|
|
5167
|
+
ctx[key] = value;
|
|
5168
|
+
}
|
|
5169
|
+
function toNumber(val) {
|
|
5170
|
+
const n = parseFloat(val);
|
|
5171
|
+
return isNaN(n) ? val : n;
|
|
5172
|
+
}
|
|
5173
|
+
function shallowEqual(l1, l2) {
|
|
5174
|
+
for (let i = 0, l = l1.length; i < l; i++) {
|
|
5175
|
+
if (l1[i] !== l2[i]) {
|
|
5176
|
+
return false;
|
|
5177
|
+
}
|
|
5178
|
+
}
|
|
5179
|
+
return true;
|
|
5180
|
+
}
|
|
5181
|
+
class LazyValue {
|
|
5182
|
+
constructor(fn, ctx, node) {
|
|
5183
|
+
this.fn = fn;
|
|
5184
|
+
this.ctx = capture(ctx);
|
|
5185
|
+
this.node = node;
|
|
5186
|
+
}
|
|
5187
|
+
evaluate() {
|
|
5188
|
+
return this.fn(this.ctx, this.node);
|
|
5189
|
+
}
|
|
5190
|
+
toString() {
|
|
5191
|
+
return this.evaluate().toString();
|
|
5192
|
+
}
|
|
5193
|
+
}
|
|
5194
|
+
/*
|
|
5195
|
+
* Safely outputs `value` as a block depending on the nature of `value`
|
|
5196
|
+
*/
|
|
5197
|
+
function safeOutput(value) {
|
|
5198
|
+
if (!value) {
|
|
5199
|
+
return value;
|
|
5200
|
+
}
|
|
5201
|
+
let safeKey;
|
|
5202
|
+
let block;
|
|
5203
|
+
if (value instanceof Markup) {
|
|
5204
|
+
safeKey = `string_safe`;
|
|
5205
|
+
block = html(value);
|
|
5206
|
+
}
|
|
5207
|
+
else if (value instanceof LazyValue) {
|
|
5208
|
+
safeKey = `lazy_value`;
|
|
5209
|
+
block = value.evaluate();
|
|
5210
|
+
}
|
|
5211
|
+
else if (value instanceof String || typeof value === "string") {
|
|
5212
|
+
safeKey = "string_unsafe";
|
|
5213
|
+
block = text(value);
|
|
5214
|
+
}
|
|
5215
|
+
else {
|
|
5216
|
+
// Assuming it is a block
|
|
5217
|
+
safeKey = "block_safe";
|
|
5218
|
+
block = value;
|
|
5219
|
+
}
|
|
5220
|
+
return toggler(safeKey, block);
|
|
5221
|
+
}
|
|
5222
|
+
let boundFunctions = new WeakMap();
|
|
5223
|
+
function bind(ctx, fn) {
|
|
5224
|
+
let component = ctx.__owl__.component;
|
|
5225
|
+
let boundFnMap = boundFunctions.get(component);
|
|
5226
|
+
if (!boundFnMap) {
|
|
5227
|
+
boundFnMap = new WeakMap();
|
|
5228
|
+
boundFunctions.set(component, boundFnMap);
|
|
5229
|
+
}
|
|
5230
|
+
let boundFn = boundFnMap.get(fn);
|
|
5231
|
+
if (!boundFn) {
|
|
5232
|
+
boundFn = fn.bind(component);
|
|
5233
|
+
boundFnMap.set(fn, boundFn);
|
|
5234
|
+
}
|
|
5235
|
+
return boundFn;
|
|
5236
|
+
}
|
|
5237
|
+
function multiRefSetter(refs, name) {
|
|
5238
|
+
let count = 0;
|
|
5239
|
+
return (el) => {
|
|
5240
|
+
if (el) {
|
|
5241
|
+
count++;
|
|
5242
|
+
if (count > 1) {
|
|
5243
|
+
throw new Error("Cannot have 2 elements with same ref name at the same time");
|
|
5244
|
+
}
|
|
5245
|
+
}
|
|
5246
|
+
if (count === 0 || el) {
|
|
5247
|
+
refs[name] = el;
|
|
5248
|
+
}
|
|
5249
|
+
};
|
|
5250
|
+
}
|
|
5251
|
+
const helpers = {
|
|
5252
|
+
withDefault,
|
|
5253
|
+
zero: Symbol("zero"),
|
|
5254
|
+
isBoundary,
|
|
5255
|
+
callSlot,
|
|
5256
|
+
capture,
|
|
5257
|
+
withKey,
|
|
5258
|
+
prepareList,
|
|
5259
|
+
setContextValue,
|
|
5260
|
+
multiRefSetter,
|
|
5261
|
+
shallowEqual,
|
|
5262
|
+
toNumber,
|
|
5263
|
+
validateProps,
|
|
5264
|
+
LazyValue,
|
|
5265
|
+
safeOutput,
|
|
5266
|
+
bind,
|
|
5267
|
+
createCatcher,
|
|
5268
|
+
};
|
|
5269
|
+
|
|
5270
|
+
const bdom = { text, createBlock, list, multi, html, toggler, component, comment };
|
|
5271
|
+
function parseXML(xml) {
|
|
5272
|
+
const parser = new DOMParser();
|
|
5273
|
+
const doc = parser.parseFromString(xml, "text/xml");
|
|
5274
|
+
if (doc.getElementsByTagName("parsererror").length) {
|
|
5275
|
+
let msg = "Invalid XML in template.";
|
|
5276
|
+
const parsererrorText = doc.getElementsByTagName("parsererror")[0].textContent;
|
|
5277
|
+
if (parsererrorText) {
|
|
5278
|
+
msg += "\nThe parser has produced the following error message:\n" + parsererrorText;
|
|
5279
|
+
const re = /\d+/g;
|
|
5280
|
+
const firstMatch = re.exec(parsererrorText);
|
|
5281
|
+
if (firstMatch) {
|
|
5282
|
+
const lineNumber = Number(firstMatch[0]);
|
|
5283
|
+
const line = xml.split("\n")[lineNumber - 1];
|
|
5284
|
+
const secondMatch = re.exec(parsererrorText);
|
|
5285
|
+
if (line && secondMatch) {
|
|
5286
|
+
const columnIndex = Number(secondMatch[0]) - 1;
|
|
5287
|
+
if (line[columnIndex]) {
|
|
5288
|
+
msg +=
|
|
5289
|
+
`\nThe error might be located at xml line ${lineNumber} column ${columnIndex}\n` +
|
|
5290
|
+
`${line}\n${"-".repeat(columnIndex - 1)}^`;
|
|
5291
|
+
}
|
|
5292
|
+
}
|
|
5293
|
+
}
|
|
5294
|
+
}
|
|
5295
|
+
throw new Error(msg);
|
|
4568
5296
|
}
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
5297
|
+
return doc;
|
|
5298
|
+
}
|
|
5299
|
+
/**
|
|
5300
|
+
* Returns the helpers object that will be injected in each template closure
|
|
5301
|
+
* function
|
|
5302
|
+
*/
|
|
5303
|
+
function makeHelpers(getTemplate) {
|
|
5304
|
+
return Object.assign({}, helpers, {
|
|
5305
|
+
Portal,
|
|
5306
|
+
markRaw,
|
|
5307
|
+
getTemplate,
|
|
5308
|
+
call: (owner, subTemplate, ctx, parent, key) => {
|
|
5309
|
+
const template = getTemplate(subTemplate);
|
|
5310
|
+
return toggler(subTemplate, template.call(owner, ctx, parent, key));
|
|
5311
|
+
},
|
|
5312
|
+
});
|
|
5313
|
+
}
|
|
5314
|
+
class TemplateSet {
|
|
5315
|
+
constructor(config = {}) {
|
|
5316
|
+
this.rawTemplates = Object.create(globalTemplates);
|
|
5317
|
+
this.templates = {};
|
|
5318
|
+
this.dev = config.dev || false;
|
|
5319
|
+
this.translateFn = config.translateFn;
|
|
5320
|
+
this.translatableAttributes = config.translatableAttributes;
|
|
5321
|
+
if (config.templates) {
|
|
5322
|
+
this.addTemplates(config.templates);
|
|
5323
|
+
}
|
|
5324
|
+
this.helpers = makeHelpers(this.getTemplate.bind(this));
|
|
4572
5325
|
}
|
|
4573
|
-
|
|
4574
|
-
this.
|
|
5326
|
+
addTemplate(name, template, options = {}) {
|
|
5327
|
+
if (name in this.rawTemplates && !options.allowDuplicate) {
|
|
5328
|
+
throw new Error(`Template ${name} already defined`);
|
|
5329
|
+
}
|
|
5330
|
+
this.rawTemplates[name] = template;
|
|
4575
5331
|
}
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
5332
|
+
addTemplates(xml, options = {}) {
|
|
5333
|
+
if (!xml) {
|
|
5334
|
+
// empty string
|
|
5335
|
+
return;
|
|
5336
|
+
}
|
|
5337
|
+
xml = xml instanceof Document ? xml : parseXML(xml);
|
|
5338
|
+
for (const template of xml.querySelectorAll("[t-name]")) {
|
|
5339
|
+
const name = template.getAttribute("t-name");
|
|
5340
|
+
this.addTemplate(name, template, options);
|
|
4580
5341
|
}
|
|
4581
5342
|
}
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
return;
|
|
4591
|
-
}
|
|
4592
|
-
const hasError = fibersInError.has(fiber);
|
|
4593
|
-
if (hasError && fiber.counter !== 0) {
|
|
4594
|
-
this.tasks.delete(fiber);
|
|
4595
|
-
return;
|
|
4596
|
-
}
|
|
4597
|
-
if (fiber.node.status === 2 /* DESTROYED */) {
|
|
4598
|
-
this.tasks.delete(fiber);
|
|
4599
|
-
return;
|
|
4600
|
-
}
|
|
4601
|
-
if (fiber.counter === 0) {
|
|
4602
|
-
if (!hasError) {
|
|
4603
|
-
fiber.complete();
|
|
5343
|
+
getTemplate(name) {
|
|
5344
|
+
if (!(name in this.templates)) {
|
|
5345
|
+
const rawTemplate = this.rawTemplates[name];
|
|
5346
|
+
if (rawTemplate === undefined) {
|
|
5347
|
+
let extraInfo = "";
|
|
5348
|
+
try {
|
|
5349
|
+
const componentName = getCurrent().component.constructor.name;
|
|
5350
|
+
extraInfo = ` (for component "${componentName}")`;
|
|
4604
5351
|
}
|
|
4605
|
-
|
|
5352
|
+
catch { }
|
|
5353
|
+
throw new Error(`Missing template: "${name}"${extraInfo}`);
|
|
4606
5354
|
}
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
5355
|
+
const templateFn = this._compileTemplate(name, rawTemplate);
|
|
5356
|
+
// first add a function to lazily get the template, in case there is a
|
|
5357
|
+
// recursive call to the template name
|
|
5358
|
+
const templates = this.templates;
|
|
5359
|
+
this.templates[name] = function (context, parent) {
|
|
5360
|
+
return templates[name].call(this, context, parent);
|
|
5361
|
+
};
|
|
5362
|
+
const template = templateFn(bdom, this.helpers);
|
|
5363
|
+
this.templates[name] = template;
|
|
4610
5364
|
}
|
|
5365
|
+
return this.templates[name];
|
|
4611
5366
|
}
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
5367
|
+
_compileTemplate(name, template) {
|
|
5368
|
+
return compile(template, {
|
|
5369
|
+
name,
|
|
5370
|
+
dev: this.dev,
|
|
5371
|
+
translateFn: this.translateFn,
|
|
5372
|
+
translatableAttributes: this.translatableAttributes,
|
|
4618
5373
|
});
|
|
4619
5374
|
}
|
|
4620
|
-
}
|
|
4621
|
-
// capture the value of requestAnimationFrame as soon as possible, to avoid
|
|
4622
|
-
// interactions with other code, such as test frameworks that override them
|
|
4623
|
-
Scheduler.requestAnimationFrame = window.requestAnimationFrame.bind(window);
|
|
5375
|
+
}
|
|
4624
5376
|
|
|
4625
|
-
|
|
5377
|
+
let hasBeenLogged = false;
|
|
5378
|
+
const DEV_MSG = () => {
|
|
5379
|
+
const hash = window.owl ? window.owl.__info__.hash : "master";
|
|
5380
|
+
return `Owl is running in 'dev' mode.
|
|
4626
5381
|
|
|
4627
5382
|
This is not suitable for production use.
|
|
4628
|
-
See https://github.com/odoo/owl/blob/
|
|
5383
|
+
See https://github.com/odoo/owl/blob/${hash}/doc/reference/app.md#configuration for more information.`;
|
|
5384
|
+
};
|
|
4629
5385
|
class App extends TemplateSet {
|
|
4630
5386
|
constructor(Root, config = {}) {
|
|
4631
5387
|
super(config);
|
|
@@ -4635,11 +5391,13 @@ class App extends TemplateSet {
|
|
|
4635
5391
|
if (config.test) {
|
|
4636
5392
|
this.dev = true;
|
|
4637
5393
|
}
|
|
4638
|
-
if (this.dev && !config.test) {
|
|
4639
|
-
console.info(DEV_MSG);
|
|
5394
|
+
if (this.dev && !config.test && !hasBeenLogged) {
|
|
5395
|
+
console.info(DEV_MSG());
|
|
5396
|
+
hasBeenLogged = true;
|
|
4640
5397
|
}
|
|
4641
|
-
const
|
|
4642
|
-
|
|
5398
|
+
const env = config.env || {};
|
|
5399
|
+
const descrs = Object.getOwnPropertyDescriptors(env);
|
|
5400
|
+
this.env = Object.freeze(Object.create(Object.getPrototypeOf(env), descrs));
|
|
4643
5401
|
this.props = config.props || {};
|
|
4644
5402
|
}
|
|
4645
5403
|
mount(target, options) {
|
|
@@ -4682,6 +5440,7 @@ class App extends TemplateSet {
|
|
|
4682
5440
|
}
|
|
4683
5441
|
destroy() {
|
|
4684
5442
|
if (this.root) {
|
|
5443
|
+
this.scheduler.flush();
|
|
4685
5444
|
this.root.destroy();
|
|
4686
5445
|
}
|
|
4687
5446
|
}
|
|
@@ -4702,270 +5461,6 @@ function status(component) {
|
|
|
4702
5461
|
}
|
|
4703
5462
|
}
|
|
4704
5463
|
|
|
4705
|
-
class Memo extends Component {
|
|
4706
|
-
constructor(props, env, node) {
|
|
4707
|
-
super(props, env, node);
|
|
4708
|
-
// prevent patching process conditionally
|
|
4709
|
-
let applyPatch = false;
|
|
4710
|
-
const patchFn = node.patch;
|
|
4711
|
-
node.patch = () => {
|
|
4712
|
-
if (applyPatch) {
|
|
4713
|
-
patchFn.call(node);
|
|
4714
|
-
applyPatch = false;
|
|
4715
|
-
}
|
|
4716
|
-
};
|
|
4717
|
-
// check props change, and render/apply patch if it changed
|
|
4718
|
-
let prevProps = props;
|
|
4719
|
-
const updateAndRender = node.updateAndRender;
|
|
4720
|
-
node.updateAndRender = function (props, parentFiber) {
|
|
4721
|
-
const shouldUpdate = !shallowEqual(prevProps, props);
|
|
4722
|
-
if (shouldUpdate) {
|
|
4723
|
-
prevProps = props;
|
|
4724
|
-
updateAndRender.call(node, props, parentFiber);
|
|
4725
|
-
applyPatch = true;
|
|
4726
|
-
}
|
|
4727
|
-
return Promise.resolve();
|
|
4728
|
-
};
|
|
4729
|
-
}
|
|
4730
|
-
}
|
|
4731
|
-
Memo.template = xml `<t t-slot="default"/>`;
|
|
4732
|
-
/**
|
|
4733
|
-
* we assume that each object have the same set of keys
|
|
4734
|
-
*/
|
|
4735
|
-
function shallowEqual(p1, p2) {
|
|
4736
|
-
for (let k in p1) {
|
|
4737
|
-
if (k !== "slots" && p1[k] !== p2[k]) {
|
|
4738
|
-
return false;
|
|
4739
|
-
}
|
|
4740
|
-
}
|
|
4741
|
-
return true;
|
|
4742
|
-
}
|
|
4743
|
-
|
|
4744
|
-
// Allows to get the target of a Reactive (used for making a new Reactive from the underlying object)
|
|
4745
|
-
const TARGET = Symbol("Target");
|
|
4746
|
-
// Escape hatch to prevent reactivity system to turn something into a reactive
|
|
4747
|
-
const SKIP = Symbol("Skip");
|
|
4748
|
-
// Special key to subscribe to, to be notified of key creation/deletion
|
|
4749
|
-
const KEYCHANGES = Symbol("Key changes");
|
|
4750
|
-
const objectToString = Object.prototype.toString;
|
|
4751
|
-
/**
|
|
4752
|
-
* Checks whether a given value can be made into a reactive object.
|
|
4753
|
-
*
|
|
4754
|
-
* @param value the value to check
|
|
4755
|
-
* @returns whether the value can be made reactive
|
|
4756
|
-
*/
|
|
4757
|
-
function canBeMadeReactive(value) {
|
|
4758
|
-
if (typeof value !== "object") {
|
|
4759
|
-
return false;
|
|
4760
|
-
}
|
|
4761
|
-
// extract "RawType" from strings like "[object RawType]" => this lets us
|
|
4762
|
-
// ignore many native objects such as Promise (whose toString is [object Promise])
|
|
4763
|
-
// or Date ([object Date]).
|
|
4764
|
-
const rawType = objectToString.call(value).slice(8, -1);
|
|
4765
|
-
return rawType === "Object" || rawType === "Array";
|
|
4766
|
-
}
|
|
4767
|
-
/**
|
|
4768
|
-
* Mark an object or array so that it is ignored by the reactivity system
|
|
4769
|
-
*
|
|
4770
|
-
* @param value the value to mark
|
|
4771
|
-
* @returns the object itself
|
|
4772
|
-
*/
|
|
4773
|
-
function markRaw(value) {
|
|
4774
|
-
value[SKIP] = true;
|
|
4775
|
-
return value;
|
|
4776
|
-
}
|
|
4777
|
-
/**
|
|
4778
|
-
* Given a reactive objet, return the raw (non reactive) underlying object
|
|
4779
|
-
*
|
|
4780
|
-
* @param value a reactive value
|
|
4781
|
-
* @returns the underlying value
|
|
4782
|
-
*/
|
|
4783
|
-
function toRaw(value) {
|
|
4784
|
-
return value[TARGET];
|
|
4785
|
-
}
|
|
4786
|
-
const targetToKeysToCallbacks = new WeakMap();
|
|
4787
|
-
/**
|
|
4788
|
-
* Observes a given key on a target with an callback. The callback will be
|
|
4789
|
-
* called when the given key changes on the target.
|
|
4790
|
-
*
|
|
4791
|
-
* @param target the target whose key should be observed
|
|
4792
|
-
* @param key the key to observe (or Symbol(KEYCHANGES) for key creation
|
|
4793
|
-
* or deletion)
|
|
4794
|
-
* @param callback the function to call when the key changes
|
|
4795
|
-
*/
|
|
4796
|
-
function observeTargetKey(target, key, callback) {
|
|
4797
|
-
if (!targetToKeysToCallbacks.get(target)) {
|
|
4798
|
-
targetToKeysToCallbacks.set(target, new Map());
|
|
4799
|
-
}
|
|
4800
|
-
const keyToCallbacks = targetToKeysToCallbacks.get(target);
|
|
4801
|
-
if (!keyToCallbacks.get(key)) {
|
|
4802
|
-
keyToCallbacks.set(key, new Set());
|
|
4803
|
-
}
|
|
4804
|
-
keyToCallbacks.get(key).add(callback);
|
|
4805
|
-
if (!callbacksToTargets.has(callback)) {
|
|
4806
|
-
callbacksToTargets.set(callback, new Set());
|
|
4807
|
-
}
|
|
4808
|
-
callbacksToTargets.get(callback).add(target);
|
|
4809
|
-
}
|
|
4810
|
-
/**
|
|
4811
|
-
* Notify Reactives that are observing a given target that a key has changed on
|
|
4812
|
-
* the target.
|
|
4813
|
-
*
|
|
4814
|
-
* @param target target whose Reactives should be notified that the target was
|
|
4815
|
-
* changed.
|
|
4816
|
-
* @param key the key that changed (or Symbol `KEYCHANGES` if a key was created
|
|
4817
|
-
* or deleted)
|
|
4818
|
-
*/
|
|
4819
|
-
function notifyReactives(target, key) {
|
|
4820
|
-
const keyToCallbacks = targetToKeysToCallbacks.get(target);
|
|
4821
|
-
if (!keyToCallbacks) {
|
|
4822
|
-
return;
|
|
4823
|
-
}
|
|
4824
|
-
const callbacks = keyToCallbacks.get(key);
|
|
4825
|
-
if (!callbacks) {
|
|
4826
|
-
return;
|
|
4827
|
-
}
|
|
4828
|
-
// Loop on copy because clearReactivesForCallback will modify the set in place
|
|
4829
|
-
for (const callback of [...callbacks]) {
|
|
4830
|
-
clearReactivesForCallback(callback);
|
|
4831
|
-
callback();
|
|
4832
|
-
}
|
|
4833
|
-
}
|
|
4834
|
-
const callbacksToTargets = new WeakMap();
|
|
4835
|
-
/**
|
|
4836
|
-
* Clears all subscriptions of the Reactives associated with a given callback.
|
|
4837
|
-
*
|
|
4838
|
-
* @param callback the callback for which the reactives need to be cleared
|
|
4839
|
-
*/
|
|
4840
|
-
function clearReactivesForCallback(callback) {
|
|
4841
|
-
const targetsToClear = callbacksToTargets.get(callback);
|
|
4842
|
-
if (!targetsToClear) {
|
|
4843
|
-
return;
|
|
4844
|
-
}
|
|
4845
|
-
for (const target of targetsToClear) {
|
|
4846
|
-
const observedKeys = targetToKeysToCallbacks.get(target);
|
|
4847
|
-
if (!observedKeys) {
|
|
4848
|
-
continue;
|
|
4849
|
-
}
|
|
4850
|
-
for (const callbacks of observedKeys.values()) {
|
|
4851
|
-
callbacks.delete(callback);
|
|
4852
|
-
}
|
|
4853
|
-
}
|
|
4854
|
-
targetsToClear.clear();
|
|
4855
|
-
}
|
|
4856
|
-
const reactiveCache = new WeakMap();
|
|
4857
|
-
/**
|
|
4858
|
-
* Creates a reactive proxy for an object. Reading data on the reactive object
|
|
4859
|
-
* subscribes to changes to the data. Writing data on the object will cause the
|
|
4860
|
-
* notify callback to be called if there are suscriptions to that data. Nested
|
|
4861
|
-
* objects and arrays are automatically made reactive as well.
|
|
4862
|
-
*
|
|
4863
|
-
* Whenever you are notified of a change, all subscriptions are cleared, and if
|
|
4864
|
-
* you would like to be notified of any further changes, you should go read
|
|
4865
|
-
* the underlying data again. We assume that if you don't go read it again after
|
|
4866
|
-
* being notified, it means that you are no longer interested in that data.
|
|
4867
|
-
*
|
|
4868
|
-
* Subscriptions:
|
|
4869
|
-
* + Reading a property on an object will subscribe you to changes in the value
|
|
4870
|
-
* of that property.
|
|
4871
|
-
* + Accessing an object keys (eg with Object.keys or with `for..in`) will
|
|
4872
|
-
* subscribe you to the creation/deletion of keys. Checking the presence of a
|
|
4873
|
-
* key on the object with 'in' has the same effect.
|
|
4874
|
-
* - getOwnPropertyDescriptor does not currently subscribe you to the property.
|
|
4875
|
-
* This is a choice that was made because changing a key's value will trigger
|
|
4876
|
-
* this trap and we do not want to subscribe by writes. This also means that
|
|
4877
|
-
* Object.hasOwnProperty doesn't subscribe as it goes through this trap.
|
|
4878
|
-
*
|
|
4879
|
-
* @param target the object for which to create a reactive proxy
|
|
4880
|
-
* @param callback the function to call when an observed property of the
|
|
4881
|
-
* reactive has changed
|
|
4882
|
-
* @returns a proxy that tracks changes to it
|
|
4883
|
-
*/
|
|
4884
|
-
function reactive(target, callback = () => { }) {
|
|
4885
|
-
if (!canBeMadeReactive(target)) {
|
|
4886
|
-
throw new Error(`Cannot make the given value reactive`);
|
|
4887
|
-
}
|
|
4888
|
-
if (SKIP in target) {
|
|
4889
|
-
return target;
|
|
4890
|
-
}
|
|
4891
|
-
const originalTarget = target[TARGET];
|
|
4892
|
-
if (originalTarget) {
|
|
4893
|
-
return reactive(originalTarget, callback);
|
|
4894
|
-
}
|
|
4895
|
-
if (!reactiveCache.has(target)) {
|
|
4896
|
-
reactiveCache.set(target, new Map());
|
|
4897
|
-
}
|
|
4898
|
-
const reactivesForTarget = reactiveCache.get(target);
|
|
4899
|
-
if (!reactivesForTarget.has(callback)) {
|
|
4900
|
-
const proxy = new Proxy(target, {
|
|
4901
|
-
get(target, key, proxy) {
|
|
4902
|
-
if (key === TARGET) {
|
|
4903
|
-
return target;
|
|
4904
|
-
}
|
|
4905
|
-
observeTargetKey(target, key, callback);
|
|
4906
|
-
const value = Reflect.get(target, key, proxy);
|
|
4907
|
-
if (!canBeMadeReactive(value)) {
|
|
4908
|
-
return value;
|
|
4909
|
-
}
|
|
4910
|
-
return reactive(value, callback);
|
|
4911
|
-
},
|
|
4912
|
-
set(target, key, value, proxy) {
|
|
4913
|
-
const isNewKey = !Object.hasOwnProperty.call(target, key);
|
|
4914
|
-
const originalValue = Reflect.get(target, key, proxy);
|
|
4915
|
-
const ret = Reflect.set(target, key, value, proxy);
|
|
4916
|
-
if (isNewKey) {
|
|
4917
|
-
notifyReactives(target, KEYCHANGES);
|
|
4918
|
-
}
|
|
4919
|
-
// While Array length may trigger the set trap, it's not actually set by this
|
|
4920
|
-
// method but is updated behind the scenes, and the trap is not called with the
|
|
4921
|
-
// new value. We disable the "same-value-optimization" for it because of that.
|
|
4922
|
-
if (originalValue !== value || (Array.isArray(target) && key === "length")) {
|
|
4923
|
-
notifyReactives(target, key);
|
|
4924
|
-
}
|
|
4925
|
-
return ret;
|
|
4926
|
-
},
|
|
4927
|
-
deleteProperty(target, key) {
|
|
4928
|
-
const ret = Reflect.deleteProperty(target, key);
|
|
4929
|
-
notifyReactives(target, KEYCHANGES);
|
|
4930
|
-
notifyReactives(target, key);
|
|
4931
|
-
return ret;
|
|
4932
|
-
},
|
|
4933
|
-
ownKeys(target) {
|
|
4934
|
-
observeTargetKey(target, KEYCHANGES, callback);
|
|
4935
|
-
return Reflect.ownKeys(target);
|
|
4936
|
-
},
|
|
4937
|
-
has(target, key) {
|
|
4938
|
-
// TODO: this observes all key changes instead of only the presence of the argument key
|
|
4939
|
-
observeTargetKey(target, KEYCHANGES, callback);
|
|
4940
|
-
return Reflect.has(target, key);
|
|
4941
|
-
},
|
|
4942
|
-
});
|
|
4943
|
-
reactivesForTarget.set(callback, proxy);
|
|
4944
|
-
}
|
|
4945
|
-
return reactivesForTarget.get(callback);
|
|
4946
|
-
}
|
|
4947
|
-
const batchedRenderFunctions = new WeakMap();
|
|
4948
|
-
/**
|
|
4949
|
-
* Creates a reactive object that will be observed by the current component.
|
|
4950
|
-
* Reading data from the returned object (eg during rendering) will cause the
|
|
4951
|
-
* component to subscribe to that data and be rerendered when it changes.
|
|
4952
|
-
*
|
|
4953
|
-
* @param state the state to observe
|
|
4954
|
-
* @returns a reactive object that will cause the component to re-render on
|
|
4955
|
-
* relevant changes
|
|
4956
|
-
* @see reactive
|
|
4957
|
-
*/
|
|
4958
|
-
function useState(state) {
|
|
4959
|
-
const node = getCurrent();
|
|
4960
|
-
if (!batchedRenderFunctions.has(node)) {
|
|
4961
|
-
batchedRenderFunctions.set(node, batched(() => node.render()));
|
|
4962
|
-
onWillDestroy(() => clearReactivesForCallback(render));
|
|
4963
|
-
}
|
|
4964
|
-
const render = batchedRenderFunctions.get(node);
|
|
4965
|
-
const reactiveState = reactive(state, render);
|
|
4966
|
-
return reactiveState;
|
|
4967
|
-
}
|
|
4968
|
-
|
|
4969
5464
|
// -----------------------------------------------------------------------------
|
|
4970
5465
|
// useRef
|
|
4971
5466
|
// -----------------------------------------------------------------------------
|
|
@@ -5011,10 +5506,6 @@ function useChildSubEnv(envExtension) {
|
|
|
5011
5506
|
const node = getCurrent();
|
|
5012
5507
|
node.childEnv = extendEnv(node.childEnv, envExtension);
|
|
5013
5508
|
}
|
|
5014
|
-
// -----------------------------------------------------------------------------
|
|
5015
|
-
// useEffect
|
|
5016
|
-
// -----------------------------------------------------------------------------
|
|
5017
|
-
const NO_OP = () => { };
|
|
5018
5509
|
/**
|
|
5019
5510
|
* This hook will run a callback when a component is mounted and patched, and
|
|
5020
5511
|
* will run a cleanup function before patching and before unmounting the
|
|
@@ -5032,18 +5523,20 @@ function useEffect(effect, computeDependencies = () => [NaN]) {
|
|
|
5032
5523
|
let dependencies;
|
|
5033
5524
|
onMounted(() => {
|
|
5034
5525
|
dependencies = computeDependencies();
|
|
5035
|
-
cleanup = effect(...dependencies)
|
|
5526
|
+
cleanup = effect(...dependencies);
|
|
5036
5527
|
});
|
|
5037
5528
|
onPatched(() => {
|
|
5038
5529
|
const newDeps = computeDependencies();
|
|
5039
5530
|
const shouldReapply = newDeps.some((val, i) => val !== dependencies[i]);
|
|
5040
5531
|
if (shouldReapply) {
|
|
5041
5532
|
dependencies = newDeps;
|
|
5042
|
-
cleanup
|
|
5043
|
-
|
|
5533
|
+
if (cleanup) {
|
|
5534
|
+
cleanup();
|
|
5535
|
+
}
|
|
5536
|
+
cleanup = effect(...dependencies);
|
|
5044
5537
|
}
|
|
5045
5538
|
});
|
|
5046
|
-
onWillUnmount(() => cleanup());
|
|
5539
|
+
onWillUnmount(() => cleanup && cleanup());
|
|
5047
5540
|
}
|
|
5048
5541
|
// -----------------------------------------------------------------------------
|
|
5049
5542
|
// useExternalListener
|
|
@@ -5070,7 +5563,6 @@ function useExternalListener(target, eventName, handler, eventParams) {
|
|
|
5070
5563
|
|
|
5071
5564
|
config.shouldNormalizeDom = false;
|
|
5072
5565
|
config.mainEventHandler = mainEventHandler;
|
|
5073
|
-
UTILS.Portal = Portal;
|
|
5074
5566
|
const blockDom = {
|
|
5075
5567
|
config,
|
|
5076
5568
|
// bdom entry points
|
|
@@ -5088,10 +5580,10 @@ const blockDom = {
|
|
|
5088
5580
|
};
|
|
5089
5581
|
const __info__ = {};
|
|
5090
5582
|
|
|
5091
|
-
export { App, Component, EventBus,
|
|
5583
|
+
export { App, Component, EventBus, __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, whenReady, xml };
|
|
5092
5584
|
|
|
5093
5585
|
|
|
5094
|
-
__info__.version = '2.0.0-
|
|
5095
|
-
__info__.date = '2022-
|
|
5096
|
-
__info__.hash = '
|
|
5586
|
+
__info__.version = '2.0.0-beta-4';
|
|
5587
|
+
__info__.date = '2022-03-29T13:50:04.545Z';
|
|
5588
|
+
__info__.hash = '55dbc01';
|
|
5097
5589
|
__info__.url = 'https://github.com/odoo/owl';
|