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