@odoo/owl 2.0.0-alpha.2 → 2.0.0-beta-5
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 +2 -0
- package/dist/owl.cjs.js +1538 -1065
- package/dist/owl.cjs.min.js +1 -1
- package/dist/owl.es.js +1539 -1065
- package/dist/owl.es.min.js +1 -1
- package/dist/owl.iife.js +1538 -1065
- 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/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 +22 -7
- package/dist/types/component/fibers.d.ts +5 -0
- package/dist/types/component/scheduler.d.ts +3 -4
- package/dist/types/index.d.ts +3 -5
- package/dist/types/reactivity.d.ts +12 -13
- package/dist/types/utils.d.ts +7 -0
- package/package.json +1 -1
- package/dist/types/memo.d.ts +0 -6
package/dist/owl.es.js
CHANGED
|
@@ -209,7 +209,8 @@ function updateClass(val, oldVal) {
|
|
|
209
209
|
}
|
|
210
210
|
function makePropSetter(name) {
|
|
211
211
|
return function setProp(value) {
|
|
212
|
-
|
|
212
|
+
// support 0, fallback to empty string for other falsy values
|
|
213
|
+
this[name] = value === 0 ? 0 : value || "";
|
|
213
214
|
};
|
|
214
215
|
}
|
|
215
216
|
function isProp(tag, key) {
|
|
@@ -263,10 +264,14 @@ function createElementHandler(evName, capture = false) {
|
|
|
263
264
|
this[eventKey] = data;
|
|
264
265
|
this.addEventListener(evName, listener, { capture });
|
|
265
266
|
}
|
|
267
|
+
function remove() {
|
|
268
|
+
delete this[eventKey];
|
|
269
|
+
this.removeEventListener(evName, listener, { capture });
|
|
270
|
+
}
|
|
266
271
|
function update(data) {
|
|
267
272
|
this[eventKey] = data;
|
|
268
273
|
}
|
|
269
|
-
return { setup, update };
|
|
274
|
+
return { setup, update, remove };
|
|
270
275
|
}
|
|
271
276
|
// Synthetic handler: a form of event delegation that allows placing only one
|
|
272
277
|
// listener per event type.
|
|
@@ -283,7 +288,10 @@ function createSyntheticHandler(evName, capture = false) {
|
|
|
283
288
|
_data[currentId] = data;
|
|
284
289
|
this[eventKey] = _data;
|
|
285
290
|
}
|
|
286
|
-
|
|
291
|
+
function remove() {
|
|
292
|
+
delete this[eventKey];
|
|
293
|
+
}
|
|
294
|
+
return { setup, update: setup, remove };
|
|
287
295
|
}
|
|
288
296
|
function nativeToSyntheticEvent(eventKey, event) {
|
|
289
297
|
let dom = event.target;
|
|
@@ -508,7 +516,7 @@ const characterDataProto = CharacterData.prototype;
|
|
|
508
516
|
const characterDataSetData = getDescriptor$1(characterDataProto, "data").set;
|
|
509
517
|
const nodeGetFirstChild = getDescriptor$1(nodeProto$2, "firstChild").get;
|
|
510
518
|
const nodeGetNextSibling = getDescriptor$1(nodeProto$2, "nextSibling").get;
|
|
511
|
-
const NO_OP
|
|
519
|
+
const NO_OP = () => { };
|
|
512
520
|
const cache$1 = {};
|
|
513
521
|
/**
|
|
514
522
|
* Compiling blocks is a multi-step process:
|
|
@@ -812,7 +820,7 @@ function updateCtx(ctx, tree) {
|
|
|
812
820
|
idx: info.idx,
|
|
813
821
|
refIdx: info.refIdx,
|
|
814
822
|
setData: makeRefSetter(index, ctx.refList),
|
|
815
|
-
updateData: NO_OP
|
|
823
|
+
updateData: NO_OP,
|
|
816
824
|
});
|
|
817
825
|
}
|
|
818
826
|
}
|
|
@@ -1278,6 +1286,75 @@ function html(str) {
|
|
|
1278
1286
|
return new VHtml(str);
|
|
1279
1287
|
}
|
|
1280
1288
|
|
|
1289
|
+
function createCatcher(eventsSpec) {
|
|
1290
|
+
let setupFns = [];
|
|
1291
|
+
let removeFns = [];
|
|
1292
|
+
for (let name in eventsSpec) {
|
|
1293
|
+
let index = eventsSpec[name];
|
|
1294
|
+
let { setup, remove } = createEventHandler(name);
|
|
1295
|
+
setupFns[index] = setup;
|
|
1296
|
+
removeFns[index] = remove;
|
|
1297
|
+
}
|
|
1298
|
+
let n = setupFns.length;
|
|
1299
|
+
class VCatcher {
|
|
1300
|
+
constructor(child, handlers) {
|
|
1301
|
+
this.afterNode = null;
|
|
1302
|
+
this.child = child;
|
|
1303
|
+
this.handlers = handlers;
|
|
1304
|
+
}
|
|
1305
|
+
mount(parent, afterNode) {
|
|
1306
|
+
this.parentEl = parent;
|
|
1307
|
+
this.afterNode = afterNode;
|
|
1308
|
+
this.child.mount(parent, afterNode);
|
|
1309
|
+
for (let i = 0; i < n; i++) {
|
|
1310
|
+
let origFn = this.handlers[i][0];
|
|
1311
|
+
const self = this;
|
|
1312
|
+
this.handlers[i][0] = function (ev) {
|
|
1313
|
+
const target = ev.target;
|
|
1314
|
+
let currentNode = self.child.firstNode();
|
|
1315
|
+
const afterNode = self.afterNode;
|
|
1316
|
+
while (currentNode !== afterNode) {
|
|
1317
|
+
if (currentNode.contains(target)) {
|
|
1318
|
+
return origFn.call(this, ev);
|
|
1319
|
+
}
|
|
1320
|
+
currentNode = currentNode.nextSibling;
|
|
1321
|
+
}
|
|
1322
|
+
};
|
|
1323
|
+
setupFns[i].call(parent, this.handlers[i]);
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
moveBefore(other, afterNode) {
|
|
1327
|
+
this.afterNode = null;
|
|
1328
|
+
this.child.moveBefore(other ? other.child : null, afterNode);
|
|
1329
|
+
}
|
|
1330
|
+
patch(other, withBeforeRemove) {
|
|
1331
|
+
if (this === other) {
|
|
1332
|
+
return;
|
|
1333
|
+
}
|
|
1334
|
+
this.handlers = other.handlers;
|
|
1335
|
+
this.child.patch(other.child, withBeforeRemove);
|
|
1336
|
+
}
|
|
1337
|
+
beforeRemove() {
|
|
1338
|
+
this.child.beforeRemove();
|
|
1339
|
+
}
|
|
1340
|
+
remove() {
|
|
1341
|
+
for (let i = 0; i < n; i++) {
|
|
1342
|
+
removeFns[i].call(this.parentEl);
|
|
1343
|
+
}
|
|
1344
|
+
this.child.remove();
|
|
1345
|
+
}
|
|
1346
|
+
firstNode() {
|
|
1347
|
+
return this.child.firstNode();
|
|
1348
|
+
}
|
|
1349
|
+
toString() {
|
|
1350
|
+
return this.child.toString();
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
return function (child, handlers) {
|
|
1354
|
+
return new VCatcher(child, handlers);
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1281
1358
|
function mount$1(vnode, fixture, afterNode = null) {
|
|
1282
1359
|
vnode.mount(fixture, afterNode);
|
|
1283
1360
|
}
|
|
@@ -1291,143 +1368,464 @@ function remove(vnode, withBeforeRemove = false) {
|
|
|
1291
1368
|
vnode.remove();
|
|
1292
1369
|
}
|
|
1293
1370
|
|
|
1371
|
+
const mainEventHandler = (data, ev, currentTarget) => {
|
|
1372
|
+
const { data: _data, modifiers } = filterOutModifiersFromData(data);
|
|
1373
|
+
data = _data;
|
|
1374
|
+
let stopped = false;
|
|
1375
|
+
if (modifiers.length) {
|
|
1376
|
+
let selfMode = false;
|
|
1377
|
+
const isSelf = ev.target === currentTarget;
|
|
1378
|
+
for (const mod of modifiers) {
|
|
1379
|
+
switch (mod) {
|
|
1380
|
+
case "self":
|
|
1381
|
+
selfMode = true;
|
|
1382
|
+
if (isSelf) {
|
|
1383
|
+
continue;
|
|
1384
|
+
}
|
|
1385
|
+
else {
|
|
1386
|
+
return stopped;
|
|
1387
|
+
}
|
|
1388
|
+
case "prevent":
|
|
1389
|
+
if ((selfMode && isSelf) || !selfMode)
|
|
1390
|
+
ev.preventDefault();
|
|
1391
|
+
continue;
|
|
1392
|
+
case "stop":
|
|
1393
|
+
if ((selfMode && isSelf) || !selfMode)
|
|
1394
|
+
ev.stopPropagation();
|
|
1395
|
+
stopped = true;
|
|
1396
|
+
continue;
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
// If handler is empty, the array slot 0 will also be empty, and data will not have the property 0
|
|
1401
|
+
// We check this rather than data[0] being truthy (or typeof function) so that it crashes
|
|
1402
|
+
// as expected when there is a handler expression that evaluates to a falsy value
|
|
1403
|
+
if (Object.hasOwnProperty.call(data, 0)) {
|
|
1404
|
+
const handler = data[0];
|
|
1405
|
+
if (typeof handler !== "function") {
|
|
1406
|
+
throw new Error(`Invalid handler (expected a function, received: '${handler}')`);
|
|
1407
|
+
}
|
|
1408
|
+
let node = data[1] ? data[1].__owl__ : null;
|
|
1409
|
+
if (node ? node.status === 1 /* MOUNTED */ : true) {
|
|
1410
|
+
handler.call(node ? node.component : null, ev);
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
return stopped;
|
|
1414
|
+
};
|
|
1415
|
+
|
|
1416
|
+
// Allows to get the target of a Reactive (used for making a new Reactive from the underlying object)
|
|
1417
|
+
const TARGET = Symbol("Target");
|
|
1418
|
+
// Escape hatch to prevent reactivity system to turn something into a reactive
|
|
1419
|
+
const SKIP = Symbol("Skip");
|
|
1420
|
+
// Special key to subscribe to, to be notified of key creation/deletion
|
|
1421
|
+
const KEYCHANGES = Symbol("Key changes");
|
|
1422
|
+
const objectToString = Object.prototype.toString;
|
|
1423
|
+
const objectHasOwnProperty = Object.prototype.hasOwnProperty;
|
|
1424
|
+
const SUPPORTED_RAW_TYPES = new Set(["Object", "Array", "Set", "Map", "WeakMap"]);
|
|
1425
|
+
const COLLECTION_RAWTYPES = new Set(["Set", "Map", "WeakMap"]);
|
|
1294
1426
|
/**
|
|
1295
|
-
*
|
|
1427
|
+
* extract "RawType" from strings like "[object RawType]" => this lets us ignore
|
|
1428
|
+
* many native objects such as Promise (whose toString is [object Promise])
|
|
1429
|
+
* or Date ([object Date]), while also supporting collections without using
|
|
1430
|
+
* instanceof in a loop
|
|
1296
1431
|
*
|
|
1297
|
-
*
|
|
1432
|
+
* @param obj the object to check
|
|
1433
|
+
* @returns the raw type of the object
|
|
1298
1434
|
*/
|
|
1299
|
-
function
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1435
|
+
function rawType(obj) {
|
|
1436
|
+
return objectToString.call(obj).slice(8, -1);
|
|
1437
|
+
}
|
|
1438
|
+
/**
|
|
1439
|
+
* Checks whether a given value can be made into a reactive object.
|
|
1440
|
+
*
|
|
1441
|
+
* @param value the value to check
|
|
1442
|
+
* @returns whether the value can be made reactive
|
|
1443
|
+
*/
|
|
1444
|
+
function canBeMadeReactive(value) {
|
|
1445
|
+
if (typeof value !== "object") {
|
|
1446
|
+
return false;
|
|
1307
1447
|
}
|
|
1448
|
+
return SUPPORTED_RAW_TYPES.has(rawType(value));
|
|
1308
1449
|
}
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1450
|
+
/**
|
|
1451
|
+
* Creates a reactive from the given object/callback if possible and returns it,
|
|
1452
|
+
* returns the original object otherwise.
|
|
1453
|
+
*
|
|
1454
|
+
* @param value the value make reactive
|
|
1455
|
+
* @returns a reactive for the given object when possible, the original otherwise
|
|
1456
|
+
*/
|
|
1457
|
+
function possiblyReactive(val, cb) {
|
|
1458
|
+
return canBeMadeReactive(val) ? reactive(val, cb) : val;
|
|
1459
|
+
}
|
|
1460
|
+
/**
|
|
1461
|
+
* Mark an object or array so that it is ignored by the reactivity system
|
|
1462
|
+
*
|
|
1463
|
+
* @param value the value to mark
|
|
1464
|
+
* @returns the object itself
|
|
1465
|
+
*/
|
|
1466
|
+
function markRaw(value) {
|
|
1467
|
+
value[SKIP] = true;
|
|
1468
|
+
return value;
|
|
1469
|
+
}
|
|
1470
|
+
/**
|
|
1471
|
+
* Given a reactive objet, return the raw (non reactive) underlying object
|
|
1472
|
+
*
|
|
1473
|
+
* @param value a reactive value
|
|
1474
|
+
* @returns the underlying value
|
|
1475
|
+
*/
|
|
1476
|
+
function toRaw(value) {
|
|
1477
|
+
return value[TARGET] || value;
|
|
1478
|
+
}
|
|
1479
|
+
const targetToKeysToCallbacks = new WeakMap();
|
|
1480
|
+
/**
|
|
1481
|
+
* Observes a given key on a target with an callback. The callback will be
|
|
1482
|
+
* called when the given key changes on the target.
|
|
1483
|
+
*
|
|
1484
|
+
* @param target the target whose key should be observed
|
|
1485
|
+
* @param key the key to observe (or Symbol(KEYCHANGES) for key creation
|
|
1486
|
+
* or deletion)
|
|
1487
|
+
* @param callback the function to call when the key changes
|
|
1488
|
+
*/
|
|
1489
|
+
function observeTargetKey(target, key, callback) {
|
|
1490
|
+
if (!targetToKeysToCallbacks.get(target)) {
|
|
1491
|
+
targetToKeysToCallbacks.set(target, new Map());
|
|
1315
1492
|
}
|
|
1316
|
-
|
|
1493
|
+
const keyToCallbacks = targetToKeysToCallbacks.get(target);
|
|
1494
|
+
if (!keyToCallbacks.get(key)) {
|
|
1495
|
+
keyToCallbacks.set(key, new Set());
|
|
1496
|
+
}
|
|
1497
|
+
keyToCallbacks.get(key).add(callback);
|
|
1498
|
+
if (!callbacksToTargets.has(callback)) {
|
|
1499
|
+
callbacksToTargets.set(callback, new Set());
|
|
1500
|
+
}
|
|
1501
|
+
callbacksToTargets.get(callback).add(target);
|
|
1317
1502
|
}
|
|
1318
1503
|
/**
|
|
1319
|
-
*
|
|
1320
|
-
*
|
|
1321
|
-
*
|
|
1322
|
-
*
|
|
1504
|
+
* Notify Reactives that are observing a given target that a key has changed on
|
|
1505
|
+
* the target.
|
|
1506
|
+
*
|
|
1507
|
+
* @param target target whose Reactives should be notified that the target was
|
|
1508
|
+
* changed.
|
|
1509
|
+
* @param key the key that changed (or Symbol `KEYCHANGES` if a key was created
|
|
1510
|
+
* or deleted)
|
|
1323
1511
|
*/
|
|
1324
|
-
function
|
|
1325
|
-
const
|
|
1326
|
-
|
|
1327
|
-
: parent.constructor.components[name];
|
|
1328
|
-
if (!ComponentClass) {
|
|
1329
|
-
// this is an error, wrong component. We silently return here instead so the
|
|
1330
|
-
// error is triggered by the usual path ('component' function)
|
|
1512
|
+
function notifyReactives(target, key) {
|
|
1513
|
+
const keyToCallbacks = targetToKeysToCallbacks.get(target);
|
|
1514
|
+
if (!keyToCallbacks) {
|
|
1331
1515
|
return;
|
|
1332
1516
|
}
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
const allowAdditionalProps = "*" in propsDef;
|
|
1337
|
-
for (let propName in propsDef) {
|
|
1338
|
-
if (propName === "*") {
|
|
1339
|
-
continue;
|
|
1340
|
-
}
|
|
1341
|
-
const propDef = propsDef[propName];
|
|
1342
|
-
let isMandatory = !!propDef;
|
|
1343
|
-
if (typeof propDef === "object" && "optional" in propDef) {
|
|
1344
|
-
isMandatory = !propDef.optional;
|
|
1345
|
-
}
|
|
1346
|
-
if (isMandatory && propName in defaultProps) {
|
|
1347
|
-
throw new Error(`A default value cannot be defined for a mandatory prop (name: '${propName}', component: ${ComponentClass.name})`);
|
|
1348
|
-
}
|
|
1349
|
-
if (props[propName] === undefined) {
|
|
1350
|
-
if (isMandatory) {
|
|
1351
|
-
throw new Error(`Missing props '${propName}' (component '${ComponentClass.name}')`);
|
|
1352
|
-
}
|
|
1353
|
-
else {
|
|
1354
|
-
continue;
|
|
1355
|
-
}
|
|
1356
|
-
}
|
|
1357
|
-
let isValid;
|
|
1358
|
-
try {
|
|
1359
|
-
isValid = isValidProp(props[propName], propDef);
|
|
1360
|
-
}
|
|
1361
|
-
catch (e) {
|
|
1362
|
-
e.message = `Invalid prop '${propName}' in component ${ComponentClass.name} (${e.message})`;
|
|
1363
|
-
throw e;
|
|
1364
|
-
}
|
|
1365
|
-
if (!isValid) {
|
|
1366
|
-
throw new Error(`Invalid Prop '${propName}' in component '${ComponentClass.name}'`);
|
|
1367
|
-
}
|
|
1517
|
+
const callbacks = keyToCallbacks.get(key);
|
|
1518
|
+
if (!callbacks) {
|
|
1519
|
+
return;
|
|
1368
1520
|
}
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
}
|
|
1374
|
-
}
|
|
1521
|
+
// Loop on copy because clearReactivesForCallback will modify the set in place
|
|
1522
|
+
for (const callback of [...callbacks]) {
|
|
1523
|
+
clearReactivesForCallback(callback);
|
|
1524
|
+
callback();
|
|
1375
1525
|
}
|
|
1376
1526
|
}
|
|
1527
|
+
const callbacksToTargets = new WeakMap();
|
|
1377
1528
|
/**
|
|
1378
|
-
*
|
|
1529
|
+
* Clears all subscriptions of the Reactives associated with a given callback.
|
|
1530
|
+
*
|
|
1531
|
+
* @param callback the callback for which the reactives need to be cleared
|
|
1379
1532
|
*/
|
|
1380
|
-
function
|
|
1381
|
-
|
|
1382
|
-
|
|
1533
|
+
function clearReactivesForCallback(callback) {
|
|
1534
|
+
const targetsToClear = callbacksToTargets.get(callback);
|
|
1535
|
+
if (!targetsToClear) {
|
|
1536
|
+
return;
|
|
1383
1537
|
}
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
// So, even though 1 is not an instance of Number, we want to consider that
|
|
1389
|
-
// it is valid.
|
|
1390
|
-
if (typeof prop === "object") {
|
|
1391
|
-
return prop instanceof propDef;
|
|
1538
|
+
for (const target of targetsToClear) {
|
|
1539
|
+
const observedKeys = targetToKeysToCallbacks.get(target);
|
|
1540
|
+
if (!observedKeys) {
|
|
1541
|
+
continue;
|
|
1392
1542
|
}
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
else if (propDef instanceof Array) {
|
|
1396
|
-
// If this code is executed, this means that we want to check if a prop
|
|
1397
|
-
// matches at least one of its descriptor.
|
|
1398
|
-
let result = false;
|
|
1399
|
-
for (let i = 0, iLen = propDef.length; i < iLen; i++) {
|
|
1400
|
-
result = result || isValidProp(prop, propDef[i]);
|
|
1543
|
+
for (const callbacks of observedKeys.values()) {
|
|
1544
|
+
callbacks.delete(callback);
|
|
1401
1545
|
}
|
|
1402
|
-
return result;
|
|
1403
1546
|
}
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1547
|
+
targetsToClear.clear();
|
|
1548
|
+
}
|
|
1549
|
+
function getSubscriptions(callback) {
|
|
1550
|
+
const targets = callbacksToTargets.get(callback) || [];
|
|
1551
|
+
return [...targets].map((target) => {
|
|
1552
|
+
const keysToCallbacks = targetToKeysToCallbacks.get(target);
|
|
1553
|
+
return {
|
|
1554
|
+
target,
|
|
1555
|
+
keys: keysToCallbacks ? [...keysToCallbacks.keys()] : [],
|
|
1556
|
+
};
|
|
1557
|
+
});
|
|
1558
|
+
}
|
|
1559
|
+
const reactiveCache = new WeakMap();
|
|
1560
|
+
/**
|
|
1561
|
+
* Creates a reactive proxy for an object. Reading data on the reactive object
|
|
1562
|
+
* subscribes to changes to the data. Writing data on the object will cause the
|
|
1563
|
+
* notify callback to be called if there are suscriptions to that data. Nested
|
|
1564
|
+
* objects and arrays are automatically made reactive as well.
|
|
1565
|
+
*
|
|
1566
|
+
* Whenever you are notified of a change, all subscriptions are cleared, and if
|
|
1567
|
+
* you would like to be notified of any further changes, you should go read
|
|
1568
|
+
* the underlying data again. We assume that if you don't go read it again after
|
|
1569
|
+
* being notified, it means that you are no longer interested in that data.
|
|
1570
|
+
*
|
|
1571
|
+
* Subscriptions:
|
|
1572
|
+
* + Reading a property on an object will subscribe you to changes in the value
|
|
1573
|
+
* of that property.
|
|
1574
|
+
* + Accessing an object keys (eg with Object.keys or with `for..in`) will
|
|
1575
|
+
* subscribe you to the creation/deletion of keys. Checking the presence of a
|
|
1576
|
+
* key on the object with 'in' has the same effect.
|
|
1577
|
+
* - getOwnPropertyDescriptor does not currently subscribe you to the property.
|
|
1578
|
+
* This is a choice that was made because changing a key's value will trigger
|
|
1579
|
+
* this trap and we do not want to subscribe by writes. This also means that
|
|
1580
|
+
* Object.hasOwnProperty doesn't subscribe as it goes through this trap.
|
|
1581
|
+
*
|
|
1582
|
+
* @param target the object for which to create a reactive proxy
|
|
1583
|
+
* @param callback the function to call when an observed property of the
|
|
1584
|
+
* reactive has changed
|
|
1585
|
+
* @returns a proxy that tracks changes to it
|
|
1586
|
+
*/
|
|
1587
|
+
function reactive(target, callback = () => { }) {
|
|
1588
|
+
if (!canBeMadeReactive(target)) {
|
|
1589
|
+
throw new Error(`Cannot make the given value reactive`);
|
|
1407
1590
|
}
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
result = result && propDef.validate(prop);
|
|
1591
|
+
if (SKIP in target) {
|
|
1592
|
+
return target;
|
|
1411
1593
|
}
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
}
|
|
1594
|
+
const originalTarget = target[TARGET];
|
|
1595
|
+
if (originalTarget) {
|
|
1596
|
+
return reactive(originalTarget, callback);
|
|
1416
1597
|
}
|
|
1417
|
-
if (
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1598
|
+
if (!reactiveCache.has(target)) {
|
|
1599
|
+
reactiveCache.set(target, new Map());
|
|
1600
|
+
}
|
|
1601
|
+
const reactivesForTarget = reactiveCache.get(target);
|
|
1602
|
+
if (!reactivesForTarget.has(callback)) {
|
|
1603
|
+
const targetRawType = rawType(target);
|
|
1604
|
+
const handler = COLLECTION_RAWTYPES.has(targetRawType)
|
|
1605
|
+
? collectionsProxyHandler(target, callback, targetRawType)
|
|
1606
|
+
: basicProxyHandler(callback);
|
|
1607
|
+
const proxy = new Proxy(target, handler);
|
|
1608
|
+
reactivesForTarget.set(callback, proxy);
|
|
1609
|
+
}
|
|
1610
|
+
return reactivesForTarget.get(callback);
|
|
1611
|
+
}
|
|
1612
|
+
/**
|
|
1613
|
+
* Creates a basic proxy handler for regular objects and arrays.
|
|
1614
|
+
*
|
|
1615
|
+
* @param callback @see reactive
|
|
1616
|
+
* @returns a proxy handler object
|
|
1617
|
+
*/
|
|
1618
|
+
function basicProxyHandler(callback) {
|
|
1619
|
+
return {
|
|
1620
|
+
get(target, key, proxy) {
|
|
1621
|
+
if (key === TARGET) {
|
|
1622
|
+
return target;
|
|
1623
|
+
}
|
|
1624
|
+
// non-writable non-configurable properties cannot be made reactive
|
|
1625
|
+
const desc = Object.getOwnPropertyDescriptor(target, key);
|
|
1626
|
+
if (desc && !desc.writable && !desc.configurable) {
|
|
1627
|
+
return Reflect.get(target, key, proxy);
|
|
1628
|
+
}
|
|
1629
|
+
observeTargetKey(target, key, callback);
|
|
1630
|
+
return possiblyReactive(Reflect.get(target, key, proxy), callback);
|
|
1631
|
+
},
|
|
1632
|
+
set(target, key, value, proxy) {
|
|
1633
|
+
const isNewKey = !objectHasOwnProperty.call(target, key);
|
|
1634
|
+
const originalValue = Reflect.get(target, key, proxy);
|
|
1635
|
+
const ret = Reflect.set(target, key, value, proxy);
|
|
1636
|
+
if (isNewKey) {
|
|
1637
|
+
notifyReactives(target, KEYCHANGES);
|
|
1638
|
+
}
|
|
1639
|
+
// While Array length may trigger the set trap, it's not actually set by this
|
|
1640
|
+
// method but is updated behind the scenes, and the trap is not called with the
|
|
1641
|
+
// new value. We disable the "same-value-optimization" for it because of that.
|
|
1642
|
+
if (originalValue !== value || (Array.isArray(target) && key === "length")) {
|
|
1643
|
+
notifyReactives(target, key);
|
|
1427
1644
|
}
|
|
1645
|
+
return ret;
|
|
1646
|
+
},
|
|
1647
|
+
deleteProperty(target, key) {
|
|
1648
|
+
const ret = Reflect.deleteProperty(target, key);
|
|
1649
|
+
// TODO: only notify when something was actually deleted
|
|
1650
|
+
notifyReactives(target, KEYCHANGES);
|
|
1651
|
+
notifyReactives(target, key);
|
|
1652
|
+
return ret;
|
|
1653
|
+
},
|
|
1654
|
+
ownKeys(target) {
|
|
1655
|
+
observeTargetKey(target, KEYCHANGES, callback);
|
|
1656
|
+
return Reflect.ownKeys(target);
|
|
1657
|
+
},
|
|
1658
|
+
has(target, key) {
|
|
1659
|
+
// TODO: this observes all key changes instead of only the presence of the argument key
|
|
1660
|
+
// observing the key itself would observe value changes instead of presence changes
|
|
1661
|
+
// so we may need a finer grained system to distinguish observing value vs presence.
|
|
1662
|
+
observeTargetKey(target, KEYCHANGES, callback);
|
|
1663
|
+
return Reflect.has(target, key);
|
|
1664
|
+
},
|
|
1665
|
+
};
|
|
1666
|
+
}
|
|
1667
|
+
/**
|
|
1668
|
+
* Creates a function that will observe the key that is passed to it when called
|
|
1669
|
+
* and delegates to the underlying method.
|
|
1670
|
+
*
|
|
1671
|
+
* @param methodName name of the method to delegate to
|
|
1672
|
+
* @param target @see reactive
|
|
1673
|
+
* @param callback @see reactive
|
|
1674
|
+
*/
|
|
1675
|
+
function makeKeyObserver(methodName, target, callback) {
|
|
1676
|
+
return (key) => {
|
|
1677
|
+
key = toRaw(key);
|
|
1678
|
+
observeTargetKey(target, key, callback);
|
|
1679
|
+
return possiblyReactive(target[methodName](key), callback);
|
|
1680
|
+
};
|
|
1681
|
+
}
|
|
1682
|
+
/**
|
|
1683
|
+
* Creates an iterable that will delegate to the underlying iteration method and
|
|
1684
|
+
* observe keys as necessary.
|
|
1685
|
+
*
|
|
1686
|
+
* @param methodName name of the method to delegate to
|
|
1687
|
+
* @param target @see reactive
|
|
1688
|
+
* @param callback @see reactive
|
|
1689
|
+
*/
|
|
1690
|
+
function makeIteratorObserver(methodName, target, callback) {
|
|
1691
|
+
return function* () {
|
|
1692
|
+
observeTargetKey(target, KEYCHANGES, callback);
|
|
1693
|
+
const keys = target.keys();
|
|
1694
|
+
for (const item of target[methodName]()) {
|
|
1695
|
+
const key = keys.next().value;
|
|
1696
|
+
observeTargetKey(target, key, callback);
|
|
1697
|
+
yield possiblyReactive(item, callback);
|
|
1428
1698
|
}
|
|
1429
|
-
}
|
|
1430
|
-
|
|
1699
|
+
};
|
|
1700
|
+
}
|
|
1701
|
+
/**
|
|
1702
|
+
* Creates a forEach function that will delegate to forEach on the underlying
|
|
1703
|
+
* collection while observing key changes, and keys as they're iterated over,
|
|
1704
|
+
* and making the passed keys/values reactive.
|
|
1705
|
+
*
|
|
1706
|
+
* @param target @see reactive
|
|
1707
|
+
* @param callback @see reactive
|
|
1708
|
+
*/
|
|
1709
|
+
function makeForEachObserver(target, callback) {
|
|
1710
|
+
return function forEach(forEachCb, thisArg) {
|
|
1711
|
+
observeTargetKey(target, KEYCHANGES, callback);
|
|
1712
|
+
target.forEach(function (val, key, targetObj) {
|
|
1713
|
+
observeTargetKey(target, key, callback);
|
|
1714
|
+
forEachCb.call(thisArg, possiblyReactive(val, callback), possiblyReactive(key, callback), possiblyReactive(targetObj, callback));
|
|
1715
|
+
}, thisArg);
|
|
1716
|
+
};
|
|
1717
|
+
}
|
|
1718
|
+
/**
|
|
1719
|
+
* Creates a function that will delegate to an underlying method, and check if
|
|
1720
|
+
* that method has modified the presence or value of a key, and notify the
|
|
1721
|
+
* reactives appropriately.
|
|
1722
|
+
*
|
|
1723
|
+
* @param setterName name of the method to delegate to
|
|
1724
|
+
* @param getterName name of the method which should be used to retrieve the
|
|
1725
|
+
* value before calling the delegate method for comparison purposes
|
|
1726
|
+
* @param target @see reactive
|
|
1727
|
+
*/
|
|
1728
|
+
function delegateAndNotify(setterName, getterName, target) {
|
|
1729
|
+
return (key, value) => {
|
|
1730
|
+
key = toRaw(key);
|
|
1731
|
+
const hadKey = target.has(key);
|
|
1732
|
+
const originalValue = target[getterName](key);
|
|
1733
|
+
const ret = target[setterName](key, value);
|
|
1734
|
+
const hasKey = target.has(key);
|
|
1735
|
+
if (hadKey !== hasKey) {
|
|
1736
|
+
notifyReactives(target, KEYCHANGES);
|
|
1737
|
+
}
|
|
1738
|
+
if (originalValue !== value) {
|
|
1739
|
+
notifyReactives(target, key);
|
|
1740
|
+
}
|
|
1741
|
+
return ret;
|
|
1742
|
+
};
|
|
1743
|
+
}
|
|
1744
|
+
/**
|
|
1745
|
+
* Creates a function that will clear the underlying collection and notify that
|
|
1746
|
+
* the keys of the collection have changed.
|
|
1747
|
+
*
|
|
1748
|
+
* @param target @see reactive
|
|
1749
|
+
*/
|
|
1750
|
+
function makeClearNotifier(target) {
|
|
1751
|
+
return () => {
|
|
1752
|
+
const allKeys = [...target.keys()];
|
|
1753
|
+
target.clear();
|
|
1754
|
+
notifyReactives(target, KEYCHANGES);
|
|
1755
|
+
for (const key of allKeys) {
|
|
1756
|
+
notifyReactives(target, key);
|
|
1757
|
+
}
|
|
1758
|
+
};
|
|
1759
|
+
}
|
|
1760
|
+
/**
|
|
1761
|
+
* Maps raw type of an object to an object containing functions that can be used
|
|
1762
|
+
* to build an appropritate proxy handler for that raw type. Eg: when making a
|
|
1763
|
+
* reactive set, calling the has method should mark the key that is being
|
|
1764
|
+
* retrieved as observed, and calling the add or delete method should notify the
|
|
1765
|
+
* reactives that the key which is being added or deleted has been modified.
|
|
1766
|
+
*/
|
|
1767
|
+
const rawTypeToFuncHandlers = {
|
|
1768
|
+
Set: (target, callback) => ({
|
|
1769
|
+
has: makeKeyObserver("has", target, callback),
|
|
1770
|
+
add: delegateAndNotify("add", "has", target),
|
|
1771
|
+
delete: delegateAndNotify("delete", "has", target),
|
|
1772
|
+
keys: makeIteratorObserver("keys", target, callback),
|
|
1773
|
+
values: makeIteratorObserver("values", target, callback),
|
|
1774
|
+
entries: makeIteratorObserver("entries", target, callback),
|
|
1775
|
+
[Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
|
|
1776
|
+
forEach: makeForEachObserver(target, callback),
|
|
1777
|
+
clear: makeClearNotifier(target),
|
|
1778
|
+
get size() {
|
|
1779
|
+
observeTargetKey(target, KEYCHANGES, callback);
|
|
1780
|
+
return target.size;
|
|
1781
|
+
},
|
|
1782
|
+
}),
|
|
1783
|
+
Map: (target, callback) => ({
|
|
1784
|
+
has: makeKeyObserver("has", target, callback),
|
|
1785
|
+
get: makeKeyObserver("get", target, callback),
|
|
1786
|
+
set: delegateAndNotify("set", "get", target),
|
|
1787
|
+
delete: delegateAndNotify("delete", "has", target),
|
|
1788
|
+
keys: makeIteratorObserver("keys", target, callback),
|
|
1789
|
+
values: makeIteratorObserver("values", target, callback),
|
|
1790
|
+
entries: makeIteratorObserver("entries", target, callback),
|
|
1791
|
+
[Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
|
|
1792
|
+
forEach: makeForEachObserver(target, callback),
|
|
1793
|
+
clear: makeClearNotifier(target),
|
|
1794
|
+
get size() {
|
|
1795
|
+
observeTargetKey(target, KEYCHANGES, callback);
|
|
1796
|
+
return target.size;
|
|
1797
|
+
},
|
|
1798
|
+
}),
|
|
1799
|
+
WeakMap: (target, callback) => ({
|
|
1800
|
+
has: makeKeyObserver("has", target, callback),
|
|
1801
|
+
get: makeKeyObserver("get", target, callback),
|
|
1802
|
+
set: delegateAndNotify("set", "get", target),
|
|
1803
|
+
delete: delegateAndNotify("delete", "has", target),
|
|
1804
|
+
}),
|
|
1805
|
+
};
|
|
1806
|
+
/**
|
|
1807
|
+
* Creates a proxy handler for collections (Set/Map/WeakMap)
|
|
1808
|
+
*
|
|
1809
|
+
* @param callback @see reactive
|
|
1810
|
+
* @param target @see reactive
|
|
1811
|
+
* @returns a proxy handler object
|
|
1812
|
+
*/
|
|
1813
|
+
function collectionsProxyHandler(target, callback, targetRawType) {
|
|
1814
|
+
// TODO: if performance is an issue we can create the special handlers lazily when each
|
|
1815
|
+
// property is read.
|
|
1816
|
+
const specialHandlers = rawTypeToFuncHandlers[targetRawType](target, callback);
|
|
1817
|
+
return Object.assign(basicProxyHandler(callback), {
|
|
1818
|
+
get(target, key) {
|
|
1819
|
+
if (key === TARGET) {
|
|
1820
|
+
return target;
|
|
1821
|
+
}
|
|
1822
|
+
if (objectHasOwnProperty.call(specialHandlers, key)) {
|
|
1823
|
+
return specialHandlers[key];
|
|
1824
|
+
}
|
|
1825
|
+
observeTargetKey(target, key, callback);
|
|
1826
|
+
return possiblyReactive(target[key], callback);
|
|
1827
|
+
},
|
|
1828
|
+
});
|
|
1431
1829
|
}
|
|
1432
1830
|
|
|
1433
1831
|
/**
|
|
@@ -1445,11 +1843,13 @@ function batched(callback) {
|
|
|
1445
1843
|
await Promise.resolve();
|
|
1446
1844
|
if (!called) {
|
|
1447
1845
|
called = true;
|
|
1448
|
-
callback();
|
|
1449
1846
|
// wait for all calls in this microtick to fall through before resetting "called"
|
|
1450
|
-
// so that only the first call to the batched function calls the original callback
|
|
1451
|
-
|
|
1452
|
-
called
|
|
1847
|
+
// so that only the first call to the batched function calls the original callback.
|
|
1848
|
+
// Schedule this before calling the callback so that calls to the batched function
|
|
1849
|
+
// within the callback will proceed only after resetting called to false, and have
|
|
1850
|
+
// a chance to execute the callback again
|
|
1851
|
+
Promise.resolve().then(() => (called = false));
|
|
1852
|
+
callback();
|
|
1453
1853
|
}
|
|
1454
1854
|
};
|
|
1455
1855
|
}
|
|
@@ -1496,327 +1896,135 @@ class Markup extends String {
|
|
|
1496
1896
|
*/
|
|
1497
1897
|
function markup(value) {
|
|
1498
1898
|
return new Markup(value);
|
|
1499
|
-
}
|
|
1500
|
-
|
|
1501
|
-
/**
|
|
1502
|
-
* This file contains utility functions that will be injected in each template,
|
|
1503
|
-
* to perform various useful tasks in the compiled code.
|
|
1504
|
-
*/
|
|
1505
|
-
function withDefault(value, defaultValue) {
|
|
1506
|
-
return value === undefined || value === null || value === false ? defaultValue : value;
|
|
1507
1899
|
}
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1900
|
+
// -----------------------------------------------------------------------------
|
|
1901
|
+
// xml tag helper
|
|
1902
|
+
// -----------------------------------------------------------------------------
|
|
1903
|
+
const globalTemplates = {};
|
|
1904
|
+
function xml(...args) {
|
|
1905
|
+
const name = `__template__${xml.nextId++}`;
|
|
1906
|
+
const value = String.raw(...args);
|
|
1907
|
+
globalTemplates[name] = value;
|
|
1908
|
+
return name;
|
|
1909
|
+
}
|
|
1910
|
+
xml.nextId = 1;
|
|
1911
|
+
|
|
1912
|
+
// Maps fibers to thrown errors
|
|
1913
|
+
const fibersInError = new WeakMap();
|
|
1914
|
+
const nodeErrorHandlers = new WeakMap();
|
|
1915
|
+
function _handleError(node, error) {
|
|
1916
|
+
if (!node) {
|
|
1917
|
+
return false;
|
|
1515
1918
|
}
|
|
1516
|
-
const
|
|
1517
|
-
if (
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1919
|
+
const fiber = node.fiber;
|
|
1920
|
+
if (fiber) {
|
|
1921
|
+
fibersInError.set(fiber, error);
|
|
1922
|
+
}
|
|
1923
|
+
const errorHandlers = nodeErrorHandlers.get(node);
|
|
1924
|
+
if (errorHandlers) {
|
|
1925
|
+
let handled = false;
|
|
1926
|
+
// execute in the opposite order
|
|
1927
|
+
for (let i = errorHandlers.length - 1; i >= 0; i--) {
|
|
1928
|
+
try {
|
|
1929
|
+
errorHandlers[i](error);
|
|
1930
|
+
handled = true;
|
|
1931
|
+
break;
|
|
1932
|
+
}
|
|
1933
|
+
catch (e) {
|
|
1934
|
+
error = e;
|
|
1935
|
+
}
|
|
1522
1936
|
}
|
|
1523
|
-
|
|
1524
|
-
|
|
1937
|
+
if (handled) {
|
|
1938
|
+
return true;
|
|
1525
1939
|
}
|
|
1526
|
-
return multi([child1, child2]);
|
|
1527
1940
|
}
|
|
1528
|
-
return
|
|
1941
|
+
return _handleError(node.parent, error);
|
|
1529
1942
|
}
|
|
1530
|
-
function
|
|
1531
|
-
const
|
|
1532
|
-
const
|
|
1533
|
-
|
|
1534
|
-
|
|
1943
|
+
function handleError(params) {
|
|
1944
|
+
const error = params.error;
|
|
1945
|
+
const node = "node" in params ? params.node : params.fiber.node;
|
|
1946
|
+
const fiber = "fiber" in params ? params.fiber : node.fiber;
|
|
1947
|
+
// resets the fibers on components if possible. This is important so that
|
|
1948
|
+
// new renderings can be properly included in the initial one, if any.
|
|
1949
|
+
let current = fiber;
|
|
1950
|
+
do {
|
|
1951
|
+
current.node.fiber = current;
|
|
1952
|
+
current = current.parent;
|
|
1953
|
+
} while (current);
|
|
1954
|
+
fibersInError.set(fiber.root, error);
|
|
1955
|
+
const handled = _handleError(node, error);
|
|
1956
|
+
if (!handled) {
|
|
1957
|
+
console.warn(`[Owl] Unhandled error. Destroying the root component`);
|
|
1958
|
+
try {
|
|
1959
|
+
node.app.destroy();
|
|
1960
|
+
}
|
|
1961
|
+
catch (e) {
|
|
1962
|
+
console.error(e);
|
|
1963
|
+
}
|
|
1535
1964
|
}
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
function
|
|
1539
|
-
|
|
1540
|
-
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
function makeChildFiber(node, parent) {
|
|
1968
|
+
let current = node.fiber;
|
|
1969
|
+
if (current) {
|
|
1970
|
+
cancelFibers(current.children);
|
|
1971
|
+
current.root = null;
|
|
1972
|
+
}
|
|
1973
|
+
return new Fiber(node, parent);
|
|
1541
1974
|
}
|
|
1542
|
-
function
|
|
1543
|
-
let
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1975
|
+
function makeRootFiber(node) {
|
|
1976
|
+
let current = node.fiber;
|
|
1977
|
+
if (current) {
|
|
1978
|
+
let root = current.root;
|
|
1979
|
+
// lock root fiber because canceling children fibers may destroy components,
|
|
1980
|
+
// which means any arbitrary code can be run in onWillDestroy, which may
|
|
1981
|
+
// trigger new renderings
|
|
1982
|
+
root.locked = true;
|
|
1983
|
+
root.setCounter(root.counter + 1 - cancelFibers(current.children));
|
|
1984
|
+
root.locked = false;
|
|
1985
|
+
current.children = [];
|
|
1986
|
+
current.childrenMap = {};
|
|
1987
|
+
current.bdom = null;
|
|
1988
|
+
if (fibersInError.has(current)) {
|
|
1989
|
+
fibersInError.delete(current);
|
|
1990
|
+
fibersInError.delete(root);
|
|
1991
|
+
current.appliedToDom = false;
|
|
1992
|
+
}
|
|
1993
|
+
return current;
|
|
1548
1994
|
}
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1995
|
+
const fiber = new RootFiber(node, null);
|
|
1996
|
+
if (node.willPatch.length) {
|
|
1997
|
+
fiber.willPatch.push(fiber);
|
|
1552
1998
|
}
|
|
1553
|
-
|
|
1554
|
-
|
|
1999
|
+
if (node.patched.length) {
|
|
2000
|
+
fiber.patched.push(fiber);
|
|
1555
2001
|
}
|
|
1556
|
-
|
|
1557
|
-
return [keys, values, n, new Array(n)];
|
|
1558
|
-
}
|
|
1559
|
-
const isBoundary = Symbol("isBoundary");
|
|
1560
|
-
function setContextValue(ctx, key, value) {
|
|
1561
|
-
const ctx0 = ctx;
|
|
1562
|
-
while (!ctx.hasOwnProperty(key) && !ctx.hasOwnProperty(isBoundary)) {
|
|
1563
|
-
const newCtx = ctx.__proto__;
|
|
1564
|
-
if (!newCtx) {
|
|
1565
|
-
ctx = ctx0;
|
|
1566
|
-
break;
|
|
1567
|
-
}
|
|
1568
|
-
ctx = newCtx;
|
|
1569
|
-
}
|
|
1570
|
-
ctx[key] = value;
|
|
1571
|
-
}
|
|
1572
|
-
function toNumber(val) {
|
|
1573
|
-
const n = parseFloat(val);
|
|
1574
|
-
return isNaN(n) ? val : n;
|
|
1575
|
-
}
|
|
1576
|
-
function shallowEqual$1(l1, l2) {
|
|
1577
|
-
for (let i = 0, l = l1.length; i < l; i++) {
|
|
1578
|
-
if (l1[i] !== l2[i]) {
|
|
1579
|
-
return false;
|
|
1580
|
-
}
|
|
1581
|
-
}
|
|
1582
|
-
return true;
|
|
1583
|
-
}
|
|
1584
|
-
class LazyValue {
|
|
1585
|
-
constructor(fn, ctx, node) {
|
|
1586
|
-
this.fn = fn;
|
|
1587
|
-
this.ctx = capture(ctx);
|
|
1588
|
-
this.node = node;
|
|
1589
|
-
}
|
|
1590
|
-
evaluate() {
|
|
1591
|
-
return this.fn(this.ctx, this.node);
|
|
1592
|
-
}
|
|
1593
|
-
toString() {
|
|
1594
|
-
return this.evaluate().toString();
|
|
1595
|
-
}
|
|
1596
|
-
}
|
|
1597
|
-
/*
|
|
1598
|
-
* Safely outputs `value` as a block depending on the nature of `value`
|
|
1599
|
-
*/
|
|
1600
|
-
function safeOutput(value) {
|
|
1601
|
-
if (!value) {
|
|
1602
|
-
return value;
|
|
1603
|
-
}
|
|
1604
|
-
let safeKey;
|
|
1605
|
-
let block;
|
|
1606
|
-
if (value instanceof Markup) {
|
|
1607
|
-
safeKey = `string_safe`;
|
|
1608
|
-
block = html(value);
|
|
1609
|
-
}
|
|
1610
|
-
else if (value instanceof LazyValue) {
|
|
1611
|
-
safeKey = `lazy_value`;
|
|
1612
|
-
block = value.evaluate();
|
|
1613
|
-
}
|
|
1614
|
-
else if (value instanceof String || typeof value === "string") {
|
|
1615
|
-
safeKey = "string_unsafe";
|
|
1616
|
-
block = text(value);
|
|
1617
|
-
}
|
|
1618
|
-
else {
|
|
1619
|
-
// Assuming it is a block
|
|
1620
|
-
safeKey = "block_safe";
|
|
1621
|
-
block = value;
|
|
1622
|
-
}
|
|
1623
|
-
return toggler(safeKey, block);
|
|
1624
|
-
}
|
|
1625
|
-
let boundFunctions = new WeakMap();
|
|
1626
|
-
function bind(ctx, fn) {
|
|
1627
|
-
let component = ctx.__owl__.component;
|
|
1628
|
-
let boundFnMap = boundFunctions.get(component);
|
|
1629
|
-
if (!boundFnMap) {
|
|
1630
|
-
boundFnMap = new WeakMap();
|
|
1631
|
-
boundFunctions.set(component, boundFnMap);
|
|
1632
|
-
}
|
|
1633
|
-
let boundFn = boundFnMap.get(fn);
|
|
1634
|
-
if (!boundFn) {
|
|
1635
|
-
boundFn = fn.bind(component);
|
|
1636
|
-
boundFnMap.set(fn, boundFn);
|
|
1637
|
-
}
|
|
1638
|
-
return boundFn;
|
|
1639
|
-
}
|
|
1640
|
-
function multiRefSetter(refs, name) {
|
|
1641
|
-
let count = 0;
|
|
1642
|
-
return (el) => {
|
|
1643
|
-
if (el) {
|
|
1644
|
-
count++;
|
|
1645
|
-
if (count > 1) {
|
|
1646
|
-
throw new Error("Cannot have 2 elements with same ref name at the same time");
|
|
1647
|
-
}
|
|
1648
|
-
}
|
|
1649
|
-
if (count === 0 || el) {
|
|
1650
|
-
refs[name] = el;
|
|
1651
|
-
}
|
|
1652
|
-
};
|
|
1653
|
-
}
|
|
1654
|
-
const UTILS = {
|
|
1655
|
-
withDefault,
|
|
1656
|
-
zero: Symbol("zero"),
|
|
1657
|
-
isBoundary,
|
|
1658
|
-
callSlot,
|
|
1659
|
-
capture,
|
|
1660
|
-
withKey,
|
|
1661
|
-
prepareList,
|
|
1662
|
-
setContextValue,
|
|
1663
|
-
multiRefSetter,
|
|
1664
|
-
shallowEqual: shallowEqual$1,
|
|
1665
|
-
toNumber,
|
|
1666
|
-
validateProps,
|
|
1667
|
-
LazyValue,
|
|
1668
|
-
safeOutput,
|
|
1669
|
-
bind,
|
|
1670
|
-
};
|
|
1671
|
-
|
|
1672
|
-
const mainEventHandler = (data, ev, currentTarget) => {
|
|
1673
|
-
const { data: _data, modifiers } = filterOutModifiersFromData(data);
|
|
1674
|
-
data = _data;
|
|
1675
|
-
let stopped = false;
|
|
1676
|
-
if (modifiers.length) {
|
|
1677
|
-
let selfMode = false;
|
|
1678
|
-
const isSelf = ev.target === currentTarget;
|
|
1679
|
-
for (const mod of modifiers) {
|
|
1680
|
-
switch (mod) {
|
|
1681
|
-
case "self":
|
|
1682
|
-
selfMode = true;
|
|
1683
|
-
if (isSelf) {
|
|
1684
|
-
continue;
|
|
1685
|
-
}
|
|
1686
|
-
else {
|
|
1687
|
-
return stopped;
|
|
1688
|
-
}
|
|
1689
|
-
case "prevent":
|
|
1690
|
-
if ((selfMode && isSelf) || !selfMode)
|
|
1691
|
-
ev.preventDefault();
|
|
1692
|
-
continue;
|
|
1693
|
-
case "stop":
|
|
1694
|
-
if ((selfMode && isSelf) || !selfMode)
|
|
1695
|
-
ev.stopPropagation();
|
|
1696
|
-
stopped = true;
|
|
1697
|
-
continue;
|
|
1698
|
-
}
|
|
1699
|
-
}
|
|
1700
|
-
}
|
|
1701
|
-
// If handler is empty, the array slot 0 will also be empty, and data will not have the property 0
|
|
1702
|
-
// We check this rather than data[0] being truthy (or typeof function) so that it crashes
|
|
1703
|
-
// as expected when there is a handler expression that evaluates to a falsy value
|
|
1704
|
-
if (Object.hasOwnProperty.call(data, 0)) {
|
|
1705
|
-
const handler = data[0];
|
|
1706
|
-
if (typeof handler !== "function") {
|
|
1707
|
-
throw new Error(`Invalid handler (expected a function, received: '${handler}')`);
|
|
1708
|
-
}
|
|
1709
|
-
let node = data[1] ? data[1].__owl__ : null;
|
|
1710
|
-
if (node ? node.status === 1 /* MOUNTED */ : true) {
|
|
1711
|
-
handler.call(node ? node.component : null, ev);
|
|
1712
|
-
}
|
|
1713
|
-
}
|
|
1714
|
-
return stopped;
|
|
1715
|
-
};
|
|
1716
|
-
|
|
1717
|
-
// Maps fibers to thrown errors
|
|
1718
|
-
const fibersInError = new WeakMap();
|
|
1719
|
-
const nodeErrorHandlers = new WeakMap();
|
|
1720
|
-
function _handleError(node, error, isFirstRound = false) {
|
|
1721
|
-
if (!node) {
|
|
1722
|
-
return false;
|
|
1723
|
-
}
|
|
1724
|
-
const fiber = node.fiber;
|
|
1725
|
-
if (fiber) {
|
|
1726
|
-
fibersInError.set(fiber, error);
|
|
1727
|
-
}
|
|
1728
|
-
const errorHandlers = nodeErrorHandlers.get(node);
|
|
1729
|
-
if (errorHandlers) {
|
|
1730
|
-
let stopped = false;
|
|
1731
|
-
// execute in the opposite order
|
|
1732
|
-
for (let i = errorHandlers.length - 1; i >= 0; i--) {
|
|
1733
|
-
try {
|
|
1734
|
-
errorHandlers[i](error);
|
|
1735
|
-
stopped = true;
|
|
1736
|
-
break;
|
|
1737
|
-
}
|
|
1738
|
-
catch (e) {
|
|
1739
|
-
error = e;
|
|
1740
|
-
}
|
|
1741
|
-
}
|
|
1742
|
-
if (stopped) {
|
|
1743
|
-
if (isFirstRound && fiber && fiber.node.fiber) {
|
|
1744
|
-
fiber.root.counter--;
|
|
1745
|
-
}
|
|
1746
|
-
return true;
|
|
1747
|
-
}
|
|
1748
|
-
}
|
|
1749
|
-
return _handleError(node.parent, error);
|
|
1750
|
-
}
|
|
1751
|
-
function handleError(params) {
|
|
1752
|
-
const error = params.error;
|
|
1753
|
-
const node = "node" in params ? params.node : params.fiber.node;
|
|
1754
|
-
const fiber = "fiber" in params ? params.fiber : node.fiber;
|
|
1755
|
-
// resets the fibers on components if possible. This is important so that
|
|
1756
|
-
// new renderings can be properly included in the initial one, if any.
|
|
1757
|
-
let current = fiber;
|
|
1758
|
-
do {
|
|
1759
|
-
current.node.fiber = current;
|
|
1760
|
-
current = current.parent;
|
|
1761
|
-
} while (current);
|
|
1762
|
-
fibersInError.set(fiber.root, error);
|
|
1763
|
-
const handled = _handleError(node, error, true);
|
|
1764
|
-
if (!handled) {
|
|
1765
|
-
console.warn(`[Owl] Unhandled error. Destroying the root component`);
|
|
1766
|
-
try {
|
|
1767
|
-
node.app.destroy();
|
|
1768
|
-
}
|
|
1769
|
-
catch (e) {
|
|
1770
|
-
console.error(e);
|
|
1771
|
-
}
|
|
1772
|
-
}
|
|
1773
|
-
}
|
|
1774
|
-
|
|
1775
|
-
function makeChildFiber(node, parent) {
|
|
1776
|
-
let current = node.fiber;
|
|
1777
|
-
if (current) {
|
|
1778
|
-
let root = parent.root;
|
|
1779
|
-
cancelFibers(root, current.children);
|
|
1780
|
-
current.root = null;
|
|
1781
|
-
}
|
|
1782
|
-
return new Fiber(node, parent);
|
|
1783
|
-
}
|
|
1784
|
-
function makeRootFiber(node) {
|
|
1785
|
-
let current = node.fiber;
|
|
1786
|
-
if (current) {
|
|
1787
|
-
let root = current.root;
|
|
1788
|
-
root.counter -= cancelFibers(root, current.children);
|
|
1789
|
-
current.children = [];
|
|
1790
|
-
root.counter++;
|
|
1791
|
-
current.bdom = null;
|
|
1792
|
-
if (fibersInError.has(current)) {
|
|
1793
|
-
fibersInError.delete(current);
|
|
1794
|
-
fibersInError.delete(root);
|
|
1795
|
-
current.appliedToDom = false;
|
|
1796
|
-
}
|
|
1797
|
-
return current;
|
|
1798
|
-
}
|
|
1799
|
-
const fiber = new RootFiber(node, null);
|
|
1800
|
-
if (node.willPatch.length) {
|
|
1801
|
-
fiber.willPatch.push(fiber);
|
|
1802
|
-
}
|
|
1803
|
-
if (node.patched.length) {
|
|
1804
|
-
fiber.patched.push(fiber);
|
|
1805
|
-
}
|
|
1806
|
-
return fiber;
|
|
2002
|
+
return fiber;
|
|
1807
2003
|
}
|
|
1808
2004
|
/**
|
|
1809
2005
|
* @returns number of not-yet rendered fibers cancelled
|
|
1810
2006
|
*/
|
|
1811
|
-
function cancelFibers(
|
|
2007
|
+
function cancelFibers(fibers) {
|
|
1812
2008
|
let result = 0;
|
|
1813
2009
|
for (let fiber of fibers) {
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
2010
|
+
let node = fiber.node;
|
|
2011
|
+
if (node.status === 0 /* NEW */) {
|
|
2012
|
+
node.destroy();
|
|
2013
|
+
}
|
|
2014
|
+
node.fiber = null;
|
|
2015
|
+
if (fiber.bdom) {
|
|
2016
|
+
// if fiber has been rendered, this means that the component props have
|
|
2017
|
+
// been updated. however, this fiber will not be patched to the dom, so
|
|
2018
|
+
// it could happen that the next render compare the current props with
|
|
2019
|
+
// the same props, and skip the render completely. With the next line,
|
|
2020
|
+
// we kindly request the component code to force a render, so it works as
|
|
2021
|
+
// expected.
|
|
2022
|
+
node.forceNextRender = true;
|
|
2023
|
+
}
|
|
2024
|
+
else {
|
|
1817
2025
|
result++;
|
|
1818
2026
|
}
|
|
1819
|
-
result += cancelFibers(
|
|
2027
|
+
result += cancelFibers(fiber.children);
|
|
1820
2028
|
}
|
|
1821
2029
|
return result;
|
|
1822
2030
|
}
|
|
@@ -1825,11 +2033,14 @@ class Fiber {
|
|
|
1825
2033
|
this.bdom = null;
|
|
1826
2034
|
this.children = [];
|
|
1827
2035
|
this.appliedToDom = false;
|
|
2036
|
+
this.deep = false;
|
|
2037
|
+
this.childrenMap = {};
|
|
1828
2038
|
this.node = node;
|
|
1829
2039
|
this.parent = parent;
|
|
1830
2040
|
if (parent) {
|
|
2041
|
+
this.deep = parent.deep;
|
|
1831
2042
|
const root = parent.root;
|
|
1832
|
-
root.counter
|
|
2043
|
+
root.setCounter(root.counter + 1);
|
|
1833
2044
|
this.root = root;
|
|
1834
2045
|
parent.children.push(this);
|
|
1835
2046
|
}
|
|
@@ -1837,6 +2048,42 @@ class Fiber {
|
|
|
1837
2048
|
this.root = this;
|
|
1838
2049
|
}
|
|
1839
2050
|
}
|
|
2051
|
+
render() {
|
|
2052
|
+
// if some parent has a fiber => register in followup
|
|
2053
|
+
let prev = this.root.node;
|
|
2054
|
+
let scheduler = prev.app.scheduler;
|
|
2055
|
+
let current = prev.parent;
|
|
2056
|
+
while (current) {
|
|
2057
|
+
if (current.fiber) {
|
|
2058
|
+
let root = current.fiber.root;
|
|
2059
|
+
if (root.counter === 0 && prev.parentKey in current.fiber.childrenMap) {
|
|
2060
|
+
current = root.node;
|
|
2061
|
+
}
|
|
2062
|
+
else {
|
|
2063
|
+
scheduler.delayedRenders.push(this);
|
|
2064
|
+
return;
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
prev = current;
|
|
2068
|
+
current = current.parent;
|
|
2069
|
+
}
|
|
2070
|
+
// there are no current rendering from above => we can render
|
|
2071
|
+
this._render();
|
|
2072
|
+
}
|
|
2073
|
+
_render() {
|
|
2074
|
+
const node = this.node;
|
|
2075
|
+
const root = this.root;
|
|
2076
|
+
if (root) {
|
|
2077
|
+
try {
|
|
2078
|
+
this.bdom = true;
|
|
2079
|
+
this.bdom = node.renderFn();
|
|
2080
|
+
}
|
|
2081
|
+
catch (e) {
|
|
2082
|
+
handleError({ node, error: e });
|
|
2083
|
+
}
|
|
2084
|
+
root.setCounter(root.counter - 1);
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
1840
2087
|
}
|
|
1841
2088
|
class RootFiber extends Fiber {
|
|
1842
2089
|
constructor() {
|
|
@@ -1870,7 +2117,7 @@ class RootFiber extends Fiber {
|
|
|
1870
2117
|
}
|
|
1871
2118
|
current = undefined;
|
|
1872
2119
|
// Step 2: patching the dom
|
|
1873
|
-
node.
|
|
2120
|
+
node._patch();
|
|
1874
2121
|
this.locked = false;
|
|
1875
2122
|
// Step 4: calling all mounted lifecycle hooks
|
|
1876
2123
|
let mountedFibers = this.mounted;
|
|
@@ -1898,6 +2145,12 @@ class RootFiber extends Fiber {
|
|
|
1898
2145
|
handleError({ fiber: current || this, error: e });
|
|
1899
2146
|
}
|
|
1900
2147
|
}
|
|
2148
|
+
setCounter(newValue) {
|
|
2149
|
+
this.counter = newValue;
|
|
2150
|
+
if (newValue === 0) {
|
|
2151
|
+
this.node.app.scheduler.flush();
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
1901
2154
|
}
|
|
1902
2155
|
class MountFiber extends RootFiber {
|
|
1903
2156
|
constructor(node, target, options = {}) {
|
|
@@ -1909,6 +2162,7 @@ class MountFiber extends RootFiber {
|
|
|
1909
2162
|
let current = this;
|
|
1910
2163
|
try {
|
|
1911
2164
|
const node = this.node;
|
|
2165
|
+
node.children = this.childrenMap;
|
|
1912
2166
|
node.app.constructor.validateTarget(this.target);
|
|
1913
2167
|
if (node.bdom) {
|
|
1914
2168
|
// this is a complicated situation: if we mount a fiber with an existing
|
|
@@ -1947,9 +2201,148 @@ class MountFiber extends RootFiber {
|
|
|
1947
2201
|
}
|
|
1948
2202
|
}
|
|
1949
2203
|
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
2204
|
+
/**
|
|
2205
|
+
* Apply default props (only top level).
|
|
2206
|
+
*
|
|
2207
|
+
* Note that this method does modify in place the props
|
|
2208
|
+
*/
|
|
2209
|
+
function applyDefaultProps(props, ComponentClass) {
|
|
2210
|
+
const defaultProps = ComponentClass.defaultProps;
|
|
2211
|
+
if (defaultProps) {
|
|
2212
|
+
for (let propName in defaultProps) {
|
|
2213
|
+
if (props[propName] === undefined) {
|
|
2214
|
+
props[propName] = defaultProps[propName];
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
//------------------------------------------------------------------------------
|
|
2220
|
+
// Prop validation helper
|
|
2221
|
+
//------------------------------------------------------------------------------
|
|
2222
|
+
function getPropDescription(staticProps) {
|
|
2223
|
+
if (staticProps instanceof Array) {
|
|
2224
|
+
return Object.fromEntries(staticProps.map((p) => (p.endsWith("?") ? [p.slice(0, -1), false] : [p, true])));
|
|
2225
|
+
}
|
|
2226
|
+
return staticProps || { "*": true };
|
|
2227
|
+
}
|
|
2228
|
+
/**
|
|
2229
|
+
* Validate the component props (or next props) against the (static) props
|
|
2230
|
+
* description. This is potentially an expensive operation: it may needs to
|
|
2231
|
+
* visit recursively the props and all the children to check if they are valid.
|
|
2232
|
+
* This is why it is only done in 'dev' mode.
|
|
2233
|
+
*/
|
|
2234
|
+
function validateProps(name, props, parent) {
|
|
2235
|
+
const ComponentClass = typeof name !== "string"
|
|
2236
|
+
? name
|
|
2237
|
+
: parent.constructor.components[name];
|
|
2238
|
+
if (!ComponentClass) {
|
|
2239
|
+
// this is an error, wrong component. We silently return here instead so the
|
|
2240
|
+
// error is triggered by the usual path ('component' function)
|
|
2241
|
+
return;
|
|
2242
|
+
}
|
|
2243
|
+
applyDefaultProps(props, ComponentClass);
|
|
2244
|
+
const defaultProps = ComponentClass.defaultProps || {};
|
|
2245
|
+
let propsDef = getPropDescription(ComponentClass.props);
|
|
2246
|
+
const allowAdditionalProps = "*" in propsDef;
|
|
2247
|
+
for (let propName in propsDef) {
|
|
2248
|
+
if (propName === "*") {
|
|
2249
|
+
continue;
|
|
2250
|
+
}
|
|
2251
|
+
const propDef = propsDef[propName];
|
|
2252
|
+
let isMandatory = !!propDef;
|
|
2253
|
+
if (typeof propDef === "object" && "optional" in propDef) {
|
|
2254
|
+
isMandatory = !propDef.optional;
|
|
2255
|
+
}
|
|
2256
|
+
if (isMandatory && propName in defaultProps) {
|
|
2257
|
+
throw new Error(`A default value cannot be defined for a mandatory prop (name: '${propName}', component: ${ComponentClass.name})`);
|
|
2258
|
+
}
|
|
2259
|
+
if (props[propName] === undefined) {
|
|
2260
|
+
if (isMandatory) {
|
|
2261
|
+
throw new Error(`Missing props '${propName}' (component '${ComponentClass.name}')`);
|
|
2262
|
+
}
|
|
2263
|
+
else {
|
|
2264
|
+
continue;
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
let isValid;
|
|
2268
|
+
try {
|
|
2269
|
+
isValid = isValidProp(props[propName], propDef);
|
|
2270
|
+
}
|
|
2271
|
+
catch (e) {
|
|
2272
|
+
e.message = `Invalid prop '${propName}' in component ${ComponentClass.name} (${e.message})`;
|
|
2273
|
+
throw e;
|
|
2274
|
+
}
|
|
2275
|
+
if (!isValid) {
|
|
2276
|
+
throw new Error(`Invalid Prop '${propName}' in component '${ComponentClass.name}'`);
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
if (!allowAdditionalProps) {
|
|
2280
|
+
for (let propName in props) {
|
|
2281
|
+
if (!(propName in propsDef)) {
|
|
2282
|
+
throw new Error(`Unknown prop '${propName}' given to component '${ComponentClass.name}'`);
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
/**
|
|
2288
|
+
* Check if an invidual prop value matches its (static) prop definition
|
|
2289
|
+
*/
|
|
2290
|
+
function isValidProp(prop, propDef) {
|
|
2291
|
+
if (propDef === true) {
|
|
2292
|
+
return true;
|
|
2293
|
+
}
|
|
2294
|
+
if (typeof propDef === "function") {
|
|
2295
|
+
// Check if a value is constructed by some Constructor. Note that there is a
|
|
2296
|
+
// slight abuse of language: we want to consider primitive values as well.
|
|
2297
|
+
//
|
|
2298
|
+
// So, even though 1 is not an instance of Number, we want to consider that
|
|
2299
|
+
// it is valid.
|
|
2300
|
+
if (typeof prop === "object") {
|
|
2301
|
+
return prop instanceof propDef;
|
|
2302
|
+
}
|
|
2303
|
+
return typeof prop === propDef.name.toLowerCase();
|
|
2304
|
+
}
|
|
2305
|
+
else if (propDef instanceof Array) {
|
|
2306
|
+
// If this code is executed, this means that we want to check if a prop
|
|
2307
|
+
// matches at least one of its descriptor.
|
|
2308
|
+
let result = false;
|
|
2309
|
+
for (let i = 0, iLen = propDef.length; i < iLen; i++) {
|
|
2310
|
+
result = result || isValidProp(prop, propDef[i]);
|
|
2311
|
+
}
|
|
2312
|
+
return result;
|
|
2313
|
+
}
|
|
2314
|
+
// propsDef is an object
|
|
2315
|
+
if (propDef.optional && prop === undefined) {
|
|
2316
|
+
return true;
|
|
2317
|
+
}
|
|
2318
|
+
let result = propDef.type ? isValidProp(prop, propDef.type) : true;
|
|
2319
|
+
if (propDef.validate) {
|
|
2320
|
+
result = result && propDef.validate(prop);
|
|
2321
|
+
}
|
|
2322
|
+
if (propDef.type === Array && propDef.element) {
|
|
2323
|
+
for (let i = 0, iLen = prop.length; i < iLen; i++) {
|
|
2324
|
+
result = result && isValidProp(prop[i], propDef.element);
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2327
|
+
if (propDef.type === Object && propDef.shape) {
|
|
2328
|
+
const shape = propDef.shape;
|
|
2329
|
+
for (let key in shape) {
|
|
2330
|
+
result = result && isValidProp(prop[key], shape[key]);
|
|
2331
|
+
}
|
|
2332
|
+
if (result) {
|
|
2333
|
+
for (let propName in prop) {
|
|
2334
|
+
if (!(propName in shape)) {
|
|
2335
|
+
throw new Error(`unknown prop '${propName}'`);
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
return result;
|
|
2341
|
+
}
|
|
2342
|
+
|
|
2343
|
+
let currentNode = null;
|
|
2344
|
+
function getCurrent() {
|
|
2345
|
+
if (!currentNode) {
|
|
1953
2346
|
throw new Error("No active component (a hook function should only be called in 'setup')");
|
|
1954
2347
|
}
|
|
1955
2348
|
return currentNode;
|
|
@@ -1957,24 +2350,61 @@ function getCurrent() {
|
|
|
1957
2350
|
function useComponent() {
|
|
1958
2351
|
return currentNode.component;
|
|
1959
2352
|
}
|
|
2353
|
+
// -----------------------------------------------------------------------------
|
|
2354
|
+
// Integration with reactivity system (useState)
|
|
2355
|
+
// -----------------------------------------------------------------------------
|
|
2356
|
+
const batchedRenderFunctions = new WeakMap();
|
|
2357
|
+
/**
|
|
2358
|
+
* Creates a reactive object that will be observed by the current component.
|
|
2359
|
+
* Reading data from the returned object (eg during rendering) will cause the
|
|
2360
|
+
* component to subscribe to that data and be rerendered when it changes.
|
|
2361
|
+
*
|
|
2362
|
+
* @param state the state to observe
|
|
2363
|
+
* @returns a reactive object that will cause the component to re-render on
|
|
2364
|
+
* relevant changes
|
|
2365
|
+
* @see reactive
|
|
2366
|
+
*/
|
|
2367
|
+
function useState(state) {
|
|
2368
|
+
const node = getCurrent();
|
|
2369
|
+
let render = batchedRenderFunctions.get(node);
|
|
2370
|
+
if (!render) {
|
|
2371
|
+
render = batched(node.render.bind(node));
|
|
2372
|
+
batchedRenderFunctions.set(node, render);
|
|
2373
|
+
// manual implementation of onWillDestroy to break cyclic dependency
|
|
2374
|
+
node.willDestroy.push(clearReactivesForCallback.bind(null, render));
|
|
2375
|
+
}
|
|
2376
|
+
return reactive(state, render);
|
|
2377
|
+
}
|
|
2378
|
+
function arePropsDifferent(props1, props2) {
|
|
2379
|
+
for (let k in props1) {
|
|
2380
|
+
if (props1[k] !== props2[k]) {
|
|
2381
|
+
return true;
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
return Object.keys(props1).length !== Object.keys(props2).length;
|
|
2385
|
+
}
|
|
1960
2386
|
function component(name, props, key, ctx, parent) {
|
|
1961
2387
|
let node = ctx.children[key];
|
|
1962
2388
|
let isDynamic = typeof name !== "string";
|
|
1963
|
-
if (node) {
|
|
1964
|
-
|
|
1965
|
-
node.destroy();
|
|
1966
|
-
node = undefined;
|
|
1967
|
-
}
|
|
1968
|
-
else if (node.status === 2 /* DESTROYED */) {
|
|
1969
|
-
node = undefined;
|
|
1970
|
-
}
|
|
2389
|
+
if (node && node.status === 2 /* DESTROYED */) {
|
|
2390
|
+
node = undefined;
|
|
1971
2391
|
}
|
|
1972
2392
|
if (isDynamic && node && node.component.constructor !== name) {
|
|
1973
2393
|
node = undefined;
|
|
1974
2394
|
}
|
|
1975
2395
|
const parentFiber = ctx.fiber;
|
|
1976
2396
|
if (node) {
|
|
1977
|
-
node.
|
|
2397
|
+
let shouldRender = node.forceNextRender;
|
|
2398
|
+
if (shouldRender) {
|
|
2399
|
+
node.forceNextRender = false;
|
|
2400
|
+
}
|
|
2401
|
+
else {
|
|
2402
|
+
const currentProps = node.component.props[TARGET];
|
|
2403
|
+
shouldRender = parentFiber.deep || arePropsDifferent(currentProps, props);
|
|
2404
|
+
}
|
|
2405
|
+
if (shouldRender) {
|
|
2406
|
+
node.updateAndRender(props, parentFiber);
|
|
2407
|
+
}
|
|
1978
2408
|
}
|
|
1979
2409
|
else {
|
|
1980
2410
|
// new component
|
|
@@ -1988,18 +2418,19 @@ function component(name, props, key, ctx, parent) {
|
|
|
1988
2418
|
throw new Error(`Cannot find the definition of component "${name}"`);
|
|
1989
2419
|
}
|
|
1990
2420
|
}
|
|
1991
|
-
node = new ComponentNode(C, props, ctx.app, ctx);
|
|
2421
|
+
node = new ComponentNode(C, props, ctx.app, ctx, key);
|
|
1992
2422
|
ctx.children[key] = node;
|
|
1993
|
-
|
|
1994
|
-
node.initiateRender(fiber);
|
|
2423
|
+
node.initiateRender(new Fiber(node, parentFiber));
|
|
1995
2424
|
}
|
|
2425
|
+
parentFiber.childrenMap[key] = node;
|
|
1996
2426
|
return node;
|
|
1997
2427
|
}
|
|
1998
2428
|
class ComponentNode {
|
|
1999
|
-
constructor(C, props, app, parent) {
|
|
2429
|
+
constructor(C, props, app, parent, parentKey) {
|
|
2000
2430
|
this.fiber = null;
|
|
2001
2431
|
this.bdom = null;
|
|
2002
2432
|
this.status = 0 /* NEW */;
|
|
2433
|
+
this.forceNextRender = false;
|
|
2003
2434
|
this.children = Object.create(null);
|
|
2004
2435
|
this.refs = {};
|
|
2005
2436
|
this.willStart = [];
|
|
@@ -2011,11 +2442,13 @@ class ComponentNode {
|
|
|
2011
2442
|
this.willDestroy = [];
|
|
2012
2443
|
currentNode = this;
|
|
2013
2444
|
this.app = app;
|
|
2014
|
-
this.parent = parent
|
|
2445
|
+
this.parent = parent;
|
|
2446
|
+
this.parentKey = parentKey;
|
|
2015
2447
|
this.level = parent ? parent.level + 1 : 0;
|
|
2016
2448
|
applyDefaultProps(props, C);
|
|
2017
2449
|
const env = (parent && parent.childEnv) || app.env;
|
|
2018
2450
|
this.childEnv = env;
|
|
2451
|
+
props = useState(props);
|
|
2019
2452
|
this.component = new C(props, env, this);
|
|
2020
2453
|
this.renderFn = app.getTemplate(C.template).bind(this.component, this.component, this);
|
|
2021
2454
|
this.component.setup();
|
|
@@ -2040,23 +2473,32 @@ class ComponentNode {
|
|
|
2040
2473
|
return;
|
|
2041
2474
|
}
|
|
2042
2475
|
if (this.status === 0 /* NEW */ && this.fiber === fiber) {
|
|
2043
|
-
|
|
2476
|
+
fiber.render();
|
|
2044
2477
|
}
|
|
2045
2478
|
}
|
|
2046
|
-
async render() {
|
|
2479
|
+
async render(deep = false) {
|
|
2047
2480
|
let current = this.fiber;
|
|
2048
|
-
if (current && current.root.locked) {
|
|
2481
|
+
if (current && (current.root.locked || current.bdom === true)) {
|
|
2049
2482
|
await Promise.resolve();
|
|
2050
2483
|
// situation may have changed after the microtask tick
|
|
2051
2484
|
current = this.fiber;
|
|
2052
2485
|
}
|
|
2053
|
-
if (current
|
|
2054
|
-
|
|
2486
|
+
if (current) {
|
|
2487
|
+
if (!current.bdom && !fibersInError.has(current)) {
|
|
2488
|
+
if (deep) {
|
|
2489
|
+
// we want the render from this point on to be with deep=true
|
|
2490
|
+
current.deep = deep;
|
|
2491
|
+
}
|
|
2492
|
+
return;
|
|
2493
|
+
}
|
|
2494
|
+
// if current rendering was with deep=true, we want this one to be the same
|
|
2495
|
+
deep = deep || current.deep;
|
|
2055
2496
|
}
|
|
2056
|
-
if (!this.bdom
|
|
2497
|
+
else if (!this.bdom) {
|
|
2057
2498
|
return;
|
|
2058
2499
|
}
|
|
2059
2500
|
const fiber = makeRootFiber(this);
|
|
2501
|
+
fiber.deep = deep;
|
|
2060
2502
|
this.fiber = fiber;
|
|
2061
2503
|
this.app.scheduler.addFiber(fiber);
|
|
2062
2504
|
await Promise.resolve();
|
|
@@ -2075,16 +2517,7 @@ class ComponentNode {
|
|
|
2075
2517
|
// embedded in a rendering coming from above, so the fiber will be rendered
|
|
2076
2518
|
// in the next microtick anyway, so we should not render it again.
|
|
2077
2519
|
if (this.fiber === fiber && (current || !fiber.parent)) {
|
|
2078
|
-
|
|
2079
|
-
}
|
|
2080
|
-
}
|
|
2081
|
-
_render(fiber) {
|
|
2082
|
-
try {
|
|
2083
|
-
fiber.bdom = this.renderFn();
|
|
2084
|
-
fiber.root.counter--;
|
|
2085
|
-
}
|
|
2086
|
-
catch (e) {
|
|
2087
|
-
handleError({ node: this, error: e });
|
|
2520
|
+
fiber.render();
|
|
2088
2521
|
}
|
|
2089
2522
|
}
|
|
2090
2523
|
destroy() {
|
|
@@ -2104,8 +2537,15 @@ class ComponentNode {
|
|
|
2104
2537
|
for (let child of Object.values(this.children)) {
|
|
2105
2538
|
child._destroy();
|
|
2106
2539
|
}
|
|
2107
|
-
|
|
2108
|
-
|
|
2540
|
+
if (this.willDestroy.length) {
|
|
2541
|
+
try {
|
|
2542
|
+
for (let cb of this.willDestroy) {
|
|
2543
|
+
cb.call(component);
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
catch (e) {
|
|
2547
|
+
handleError({ error: e, node: this });
|
|
2548
|
+
}
|
|
2109
2549
|
}
|
|
2110
2550
|
this.status = 2 /* DESTROYED */;
|
|
2111
2551
|
}
|
|
@@ -2115,13 +2555,16 @@ class ComponentNode {
|
|
|
2115
2555
|
this.fiber = fiber;
|
|
2116
2556
|
const component = this.component;
|
|
2117
2557
|
applyDefaultProps(props, component.constructor);
|
|
2558
|
+
currentNode = this;
|
|
2559
|
+
props = useState(props);
|
|
2560
|
+
currentNode = null;
|
|
2118
2561
|
const prom = Promise.all(this.willUpdateProps.map((f) => f.call(component, props)));
|
|
2119
2562
|
await prom;
|
|
2120
2563
|
if (fiber !== this.fiber) {
|
|
2121
2564
|
return;
|
|
2122
2565
|
}
|
|
2123
2566
|
component.props = props;
|
|
2124
|
-
|
|
2567
|
+
fiber.render();
|
|
2125
2568
|
const parentRoot = parentFiber.root;
|
|
2126
2569
|
if (this.willPatch.length) {
|
|
2127
2570
|
parentRoot.willPatch.push(fiber);
|
|
@@ -2168,17 +2611,24 @@ class ComponentNode {
|
|
|
2168
2611
|
bdom.mount(parent, anchor);
|
|
2169
2612
|
this.status = 1 /* MOUNTED */;
|
|
2170
2613
|
this.fiber.appliedToDom = true;
|
|
2614
|
+
this.children = this.fiber.childrenMap;
|
|
2171
2615
|
this.fiber = null;
|
|
2172
2616
|
}
|
|
2173
2617
|
moveBefore(other, afterNode) {
|
|
2174
2618
|
this.bdom.moveBefore(other ? other.bdom : null, afterNode);
|
|
2175
2619
|
}
|
|
2176
2620
|
patch() {
|
|
2621
|
+
if (this.fiber && this.fiber.parent) {
|
|
2622
|
+
// we only patch here renderings coming from above. renderings initiated
|
|
2623
|
+
// by the component will be patched independently in the appropriate
|
|
2624
|
+
// fiber.complete
|
|
2625
|
+
this._patch();
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
_patch() {
|
|
2177
2629
|
const hasChildren = Object.keys(this.children).length > 0;
|
|
2630
|
+
this.children = this.fiber.childrenMap;
|
|
2178
2631
|
this.bdom.patch(this.fiber.bdom, hasChildren);
|
|
2179
|
-
if (hasChildren) {
|
|
2180
|
-
this.cleanOutdatedChildren();
|
|
2181
|
-
}
|
|
2182
2632
|
this.fiber.appliedToDom = true;
|
|
2183
2633
|
this.fiber = null;
|
|
2184
2634
|
}
|
|
@@ -2188,85 +2638,89 @@ class ComponentNode {
|
|
|
2188
2638
|
remove() {
|
|
2189
2639
|
this.bdom.remove();
|
|
2190
2640
|
}
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
}
|
|
2201
|
-
}
|
|
2202
|
-
}
|
|
2641
|
+
// ---------------------------------------------------------------------------
|
|
2642
|
+
// Some debug helpers
|
|
2643
|
+
// ---------------------------------------------------------------------------
|
|
2644
|
+
get name() {
|
|
2645
|
+
return this.component.constructor.name;
|
|
2646
|
+
}
|
|
2647
|
+
get subscriptions() {
|
|
2648
|
+
const render = batchedRenderFunctions.get(this);
|
|
2649
|
+
return render ? getSubscriptions(render) : [];
|
|
2203
2650
|
}
|
|
2204
2651
|
}
|
|
2205
2652
|
|
|
2206
2653
|
// -----------------------------------------------------------------------------
|
|
2207
|
-
//
|
|
2654
|
+
// Scheduler
|
|
2208
2655
|
// -----------------------------------------------------------------------------
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
node.willUpdateProps.push(fn.bind(node.component));
|
|
2216
|
-
}
|
|
2217
|
-
function onMounted(fn) {
|
|
2218
|
-
const node = getCurrent();
|
|
2219
|
-
node.mounted.push(fn.bind(node.component));
|
|
2220
|
-
}
|
|
2221
|
-
function onWillPatch(fn) {
|
|
2222
|
-
const node = getCurrent();
|
|
2223
|
-
node.willPatch.unshift(fn.bind(node.component));
|
|
2224
|
-
}
|
|
2225
|
-
function onPatched(fn) {
|
|
2226
|
-
const node = getCurrent();
|
|
2227
|
-
node.patched.push(fn.bind(node.component));
|
|
2228
|
-
}
|
|
2229
|
-
function onWillUnmount(fn) {
|
|
2230
|
-
const node = getCurrent();
|
|
2231
|
-
node.willUnmount.unshift(fn.bind(node.component));
|
|
2232
|
-
}
|
|
2233
|
-
function onWillDestroy(fn) {
|
|
2234
|
-
const node = getCurrent();
|
|
2235
|
-
node.willDestroy.push(fn.bind(node.component));
|
|
2236
|
-
}
|
|
2237
|
-
function onWillRender(fn) {
|
|
2238
|
-
const node = getCurrent();
|
|
2239
|
-
const renderFn = node.renderFn;
|
|
2240
|
-
node.renderFn = () => {
|
|
2241
|
-
fn.call(node.component);
|
|
2242
|
-
return renderFn();
|
|
2243
|
-
};
|
|
2244
|
-
}
|
|
2245
|
-
function onRendered(fn) {
|
|
2246
|
-
const node = getCurrent();
|
|
2247
|
-
const renderFn = node.renderFn;
|
|
2248
|
-
node.renderFn = () => {
|
|
2249
|
-
const result = renderFn();
|
|
2250
|
-
fn.call(node.component);
|
|
2251
|
-
return result;
|
|
2252
|
-
};
|
|
2253
|
-
}
|
|
2254
|
-
function onError(callback) {
|
|
2255
|
-
const node = getCurrent();
|
|
2256
|
-
let handlers = nodeErrorHandlers.get(node);
|
|
2257
|
-
if (!handlers) {
|
|
2258
|
-
handlers = [];
|
|
2259
|
-
nodeErrorHandlers.set(node, handlers);
|
|
2656
|
+
class Scheduler {
|
|
2657
|
+
constructor() {
|
|
2658
|
+
this.tasks = new Set();
|
|
2659
|
+
this.frame = 0;
|
|
2660
|
+
this.delayedRenders = [];
|
|
2661
|
+
this.requestAnimationFrame = Scheduler.requestAnimationFrame;
|
|
2260
2662
|
}
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
/**
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2663
|
+
addFiber(fiber) {
|
|
2664
|
+
this.tasks.add(fiber.root);
|
|
2665
|
+
}
|
|
2666
|
+
/**
|
|
2667
|
+
* Process all current tasks. This only applies to the fibers that are ready.
|
|
2668
|
+
* Other tasks are left unchanged.
|
|
2669
|
+
*/
|
|
2670
|
+
flush() {
|
|
2671
|
+
if (this.delayedRenders.length) {
|
|
2672
|
+
let renders = this.delayedRenders;
|
|
2673
|
+
this.delayedRenders = [];
|
|
2674
|
+
for (let f of renders) {
|
|
2675
|
+
if (f.root && f.node.status !== 2 /* DESTROYED */) {
|
|
2676
|
+
f.render();
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
if (this.frame === 0) {
|
|
2681
|
+
this.frame = this.requestAnimationFrame(() => {
|
|
2682
|
+
this.frame = 0;
|
|
2683
|
+
this.tasks.forEach((fiber) => this.processFiber(fiber));
|
|
2684
|
+
for (let task of this.tasks) {
|
|
2685
|
+
if (task.node.status === 2 /* DESTROYED */) {
|
|
2686
|
+
this.tasks.delete(task);
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2689
|
+
});
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
processFiber(fiber) {
|
|
2693
|
+
if (fiber.root !== fiber) {
|
|
2694
|
+
this.tasks.delete(fiber);
|
|
2695
|
+
return;
|
|
2696
|
+
}
|
|
2697
|
+
const hasError = fibersInError.has(fiber);
|
|
2698
|
+
if (hasError && fiber.counter !== 0) {
|
|
2699
|
+
this.tasks.delete(fiber);
|
|
2700
|
+
return;
|
|
2701
|
+
}
|
|
2702
|
+
if (fiber.node.status === 2 /* DESTROYED */) {
|
|
2703
|
+
this.tasks.delete(fiber);
|
|
2704
|
+
return;
|
|
2705
|
+
}
|
|
2706
|
+
if (fiber.counter === 0) {
|
|
2707
|
+
if (!hasError) {
|
|
2708
|
+
fiber.complete();
|
|
2709
|
+
}
|
|
2710
|
+
this.tasks.delete(fiber);
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
// capture the value of requestAnimationFrame as soon as possible, to avoid
|
|
2715
|
+
// interactions with other code, such as test frameworks that override them
|
|
2716
|
+
Scheduler.requestAnimationFrame = window.requestAnimationFrame.bind(window);
|
|
2717
|
+
|
|
2718
|
+
/**
|
|
2719
|
+
* Owl QWeb Expression Parser
|
|
2720
|
+
*
|
|
2721
|
+
* Owl needs in various contexts to be able to understand the structure of a
|
|
2722
|
+
* string representing a javascript expression. The usual goal is to be able
|
|
2723
|
+
* to rewrite some variables. For example, if a template has
|
|
2270
2724
|
*
|
|
2271
2725
|
* ```xml
|
|
2272
2726
|
* <t t-if="computeSomething({val: state.val})">...</t>
|
|
@@ -2413,24 +2867,31 @@ const TOKENIZERS = [
|
|
|
2413
2867
|
function tokenize(expr) {
|
|
2414
2868
|
const result = [];
|
|
2415
2869
|
let token = true;
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2870
|
+
let error;
|
|
2871
|
+
let current = expr;
|
|
2872
|
+
try {
|
|
2873
|
+
while (token) {
|
|
2874
|
+
current = current.trim();
|
|
2875
|
+
if (current) {
|
|
2876
|
+
for (let tokenizer of TOKENIZERS) {
|
|
2877
|
+
token = tokenizer(current);
|
|
2878
|
+
if (token) {
|
|
2879
|
+
result.push(token);
|
|
2880
|
+
current = current.slice(token.size || token.value.length);
|
|
2881
|
+
break;
|
|
2882
|
+
}
|
|
2425
2883
|
}
|
|
2426
2884
|
}
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2885
|
+
else {
|
|
2886
|
+
token = false;
|
|
2887
|
+
}
|
|
2430
2888
|
}
|
|
2431
2889
|
}
|
|
2432
|
-
|
|
2433
|
-
|
|
2890
|
+
catch (e) {
|
|
2891
|
+
error = e; // Silence all errors and throw a generic error below
|
|
2892
|
+
}
|
|
2893
|
+
if (current.length || error) {
|
|
2894
|
+
throw new Error(`Tokenizer error: could not tokenize \`${expr}\``);
|
|
2434
2895
|
}
|
|
2435
2896
|
return result;
|
|
2436
2897
|
}
|
|
@@ -2635,7 +3096,7 @@ function createContext(parentCtx, params) {
|
|
|
2635
3096
|
}, params);
|
|
2636
3097
|
}
|
|
2637
3098
|
class CodeTarget {
|
|
2638
|
-
constructor(name) {
|
|
3099
|
+
constructor(name, on) {
|
|
2639
3100
|
this.indentLevel = 0;
|
|
2640
3101
|
this.loopLevel = 0;
|
|
2641
3102
|
this.code = [];
|
|
@@ -2646,6 +3107,7 @@ class CodeTarget {
|
|
|
2646
3107
|
this.refInfo = {};
|
|
2647
3108
|
this.shouldProtectScope = false;
|
|
2648
3109
|
this.name = name;
|
|
3110
|
+
this.on = on || null;
|
|
2649
3111
|
}
|
|
2650
3112
|
addLine(line, idx) {
|
|
2651
3113
|
const prefix = new Array(this.indentLevel + 2).join(" ");
|
|
@@ -2695,7 +3157,7 @@ class CodeGenerator {
|
|
|
2695
3157
|
this.targets = [];
|
|
2696
3158
|
this.target = new CodeTarget("template");
|
|
2697
3159
|
this.translatableAttributes = TRANSLATABLE_ATTRS;
|
|
2698
|
-
this.
|
|
3160
|
+
this.staticDefs = [];
|
|
2699
3161
|
this.helpers = new Set();
|
|
2700
3162
|
this.translateFn = options.translateFn || ((s) => s);
|
|
2701
3163
|
if (options.translatableAttributes) {
|
|
@@ -2738,8 +3200,8 @@ class CodeGenerator {
|
|
|
2738
3200
|
if (this.templateName) {
|
|
2739
3201
|
mainCode.push(`// Template name: "${this.templateName}"`);
|
|
2740
3202
|
}
|
|
2741
|
-
for (let { id,
|
|
2742
|
-
mainCode.push(`const ${id} =
|
|
3203
|
+
for (let { id, expr } of this.staticDefs) {
|
|
3204
|
+
mainCode.push(`const ${id} = ${expr};`);
|
|
2743
3205
|
}
|
|
2744
3206
|
// define all blocks
|
|
2745
3207
|
if (this.blocks.length) {
|
|
@@ -2775,19 +3237,21 @@ class CodeGenerator {
|
|
|
2775
3237
|
}
|
|
2776
3238
|
return code;
|
|
2777
3239
|
}
|
|
2778
|
-
compileInNewTarget(prefix, ast, ctx) {
|
|
3240
|
+
compileInNewTarget(prefix, ast, ctx, on) {
|
|
2779
3241
|
const name = this.generateId(prefix);
|
|
2780
3242
|
const initialTarget = this.target;
|
|
2781
|
-
const target = new CodeTarget(name);
|
|
3243
|
+
const target = new CodeTarget(name, on);
|
|
2782
3244
|
this.targets.push(target);
|
|
2783
3245
|
this.target = target;
|
|
2784
|
-
|
|
2785
|
-
this.compileAST(ast, subCtx);
|
|
3246
|
+
this.compileAST(ast, createContext(ctx));
|
|
2786
3247
|
this.target = initialTarget;
|
|
2787
3248
|
return name;
|
|
2788
3249
|
}
|
|
2789
|
-
addLine(line) {
|
|
2790
|
-
this.target.addLine(line);
|
|
3250
|
+
addLine(line, idx) {
|
|
3251
|
+
this.target.addLine(line, idx);
|
|
3252
|
+
}
|
|
3253
|
+
define(varName, expr) {
|
|
3254
|
+
this.addLine(`const ${varName} = ${expr};`);
|
|
2791
3255
|
}
|
|
2792
3256
|
generateId(prefix = "") {
|
|
2793
3257
|
this.ids[prefix] = (this.ids[prefix] || 0) + 1;
|
|
@@ -2829,10 +3293,13 @@ class CodeGenerator {
|
|
|
2829
3293
|
blockExpr = `toggler(${tKeyExpr}, ${blockExpr})`;
|
|
2830
3294
|
}
|
|
2831
3295
|
if (block.isRoot && !ctx.preventRoot) {
|
|
3296
|
+
if (this.target.on) {
|
|
3297
|
+
blockExpr = this.wrapWithEventCatcher(blockExpr, this.target.on);
|
|
3298
|
+
}
|
|
2832
3299
|
this.addLine(`return ${blockExpr};`);
|
|
2833
3300
|
}
|
|
2834
3301
|
else {
|
|
2835
|
-
this.
|
|
3302
|
+
this.define(block.varName, blockExpr);
|
|
2836
3303
|
}
|
|
2837
3304
|
}
|
|
2838
3305
|
/**
|
|
@@ -2860,7 +3327,7 @@ class CodeGenerator {
|
|
|
2860
3327
|
if (!mapping.has(tok.varName)) {
|
|
2861
3328
|
const varId = this.generateId("v");
|
|
2862
3329
|
mapping.set(tok.varName, varId);
|
|
2863
|
-
this.
|
|
3330
|
+
this.define(varId, tok.value);
|
|
2864
3331
|
}
|
|
2865
3332
|
tok.value = mapping.get(tok.varName);
|
|
2866
3333
|
}
|
|
@@ -2999,7 +3466,7 @@ class CodeGenerator {
|
|
|
2999
3466
|
this.blocks.push(block);
|
|
3000
3467
|
if (ast.dynamicTag) {
|
|
3001
3468
|
const tagExpr = this.generateId("tag");
|
|
3002
|
-
this.
|
|
3469
|
+
this.define(tagExpr, compileExpr(ast.dynamicTag));
|
|
3003
3470
|
block.dynamicTagName = tagExpr;
|
|
3004
3471
|
}
|
|
3005
3472
|
}
|
|
@@ -3081,10 +3548,10 @@ class CodeGenerator {
|
|
|
3081
3548
|
const { hasDynamicChildren, baseExpr, expr, eventType, shouldNumberize, shouldTrim, targetAttr, specialInitTargetAttr, } = ast.model;
|
|
3082
3549
|
const baseExpression = compileExpr(baseExpr);
|
|
3083
3550
|
const bExprId = this.generateId("bExpr");
|
|
3084
|
-
this.
|
|
3551
|
+
this.define(bExprId, baseExpression);
|
|
3085
3552
|
const expression = compileExpr(expr);
|
|
3086
3553
|
const exprId = this.generateId("expr");
|
|
3087
|
-
this.
|
|
3554
|
+
this.define(exprId, expression);
|
|
3088
3555
|
const fullExpression = `${bExprId}[${exprId}]`;
|
|
3089
3556
|
let idx;
|
|
3090
3557
|
if (specialInitTargetAttr) {
|
|
@@ -3094,7 +3561,7 @@ class CodeGenerator {
|
|
|
3094
3561
|
else if (hasDynamicChildren) {
|
|
3095
3562
|
const bValueId = this.generateId("bValue");
|
|
3096
3563
|
tModelSelectedExpr = `${bValueId}`;
|
|
3097
|
-
this.
|
|
3564
|
+
this.define(tModelSelectedExpr, fullExpression);
|
|
3098
3565
|
}
|
|
3099
3566
|
else {
|
|
3100
3567
|
idx = block.insertData(`${fullExpression}`, "attr");
|
|
@@ -3142,14 +3609,14 @@ class CodeGenerator {
|
|
|
3142
3609
|
const children = block.children.slice();
|
|
3143
3610
|
let current = children.shift();
|
|
3144
3611
|
for (let i = codeIdx; i < code.length; i++) {
|
|
3145
|
-
if (code[i].trimStart().startsWith(`
|
|
3146
|
-
code[i] = code[i].replace(`
|
|
3612
|
+
if (code[i].trimStart().startsWith(`const ${current.varName} `)) {
|
|
3613
|
+
code[i] = code[i].replace(`const ${current.varName}`, current.varName);
|
|
3147
3614
|
current = children.shift();
|
|
3148
3615
|
if (!current)
|
|
3149
3616
|
break;
|
|
3150
3617
|
}
|
|
3151
3618
|
}
|
|
3152
|
-
this.
|
|
3619
|
+
this.addLine(`let ${block.children.map((c) => c.varName)};`, codeIdx);
|
|
3153
3620
|
}
|
|
3154
3621
|
}
|
|
3155
3622
|
}
|
|
@@ -3237,14 +3704,14 @@ class CodeGenerator {
|
|
|
3237
3704
|
const children = block.children.slice();
|
|
3238
3705
|
let current = children.shift();
|
|
3239
3706
|
for (let i = codeIdx; i < code.length; i++) {
|
|
3240
|
-
if (code[i].trimStart().startsWith(`
|
|
3241
|
-
code[i] = code[i].replace(`
|
|
3707
|
+
if (code[i].trimStart().startsWith(`const ${current.varName} `)) {
|
|
3708
|
+
code[i] = code[i].replace(`const ${current.varName}`, current.varName);
|
|
3242
3709
|
current = children.shift();
|
|
3243
3710
|
if (!current)
|
|
3244
3711
|
break;
|
|
3245
3712
|
}
|
|
3246
3713
|
}
|
|
3247
|
-
this.
|
|
3714
|
+
this.addLine(`let ${block.children.map((c) => c.varName)};`, codeIdx);
|
|
3248
3715
|
}
|
|
3249
3716
|
// note: this part is duplicated from end of compilemulti:
|
|
3250
3717
|
const args = block.children.map((c) => c.varName).join(", ");
|
|
@@ -3265,10 +3732,10 @@ class CodeGenerator {
|
|
|
3265
3732
|
const l = `l_block${block.id}`;
|
|
3266
3733
|
const c = `c_block${block.id}`;
|
|
3267
3734
|
this.helpers.add("prepareList");
|
|
3268
|
-
this.
|
|
3735
|
+
this.define(`[${keys}, ${vals}, ${l}, ${c}]`, `prepareList(${compileExpr(ast.collection)});`);
|
|
3269
3736
|
// Throw errors on duplicate keys in dev mode
|
|
3270
3737
|
if (this.dev) {
|
|
3271
|
-
this.
|
|
3738
|
+
this.define(`keys${block.id}`, `new Set()`);
|
|
3272
3739
|
}
|
|
3273
3740
|
this.addLine(`for (let ${loopVar} = 0; ${loopVar} < ${l}; ${loopVar}++) {`);
|
|
3274
3741
|
this.target.indentLevel++;
|
|
@@ -3285,7 +3752,7 @@ class CodeGenerator {
|
|
|
3285
3752
|
if (!ast.hasNoValue) {
|
|
3286
3753
|
this.addLine(`ctx[\`${ast.elem}_value\`] = ${keys}[${loopVar}];`);
|
|
3287
3754
|
}
|
|
3288
|
-
this.
|
|
3755
|
+
this.define(`key${this.target.loopLevel}`, ast.key ? compileExpr(ast.key) : loopVar);
|
|
3289
3756
|
if (this.dev) {
|
|
3290
3757
|
// Throw error on duplicate keys in dev mode
|
|
3291
3758
|
this.addLine(`if (keys${block.id}.has(key${this.target.loopLevel})) { throw new Error(\`Got duplicate key in t-foreach: \${key${this.target.loopLevel}}\`)}`);
|
|
@@ -3295,8 +3762,8 @@ class CodeGenerator {
|
|
|
3295
3762
|
if (ast.memo) {
|
|
3296
3763
|
this.target.hasCache = true;
|
|
3297
3764
|
id = this.generateId();
|
|
3298
|
-
this.
|
|
3299
|
-
this.
|
|
3765
|
+
this.define(`memo${id}`, compileExpr(ast.memo));
|
|
3766
|
+
this.define(`vnode${id}`, `cache[key${this.target.loopLevel}];`);
|
|
3300
3767
|
this.addLine(`if (vnode${id}) {`);
|
|
3301
3768
|
this.target.indentLevel++;
|
|
3302
3769
|
this.addLine(`if (shallowEqual(vnode${id}.memo, memo${id})) {`);
|
|
@@ -3324,7 +3791,7 @@ class CodeGenerator {
|
|
|
3324
3791
|
}
|
|
3325
3792
|
compileTKey(ast, ctx) {
|
|
3326
3793
|
const tKeyExpr = this.generateId("tKey_");
|
|
3327
|
-
this.
|
|
3794
|
+
this.define(tKeyExpr, compileExpr(ast.expr));
|
|
3328
3795
|
ctx = createContext(ctx, {
|
|
3329
3796
|
tKeyExpr,
|
|
3330
3797
|
block: ctx.block,
|
|
@@ -3369,14 +3836,14 @@ class CodeGenerator {
|
|
|
3369
3836
|
const children = block.children.slice();
|
|
3370
3837
|
let current = children.shift();
|
|
3371
3838
|
for (let i = codeIdx; i < code.length; i++) {
|
|
3372
|
-
if (code[i].trimStart().startsWith(`
|
|
3373
|
-
code[i] = code[i].replace(`
|
|
3839
|
+
if (code[i].trimStart().startsWith(`const ${current.varName} `)) {
|
|
3840
|
+
code[i] = code[i].replace(`const ${current.varName}`, current.varName);
|
|
3374
3841
|
current = children.shift();
|
|
3375
3842
|
if (!current)
|
|
3376
3843
|
break;
|
|
3377
3844
|
}
|
|
3378
3845
|
}
|
|
3379
|
-
this.
|
|
3846
|
+
this.addLine(`let ${block.children.map((c) => c.varName)};`, codeIdx);
|
|
3380
3847
|
}
|
|
3381
3848
|
}
|
|
3382
3849
|
const args = block.children.map((c) => c.varName).join(", ");
|
|
@@ -3407,7 +3874,7 @@ class CodeGenerator {
|
|
|
3407
3874
|
const key = `key + \`${this.generateComponentKey()}\``;
|
|
3408
3875
|
if (isDynamic) {
|
|
3409
3876
|
const templateVar = this.generateId("template");
|
|
3410
|
-
this.
|
|
3877
|
+
this.define(templateVar, subTemplate);
|
|
3411
3878
|
block = this.createBlock(block, "multi", ctx);
|
|
3412
3879
|
this.helpers.add("call");
|
|
3413
3880
|
this.insertBlock(`call(this, ${templateVar}, ctx, node, ${key})`, block, {
|
|
@@ -3418,7 +3885,7 @@ class CodeGenerator {
|
|
|
3418
3885
|
else {
|
|
3419
3886
|
const id = this.generateId(`callTemplate_`);
|
|
3420
3887
|
this.helpers.add("getTemplate");
|
|
3421
|
-
this.
|
|
3888
|
+
this.staticDefs.push({ id, expr: `getTemplate(${subTemplate})` });
|
|
3422
3889
|
block = this.createBlock(block, "multi", ctx);
|
|
3423
3890
|
this.insertBlock(`${id}.call(this, ctx, node, ${key})`, block, {
|
|
3424
3891
|
...ctx,
|
|
@@ -3512,27 +3979,29 @@ class CodeGenerator {
|
|
|
3512
3979
|
compileComponent(ast, ctx) {
|
|
3513
3980
|
let { block } = ctx;
|
|
3514
3981
|
// props
|
|
3515
|
-
const hasSlotsProp = "slots" in ast.props;
|
|
3982
|
+
const hasSlotsProp = "slots" in (ast.props || {});
|
|
3516
3983
|
const props = [];
|
|
3517
|
-
const propExpr = this.formatPropObject(ast.props);
|
|
3984
|
+
const propExpr = this.formatPropObject(ast.props || {});
|
|
3518
3985
|
if (propExpr) {
|
|
3519
3986
|
props.push(propExpr);
|
|
3520
3987
|
}
|
|
3521
3988
|
// slots
|
|
3522
|
-
const hasSlot = !!Object.keys(ast.slots).length;
|
|
3523
3989
|
let slotDef = "";
|
|
3524
|
-
if (
|
|
3990
|
+
if (ast.slots) {
|
|
3525
3991
|
let ctxStr = "ctx";
|
|
3526
3992
|
if (this.target.loopLevel || !this.hasSafeContext) {
|
|
3527
3993
|
ctxStr = this.generateId("ctx");
|
|
3528
3994
|
this.helpers.add("capture");
|
|
3529
|
-
this.
|
|
3995
|
+
this.define(ctxStr, `capture(ctx)`);
|
|
3530
3996
|
}
|
|
3531
3997
|
let slotStr = [];
|
|
3532
3998
|
for (let slotName in ast.slots) {
|
|
3533
|
-
const slotAst = ast.slots[slotName]
|
|
3534
|
-
const
|
|
3535
|
-
|
|
3999
|
+
const slotAst = ast.slots[slotName];
|
|
4000
|
+
const params = [];
|
|
4001
|
+
if (slotAst.content) {
|
|
4002
|
+
const name = this.compileInNewTarget("slot", slotAst.content, ctx, slotAst.on);
|
|
4003
|
+
params.push(`__render: ${name}, __ctx: ${ctxStr}`);
|
|
4004
|
+
}
|
|
3536
4005
|
const scope = ast.slots[slotName].scope;
|
|
3537
4006
|
if (scope) {
|
|
3538
4007
|
params.push(`__scope: "${scope}"`);
|
|
@@ -3546,33 +4015,30 @@ class CodeGenerator {
|
|
|
3546
4015
|
slotDef = `{${slotStr.join(", ")}}`;
|
|
3547
4016
|
}
|
|
3548
4017
|
if (slotDef && !(ast.dynamicProps || hasSlotsProp)) {
|
|
3549
|
-
|
|
4018
|
+
this.helpers.add("markRaw");
|
|
4019
|
+
props.push(`slots: markRaw(${slotDef})`);
|
|
3550
4020
|
}
|
|
3551
4021
|
const propStr = `{${props.join(",")}}`;
|
|
3552
4022
|
let propString = propStr;
|
|
3553
4023
|
if (ast.dynamicProps) {
|
|
3554
|
-
|
|
3555
|
-
propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)})`;
|
|
3556
|
-
}
|
|
3557
|
-
else {
|
|
3558
|
-
propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)}, ${propStr})`;
|
|
3559
|
-
}
|
|
4024
|
+
propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)}${props.length ? ", " + propStr : ""})`;
|
|
3560
4025
|
}
|
|
3561
4026
|
let propVar;
|
|
3562
4027
|
if ((slotDef && (ast.dynamicProps || hasSlotsProp)) || this.dev) {
|
|
3563
4028
|
propVar = this.generateId("props");
|
|
3564
|
-
this.
|
|
4029
|
+
this.define(propVar, propString);
|
|
3565
4030
|
propString = propVar;
|
|
3566
4031
|
}
|
|
3567
4032
|
if (slotDef && (ast.dynamicProps || hasSlotsProp)) {
|
|
3568
|
-
this.
|
|
4033
|
+
this.helpers.add("markRaw");
|
|
4034
|
+
this.addLine(`${propVar}.slots = markRaw(Object.assign(${slotDef}, ${propVar}.slots))`);
|
|
3569
4035
|
}
|
|
3570
4036
|
// cmap key
|
|
3571
4037
|
const key = this.generateComponentKey();
|
|
3572
4038
|
let expr;
|
|
3573
4039
|
if (ast.isDynamic) {
|
|
3574
4040
|
expr = this.generateId("Comp");
|
|
3575
|
-
this.
|
|
4041
|
+
this.define(expr, compileExpr(ast.name));
|
|
3576
4042
|
}
|
|
3577
4043
|
else {
|
|
3578
4044
|
expr = `\`${ast.name}\``;
|
|
@@ -3593,9 +4059,28 @@ class CodeGenerator {
|
|
|
3593
4059
|
if (ast.isDynamic) {
|
|
3594
4060
|
blockExpr = `toggler(${expr}, ${blockExpr})`;
|
|
3595
4061
|
}
|
|
4062
|
+
// event handling
|
|
4063
|
+
if (ast.on) {
|
|
4064
|
+
blockExpr = this.wrapWithEventCatcher(blockExpr, ast.on);
|
|
4065
|
+
}
|
|
3596
4066
|
block = this.createBlock(block, "multi", ctx);
|
|
3597
4067
|
this.insertBlock(blockExpr, block, ctx);
|
|
3598
4068
|
}
|
|
4069
|
+
wrapWithEventCatcher(expr, on) {
|
|
4070
|
+
this.helpers.add("createCatcher");
|
|
4071
|
+
let name = this.generateId("catcher");
|
|
4072
|
+
let spec = {};
|
|
4073
|
+
let handlers = [];
|
|
4074
|
+
for (let ev in on) {
|
|
4075
|
+
let handlerId = this.generateId("hdlr");
|
|
4076
|
+
let idx = handlers.push(handlerId) - 1;
|
|
4077
|
+
spec[ev] = idx;
|
|
4078
|
+
const handler = this.generateHandlerCode(ev, on[ev]);
|
|
4079
|
+
this.define(handlerId, handler);
|
|
4080
|
+
}
|
|
4081
|
+
this.staticDefs.push({ id: name, expr: `createCatcher(${JSON.stringify(spec)})` });
|
|
4082
|
+
return `${name}(${expr}, [${handlers.join(",")}])`;
|
|
4083
|
+
}
|
|
3599
4084
|
compileTSlot(ast, ctx) {
|
|
3600
4085
|
this.helpers.add("callSlot");
|
|
3601
4086
|
let { block } = ctx;
|
|
@@ -3617,13 +4102,17 @@ class CodeGenerator {
|
|
|
3617
4102
|
else {
|
|
3618
4103
|
if (dynamic) {
|
|
3619
4104
|
let name = this.generateId("slot");
|
|
3620
|
-
this.
|
|
3621
|
-
blockString = `toggler(${name}, callSlot(ctx, node, key, ${name}
|
|
4105
|
+
this.define(name, slotName);
|
|
4106
|
+
blockString = `toggler(${name}, callSlot(ctx, node, key, ${name}, ${dynamic}, ${scope}))`;
|
|
3622
4107
|
}
|
|
3623
4108
|
else {
|
|
3624
4109
|
blockString = `callSlot(ctx, node, key, ${slotName}, ${dynamic}, ${scope})`;
|
|
3625
4110
|
}
|
|
3626
4111
|
}
|
|
4112
|
+
// event handling
|
|
4113
|
+
if (ast.on) {
|
|
4114
|
+
blockString = this.wrapWithEventCatcher(blockString, ast.on);
|
|
4115
|
+
}
|
|
3627
4116
|
if (block) {
|
|
3628
4117
|
this.insertAnchor(block);
|
|
3629
4118
|
}
|
|
@@ -3644,7 +4133,7 @@ class CodeGenerator {
|
|
|
3644
4133
|
if (this.target.loopLevel || !this.hasSafeContext) {
|
|
3645
4134
|
ctxStr = this.generateId("ctx");
|
|
3646
4135
|
this.helpers.add("capture");
|
|
3647
|
-
this.
|
|
4136
|
+
this.define(ctxStr, `capture(ctx);`);
|
|
3648
4137
|
}
|
|
3649
4138
|
const blockString = `component(Portal, {target: ${ast.target},slots: {'default': {__render: ${name}, __ctx: ${ctxStr}}}}, key + \`${key}\`, node, ctx)`;
|
|
3650
4139
|
if (block) {
|
|
@@ -3765,6 +4254,9 @@ function parseDOMNode(node, ctx) {
|
|
|
3765
4254
|
if (tagName === "t" && !dynamicTag) {
|
|
3766
4255
|
return null;
|
|
3767
4256
|
}
|
|
4257
|
+
if (tagName.startsWith("block-")) {
|
|
4258
|
+
throw new Error(`Invalid tag name: '${tagName}'`);
|
|
4259
|
+
}
|
|
3768
4260
|
ctx = Object.assign({}, ctx);
|
|
3769
4261
|
if (tagName === "pre") {
|
|
3770
4262
|
ctx.inPreTag = true;
|
|
@@ -3775,8 +4267,8 @@ function parseDOMNode(node, ctx) {
|
|
|
3775
4267
|
const ref = node.getAttribute("t-ref");
|
|
3776
4268
|
node.removeAttribute("t-ref");
|
|
3777
4269
|
const nodeAttrsNames = node.getAttributeNames();
|
|
3778
|
-
|
|
3779
|
-
|
|
4270
|
+
let attrs = null;
|
|
4271
|
+
let on = null;
|
|
3780
4272
|
let model = null;
|
|
3781
4273
|
for (let attr of nodeAttrsNames) {
|
|
3782
4274
|
const value = node.getAttribute(attr);
|
|
@@ -3784,6 +4276,7 @@ function parseDOMNode(node, ctx) {
|
|
|
3784
4276
|
if (attr === "t-on") {
|
|
3785
4277
|
throw new Error("Missing event name with t-on directive");
|
|
3786
4278
|
}
|
|
4279
|
+
on = on || {};
|
|
3787
4280
|
on[attr.slice(5)] = value;
|
|
3788
4281
|
}
|
|
3789
4282
|
else if (attr.startsWith("t-model")) {
|
|
@@ -3821,6 +4314,7 @@ function parseDOMNode(node, ctx) {
|
|
|
3821
4314
|
targetAttr: isCheckboxInput ? "checked" : "value",
|
|
3822
4315
|
specialInitTargetAttr: isRadioInput ? "checked" : null,
|
|
3823
4316
|
eventType,
|
|
4317
|
+
hasDynamicChildren: false,
|
|
3824
4318
|
shouldTrim: hasTrimMod && (isOtherInput || isTextarea),
|
|
3825
4319
|
shouldNumberize: hasNumberMod && (isOtherInput || isTextarea),
|
|
3826
4320
|
};
|
|
@@ -3830,6 +4324,9 @@ function parseDOMNode(node, ctx) {
|
|
|
3830
4324
|
ctx.tModelInfo = model;
|
|
3831
4325
|
}
|
|
3832
4326
|
}
|
|
4327
|
+
else if (attr.startsWith("block-")) {
|
|
4328
|
+
throw new Error(`Invalid attribute: '${attr}'`);
|
|
4329
|
+
}
|
|
3833
4330
|
else if (attr !== "t-name") {
|
|
3834
4331
|
if (attr.startsWith("t-") && !attr.startsWith("t-att")) {
|
|
3835
4332
|
throw new Error(`Unknown QWeb directive: '${attr}'`);
|
|
@@ -3838,6 +4335,7 @@ function parseDOMNode(node, ctx) {
|
|
|
3838
4335
|
if (tModel && ["t-att-value", "t-attf-value"].includes(attr)) {
|
|
3839
4336
|
tModel.hasDynamicChildren = true;
|
|
3840
4337
|
}
|
|
4338
|
+
attrs = attrs || {};
|
|
3841
4339
|
attrs[attr] = value;
|
|
3842
4340
|
}
|
|
3843
4341
|
}
|
|
@@ -3988,7 +4486,7 @@ function parseTCall(node, ctx) {
|
|
|
3988
4486
|
if (ast && ast.type === 11 /* TComponent */) {
|
|
3989
4487
|
return {
|
|
3990
4488
|
...ast,
|
|
3991
|
-
slots: { default: { content: tcall } },
|
|
4489
|
+
slots: { default: { content: tcall, scope: null, on: null, attrs: null } },
|
|
3992
4490
|
};
|
|
3993
4491
|
}
|
|
3994
4492
|
}
|
|
@@ -4072,7 +4570,6 @@ function parseTSetNode(node, ctx) {
|
|
|
4072
4570
|
// -----------------------------------------------------------------------------
|
|
4073
4571
|
// Error messages when trying to use an unsupported directive on a component
|
|
4074
4572
|
const directiveErrorMap = new Map([
|
|
4075
|
-
["t-on", "t-on is no longer supported on components. Consider passing a callback in props."],
|
|
4076
4573
|
[
|
|
4077
4574
|
"t-ref",
|
|
4078
4575
|
"t-ref is no longer supported on components. Consider exposing only the public part of the component's API through a callback prop.",
|
|
@@ -4101,18 +4598,26 @@ function parseComponent(node, ctx) {
|
|
|
4101
4598
|
node.removeAttribute("t-props");
|
|
4102
4599
|
const defaultSlotScope = node.getAttribute("t-slot-scope");
|
|
4103
4600
|
node.removeAttribute("t-slot-scope");
|
|
4104
|
-
|
|
4601
|
+
let on = null;
|
|
4602
|
+
let props = null;
|
|
4105
4603
|
for (let name of node.getAttributeNames()) {
|
|
4106
4604
|
const value = node.getAttribute(name);
|
|
4107
4605
|
if (name.startsWith("t-")) {
|
|
4108
|
-
|
|
4109
|
-
|
|
4606
|
+
if (name.startsWith("t-on-")) {
|
|
4607
|
+
on = on || {};
|
|
4608
|
+
on[name.slice(5)] = value;
|
|
4609
|
+
}
|
|
4610
|
+
else {
|
|
4611
|
+
const message = directiveErrorMap.get(name.split("-").slice(0, 2).join("-"));
|
|
4612
|
+
throw new Error(message || `unsupported directive on Component: ${name}`);
|
|
4613
|
+
}
|
|
4110
4614
|
}
|
|
4111
4615
|
else {
|
|
4616
|
+
props = props || {};
|
|
4112
4617
|
props[name] = value;
|
|
4113
4618
|
}
|
|
4114
4619
|
}
|
|
4115
|
-
|
|
4620
|
+
let slots = null;
|
|
4116
4621
|
if (node.hasChildNodes()) {
|
|
4117
4622
|
const clone = node.cloneNode(true);
|
|
4118
4623
|
// named slots
|
|
@@ -4139,33 +4644,35 @@ function parseComponent(node, ctx) {
|
|
|
4139
4644
|
slotNode.removeAttribute("t-set-slot");
|
|
4140
4645
|
slotNode.remove();
|
|
4141
4646
|
const slotAst = parseNode(slotNode, ctx);
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
}
|
|
4151
|
-
attrs[attributeName] = value;
|
|
4647
|
+
let on = null;
|
|
4648
|
+
let attrs = null;
|
|
4649
|
+
let scope = null;
|
|
4650
|
+
for (let attributeName of slotNode.getAttributeNames()) {
|
|
4651
|
+
const value = slotNode.getAttribute(attributeName);
|
|
4652
|
+
if (attributeName === "t-slot-scope") {
|
|
4653
|
+
scope = value;
|
|
4654
|
+
continue;
|
|
4152
4655
|
}
|
|
4153
|
-
if (
|
|
4154
|
-
|
|
4656
|
+
else if (attributeName.startsWith("t-on-")) {
|
|
4657
|
+
on = on || {};
|
|
4658
|
+
on[attributeName.slice(5)] = value;
|
|
4659
|
+
}
|
|
4660
|
+
else {
|
|
4661
|
+
attrs = attrs || {};
|
|
4662
|
+
attrs[attributeName] = value;
|
|
4155
4663
|
}
|
|
4156
|
-
slots[name] = slotInfo;
|
|
4157
4664
|
}
|
|
4665
|
+
slots = slots || {};
|
|
4666
|
+
slots[name] = { content: slotAst, on, attrs, scope };
|
|
4158
4667
|
}
|
|
4159
4668
|
// default slot
|
|
4160
4669
|
const defaultContent = parseChildNodes(clone, ctx);
|
|
4161
4670
|
if (defaultContent) {
|
|
4162
|
-
slots
|
|
4163
|
-
|
|
4164
|
-
slots.default.scope = defaultSlotScope;
|
|
4165
|
-
}
|
|
4671
|
+
slots = slots || {};
|
|
4672
|
+
slots.default = { content: defaultContent, on, attrs: null, scope: defaultSlotScope };
|
|
4166
4673
|
}
|
|
4167
4674
|
}
|
|
4168
|
-
return { type: 11 /* TComponent */, name, isDynamic, dynamicProps, props, slots };
|
|
4675
|
+
return { type: 11 /* TComponent */, name, isDynamic, dynamicProps, props, slots, on };
|
|
4169
4676
|
}
|
|
4170
4677
|
// -----------------------------------------------------------------------------
|
|
4171
4678
|
// Slots
|
|
@@ -4176,15 +4683,24 @@ function parseTSlot(node, ctx) {
|
|
|
4176
4683
|
}
|
|
4177
4684
|
const name = node.getAttribute("t-slot");
|
|
4178
4685
|
node.removeAttribute("t-slot");
|
|
4179
|
-
|
|
4686
|
+
let attrs = null;
|
|
4687
|
+
let on = null;
|
|
4180
4688
|
for (let attributeName of node.getAttributeNames()) {
|
|
4181
4689
|
const value = node.getAttribute(attributeName);
|
|
4182
|
-
|
|
4690
|
+
if (attributeName.startsWith("t-on-")) {
|
|
4691
|
+
on = on || {};
|
|
4692
|
+
on[attributeName.slice(5)] = value;
|
|
4693
|
+
}
|
|
4694
|
+
else {
|
|
4695
|
+
attrs = attrs || {};
|
|
4696
|
+
attrs[attributeName] = value;
|
|
4697
|
+
}
|
|
4183
4698
|
}
|
|
4184
4699
|
return {
|
|
4185
4700
|
type: 14 /* TSlot */,
|
|
4186
4701
|
name,
|
|
4187
4702
|
attrs,
|
|
4703
|
+
on,
|
|
4188
4704
|
defaultContent: parseChildNodes(node, ctx),
|
|
4189
4705
|
};
|
|
4190
4706
|
}
|
|
@@ -4378,108 +4894,112 @@ function compile(template, options = {}) {
|
|
|
4378
4894
|
return new Function("bdom, helpers", code);
|
|
4379
4895
|
}
|
|
4380
4896
|
|
|
4381
|
-
const
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
const
|
|
4385
|
-
const
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
msg +=
|
|
4401
|
-
`\nThe error might be located at xml line ${lineNumber} column ${columnIndex}\n` +
|
|
4402
|
-
`${line}\n${"-".repeat(columnIndex - 1)}^`;
|
|
4403
|
-
}
|
|
4897
|
+
const TIMEOUT = Symbol("timeout");
|
|
4898
|
+
function wrapError(fn, hookName) {
|
|
4899
|
+
const error = new Error(`The following error occurred in ${hookName}: `);
|
|
4900
|
+
const timeoutError = new Error(`${hookName}'s promise hasn't resolved after 3 seconds`);
|
|
4901
|
+
const node = getCurrent();
|
|
4902
|
+
return (...args) => {
|
|
4903
|
+
try {
|
|
4904
|
+
const result = fn(...args);
|
|
4905
|
+
if (result instanceof Promise) {
|
|
4906
|
+
if (hookName === "onWillStart" || hookName === "onWillUpdateProps") {
|
|
4907
|
+
const fiber = node.fiber;
|
|
4908
|
+
Promise.race([
|
|
4909
|
+
result,
|
|
4910
|
+
new Promise((resolve) => setTimeout(() => resolve(TIMEOUT), 3000)),
|
|
4911
|
+
]).then((res) => {
|
|
4912
|
+
if (res === TIMEOUT && node.fiber === fiber) {
|
|
4913
|
+
console.warn(timeoutError);
|
|
4914
|
+
}
|
|
4915
|
+
});
|
|
4404
4916
|
}
|
|
4917
|
+
return result.catch((cause) => {
|
|
4918
|
+
error.cause = cause;
|
|
4919
|
+
if (cause instanceof Error) {
|
|
4920
|
+
error.message += `"${cause.message}"`;
|
|
4921
|
+
}
|
|
4922
|
+
throw error;
|
|
4923
|
+
});
|
|
4405
4924
|
}
|
|
4925
|
+
return result;
|
|
4406
4926
|
}
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
}
|
|
4411
|
-
class TemplateSet {
|
|
4412
|
-
constructor(config = {}) {
|
|
4413
|
-
this.rawTemplates = Object.create(globalTemplates);
|
|
4414
|
-
this.templates = {};
|
|
4415
|
-
this.utils = Object.assign({}, UTILS, {
|
|
4416
|
-
call: (owner, subTemplate, ctx, parent, key) => {
|
|
4417
|
-
const template = this.getTemplate(subTemplate);
|
|
4418
|
-
return toggler(subTemplate, template.call(owner, ctx, parent, key));
|
|
4419
|
-
},
|
|
4420
|
-
getTemplate: (name) => this.getTemplate(name),
|
|
4421
|
-
});
|
|
4422
|
-
this.dev = config.dev || false;
|
|
4423
|
-
this.translateFn = config.translateFn;
|
|
4424
|
-
this.translatableAttributes = config.translatableAttributes;
|
|
4425
|
-
if (config.templates) {
|
|
4426
|
-
this.addTemplates(config.templates);
|
|
4427
|
-
}
|
|
4428
|
-
}
|
|
4429
|
-
addTemplate(name, template, options = {}) {
|
|
4430
|
-
if (name in this.rawTemplates && !options.allowDuplicate) {
|
|
4431
|
-
throw new Error(`Template ${name} already defined`);
|
|
4432
|
-
}
|
|
4433
|
-
this.rawTemplates[name] = template;
|
|
4434
|
-
}
|
|
4435
|
-
addTemplates(xml, options = {}) {
|
|
4436
|
-
if (!xml) {
|
|
4437
|
-
// empty string
|
|
4438
|
-
return;
|
|
4439
|
-
}
|
|
4440
|
-
xml = xml instanceof Document ? xml : parseXML(xml);
|
|
4441
|
-
for (const template of xml.querySelectorAll("[t-name]")) {
|
|
4442
|
-
const name = template.getAttribute("t-name");
|
|
4443
|
-
this.addTemplate(name, template, options);
|
|
4444
|
-
}
|
|
4445
|
-
}
|
|
4446
|
-
getTemplate(name) {
|
|
4447
|
-
if (!(name in this.templates)) {
|
|
4448
|
-
const rawTemplate = this.rawTemplates[name];
|
|
4449
|
-
if (rawTemplate === undefined) {
|
|
4450
|
-
throw new Error(`Missing template: "${name}"`);
|
|
4927
|
+
catch (cause) {
|
|
4928
|
+
if (cause instanceof Error) {
|
|
4929
|
+
error.message += `"${cause.message}"`;
|
|
4451
4930
|
}
|
|
4452
|
-
|
|
4453
|
-
// first add a function to lazily get the template, in case there is a
|
|
4454
|
-
// recursive call to the template name
|
|
4455
|
-
const templates = this.templates;
|
|
4456
|
-
this.templates[name] = function (context, parent) {
|
|
4457
|
-
return templates[name].call(this, context, parent);
|
|
4458
|
-
};
|
|
4459
|
-
const template = templateFn(bdom, this.utils);
|
|
4460
|
-
this.templates[name] = template;
|
|
4931
|
+
throw error;
|
|
4461
4932
|
}
|
|
4462
|
-
|
|
4463
|
-
}
|
|
4464
|
-
_compileTemplate(name, template) {
|
|
4465
|
-
return compile(template, {
|
|
4466
|
-
name,
|
|
4467
|
-
dev: this.dev,
|
|
4468
|
-
translateFn: this.translateFn,
|
|
4469
|
-
translatableAttributes: this.translatableAttributes,
|
|
4470
|
-
});
|
|
4471
|
-
}
|
|
4933
|
+
};
|
|
4472
4934
|
}
|
|
4473
4935
|
// -----------------------------------------------------------------------------
|
|
4474
|
-
//
|
|
4936
|
+
// hooks
|
|
4475
4937
|
// -----------------------------------------------------------------------------
|
|
4476
|
-
function
|
|
4477
|
-
const
|
|
4478
|
-
const
|
|
4479
|
-
|
|
4480
|
-
return name;
|
|
4938
|
+
function onWillStart(fn) {
|
|
4939
|
+
const node = getCurrent();
|
|
4940
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4941
|
+
node.willStart.push(decorate(fn.bind(node.component), "onWillStart"));
|
|
4481
4942
|
}
|
|
4482
|
-
|
|
4943
|
+
function onWillUpdateProps(fn) {
|
|
4944
|
+
const node = getCurrent();
|
|
4945
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4946
|
+
node.willUpdateProps.push(decorate(fn.bind(node.component), "onWillUpdateProps"));
|
|
4947
|
+
}
|
|
4948
|
+
function onMounted(fn) {
|
|
4949
|
+
const node = getCurrent();
|
|
4950
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4951
|
+
node.mounted.push(decorate(fn.bind(node.component), "onMounted"));
|
|
4952
|
+
}
|
|
4953
|
+
function onWillPatch(fn) {
|
|
4954
|
+
const node = getCurrent();
|
|
4955
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4956
|
+
node.willPatch.unshift(decorate(fn.bind(node.component), "onWillPatch"));
|
|
4957
|
+
}
|
|
4958
|
+
function onPatched(fn) {
|
|
4959
|
+
const node = getCurrent();
|
|
4960
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4961
|
+
node.patched.push(decorate(fn.bind(node.component), "onPatched"));
|
|
4962
|
+
}
|
|
4963
|
+
function onWillUnmount(fn) {
|
|
4964
|
+
const node = getCurrent();
|
|
4965
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4966
|
+
node.willUnmount.unshift(decorate(fn.bind(node.component), "onWillUnmount"));
|
|
4967
|
+
}
|
|
4968
|
+
function onWillDestroy(fn) {
|
|
4969
|
+
const node = getCurrent();
|
|
4970
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4971
|
+
node.willDestroy.push(decorate(fn.bind(node.component), "onWillDestroy"));
|
|
4972
|
+
}
|
|
4973
|
+
function onWillRender(fn) {
|
|
4974
|
+
const node = getCurrent();
|
|
4975
|
+
const renderFn = node.renderFn;
|
|
4976
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4977
|
+
fn = decorate(fn.bind(node.component), "onWillRender");
|
|
4978
|
+
node.renderFn = () => {
|
|
4979
|
+
fn();
|
|
4980
|
+
return renderFn();
|
|
4981
|
+
};
|
|
4982
|
+
}
|
|
4983
|
+
function onRendered(fn) {
|
|
4984
|
+
const node = getCurrent();
|
|
4985
|
+
const renderFn = node.renderFn;
|
|
4986
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4987
|
+
fn = decorate(fn.bind(node.component), "onRendered");
|
|
4988
|
+
node.renderFn = () => {
|
|
4989
|
+
const result = renderFn();
|
|
4990
|
+
fn();
|
|
4991
|
+
return result;
|
|
4992
|
+
};
|
|
4993
|
+
}
|
|
4994
|
+
function onError(callback) {
|
|
4995
|
+
const node = getCurrent();
|
|
4996
|
+
let handlers = nodeErrorHandlers.get(node);
|
|
4997
|
+
if (!handlers) {
|
|
4998
|
+
handlers = [];
|
|
4999
|
+
nodeErrorHandlers.set(node, handlers);
|
|
5000
|
+
}
|
|
5001
|
+
handlers.push(callback.bind(node.component));
|
|
5002
|
+
}
|
|
4483
5003
|
|
|
4484
5004
|
class Component {
|
|
4485
5005
|
constructor(props, env, node) {
|
|
@@ -4488,8 +5008,8 @@ class Component {
|
|
|
4488
5008
|
this.__owl__ = node;
|
|
4489
5009
|
}
|
|
4490
5010
|
setup() { }
|
|
4491
|
-
render() {
|
|
4492
|
-
this.__owl__.render();
|
|
5011
|
+
render(deep = false) {
|
|
5012
|
+
this.__owl__.render(deep);
|
|
4493
5013
|
}
|
|
4494
5014
|
}
|
|
4495
5015
|
Component.template = "";
|
|
@@ -4558,75 +5078,293 @@ Portal.props = {
|
|
|
4558
5078
|
slots: true,
|
|
4559
5079
|
};
|
|
4560
5080
|
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
5081
|
+
/**
|
|
5082
|
+
* This file contains utility functions that will be injected in each template,
|
|
5083
|
+
* to perform various useful tasks in the compiled code.
|
|
5084
|
+
*/
|
|
5085
|
+
function withDefault(value, defaultValue) {
|
|
5086
|
+
return value === undefined || value === null || value === false ? defaultValue : value;
|
|
5087
|
+
}
|
|
5088
|
+
function callSlot(ctx, parent, key, name, dynamic, extra, defaultContent) {
|
|
5089
|
+
key = key + "__slot_" + name;
|
|
5090
|
+
const slots = ctx.props[TARGET].slots || {};
|
|
5091
|
+
const { __render, __ctx, __scope } = slots[name] || {};
|
|
5092
|
+
const slotScope = Object.create(__ctx || {});
|
|
5093
|
+
if (__scope) {
|
|
5094
|
+
slotScope[__scope] = extra;
|
|
5095
|
+
}
|
|
5096
|
+
const slotBDom = __render ? __render.call(__ctx.__owl__.component, slotScope, parent, key) : null;
|
|
5097
|
+
if (defaultContent) {
|
|
5098
|
+
let child1 = undefined;
|
|
5099
|
+
let child2 = undefined;
|
|
5100
|
+
if (slotBDom) {
|
|
5101
|
+
child1 = dynamic ? toggler(name, slotBDom) : slotBDom;
|
|
5102
|
+
}
|
|
5103
|
+
else {
|
|
5104
|
+
child2 = defaultContent.call(ctx.__owl__.component, ctx, parent, key);
|
|
5105
|
+
}
|
|
5106
|
+
return multi([child1, child2]);
|
|
5107
|
+
}
|
|
5108
|
+
return slotBDom || text("");
|
|
5109
|
+
}
|
|
5110
|
+
function capture(ctx) {
|
|
5111
|
+
const component = ctx.__owl__.component;
|
|
5112
|
+
const result = Object.create(component);
|
|
5113
|
+
for (let k in ctx) {
|
|
5114
|
+
result[k] = ctx[k];
|
|
5115
|
+
}
|
|
5116
|
+
return result;
|
|
5117
|
+
}
|
|
5118
|
+
function withKey(elem, k) {
|
|
5119
|
+
elem.key = k;
|
|
5120
|
+
return elem;
|
|
5121
|
+
}
|
|
5122
|
+
function prepareList(collection) {
|
|
5123
|
+
let keys;
|
|
5124
|
+
let values;
|
|
5125
|
+
if (Array.isArray(collection)) {
|
|
5126
|
+
keys = collection;
|
|
5127
|
+
values = collection;
|
|
5128
|
+
}
|
|
5129
|
+
else if (collection) {
|
|
5130
|
+
values = Object.keys(collection);
|
|
5131
|
+
keys = Object.values(collection);
|
|
4569
5132
|
}
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
5133
|
+
else {
|
|
5134
|
+
throw new Error("Invalid loop expression");
|
|
5135
|
+
}
|
|
5136
|
+
const n = values.length;
|
|
5137
|
+
return [keys, values, n, new Array(n)];
|
|
5138
|
+
}
|
|
5139
|
+
const isBoundary = Symbol("isBoundary");
|
|
5140
|
+
function setContextValue(ctx, key, value) {
|
|
5141
|
+
const ctx0 = ctx;
|
|
5142
|
+
while (!ctx.hasOwnProperty(key) && !ctx.hasOwnProperty(isBoundary)) {
|
|
5143
|
+
const newCtx = ctx.__proto__;
|
|
5144
|
+
if (!newCtx) {
|
|
5145
|
+
ctx = ctx0;
|
|
5146
|
+
break;
|
|
5147
|
+
}
|
|
5148
|
+
ctx = newCtx;
|
|
5149
|
+
}
|
|
5150
|
+
ctx[key] = value;
|
|
5151
|
+
}
|
|
5152
|
+
function toNumber(val) {
|
|
5153
|
+
const n = parseFloat(val);
|
|
5154
|
+
return isNaN(n) ? val : n;
|
|
5155
|
+
}
|
|
5156
|
+
function shallowEqual(l1, l2) {
|
|
5157
|
+
for (let i = 0, l = l1.length; i < l; i++) {
|
|
5158
|
+
if (l1[i] !== l2[i]) {
|
|
5159
|
+
return false;
|
|
5160
|
+
}
|
|
5161
|
+
}
|
|
5162
|
+
return true;
|
|
5163
|
+
}
|
|
5164
|
+
class LazyValue {
|
|
5165
|
+
constructor(fn, ctx, node) {
|
|
5166
|
+
this.fn = fn;
|
|
5167
|
+
this.ctx = capture(ctx);
|
|
5168
|
+
this.node = node;
|
|
5169
|
+
}
|
|
5170
|
+
evaluate() {
|
|
5171
|
+
return this.fn(this.ctx, this.node);
|
|
5172
|
+
}
|
|
5173
|
+
toString() {
|
|
5174
|
+
return this.evaluate().toString();
|
|
5175
|
+
}
|
|
5176
|
+
}
|
|
5177
|
+
/*
|
|
5178
|
+
* Safely outputs `value` as a block depending on the nature of `value`
|
|
5179
|
+
*/
|
|
5180
|
+
function safeOutput(value) {
|
|
5181
|
+
if (!value) {
|
|
5182
|
+
return value;
|
|
5183
|
+
}
|
|
5184
|
+
let safeKey;
|
|
5185
|
+
let block;
|
|
5186
|
+
if (value instanceof Markup) {
|
|
5187
|
+
safeKey = `string_safe`;
|
|
5188
|
+
block = html(value);
|
|
5189
|
+
}
|
|
5190
|
+
else if (value instanceof LazyValue) {
|
|
5191
|
+
safeKey = `lazy_value`;
|
|
5192
|
+
block = value.evaluate();
|
|
5193
|
+
}
|
|
5194
|
+
else if (value instanceof String || typeof value === "string") {
|
|
5195
|
+
safeKey = "string_unsafe";
|
|
5196
|
+
block = text(value);
|
|
5197
|
+
}
|
|
5198
|
+
else {
|
|
5199
|
+
// Assuming it is a block
|
|
5200
|
+
safeKey = "block_safe";
|
|
5201
|
+
block = value;
|
|
5202
|
+
}
|
|
5203
|
+
return toggler(safeKey, block);
|
|
5204
|
+
}
|
|
5205
|
+
let boundFunctions = new WeakMap();
|
|
5206
|
+
function bind(ctx, fn) {
|
|
5207
|
+
let component = ctx.__owl__.component;
|
|
5208
|
+
let boundFnMap = boundFunctions.get(component);
|
|
5209
|
+
if (!boundFnMap) {
|
|
5210
|
+
boundFnMap = new WeakMap();
|
|
5211
|
+
boundFunctions.set(component, boundFnMap);
|
|
5212
|
+
}
|
|
5213
|
+
let boundFn = boundFnMap.get(fn);
|
|
5214
|
+
if (!boundFn) {
|
|
5215
|
+
boundFn = fn.bind(component);
|
|
5216
|
+
boundFnMap.set(fn, boundFn);
|
|
5217
|
+
}
|
|
5218
|
+
return boundFn;
|
|
5219
|
+
}
|
|
5220
|
+
function multiRefSetter(refs, name) {
|
|
5221
|
+
let count = 0;
|
|
5222
|
+
return (el) => {
|
|
5223
|
+
if (el) {
|
|
5224
|
+
count++;
|
|
5225
|
+
if (count > 1) {
|
|
5226
|
+
throw new Error("Cannot have 2 elements with same ref name at the same time");
|
|
5227
|
+
}
|
|
5228
|
+
}
|
|
5229
|
+
if (count === 0 || el) {
|
|
5230
|
+
refs[name] = el;
|
|
5231
|
+
}
|
|
5232
|
+
};
|
|
5233
|
+
}
|
|
5234
|
+
const helpers = {
|
|
5235
|
+
withDefault,
|
|
5236
|
+
zero: Symbol("zero"),
|
|
5237
|
+
isBoundary,
|
|
5238
|
+
callSlot,
|
|
5239
|
+
capture,
|
|
5240
|
+
withKey,
|
|
5241
|
+
prepareList,
|
|
5242
|
+
setContextValue,
|
|
5243
|
+
multiRefSetter,
|
|
5244
|
+
shallowEqual,
|
|
5245
|
+
toNumber,
|
|
5246
|
+
validateProps,
|
|
5247
|
+
LazyValue,
|
|
5248
|
+
safeOutput,
|
|
5249
|
+
bind,
|
|
5250
|
+
createCatcher,
|
|
5251
|
+
};
|
|
5252
|
+
|
|
5253
|
+
const bdom = { text, createBlock, list, multi, html, toggler, component, comment };
|
|
5254
|
+
function parseXML(xml) {
|
|
5255
|
+
const parser = new DOMParser();
|
|
5256
|
+
const doc = parser.parseFromString(xml, "text/xml");
|
|
5257
|
+
if (doc.getElementsByTagName("parsererror").length) {
|
|
5258
|
+
let msg = "Invalid XML in template.";
|
|
5259
|
+
const parsererrorText = doc.getElementsByTagName("parsererror")[0].textContent;
|
|
5260
|
+
if (parsererrorText) {
|
|
5261
|
+
msg += "\nThe parser has produced the following error message:\n" + parsererrorText;
|
|
5262
|
+
const re = /\d+/g;
|
|
5263
|
+
const firstMatch = re.exec(parsererrorText);
|
|
5264
|
+
if (firstMatch) {
|
|
5265
|
+
const lineNumber = Number(firstMatch[0]);
|
|
5266
|
+
const line = xml.split("\n")[lineNumber - 1];
|
|
5267
|
+
const secondMatch = re.exec(parsererrorText);
|
|
5268
|
+
if (line && secondMatch) {
|
|
5269
|
+
const columnIndex = Number(secondMatch[0]) - 1;
|
|
5270
|
+
if (line[columnIndex]) {
|
|
5271
|
+
msg +=
|
|
5272
|
+
`\nThe error might be located at xml line ${lineNumber} column ${columnIndex}\n` +
|
|
5273
|
+
`${line}\n${"-".repeat(columnIndex - 1)}^`;
|
|
5274
|
+
}
|
|
5275
|
+
}
|
|
5276
|
+
}
|
|
5277
|
+
}
|
|
5278
|
+
throw new Error(msg);
|
|
5279
|
+
}
|
|
5280
|
+
return doc;
|
|
5281
|
+
}
|
|
5282
|
+
/**
|
|
5283
|
+
* Returns the helpers object that will be injected in each template closure
|
|
5284
|
+
* function
|
|
5285
|
+
*/
|
|
5286
|
+
function makeHelpers(getTemplate) {
|
|
5287
|
+
return Object.assign({}, helpers, {
|
|
5288
|
+
Portal,
|
|
5289
|
+
markRaw,
|
|
5290
|
+
getTemplate,
|
|
5291
|
+
call: (owner, subTemplate, ctx, parent, key) => {
|
|
5292
|
+
const template = getTemplate(subTemplate);
|
|
5293
|
+
return toggler(subTemplate, template.call(owner, ctx, parent, key));
|
|
5294
|
+
},
|
|
5295
|
+
});
|
|
5296
|
+
}
|
|
5297
|
+
class TemplateSet {
|
|
5298
|
+
constructor(config = {}) {
|
|
5299
|
+
this.rawTemplates = Object.create(globalTemplates);
|
|
5300
|
+
this.templates = {};
|
|
5301
|
+
this.dev = config.dev || false;
|
|
5302
|
+
this.translateFn = config.translateFn;
|
|
5303
|
+
this.translatableAttributes = config.translatableAttributes;
|
|
5304
|
+
if (config.templates) {
|
|
5305
|
+
this.addTemplates(config.templates);
|
|
5306
|
+
}
|
|
5307
|
+
this.helpers = makeHelpers(this.getTemplate.bind(this));
|
|
4573
5308
|
}
|
|
4574
|
-
|
|
4575
|
-
this.
|
|
5309
|
+
addTemplate(name, template, options = {}) {
|
|
5310
|
+
if (name in this.rawTemplates && !options.allowDuplicate) {
|
|
5311
|
+
throw new Error(`Template ${name} already defined`);
|
|
5312
|
+
}
|
|
5313
|
+
this.rawTemplates[name] = template;
|
|
4576
5314
|
}
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
5315
|
+
addTemplates(xml, options = {}) {
|
|
5316
|
+
if (!xml) {
|
|
5317
|
+
// empty string
|
|
5318
|
+
return;
|
|
5319
|
+
}
|
|
5320
|
+
xml = xml instanceof Document ? xml : parseXML(xml);
|
|
5321
|
+
for (const template of xml.querySelectorAll("[t-name]")) {
|
|
5322
|
+
const name = template.getAttribute("t-name");
|
|
5323
|
+
this.addTemplate(name, template, options);
|
|
4581
5324
|
}
|
|
4582
5325
|
}
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
return;
|
|
4592
|
-
}
|
|
4593
|
-
const hasError = fibersInError.has(fiber);
|
|
4594
|
-
if (hasError && fiber.counter !== 0) {
|
|
4595
|
-
this.tasks.delete(fiber);
|
|
4596
|
-
return;
|
|
4597
|
-
}
|
|
4598
|
-
if (fiber.node.status === 2 /* DESTROYED */) {
|
|
4599
|
-
this.tasks.delete(fiber);
|
|
4600
|
-
return;
|
|
4601
|
-
}
|
|
4602
|
-
if (fiber.counter === 0) {
|
|
4603
|
-
if (!hasError) {
|
|
4604
|
-
fiber.complete();
|
|
5326
|
+
getTemplate(name) {
|
|
5327
|
+
if (!(name in this.templates)) {
|
|
5328
|
+
const rawTemplate = this.rawTemplates[name];
|
|
5329
|
+
if (rawTemplate === undefined) {
|
|
5330
|
+
let extraInfo = "";
|
|
5331
|
+
try {
|
|
5332
|
+
const componentName = getCurrent().component.constructor.name;
|
|
5333
|
+
extraInfo = ` (for component "${componentName}")`;
|
|
4605
5334
|
}
|
|
4606
|
-
|
|
5335
|
+
catch { }
|
|
5336
|
+
throw new Error(`Missing template: "${name}"${extraInfo}`);
|
|
4607
5337
|
}
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
5338
|
+
const templateFn = this._compileTemplate(name, rawTemplate);
|
|
5339
|
+
// first add a function to lazily get the template, in case there is a
|
|
5340
|
+
// recursive call to the template name
|
|
5341
|
+
const templates = this.templates;
|
|
5342
|
+
this.templates[name] = function (context, parent) {
|
|
5343
|
+
return templates[name].call(this, context, parent);
|
|
5344
|
+
};
|
|
5345
|
+
const template = templateFn(bdom, this.helpers);
|
|
5346
|
+
this.templates[name] = template;
|
|
4611
5347
|
}
|
|
5348
|
+
return this.templates[name];
|
|
4612
5349
|
}
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
5350
|
+
_compileTemplate(name, template) {
|
|
5351
|
+
return compile(template, {
|
|
5352
|
+
name,
|
|
5353
|
+
dev: this.dev,
|
|
5354
|
+
translateFn: this.translateFn,
|
|
5355
|
+
translatableAttributes: this.translatableAttributes,
|
|
4619
5356
|
});
|
|
4620
5357
|
}
|
|
4621
|
-
}
|
|
4622
|
-
// capture the value of requestAnimationFrame as soon as possible, to avoid
|
|
4623
|
-
// interactions with other code, such as test frameworks that override them
|
|
4624
|
-
Scheduler.requestAnimationFrame = window.requestAnimationFrame.bind(window);
|
|
5358
|
+
}
|
|
4625
5359
|
|
|
4626
|
-
|
|
5360
|
+
let hasBeenLogged = false;
|
|
5361
|
+
const DEV_MSG = () => {
|
|
5362
|
+
const hash = window.owl ? window.owl.__info__.hash : "master";
|
|
5363
|
+
return `Owl is running in 'dev' mode.
|
|
4627
5364
|
|
|
4628
5365
|
This is not suitable for production use.
|
|
4629
|
-
See https://github.com/odoo/owl/blob/
|
|
5366
|
+
See https://github.com/odoo/owl/blob/${hash}/doc/reference/app.md#configuration for more information.`;
|
|
5367
|
+
};
|
|
4630
5368
|
class App extends TemplateSet {
|
|
4631
5369
|
constructor(Root, config = {}) {
|
|
4632
5370
|
super(config);
|
|
@@ -4636,11 +5374,13 @@ class App extends TemplateSet {
|
|
|
4636
5374
|
if (config.test) {
|
|
4637
5375
|
this.dev = true;
|
|
4638
5376
|
}
|
|
4639
|
-
if (this.dev && !config.test) {
|
|
4640
|
-
console.info(DEV_MSG);
|
|
5377
|
+
if (this.dev && !config.test && !hasBeenLogged) {
|
|
5378
|
+
console.info(DEV_MSG());
|
|
5379
|
+
hasBeenLogged = true;
|
|
4641
5380
|
}
|
|
4642
|
-
const
|
|
4643
|
-
|
|
5381
|
+
const env = config.env || {};
|
|
5382
|
+
const descrs = Object.getOwnPropertyDescriptors(env);
|
|
5383
|
+
this.env = Object.freeze(Object.create(Object.getPrototypeOf(env), descrs));
|
|
4644
5384
|
this.props = config.props || {};
|
|
4645
5385
|
}
|
|
4646
5386
|
mount(target, options) {
|
|
@@ -4651,7 +5391,7 @@ class App extends TemplateSet {
|
|
|
4651
5391
|
return prom;
|
|
4652
5392
|
}
|
|
4653
5393
|
makeNode(Component, props) {
|
|
4654
|
-
return new ComponentNode(Component, props, this);
|
|
5394
|
+
return new ComponentNode(Component, props, this, null, null);
|
|
4655
5395
|
}
|
|
4656
5396
|
mountNode(node, target, options) {
|
|
4657
5397
|
const promise = new Promise((resolve, reject) => {
|
|
@@ -4683,6 +5423,7 @@ class App extends TemplateSet {
|
|
|
4683
5423
|
}
|
|
4684
5424
|
destroy() {
|
|
4685
5425
|
if (this.root) {
|
|
5426
|
+
this.scheduler.flush();
|
|
4686
5427
|
this.root.destroy();
|
|
4687
5428
|
}
|
|
4688
5429
|
}
|
|
@@ -4703,270 +5444,6 @@ function status(component) {
|
|
|
4703
5444
|
}
|
|
4704
5445
|
}
|
|
4705
5446
|
|
|
4706
|
-
class Memo extends Component {
|
|
4707
|
-
constructor(props, env, node) {
|
|
4708
|
-
super(props, env, node);
|
|
4709
|
-
// prevent patching process conditionally
|
|
4710
|
-
let applyPatch = false;
|
|
4711
|
-
const patchFn = node.patch;
|
|
4712
|
-
node.patch = () => {
|
|
4713
|
-
if (applyPatch) {
|
|
4714
|
-
patchFn.call(node);
|
|
4715
|
-
applyPatch = false;
|
|
4716
|
-
}
|
|
4717
|
-
};
|
|
4718
|
-
// check props change, and render/apply patch if it changed
|
|
4719
|
-
let prevProps = props;
|
|
4720
|
-
const updateAndRender = node.updateAndRender;
|
|
4721
|
-
node.updateAndRender = function (props, parentFiber) {
|
|
4722
|
-
const shouldUpdate = !shallowEqual(prevProps, props);
|
|
4723
|
-
if (shouldUpdate) {
|
|
4724
|
-
prevProps = props;
|
|
4725
|
-
updateAndRender.call(node, props, parentFiber);
|
|
4726
|
-
applyPatch = true;
|
|
4727
|
-
}
|
|
4728
|
-
return Promise.resolve();
|
|
4729
|
-
};
|
|
4730
|
-
}
|
|
4731
|
-
}
|
|
4732
|
-
Memo.template = xml `<t t-slot="default"/>`;
|
|
4733
|
-
/**
|
|
4734
|
-
* we assume that each object have the same set of keys
|
|
4735
|
-
*/
|
|
4736
|
-
function shallowEqual(p1, p2) {
|
|
4737
|
-
for (let k in p1) {
|
|
4738
|
-
if (k !== "slots" && p1[k] !== p2[k]) {
|
|
4739
|
-
return false;
|
|
4740
|
-
}
|
|
4741
|
-
}
|
|
4742
|
-
return true;
|
|
4743
|
-
}
|
|
4744
|
-
|
|
4745
|
-
// Allows to get the target of a Reactive (used for making a new Reactive from the underlying object)
|
|
4746
|
-
const TARGET = Symbol("Target");
|
|
4747
|
-
// Escape hatch to prevent reactivity system to turn something into a reactive
|
|
4748
|
-
const SKIP = Symbol("Skip");
|
|
4749
|
-
// Special key to subscribe to, to be notified of key creation/deletion
|
|
4750
|
-
const KEYCHANGES = Symbol("Key changes");
|
|
4751
|
-
const objectToString = Object.prototype.toString;
|
|
4752
|
-
/**
|
|
4753
|
-
* Checks whether a given value can be made into a reactive object.
|
|
4754
|
-
*
|
|
4755
|
-
* @param value the value to check
|
|
4756
|
-
* @returns whether the value can be made reactive
|
|
4757
|
-
*/
|
|
4758
|
-
function canBeMadeReactive(value) {
|
|
4759
|
-
if (typeof value !== "object") {
|
|
4760
|
-
return false;
|
|
4761
|
-
}
|
|
4762
|
-
// extract "RawType" from strings like "[object RawType]" => this lets us
|
|
4763
|
-
// ignore many native objects such as Promise (whose toString is [object Promise])
|
|
4764
|
-
// or Date ([object Date]).
|
|
4765
|
-
const rawType = objectToString.call(value).slice(8, -1);
|
|
4766
|
-
return rawType === "Object" || rawType === "Array";
|
|
4767
|
-
}
|
|
4768
|
-
/**
|
|
4769
|
-
* Mark an object or array so that it is ignored by the reactivity system
|
|
4770
|
-
*
|
|
4771
|
-
* @param value the value to mark
|
|
4772
|
-
* @returns the object itself
|
|
4773
|
-
*/
|
|
4774
|
-
function markRaw(value) {
|
|
4775
|
-
value[SKIP] = true;
|
|
4776
|
-
return value;
|
|
4777
|
-
}
|
|
4778
|
-
/**
|
|
4779
|
-
* Given a reactive objet, return the raw (non reactive) underlying object
|
|
4780
|
-
*
|
|
4781
|
-
* @param value a reactive value
|
|
4782
|
-
* @returns the underlying value
|
|
4783
|
-
*/
|
|
4784
|
-
function toRaw(value) {
|
|
4785
|
-
return value[TARGET];
|
|
4786
|
-
}
|
|
4787
|
-
const targetToKeysToCallbacks = new WeakMap();
|
|
4788
|
-
/**
|
|
4789
|
-
* Observes a given key on a target with an callback. The callback will be
|
|
4790
|
-
* called when the given key changes on the target.
|
|
4791
|
-
*
|
|
4792
|
-
* @param target the target whose key should be observed
|
|
4793
|
-
* @param key the key to observe (or Symbol(KEYCHANGES) for key creation
|
|
4794
|
-
* or deletion)
|
|
4795
|
-
* @param callback the function to call when the key changes
|
|
4796
|
-
*/
|
|
4797
|
-
function observeTargetKey(target, key, callback) {
|
|
4798
|
-
if (!targetToKeysToCallbacks.get(target)) {
|
|
4799
|
-
targetToKeysToCallbacks.set(target, new Map());
|
|
4800
|
-
}
|
|
4801
|
-
const keyToCallbacks = targetToKeysToCallbacks.get(target);
|
|
4802
|
-
if (!keyToCallbacks.get(key)) {
|
|
4803
|
-
keyToCallbacks.set(key, new Set());
|
|
4804
|
-
}
|
|
4805
|
-
keyToCallbacks.get(key).add(callback);
|
|
4806
|
-
if (!callbacksToTargets.has(callback)) {
|
|
4807
|
-
callbacksToTargets.set(callback, new Set());
|
|
4808
|
-
}
|
|
4809
|
-
callbacksToTargets.get(callback).add(target);
|
|
4810
|
-
}
|
|
4811
|
-
/**
|
|
4812
|
-
* Notify Reactives that are observing a given target that a key has changed on
|
|
4813
|
-
* the target.
|
|
4814
|
-
*
|
|
4815
|
-
* @param target target whose Reactives should be notified that the target was
|
|
4816
|
-
* changed.
|
|
4817
|
-
* @param key the key that changed (or Symbol `KEYCHANGES` if a key was created
|
|
4818
|
-
* or deleted)
|
|
4819
|
-
*/
|
|
4820
|
-
function notifyReactives(target, key) {
|
|
4821
|
-
const keyToCallbacks = targetToKeysToCallbacks.get(target);
|
|
4822
|
-
if (!keyToCallbacks) {
|
|
4823
|
-
return;
|
|
4824
|
-
}
|
|
4825
|
-
const callbacks = keyToCallbacks.get(key);
|
|
4826
|
-
if (!callbacks) {
|
|
4827
|
-
return;
|
|
4828
|
-
}
|
|
4829
|
-
// Loop on copy because clearReactivesForCallback will modify the set in place
|
|
4830
|
-
for (const callback of [...callbacks]) {
|
|
4831
|
-
clearReactivesForCallback(callback);
|
|
4832
|
-
callback();
|
|
4833
|
-
}
|
|
4834
|
-
}
|
|
4835
|
-
const callbacksToTargets = new WeakMap();
|
|
4836
|
-
/**
|
|
4837
|
-
* Clears all subscriptions of the Reactives associated with a given callback.
|
|
4838
|
-
*
|
|
4839
|
-
* @param callback the callback for which the reactives need to be cleared
|
|
4840
|
-
*/
|
|
4841
|
-
function clearReactivesForCallback(callback) {
|
|
4842
|
-
const targetsToClear = callbacksToTargets.get(callback);
|
|
4843
|
-
if (!targetsToClear) {
|
|
4844
|
-
return;
|
|
4845
|
-
}
|
|
4846
|
-
for (const target of targetsToClear) {
|
|
4847
|
-
const observedKeys = targetToKeysToCallbacks.get(target);
|
|
4848
|
-
if (!observedKeys) {
|
|
4849
|
-
continue;
|
|
4850
|
-
}
|
|
4851
|
-
for (const callbacks of observedKeys.values()) {
|
|
4852
|
-
callbacks.delete(callback);
|
|
4853
|
-
}
|
|
4854
|
-
}
|
|
4855
|
-
targetsToClear.clear();
|
|
4856
|
-
}
|
|
4857
|
-
const reactiveCache = new WeakMap();
|
|
4858
|
-
/**
|
|
4859
|
-
* Creates a reactive proxy for an object. Reading data on the reactive object
|
|
4860
|
-
* subscribes to changes to the data. Writing data on the object will cause the
|
|
4861
|
-
* notify callback to be called if there are suscriptions to that data. Nested
|
|
4862
|
-
* objects and arrays are automatically made reactive as well.
|
|
4863
|
-
*
|
|
4864
|
-
* Whenever you are notified of a change, all subscriptions are cleared, and if
|
|
4865
|
-
* you would like to be notified of any further changes, you should go read
|
|
4866
|
-
* the underlying data again. We assume that if you don't go read it again after
|
|
4867
|
-
* being notified, it means that you are no longer interested in that data.
|
|
4868
|
-
*
|
|
4869
|
-
* Subscriptions:
|
|
4870
|
-
* + Reading a property on an object will subscribe you to changes in the value
|
|
4871
|
-
* of that property.
|
|
4872
|
-
* + Accessing an object keys (eg with Object.keys or with `for..in`) will
|
|
4873
|
-
* subscribe you to the creation/deletion of keys. Checking the presence of a
|
|
4874
|
-
* key on the object with 'in' has the same effect.
|
|
4875
|
-
* - getOwnPropertyDescriptor does not currently subscribe you to the property.
|
|
4876
|
-
* This is a choice that was made because changing a key's value will trigger
|
|
4877
|
-
* this trap and we do not want to subscribe by writes. This also means that
|
|
4878
|
-
* Object.hasOwnProperty doesn't subscribe as it goes through this trap.
|
|
4879
|
-
*
|
|
4880
|
-
* @param target the object for which to create a reactive proxy
|
|
4881
|
-
* @param callback the function to call when an observed property of the
|
|
4882
|
-
* reactive has changed
|
|
4883
|
-
* @returns a proxy that tracks changes to it
|
|
4884
|
-
*/
|
|
4885
|
-
function reactive(target, callback = () => { }) {
|
|
4886
|
-
if (!canBeMadeReactive(target)) {
|
|
4887
|
-
throw new Error(`Cannot make the given value reactive`);
|
|
4888
|
-
}
|
|
4889
|
-
if (SKIP in target) {
|
|
4890
|
-
return target;
|
|
4891
|
-
}
|
|
4892
|
-
const originalTarget = target[TARGET];
|
|
4893
|
-
if (originalTarget) {
|
|
4894
|
-
return reactive(originalTarget, callback);
|
|
4895
|
-
}
|
|
4896
|
-
if (!reactiveCache.has(target)) {
|
|
4897
|
-
reactiveCache.set(target, new Map());
|
|
4898
|
-
}
|
|
4899
|
-
const reactivesForTarget = reactiveCache.get(target);
|
|
4900
|
-
if (!reactivesForTarget.has(callback)) {
|
|
4901
|
-
const proxy = new Proxy(target, {
|
|
4902
|
-
get(target, key, proxy) {
|
|
4903
|
-
if (key === TARGET) {
|
|
4904
|
-
return target;
|
|
4905
|
-
}
|
|
4906
|
-
observeTargetKey(target, key, callback);
|
|
4907
|
-
const value = Reflect.get(target, key, proxy);
|
|
4908
|
-
if (!canBeMadeReactive(value)) {
|
|
4909
|
-
return value;
|
|
4910
|
-
}
|
|
4911
|
-
return reactive(value, callback);
|
|
4912
|
-
},
|
|
4913
|
-
set(target, key, value, proxy) {
|
|
4914
|
-
const isNewKey = !Object.hasOwnProperty.call(target, key);
|
|
4915
|
-
const originalValue = Reflect.get(target, key, proxy);
|
|
4916
|
-
const ret = Reflect.set(target, key, value, proxy);
|
|
4917
|
-
if (isNewKey) {
|
|
4918
|
-
notifyReactives(target, KEYCHANGES);
|
|
4919
|
-
}
|
|
4920
|
-
// While Array length may trigger the set trap, it's not actually set by this
|
|
4921
|
-
// method but is updated behind the scenes, and the trap is not called with the
|
|
4922
|
-
// new value. We disable the "same-value-optimization" for it because of that.
|
|
4923
|
-
if (originalValue !== value || (Array.isArray(target) && key === "length")) {
|
|
4924
|
-
notifyReactives(target, key);
|
|
4925
|
-
}
|
|
4926
|
-
return ret;
|
|
4927
|
-
},
|
|
4928
|
-
deleteProperty(target, key) {
|
|
4929
|
-
const ret = Reflect.deleteProperty(target, key);
|
|
4930
|
-
notifyReactives(target, KEYCHANGES);
|
|
4931
|
-
notifyReactives(target, key);
|
|
4932
|
-
return ret;
|
|
4933
|
-
},
|
|
4934
|
-
ownKeys(target) {
|
|
4935
|
-
observeTargetKey(target, KEYCHANGES, callback);
|
|
4936
|
-
return Reflect.ownKeys(target);
|
|
4937
|
-
},
|
|
4938
|
-
has(target, key) {
|
|
4939
|
-
// TODO: this observes all key changes instead of only the presence of the argument key
|
|
4940
|
-
observeTargetKey(target, KEYCHANGES, callback);
|
|
4941
|
-
return Reflect.has(target, key);
|
|
4942
|
-
},
|
|
4943
|
-
});
|
|
4944
|
-
reactivesForTarget.set(callback, proxy);
|
|
4945
|
-
}
|
|
4946
|
-
return reactivesForTarget.get(callback);
|
|
4947
|
-
}
|
|
4948
|
-
const batchedRenderFunctions = new WeakMap();
|
|
4949
|
-
/**
|
|
4950
|
-
* Creates a reactive object that will be observed by the current component.
|
|
4951
|
-
* Reading data from the returned object (eg during rendering) will cause the
|
|
4952
|
-
* component to subscribe to that data and be rerendered when it changes.
|
|
4953
|
-
*
|
|
4954
|
-
* @param state the state to observe
|
|
4955
|
-
* @returns a reactive object that will cause the component to re-render on
|
|
4956
|
-
* relevant changes
|
|
4957
|
-
* @see reactive
|
|
4958
|
-
*/
|
|
4959
|
-
function useState(state) {
|
|
4960
|
-
const node = getCurrent();
|
|
4961
|
-
if (!batchedRenderFunctions.has(node)) {
|
|
4962
|
-
batchedRenderFunctions.set(node, batched(() => node.render()));
|
|
4963
|
-
onWillDestroy(() => clearReactivesForCallback(render));
|
|
4964
|
-
}
|
|
4965
|
-
const render = batchedRenderFunctions.get(node);
|
|
4966
|
-
const reactiveState = reactive(state, render);
|
|
4967
|
-
return reactiveState;
|
|
4968
|
-
}
|
|
4969
|
-
|
|
4970
5447
|
// -----------------------------------------------------------------------------
|
|
4971
5448
|
// useRef
|
|
4972
5449
|
// -----------------------------------------------------------------------------
|
|
@@ -5012,10 +5489,6 @@ function useChildSubEnv(envExtension) {
|
|
|
5012
5489
|
const node = getCurrent();
|
|
5013
5490
|
node.childEnv = extendEnv(node.childEnv, envExtension);
|
|
5014
5491
|
}
|
|
5015
|
-
// -----------------------------------------------------------------------------
|
|
5016
|
-
// useEffect
|
|
5017
|
-
// -----------------------------------------------------------------------------
|
|
5018
|
-
const NO_OP = () => { };
|
|
5019
5492
|
/**
|
|
5020
5493
|
* This hook will run a callback when a component is mounted and patched, and
|
|
5021
5494
|
* will run a cleanup function before patching and before unmounting the
|
|
@@ -5033,18 +5506,20 @@ function useEffect(effect, computeDependencies = () => [NaN]) {
|
|
|
5033
5506
|
let dependencies;
|
|
5034
5507
|
onMounted(() => {
|
|
5035
5508
|
dependencies = computeDependencies();
|
|
5036
|
-
cleanup = effect(...dependencies)
|
|
5509
|
+
cleanup = effect(...dependencies);
|
|
5037
5510
|
});
|
|
5038
5511
|
onPatched(() => {
|
|
5039
5512
|
const newDeps = computeDependencies();
|
|
5040
5513
|
const shouldReapply = newDeps.some((val, i) => val !== dependencies[i]);
|
|
5041
5514
|
if (shouldReapply) {
|
|
5042
5515
|
dependencies = newDeps;
|
|
5043
|
-
cleanup
|
|
5044
|
-
|
|
5516
|
+
if (cleanup) {
|
|
5517
|
+
cleanup();
|
|
5518
|
+
}
|
|
5519
|
+
cleanup = effect(...dependencies);
|
|
5045
5520
|
}
|
|
5046
5521
|
});
|
|
5047
|
-
onWillUnmount(() => cleanup());
|
|
5522
|
+
onWillUnmount(() => cleanup && cleanup());
|
|
5048
5523
|
}
|
|
5049
5524
|
// -----------------------------------------------------------------------------
|
|
5050
5525
|
// useExternalListener
|
|
@@ -5071,7 +5546,6 @@ function useExternalListener(target, eventName, handler, eventParams) {
|
|
|
5071
5546
|
|
|
5072
5547
|
config.shouldNormalizeDom = false;
|
|
5073
5548
|
config.mainEventHandler = mainEventHandler;
|
|
5074
|
-
UTILS.Portal = Portal;
|
|
5075
5549
|
const blockDom = {
|
|
5076
5550
|
config,
|
|
5077
5551
|
// bdom entry points
|
|
@@ -5089,10 +5563,10 @@ const blockDom = {
|
|
|
5089
5563
|
};
|
|
5090
5564
|
const __info__ = {};
|
|
5091
5565
|
|
|
5092
|
-
export { App, Component, EventBus,
|
|
5566
|
+
export { App, Component, EventBus, __info__, blockDom, loadFile, markRaw, markup, mount, onError, onMounted, onPatched, onRendered, onWillDestroy, onWillPatch, onWillRender, onWillStart, onWillUnmount, onWillUpdateProps, reactive, status, toRaw, useChildSubEnv, useComponent, useEffect, useEnv, useExternalListener, useRef, useState, useSubEnv, whenReady, xml };
|
|
5093
5567
|
|
|
5094
5568
|
|
|
5095
|
-
__info__.version = '2.0.0-
|
|
5096
|
-
__info__.date = '2022-
|
|
5097
|
-
__info__.hash = '
|
|
5569
|
+
__info__.version = '2.0.0-beta-5';
|
|
5570
|
+
__info__.date = '2022-04-07T13:36:37.300Z';
|
|
5571
|
+
__info__.hash = '1179e84';
|
|
5098
5572
|
__info__.url = 'https://github.com/odoo/owl';
|