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