@odoo/owl 2.0.0-alpha.3 → 2.0.0-beta-6
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 +1549 -1156
- package/dist/owl.cjs.min.js +1 -1
- package/dist/owl.es.js +1550 -1156
- package/dist/owl.es.min.js +1 -1
- package/dist/owl.iife.js +1549 -1156
- package/dist/owl.iife.min.js +1 -1
- 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_node.d.ts +8 -6
- package/dist/types/component/fibers.d.ts +4 -0
- package/dist/types/component/scheduler.d.ts +3 -4
- package/dist/types/index.d.ts +1 -3
- package/dist/types/reactivity.d.ts +4 -0
- 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,562 +1896,206 @@ class Markup extends String {
|
|
|
1496
1896
|
*/
|
|
1497
1897
|
function markup(value) {
|
|
1498
1898
|
return new Markup(value);
|
|
1499
|
-
}
|
|
1500
|
-
|
|
1501
|
-
// Allows to get the target of a Reactive (used for making a new Reactive from the underlying object)
|
|
1502
|
-
const TARGET = Symbol("Target");
|
|
1503
|
-
// Escape hatch to prevent reactivity system to turn something into a reactive
|
|
1504
|
-
const SKIP = Symbol("Skip");
|
|
1505
|
-
// Special key to subscribe to, to be notified of key creation/deletion
|
|
1506
|
-
const KEYCHANGES = Symbol("Key changes");
|
|
1507
|
-
const objectToString = Object.prototype.toString;
|
|
1508
|
-
/**
|
|
1509
|
-
* Checks whether a given value can be made into a reactive object.
|
|
1510
|
-
*
|
|
1511
|
-
* @param value the value to check
|
|
1512
|
-
* @returns whether the value can be made reactive
|
|
1513
|
-
*/
|
|
1514
|
-
function canBeMadeReactive(value) {
|
|
1515
|
-
if (typeof value !== "object") {
|
|
1516
|
-
return false;
|
|
1517
|
-
}
|
|
1518
|
-
// extract "RawType" from strings like "[object RawType]" => this lets us
|
|
1519
|
-
// ignore many native objects such as Promise (whose toString is [object Promise])
|
|
1520
|
-
// or Date ([object Date]).
|
|
1521
|
-
const rawType = objectToString.call(value).slice(8, -1);
|
|
1522
|
-
return rawType === "Object" || rawType === "Array";
|
|
1523
|
-
}
|
|
1524
|
-
/**
|
|
1525
|
-
* Mark an object or array so that it is ignored by the reactivity system
|
|
1526
|
-
*
|
|
1527
|
-
* @param value the value to mark
|
|
1528
|
-
* @returns the object itself
|
|
1529
|
-
*/
|
|
1530
|
-
function markRaw(value) {
|
|
1531
|
-
value[SKIP] = true;
|
|
1532
|
-
return value;
|
|
1533
1899
|
}
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
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;
|
|
1542
1909
|
}
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
* @param callback the function to call when the key changes
|
|
1552
|
-
*/
|
|
1553
|
-
function observeTargetKey(target, key, callback) {
|
|
1554
|
-
if (!targetToKeysToCallbacks.get(target)) {
|
|
1555
|
-
targetToKeysToCallbacks.set(target, new Map());
|
|
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;
|
|
1556
1918
|
}
|
|
1557
|
-
const
|
|
1558
|
-
if (
|
|
1559
|
-
|
|
1919
|
+
const fiber = node.fiber;
|
|
1920
|
+
if (fiber) {
|
|
1921
|
+
fibersInError.set(fiber, error);
|
|
1560
1922
|
}
|
|
1561
|
-
|
|
1562
|
-
if (
|
|
1563
|
-
|
|
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
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
if (handled) {
|
|
1938
|
+
return true;
|
|
1939
|
+
}
|
|
1564
1940
|
}
|
|
1565
|
-
|
|
1941
|
+
return _handleError(node.parent, error);
|
|
1566
1942
|
}
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
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
|
+
}
|
|
1584
1964
|
}
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
function makeChildFiber(node, parent) {
|
|
1968
|
+
let current = node.fiber;
|
|
1969
|
+
if (current) {
|
|
1970
|
+
cancelFibers(current.children);
|
|
1971
|
+
current.root = null;
|
|
1589
1972
|
}
|
|
1973
|
+
return new Fiber(node, parent);
|
|
1590
1974
|
}
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
if (
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
callbacks.delete(callback);
|
|
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;
|
|
1609
1992
|
}
|
|
1993
|
+
return current;
|
|
1610
1994
|
}
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
/**
|
|
1615
|
-
* Creates a reactive proxy for an object. Reading data on the reactive object
|
|
1616
|
-
* subscribes to changes to the data. Writing data on the object will cause the
|
|
1617
|
-
* notify callback to be called if there are suscriptions to that data. Nested
|
|
1618
|
-
* objects and arrays are automatically made reactive as well.
|
|
1619
|
-
*
|
|
1620
|
-
* Whenever you are notified of a change, all subscriptions are cleared, and if
|
|
1621
|
-
* you would like to be notified of any further changes, you should go read
|
|
1622
|
-
* the underlying data again. We assume that if you don't go read it again after
|
|
1623
|
-
* being notified, it means that you are no longer interested in that data.
|
|
1624
|
-
*
|
|
1625
|
-
* Subscriptions:
|
|
1626
|
-
* + Reading a property on an object will subscribe you to changes in the value
|
|
1627
|
-
* of that property.
|
|
1628
|
-
* + Accessing an object keys (eg with Object.keys or with `for..in`) will
|
|
1629
|
-
* subscribe you to the creation/deletion of keys. Checking the presence of a
|
|
1630
|
-
* key on the object with 'in' has the same effect.
|
|
1631
|
-
* - getOwnPropertyDescriptor does not currently subscribe you to the property.
|
|
1632
|
-
* This is a choice that was made because changing a key's value will trigger
|
|
1633
|
-
* this trap and we do not want to subscribe by writes. This also means that
|
|
1634
|
-
* Object.hasOwnProperty doesn't subscribe as it goes through this trap.
|
|
1635
|
-
*
|
|
1636
|
-
* @param target the object for which to create a reactive proxy
|
|
1637
|
-
* @param callback the function to call when an observed property of the
|
|
1638
|
-
* reactive has changed
|
|
1639
|
-
* @returns a proxy that tracks changes to it
|
|
1640
|
-
*/
|
|
1641
|
-
function reactive(target, callback = () => { }) {
|
|
1642
|
-
if (!canBeMadeReactive(target)) {
|
|
1643
|
-
throw new Error(`Cannot make the given value reactive`);
|
|
1644
|
-
}
|
|
1645
|
-
if (SKIP in target) {
|
|
1646
|
-
return target;
|
|
1647
|
-
}
|
|
1648
|
-
const originalTarget = target[TARGET];
|
|
1649
|
-
if (originalTarget) {
|
|
1650
|
-
return reactive(originalTarget, callback);
|
|
1651
|
-
}
|
|
1652
|
-
if (!reactiveCache.has(target)) {
|
|
1653
|
-
reactiveCache.set(target, new Map());
|
|
1995
|
+
const fiber = new RootFiber(node, null);
|
|
1996
|
+
if (node.willPatch.length) {
|
|
1997
|
+
fiber.willPatch.push(fiber);
|
|
1654
1998
|
}
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
const proxy = new Proxy(target, {
|
|
1658
|
-
get(target, key, proxy) {
|
|
1659
|
-
if (key === TARGET) {
|
|
1660
|
-
return target;
|
|
1661
|
-
}
|
|
1662
|
-
observeTargetKey(target, key, callback);
|
|
1663
|
-
const value = Reflect.get(target, key, proxy);
|
|
1664
|
-
if (!canBeMadeReactive(value)) {
|
|
1665
|
-
return value;
|
|
1666
|
-
}
|
|
1667
|
-
return reactive(value, callback);
|
|
1668
|
-
},
|
|
1669
|
-
set(target, key, value, proxy) {
|
|
1670
|
-
const isNewKey = !Object.hasOwnProperty.call(target, key);
|
|
1671
|
-
const originalValue = Reflect.get(target, key, proxy);
|
|
1672
|
-
const ret = Reflect.set(target, key, value, proxy);
|
|
1673
|
-
if (isNewKey) {
|
|
1674
|
-
notifyReactives(target, KEYCHANGES);
|
|
1675
|
-
}
|
|
1676
|
-
// While Array length may trigger the set trap, it's not actually set by this
|
|
1677
|
-
// method but is updated behind the scenes, and the trap is not called with the
|
|
1678
|
-
// new value. We disable the "same-value-optimization" for it because of that.
|
|
1679
|
-
if (originalValue !== value || (Array.isArray(target) && key === "length")) {
|
|
1680
|
-
notifyReactives(target, key);
|
|
1681
|
-
}
|
|
1682
|
-
return ret;
|
|
1683
|
-
},
|
|
1684
|
-
deleteProperty(target, key) {
|
|
1685
|
-
const ret = Reflect.deleteProperty(target, key);
|
|
1686
|
-
notifyReactives(target, KEYCHANGES);
|
|
1687
|
-
notifyReactives(target, key);
|
|
1688
|
-
return ret;
|
|
1689
|
-
},
|
|
1690
|
-
ownKeys(target) {
|
|
1691
|
-
observeTargetKey(target, KEYCHANGES, callback);
|
|
1692
|
-
return Reflect.ownKeys(target);
|
|
1693
|
-
},
|
|
1694
|
-
has(target, key) {
|
|
1695
|
-
// TODO: this observes all key changes instead of only the presence of the argument key
|
|
1696
|
-
observeTargetKey(target, KEYCHANGES, callback);
|
|
1697
|
-
return Reflect.has(target, key);
|
|
1698
|
-
},
|
|
1699
|
-
});
|
|
1700
|
-
reactivesForTarget.set(callback, proxy);
|
|
1999
|
+
if (node.patched.length) {
|
|
2000
|
+
fiber.patched.push(fiber);
|
|
1701
2001
|
}
|
|
1702
|
-
return
|
|
1703
|
-
}
|
|
1704
|
-
|
|
2002
|
+
return fiber;
|
|
2003
|
+
}
|
|
1705
2004
|
/**
|
|
1706
|
-
*
|
|
1707
|
-
* to perform various useful tasks in the compiled code.
|
|
2005
|
+
* @returns number of not-yet rendered fibers cancelled
|
|
1708
2006
|
*/
|
|
1709
|
-
function
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
child1 = dynamic ? toggler(name, slotBDom) : slotBDom;
|
|
2007
|
+
function cancelFibers(fibers) {
|
|
2008
|
+
let result = 0;
|
|
2009
|
+
for (let fiber of fibers) {
|
|
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;
|
|
1726
2023
|
}
|
|
1727
2024
|
else {
|
|
1728
|
-
|
|
2025
|
+
result++;
|
|
1729
2026
|
}
|
|
1730
|
-
|
|
1731
|
-
}
|
|
1732
|
-
return slotBDom || text("");
|
|
1733
|
-
}
|
|
1734
|
-
function capture(ctx) {
|
|
1735
|
-
const component = ctx.__owl__.component;
|
|
1736
|
-
const result = Object.create(component);
|
|
1737
|
-
for (let k in ctx) {
|
|
1738
|
-
result[k] = ctx[k];
|
|
2027
|
+
result += cancelFibers(fiber.children);
|
|
1739
2028
|
}
|
|
1740
2029
|
return result;
|
|
1741
2030
|
}
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
else {
|
|
1758
|
-
throw new Error("Invalid loop expression");
|
|
1759
|
-
}
|
|
1760
|
-
const n = values.length;
|
|
1761
|
-
return [keys, values, n, new Array(n)];
|
|
1762
|
-
}
|
|
1763
|
-
const isBoundary = Symbol("isBoundary");
|
|
1764
|
-
function setContextValue(ctx, key, value) {
|
|
1765
|
-
const ctx0 = ctx;
|
|
1766
|
-
while (!ctx.hasOwnProperty(key) && !ctx.hasOwnProperty(isBoundary)) {
|
|
1767
|
-
const newCtx = ctx.__proto__;
|
|
1768
|
-
if (!newCtx) {
|
|
1769
|
-
ctx = ctx0;
|
|
1770
|
-
break;
|
|
2031
|
+
class Fiber {
|
|
2032
|
+
constructor(node, parent) {
|
|
2033
|
+
this.bdom = null;
|
|
2034
|
+
this.children = [];
|
|
2035
|
+
this.appliedToDom = false;
|
|
2036
|
+
this.deep = false;
|
|
2037
|
+
this.childrenMap = {};
|
|
2038
|
+
this.node = node;
|
|
2039
|
+
this.parent = parent;
|
|
2040
|
+
if (parent) {
|
|
2041
|
+
this.deep = parent.deep;
|
|
2042
|
+
const root = parent.root;
|
|
2043
|
+
root.setCounter(root.counter + 1);
|
|
2044
|
+
this.root = root;
|
|
2045
|
+
parent.children.push(this);
|
|
1771
2046
|
}
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
ctx[key] = value;
|
|
1775
|
-
}
|
|
1776
|
-
function toNumber(val) {
|
|
1777
|
-
const n = parseFloat(val);
|
|
1778
|
-
return isNaN(n) ? val : n;
|
|
1779
|
-
}
|
|
1780
|
-
function shallowEqual$1(l1, l2) {
|
|
1781
|
-
for (let i = 0, l = l1.length; i < l; i++) {
|
|
1782
|
-
if (l1[i] !== l2[i]) {
|
|
1783
|
-
return false;
|
|
2047
|
+
else {
|
|
2048
|
+
this.root = this;
|
|
1784
2049
|
}
|
|
1785
2050
|
}
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
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();
|
|
1796
2072
|
}
|
|
1797
|
-
|
|
1798
|
-
|
|
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
|
+
}
|
|
1799
2086
|
}
|
|
1800
2087
|
}
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
block = html(value);
|
|
1813
|
-
}
|
|
1814
|
-
else if (value instanceof LazyValue) {
|
|
1815
|
-
safeKey = `lazy_value`;
|
|
1816
|
-
block = value.evaluate();
|
|
1817
|
-
}
|
|
1818
|
-
else if (value instanceof String || typeof value === "string") {
|
|
1819
|
-
safeKey = "string_unsafe";
|
|
1820
|
-
block = text(value);
|
|
1821
|
-
}
|
|
1822
|
-
else {
|
|
1823
|
-
// Assuming it is a block
|
|
1824
|
-
safeKey = "block_safe";
|
|
1825
|
-
block = value;
|
|
1826
|
-
}
|
|
1827
|
-
return toggler(safeKey, block);
|
|
1828
|
-
}
|
|
1829
|
-
let boundFunctions = new WeakMap();
|
|
1830
|
-
function bind(ctx, fn) {
|
|
1831
|
-
let component = ctx.__owl__.component;
|
|
1832
|
-
let boundFnMap = boundFunctions.get(component);
|
|
1833
|
-
if (!boundFnMap) {
|
|
1834
|
-
boundFnMap = new WeakMap();
|
|
1835
|
-
boundFunctions.set(component, boundFnMap);
|
|
1836
|
-
}
|
|
1837
|
-
let boundFn = boundFnMap.get(fn);
|
|
1838
|
-
if (!boundFn) {
|
|
1839
|
-
boundFn = fn.bind(component);
|
|
1840
|
-
boundFnMap.set(fn, boundFn);
|
|
1841
|
-
}
|
|
1842
|
-
return boundFn;
|
|
1843
|
-
}
|
|
1844
|
-
function multiRefSetter(refs, name) {
|
|
1845
|
-
let count = 0;
|
|
1846
|
-
return (el) => {
|
|
1847
|
-
if (el) {
|
|
1848
|
-
count++;
|
|
1849
|
-
if (count > 1) {
|
|
1850
|
-
throw new Error("Cannot have 2 elements with same ref name at the same time");
|
|
1851
|
-
}
|
|
1852
|
-
}
|
|
1853
|
-
if (count === 0 || el) {
|
|
1854
|
-
refs[name] = el;
|
|
1855
|
-
}
|
|
1856
|
-
};
|
|
1857
|
-
}
|
|
1858
|
-
const UTILS = {
|
|
1859
|
-
withDefault,
|
|
1860
|
-
zero: Symbol("zero"),
|
|
1861
|
-
isBoundary,
|
|
1862
|
-
callSlot,
|
|
1863
|
-
capture,
|
|
1864
|
-
withKey,
|
|
1865
|
-
prepareList,
|
|
1866
|
-
setContextValue,
|
|
1867
|
-
multiRefSetter,
|
|
1868
|
-
shallowEqual: shallowEqual$1,
|
|
1869
|
-
toNumber,
|
|
1870
|
-
validateProps,
|
|
1871
|
-
LazyValue,
|
|
1872
|
-
safeOutput,
|
|
1873
|
-
bind,
|
|
1874
|
-
};
|
|
1875
|
-
|
|
1876
|
-
const mainEventHandler = (data, ev, currentTarget) => {
|
|
1877
|
-
const { data: _data, modifiers } = filterOutModifiersFromData(data);
|
|
1878
|
-
data = _data;
|
|
1879
|
-
let stopped = false;
|
|
1880
|
-
if (modifiers.length) {
|
|
1881
|
-
let selfMode = false;
|
|
1882
|
-
const isSelf = ev.target === currentTarget;
|
|
1883
|
-
for (const mod of modifiers) {
|
|
1884
|
-
switch (mod) {
|
|
1885
|
-
case "self":
|
|
1886
|
-
selfMode = true;
|
|
1887
|
-
if (isSelf) {
|
|
1888
|
-
continue;
|
|
1889
|
-
}
|
|
1890
|
-
else {
|
|
1891
|
-
return stopped;
|
|
1892
|
-
}
|
|
1893
|
-
case "prevent":
|
|
1894
|
-
if ((selfMode && isSelf) || !selfMode)
|
|
1895
|
-
ev.preventDefault();
|
|
1896
|
-
continue;
|
|
1897
|
-
case "stop":
|
|
1898
|
-
if ((selfMode && isSelf) || !selfMode)
|
|
1899
|
-
ev.stopPropagation();
|
|
1900
|
-
stopped = true;
|
|
1901
|
-
continue;
|
|
1902
|
-
}
|
|
1903
|
-
}
|
|
1904
|
-
}
|
|
1905
|
-
// If handler is empty, the array slot 0 will also be empty, and data will not have the property 0
|
|
1906
|
-
// We check this rather than data[0] being truthy (or typeof function) so that it crashes
|
|
1907
|
-
// as expected when there is a handler expression that evaluates to a falsy value
|
|
1908
|
-
if (Object.hasOwnProperty.call(data, 0)) {
|
|
1909
|
-
const handler = data[0];
|
|
1910
|
-
if (typeof handler !== "function") {
|
|
1911
|
-
throw new Error(`Invalid handler (expected a function, received: '${handler}')`);
|
|
1912
|
-
}
|
|
1913
|
-
let node = data[1] ? data[1].__owl__ : null;
|
|
1914
|
-
if (node ? node.status === 1 /* MOUNTED */ : true) {
|
|
1915
|
-
handler.call(node ? node.component : null, ev);
|
|
1916
|
-
}
|
|
1917
|
-
}
|
|
1918
|
-
return stopped;
|
|
1919
|
-
};
|
|
1920
|
-
|
|
1921
|
-
// Maps fibers to thrown errors
|
|
1922
|
-
const fibersInError = new WeakMap();
|
|
1923
|
-
const nodeErrorHandlers = new WeakMap();
|
|
1924
|
-
function _handleError(node, error, isFirstRound = false) {
|
|
1925
|
-
if (!node) {
|
|
1926
|
-
return false;
|
|
1927
|
-
}
|
|
1928
|
-
const fiber = node.fiber;
|
|
1929
|
-
if (fiber) {
|
|
1930
|
-
fibersInError.set(fiber, error);
|
|
1931
|
-
}
|
|
1932
|
-
const errorHandlers = nodeErrorHandlers.get(node);
|
|
1933
|
-
if (errorHandlers) {
|
|
1934
|
-
let stopped = false;
|
|
1935
|
-
// execute in the opposite order
|
|
1936
|
-
for (let i = errorHandlers.length - 1; i >= 0; i--) {
|
|
1937
|
-
try {
|
|
1938
|
-
errorHandlers[i](error);
|
|
1939
|
-
stopped = true;
|
|
1940
|
-
break;
|
|
1941
|
-
}
|
|
1942
|
-
catch (e) {
|
|
1943
|
-
error = e;
|
|
1944
|
-
}
|
|
1945
|
-
}
|
|
1946
|
-
if (stopped) {
|
|
1947
|
-
if (isFirstRound && fiber && fiber.node.fiber) {
|
|
1948
|
-
fiber.root.counter--;
|
|
1949
|
-
}
|
|
1950
|
-
return true;
|
|
1951
|
-
}
|
|
1952
|
-
}
|
|
1953
|
-
return _handleError(node.parent, error);
|
|
1954
|
-
}
|
|
1955
|
-
function handleError(params) {
|
|
1956
|
-
const error = params.error;
|
|
1957
|
-
const node = "node" in params ? params.node : params.fiber.node;
|
|
1958
|
-
const fiber = "fiber" in params ? params.fiber : node.fiber;
|
|
1959
|
-
// resets the fibers on components if possible. This is important so that
|
|
1960
|
-
// new renderings can be properly included in the initial one, if any.
|
|
1961
|
-
let current = fiber;
|
|
1962
|
-
do {
|
|
1963
|
-
current.node.fiber = current;
|
|
1964
|
-
current = current.parent;
|
|
1965
|
-
} while (current);
|
|
1966
|
-
fibersInError.set(fiber.root, error);
|
|
1967
|
-
const handled = _handleError(node, error, true);
|
|
1968
|
-
if (!handled) {
|
|
1969
|
-
console.warn(`[Owl] Unhandled error. Destroying the root component`);
|
|
1970
|
-
try {
|
|
1971
|
-
node.app.destroy();
|
|
1972
|
-
}
|
|
1973
|
-
catch (e) {
|
|
1974
|
-
console.error(e);
|
|
1975
|
-
}
|
|
1976
|
-
}
|
|
1977
|
-
}
|
|
1978
|
-
|
|
1979
|
-
function makeChildFiber(node, parent) {
|
|
1980
|
-
let current = node.fiber;
|
|
1981
|
-
if (current) {
|
|
1982
|
-
cancelFibers(current.children);
|
|
1983
|
-
current.root = null;
|
|
1984
|
-
}
|
|
1985
|
-
return new Fiber(node, parent);
|
|
1986
|
-
}
|
|
1987
|
-
function makeRootFiber(node) {
|
|
1988
|
-
let current = node.fiber;
|
|
1989
|
-
if (current) {
|
|
1990
|
-
let root = current.root;
|
|
1991
|
-
root.counter = root.counter + 1 - cancelFibers(current.children);
|
|
1992
|
-
current.children = [];
|
|
1993
|
-
current.bdom = null;
|
|
1994
|
-
if (fibersInError.has(current)) {
|
|
1995
|
-
fibersInError.delete(current);
|
|
1996
|
-
fibersInError.delete(root);
|
|
1997
|
-
current.appliedToDom = false;
|
|
1998
|
-
}
|
|
1999
|
-
return current;
|
|
2000
|
-
}
|
|
2001
|
-
const fiber = new RootFiber(node, null);
|
|
2002
|
-
if (node.willPatch.length) {
|
|
2003
|
-
fiber.willPatch.push(fiber);
|
|
2004
|
-
}
|
|
2005
|
-
if (node.patched.length) {
|
|
2006
|
-
fiber.patched.push(fiber);
|
|
2007
|
-
}
|
|
2008
|
-
return fiber;
|
|
2009
|
-
}
|
|
2010
|
-
/**
|
|
2011
|
-
* @returns number of not-yet rendered fibers cancelled
|
|
2012
|
-
*/
|
|
2013
|
-
function cancelFibers(fibers) {
|
|
2014
|
-
let result = 0;
|
|
2015
|
-
for (let fiber of fibers) {
|
|
2016
|
-
fiber.node.fiber = null;
|
|
2017
|
-
if (!fiber.bdom) {
|
|
2018
|
-
result++;
|
|
2019
|
-
}
|
|
2020
|
-
result += cancelFibers(fiber.children);
|
|
2021
|
-
}
|
|
2022
|
-
return result;
|
|
2023
|
-
}
|
|
2024
|
-
class Fiber {
|
|
2025
|
-
constructor(node, parent) {
|
|
2026
|
-
this.bdom = null;
|
|
2027
|
-
this.children = [];
|
|
2028
|
-
this.appliedToDom = false;
|
|
2029
|
-
this.deep = false;
|
|
2030
|
-
this.node = node;
|
|
2031
|
-
this.parent = parent;
|
|
2032
|
-
if (parent) {
|
|
2033
|
-
this.deep = parent.deep;
|
|
2034
|
-
const root = parent.root;
|
|
2035
|
-
root.counter++;
|
|
2036
|
-
this.root = root;
|
|
2037
|
-
parent.children.push(this);
|
|
2038
|
-
}
|
|
2039
|
-
else {
|
|
2040
|
-
this.root = this;
|
|
2041
|
-
}
|
|
2042
|
-
}
|
|
2043
|
-
}
|
|
2044
|
-
class RootFiber extends Fiber {
|
|
2045
|
-
constructor() {
|
|
2046
|
-
super(...arguments);
|
|
2047
|
-
this.counter = 1;
|
|
2048
|
-
// only add stuff in this if they have registered some hooks
|
|
2049
|
-
this.willPatch = [];
|
|
2050
|
-
this.patched = [];
|
|
2051
|
-
this.mounted = [];
|
|
2052
|
-
// A fiber is typically locked when it is completing and the patch has not, or is being applied.
|
|
2053
|
-
// i.e.: render triggered in onWillUnmount or in willPatch will be delayed
|
|
2054
|
-
this.locked = false;
|
|
2088
|
+
class RootFiber extends Fiber {
|
|
2089
|
+
constructor() {
|
|
2090
|
+
super(...arguments);
|
|
2091
|
+
this.counter = 1;
|
|
2092
|
+
// only add stuff in this if they have registered some hooks
|
|
2093
|
+
this.willPatch = [];
|
|
2094
|
+
this.patched = [];
|
|
2095
|
+
this.mounted = [];
|
|
2096
|
+
// A fiber is typically locked when it is completing and the patch has not, or is being applied.
|
|
2097
|
+
// i.e.: render triggered in onWillUnmount or in willPatch will be delayed
|
|
2098
|
+
this.locked = false;
|
|
2055
2099
|
}
|
|
2056
2100
|
complete() {
|
|
2057
2101
|
const node = this.node;
|
|
@@ -2101,6 +2145,12 @@ class RootFiber extends Fiber {
|
|
|
2101
2145
|
handleError({ fiber: current || this, error: e });
|
|
2102
2146
|
}
|
|
2103
2147
|
}
|
|
2148
|
+
setCounter(newValue) {
|
|
2149
|
+
this.counter = newValue;
|
|
2150
|
+
if (newValue === 0) {
|
|
2151
|
+
this.node.app.scheduler.flush();
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
2104
2154
|
}
|
|
2105
2155
|
class MountFiber extends RootFiber {
|
|
2106
2156
|
constructor(node, target, options = {}) {
|
|
@@ -2112,6 +2162,7 @@ class MountFiber extends RootFiber {
|
|
|
2112
2162
|
let current = this;
|
|
2113
2163
|
try {
|
|
2114
2164
|
const node = this.node;
|
|
2165
|
+
node.children = this.childrenMap;
|
|
2115
2166
|
node.app.constructor.validateTarget(this.target);
|
|
2116
2167
|
if (node.bdom) {
|
|
2117
2168
|
// this is a complicated situation: if we mount a fiber with an existing
|
|
@@ -2150,23 +2201,162 @@ class MountFiber extends RootFiber {
|
|
|
2150
2201
|
}
|
|
2151
2202
|
}
|
|
2152
2203
|
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
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
|
+
}
|
|
2157
2217
|
}
|
|
2158
|
-
return currentNode;
|
|
2159
2218
|
}
|
|
2160
|
-
|
|
2161
|
-
|
|
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 };
|
|
2162
2227
|
}
|
|
2163
|
-
// -----------------------------------------------------------------------------
|
|
2164
|
-
// Integration with reactivity system (useState)
|
|
2165
|
-
// -----------------------------------------------------------------------------
|
|
2166
|
-
const batchedRenderFunctions = new WeakMap();
|
|
2167
2228
|
/**
|
|
2168
|
-
*
|
|
2169
|
-
*
|
|
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) {
|
|
2346
|
+
throw new Error("No active component (a hook function should only be called in 'setup')");
|
|
2347
|
+
}
|
|
2348
|
+
return currentNode;
|
|
2349
|
+
}
|
|
2350
|
+
function useComponent() {
|
|
2351
|
+
return currentNode.component;
|
|
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
|
|
2170
2360
|
* component to subscribe to that data and be rerendered when it changes.
|
|
2171
2361
|
*
|
|
2172
2362
|
* @param state the state to observe
|
|
@@ -2178,7 +2368,7 @@ function useState(state) {
|
|
|
2178
2368
|
const node = getCurrent();
|
|
2179
2369
|
let render = batchedRenderFunctions.get(node);
|
|
2180
2370
|
if (!render) {
|
|
2181
|
-
render = batched(node.render.bind(node));
|
|
2371
|
+
render = batched(node.render.bind(node, false));
|
|
2182
2372
|
batchedRenderFunctions.set(node, render);
|
|
2183
2373
|
// manual implementation of onWillDestroy to break cyclic dependency
|
|
2184
2374
|
node.willDestroy.push(clearReactivesForCallback.bind(null, render));
|
|
@@ -2196,22 +2386,23 @@ function arePropsDifferent(props1, props2) {
|
|
|
2196
2386
|
function component(name, props, key, ctx, parent) {
|
|
2197
2387
|
let node = ctx.children[key];
|
|
2198
2388
|
let isDynamic = typeof name !== "string";
|
|
2199
|
-
if (node) {
|
|
2200
|
-
|
|
2201
|
-
node.destroy();
|
|
2202
|
-
node = undefined;
|
|
2203
|
-
}
|
|
2204
|
-
else if (node.status === 2 /* DESTROYED */) {
|
|
2205
|
-
node = undefined;
|
|
2206
|
-
}
|
|
2389
|
+
if (node && node.status === 2 /* DESTROYED */) {
|
|
2390
|
+
node = undefined;
|
|
2207
2391
|
}
|
|
2208
2392
|
if (isDynamic && node && node.component.constructor !== name) {
|
|
2209
2393
|
node = undefined;
|
|
2210
2394
|
}
|
|
2211
2395
|
const parentFiber = ctx.fiber;
|
|
2212
2396
|
if (node) {
|
|
2213
|
-
|
|
2214
|
-
if (
|
|
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) {
|
|
2215
2406
|
node.updateAndRender(props, parentFiber);
|
|
2216
2407
|
}
|
|
2217
2408
|
}
|
|
@@ -2227,17 +2418,19 @@ function component(name, props, key, ctx, parent) {
|
|
|
2227
2418
|
throw new Error(`Cannot find the definition of component "${name}"`);
|
|
2228
2419
|
}
|
|
2229
2420
|
}
|
|
2230
|
-
node = new ComponentNode(C, props, ctx.app, ctx);
|
|
2421
|
+
node = new ComponentNode(C, props, ctx.app, ctx, key);
|
|
2231
2422
|
ctx.children[key] = node;
|
|
2232
2423
|
node.initiateRender(new Fiber(node, parentFiber));
|
|
2233
2424
|
}
|
|
2425
|
+
parentFiber.childrenMap[key] = node;
|
|
2234
2426
|
return node;
|
|
2235
2427
|
}
|
|
2236
2428
|
class ComponentNode {
|
|
2237
|
-
constructor(C, props, app, parent) {
|
|
2429
|
+
constructor(C, props, app, parent, parentKey) {
|
|
2238
2430
|
this.fiber = null;
|
|
2239
2431
|
this.bdom = null;
|
|
2240
2432
|
this.status = 0 /* NEW */;
|
|
2433
|
+
this.forceNextRender = false;
|
|
2241
2434
|
this.children = Object.create(null);
|
|
2242
2435
|
this.refs = {};
|
|
2243
2436
|
this.willStart = [];
|
|
@@ -2249,7 +2442,8 @@ class ComponentNode {
|
|
|
2249
2442
|
this.willDestroy = [];
|
|
2250
2443
|
currentNode = this;
|
|
2251
2444
|
this.app = app;
|
|
2252
|
-
this.parent = parent
|
|
2445
|
+
this.parent = parent;
|
|
2446
|
+
this.parentKey = parentKey;
|
|
2253
2447
|
this.level = parent ? parent.level + 1 : 0;
|
|
2254
2448
|
applyDefaultProps(props, C);
|
|
2255
2449
|
const env = (parent && parent.childEnv) || app.env;
|
|
@@ -2279,12 +2473,12 @@ class ComponentNode {
|
|
|
2279
2473
|
return;
|
|
2280
2474
|
}
|
|
2281
2475
|
if (this.status === 0 /* NEW */ && this.fiber === fiber) {
|
|
2282
|
-
|
|
2476
|
+
fiber.render();
|
|
2283
2477
|
}
|
|
2284
2478
|
}
|
|
2285
|
-
async render(deep
|
|
2479
|
+
async render(deep) {
|
|
2286
2480
|
let current = this.fiber;
|
|
2287
|
-
if (current && current.root.locked) {
|
|
2481
|
+
if (current && (current.root.locked || current.bdom === true)) {
|
|
2288
2482
|
await Promise.resolve();
|
|
2289
2483
|
// situation may have changed after the microtask tick
|
|
2290
2484
|
current = this.fiber;
|
|
@@ -2323,16 +2517,7 @@ class ComponentNode {
|
|
|
2323
2517
|
// embedded in a rendering coming from above, so the fiber will be rendered
|
|
2324
2518
|
// in the next microtick anyway, so we should not render it again.
|
|
2325
2519
|
if (this.fiber === fiber && (current || !fiber.parent)) {
|
|
2326
|
-
|
|
2327
|
-
}
|
|
2328
|
-
}
|
|
2329
|
-
_render(fiber) {
|
|
2330
|
-
try {
|
|
2331
|
-
fiber.bdom = this.renderFn();
|
|
2332
|
-
fiber.root.counter--;
|
|
2333
|
-
}
|
|
2334
|
-
catch (e) {
|
|
2335
|
-
handleError({ node: this, error: e });
|
|
2520
|
+
fiber.render();
|
|
2336
2521
|
}
|
|
2337
2522
|
}
|
|
2338
2523
|
destroy() {
|
|
@@ -2352,8 +2537,15 @@ class ComponentNode {
|
|
|
2352
2537
|
for (let child of Object.values(this.children)) {
|
|
2353
2538
|
child._destroy();
|
|
2354
2539
|
}
|
|
2355
|
-
|
|
2356
|
-
|
|
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
|
+
}
|
|
2357
2549
|
}
|
|
2358
2550
|
this.status = 2 /* DESTROYED */;
|
|
2359
2551
|
}
|
|
@@ -2372,7 +2564,7 @@ class ComponentNode {
|
|
|
2372
2564
|
return;
|
|
2373
2565
|
}
|
|
2374
2566
|
component.props = props;
|
|
2375
|
-
|
|
2567
|
+
fiber.render();
|
|
2376
2568
|
const parentRoot = parentFiber.root;
|
|
2377
2569
|
if (this.willPatch.length) {
|
|
2378
2570
|
parentRoot.willPatch.push(fiber);
|
|
@@ -2419,6 +2611,7 @@ class ComponentNode {
|
|
|
2419
2611
|
bdom.mount(parent, anchor);
|
|
2420
2612
|
this.status = 1 /* MOUNTED */;
|
|
2421
2613
|
this.fiber.appliedToDom = true;
|
|
2614
|
+
this.children = this.fiber.childrenMap;
|
|
2422
2615
|
this.fiber = null;
|
|
2423
2616
|
}
|
|
2424
2617
|
moveBefore(other, afterNode) {
|
|
@@ -2434,10 +2627,8 @@ class ComponentNode {
|
|
|
2434
2627
|
}
|
|
2435
2628
|
_patch() {
|
|
2436
2629
|
const hasChildren = Object.keys(this.children).length > 0;
|
|
2630
|
+
this.children = this.fiber.childrenMap;
|
|
2437
2631
|
this.bdom.patch(this.fiber.bdom, hasChildren);
|
|
2438
|
-
if (hasChildren) {
|
|
2439
|
-
this.cleanOutdatedChildren();
|
|
2440
|
-
}
|
|
2441
2632
|
this.fiber.appliedToDom = true;
|
|
2442
2633
|
this.fiber = null;
|
|
2443
2634
|
}
|
|
@@ -2447,111 +2638,82 @@ class ComponentNode {
|
|
|
2447
2638
|
remove() {
|
|
2448
2639
|
this.bdom.remove();
|
|
2449
2640
|
}
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
}
|
|
2460
|
-
}
|
|
2461
|
-
}
|
|
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) : [];
|
|
2462
2650
|
}
|
|
2463
2651
|
}
|
|
2464
2652
|
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2653
|
+
// -----------------------------------------------------------------------------
|
|
2654
|
+
// Scheduler
|
|
2655
|
+
// -----------------------------------------------------------------------------
|
|
2656
|
+
class Scheduler {
|
|
2657
|
+
constructor() {
|
|
2658
|
+
this.tasks = new Set();
|
|
2659
|
+
this.frame = 0;
|
|
2660
|
+
this.delayedRenders = [];
|
|
2661
|
+
this.requestAnimationFrame = Scheduler.requestAnimationFrame;
|
|
2662
|
+
}
|
|
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
|
+
}
|
|
2478
2678
|
}
|
|
2479
|
-
return result;
|
|
2480
2679
|
}
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
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();
|
|
2484
2709
|
}
|
|
2485
|
-
|
|
2710
|
+
this.tasks.delete(fiber);
|
|
2486
2711
|
}
|
|
2487
|
-
}
|
|
2488
|
-
}
|
|
2489
|
-
// -----------------------------------------------------------------------------
|
|
2490
|
-
// hooks
|
|
2491
|
-
// -----------------------------------------------------------------------------
|
|
2492
|
-
function onWillStart(fn) {
|
|
2493
|
-
const node = getCurrent();
|
|
2494
|
-
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
2495
|
-
node.willStart.push(decorate(fn.bind(node.component), "onWillStart"));
|
|
2496
|
-
}
|
|
2497
|
-
function onWillUpdateProps(fn) {
|
|
2498
|
-
const node = getCurrent();
|
|
2499
|
-
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
2500
|
-
node.willUpdateProps.push(decorate(fn.bind(node.component), "onWillUpdateProps"));
|
|
2501
|
-
}
|
|
2502
|
-
function onMounted(fn) {
|
|
2503
|
-
const node = getCurrent();
|
|
2504
|
-
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
2505
|
-
node.mounted.push(decorate(fn.bind(node.component), "onMounted"));
|
|
2506
|
-
}
|
|
2507
|
-
function onWillPatch(fn) {
|
|
2508
|
-
const node = getCurrent();
|
|
2509
|
-
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
2510
|
-
node.willPatch.unshift(decorate(fn.bind(node.component), "onWillPatch"));
|
|
2511
|
-
}
|
|
2512
|
-
function onPatched(fn) {
|
|
2513
|
-
const node = getCurrent();
|
|
2514
|
-
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
2515
|
-
node.patched.push(decorate(fn.bind(node.component), "onPatched"));
|
|
2516
|
-
}
|
|
2517
|
-
function onWillUnmount(fn) {
|
|
2518
|
-
const node = getCurrent();
|
|
2519
|
-
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
2520
|
-
node.willUnmount.unshift(decorate(fn.bind(node.component), "onWillUnmount"));
|
|
2521
|
-
}
|
|
2522
|
-
function onWillDestroy(fn) {
|
|
2523
|
-
const node = getCurrent();
|
|
2524
|
-
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
2525
|
-
node.willDestroy.push(decorate(fn.bind(node.component), "onWillDestroy"));
|
|
2526
|
-
}
|
|
2527
|
-
function onWillRender(fn) {
|
|
2528
|
-
const node = getCurrent();
|
|
2529
|
-
const renderFn = node.renderFn;
|
|
2530
|
-
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
2531
|
-
node.renderFn = decorate(() => {
|
|
2532
|
-
fn.call(node.component);
|
|
2533
|
-
return renderFn();
|
|
2534
|
-
}, "onWillRender");
|
|
2712
|
+
}
|
|
2535
2713
|
}
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
2540
|
-
node.renderFn = decorate(() => {
|
|
2541
|
-
const result = renderFn();
|
|
2542
|
-
fn.call(node.component);
|
|
2543
|
-
return result;
|
|
2544
|
-
}, "onRendered");
|
|
2545
|
-
}
|
|
2546
|
-
function onError(callback) {
|
|
2547
|
-
const node = getCurrent();
|
|
2548
|
-
let handlers = nodeErrorHandlers.get(node);
|
|
2549
|
-
if (!handlers) {
|
|
2550
|
-
handlers = [];
|
|
2551
|
-
nodeErrorHandlers.set(node, handlers);
|
|
2552
|
-
}
|
|
2553
|
-
handlers.push(callback.bind(node.component));
|
|
2554
|
-
}
|
|
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);
|
|
2555
2717
|
|
|
2556
2718
|
/**
|
|
2557
2719
|
* Owl QWeb Expression Parser
|
|
@@ -2705,24 +2867,31 @@ const TOKENIZERS = [
|
|
|
2705
2867
|
function tokenize(expr) {
|
|
2706
2868
|
const result = [];
|
|
2707
2869
|
let token = true;
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
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
|
+
}
|
|
2717
2883
|
}
|
|
2718
2884
|
}
|
|
2885
|
+
else {
|
|
2886
|
+
token = false;
|
|
2887
|
+
}
|
|
2719
2888
|
}
|
|
2720
|
-
else {
|
|
2721
|
-
token = false;
|
|
2722
|
-
}
|
|
2723
2889
|
}
|
|
2724
|
-
|
|
2725
|
-
|
|
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}\``);
|
|
2726
2895
|
}
|
|
2727
2896
|
return result;
|
|
2728
2897
|
}
|
|
@@ -2927,7 +3096,7 @@ function createContext(parentCtx, params) {
|
|
|
2927
3096
|
}, params);
|
|
2928
3097
|
}
|
|
2929
3098
|
class CodeTarget {
|
|
2930
|
-
constructor(name) {
|
|
3099
|
+
constructor(name, on) {
|
|
2931
3100
|
this.indentLevel = 0;
|
|
2932
3101
|
this.loopLevel = 0;
|
|
2933
3102
|
this.code = [];
|
|
@@ -2938,6 +3107,7 @@ class CodeTarget {
|
|
|
2938
3107
|
this.refInfo = {};
|
|
2939
3108
|
this.shouldProtectScope = false;
|
|
2940
3109
|
this.name = name;
|
|
3110
|
+
this.on = on || null;
|
|
2941
3111
|
}
|
|
2942
3112
|
addLine(line, idx) {
|
|
2943
3113
|
const prefix = new Array(this.indentLevel + 2).join(" ");
|
|
@@ -2987,7 +3157,7 @@ class CodeGenerator {
|
|
|
2987
3157
|
this.targets = [];
|
|
2988
3158
|
this.target = new CodeTarget("template");
|
|
2989
3159
|
this.translatableAttributes = TRANSLATABLE_ATTRS;
|
|
2990
|
-
this.
|
|
3160
|
+
this.staticDefs = [];
|
|
2991
3161
|
this.helpers = new Set();
|
|
2992
3162
|
this.translateFn = options.translateFn || ((s) => s);
|
|
2993
3163
|
if (options.translatableAttributes) {
|
|
@@ -3030,8 +3200,8 @@ class CodeGenerator {
|
|
|
3030
3200
|
if (this.templateName) {
|
|
3031
3201
|
mainCode.push(`// Template name: "${this.templateName}"`);
|
|
3032
3202
|
}
|
|
3033
|
-
for (let { id,
|
|
3034
|
-
mainCode.push(`const ${id} =
|
|
3203
|
+
for (let { id, expr } of this.staticDefs) {
|
|
3204
|
+
mainCode.push(`const ${id} = ${expr};`);
|
|
3035
3205
|
}
|
|
3036
3206
|
// define all blocks
|
|
3037
3207
|
if (this.blocks.length) {
|
|
@@ -3067,19 +3237,21 @@ class CodeGenerator {
|
|
|
3067
3237
|
}
|
|
3068
3238
|
return code;
|
|
3069
3239
|
}
|
|
3070
|
-
compileInNewTarget(prefix, ast, ctx) {
|
|
3240
|
+
compileInNewTarget(prefix, ast, ctx, on) {
|
|
3071
3241
|
const name = this.generateId(prefix);
|
|
3072
3242
|
const initialTarget = this.target;
|
|
3073
|
-
const target = new CodeTarget(name);
|
|
3243
|
+
const target = new CodeTarget(name, on);
|
|
3074
3244
|
this.targets.push(target);
|
|
3075
3245
|
this.target = target;
|
|
3076
|
-
|
|
3077
|
-
this.compileAST(ast, subCtx);
|
|
3246
|
+
this.compileAST(ast, createContext(ctx));
|
|
3078
3247
|
this.target = initialTarget;
|
|
3079
3248
|
return name;
|
|
3080
3249
|
}
|
|
3081
|
-
addLine(line) {
|
|
3082
|
-
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};`);
|
|
3083
3255
|
}
|
|
3084
3256
|
generateId(prefix = "") {
|
|
3085
3257
|
this.ids[prefix] = (this.ids[prefix] || 0) + 1;
|
|
@@ -3121,10 +3293,13 @@ class CodeGenerator {
|
|
|
3121
3293
|
blockExpr = `toggler(${tKeyExpr}, ${blockExpr})`;
|
|
3122
3294
|
}
|
|
3123
3295
|
if (block.isRoot && !ctx.preventRoot) {
|
|
3296
|
+
if (this.target.on) {
|
|
3297
|
+
blockExpr = this.wrapWithEventCatcher(blockExpr, this.target.on);
|
|
3298
|
+
}
|
|
3124
3299
|
this.addLine(`return ${blockExpr};`);
|
|
3125
3300
|
}
|
|
3126
3301
|
else {
|
|
3127
|
-
this.
|
|
3302
|
+
this.define(block.varName, blockExpr);
|
|
3128
3303
|
}
|
|
3129
3304
|
}
|
|
3130
3305
|
/**
|
|
@@ -3152,7 +3327,7 @@ class CodeGenerator {
|
|
|
3152
3327
|
if (!mapping.has(tok.varName)) {
|
|
3153
3328
|
const varId = this.generateId("v");
|
|
3154
3329
|
mapping.set(tok.varName, varId);
|
|
3155
|
-
this.
|
|
3330
|
+
this.define(varId, tok.value);
|
|
3156
3331
|
}
|
|
3157
3332
|
tok.value = mapping.get(tok.varName);
|
|
3158
3333
|
}
|
|
@@ -3291,7 +3466,7 @@ class CodeGenerator {
|
|
|
3291
3466
|
this.blocks.push(block);
|
|
3292
3467
|
if (ast.dynamicTag) {
|
|
3293
3468
|
const tagExpr = this.generateId("tag");
|
|
3294
|
-
this.
|
|
3469
|
+
this.define(tagExpr, compileExpr(ast.dynamicTag));
|
|
3295
3470
|
block.dynamicTagName = tagExpr;
|
|
3296
3471
|
}
|
|
3297
3472
|
}
|
|
@@ -3373,10 +3548,10 @@ class CodeGenerator {
|
|
|
3373
3548
|
const { hasDynamicChildren, baseExpr, expr, eventType, shouldNumberize, shouldTrim, targetAttr, specialInitTargetAttr, } = ast.model;
|
|
3374
3549
|
const baseExpression = compileExpr(baseExpr);
|
|
3375
3550
|
const bExprId = this.generateId("bExpr");
|
|
3376
|
-
this.
|
|
3551
|
+
this.define(bExprId, baseExpression);
|
|
3377
3552
|
const expression = compileExpr(expr);
|
|
3378
3553
|
const exprId = this.generateId("expr");
|
|
3379
|
-
this.
|
|
3554
|
+
this.define(exprId, expression);
|
|
3380
3555
|
const fullExpression = `${bExprId}[${exprId}]`;
|
|
3381
3556
|
let idx;
|
|
3382
3557
|
if (specialInitTargetAttr) {
|
|
@@ -3386,7 +3561,7 @@ class CodeGenerator {
|
|
|
3386
3561
|
else if (hasDynamicChildren) {
|
|
3387
3562
|
const bValueId = this.generateId("bValue");
|
|
3388
3563
|
tModelSelectedExpr = `${bValueId}`;
|
|
3389
|
-
this.
|
|
3564
|
+
this.define(tModelSelectedExpr, fullExpression);
|
|
3390
3565
|
}
|
|
3391
3566
|
else {
|
|
3392
3567
|
idx = block.insertData(`${fullExpression}`, "attr");
|
|
@@ -3434,14 +3609,14 @@ class CodeGenerator {
|
|
|
3434
3609
|
const children = block.children.slice();
|
|
3435
3610
|
let current = children.shift();
|
|
3436
3611
|
for (let i = codeIdx; i < code.length; i++) {
|
|
3437
|
-
if (code[i].trimStart().startsWith(`
|
|
3438
|
-
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);
|
|
3439
3614
|
current = children.shift();
|
|
3440
3615
|
if (!current)
|
|
3441
3616
|
break;
|
|
3442
3617
|
}
|
|
3443
3618
|
}
|
|
3444
|
-
this.
|
|
3619
|
+
this.addLine(`let ${block.children.map((c) => c.varName)};`, codeIdx);
|
|
3445
3620
|
}
|
|
3446
3621
|
}
|
|
3447
3622
|
}
|
|
@@ -3529,14 +3704,14 @@ class CodeGenerator {
|
|
|
3529
3704
|
const children = block.children.slice();
|
|
3530
3705
|
let current = children.shift();
|
|
3531
3706
|
for (let i = codeIdx; i < code.length; i++) {
|
|
3532
|
-
if (code[i].trimStart().startsWith(`
|
|
3533
|
-
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);
|
|
3534
3709
|
current = children.shift();
|
|
3535
3710
|
if (!current)
|
|
3536
3711
|
break;
|
|
3537
3712
|
}
|
|
3538
3713
|
}
|
|
3539
|
-
this.
|
|
3714
|
+
this.addLine(`let ${block.children.map((c) => c.varName)};`, codeIdx);
|
|
3540
3715
|
}
|
|
3541
3716
|
// note: this part is duplicated from end of compilemulti:
|
|
3542
3717
|
const args = block.children.map((c) => c.varName).join(", ");
|
|
@@ -3557,10 +3732,10 @@ class CodeGenerator {
|
|
|
3557
3732
|
const l = `l_block${block.id}`;
|
|
3558
3733
|
const c = `c_block${block.id}`;
|
|
3559
3734
|
this.helpers.add("prepareList");
|
|
3560
|
-
this.
|
|
3735
|
+
this.define(`[${keys}, ${vals}, ${l}, ${c}]`, `prepareList(${compileExpr(ast.collection)});`);
|
|
3561
3736
|
// Throw errors on duplicate keys in dev mode
|
|
3562
3737
|
if (this.dev) {
|
|
3563
|
-
this.
|
|
3738
|
+
this.define(`keys${block.id}`, `new Set()`);
|
|
3564
3739
|
}
|
|
3565
3740
|
this.addLine(`for (let ${loopVar} = 0; ${loopVar} < ${l}; ${loopVar}++) {`);
|
|
3566
3741
|
this.target.indentLevel++;
|
|
@@ -3577,7 +3752,7 @@ class CodeGenerator {
|
|
|
3577
3752
|
if (!ast.hasNoValue) {
|
|
3578
3753
|
this.addLine(`ctx[\`${ast.elem}_value\`] = ${keys}[${loopVar}];`);
|
|
3579
3754
|
}
|
|
3580
|
-
this.
|
|
3755
|
+
this.define(`key${this.target.loopLevel}`, ast.key ? compileExpr(ast.key) : loopVar);
|
|
3581
3756
|
if (this.dev) {
|
|
3582
3757
|
// Throw error on duplicate keys in dev mode
|
|
3583
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}}\`)}`);
|
|
@@ -3587,8 +3762,8 @@ class CodeGenerator {
|
|
|
3587
3762
|
if (ast.memo) {
|
|
3588
3763
|
this.target.hasCache = true;
|
|
3589
3764
|
id = this.generateId();
|
|
3590
|
-
this.
|
|
3591
|
-
this.
|
|
3765
|
+
this.define(`memo${id}`, compileExpr(ast.memo));
|
|
3766
|
+
this.define(`vnode${id}`, `cache[key${this.target.loopLevel}];`);
|
|
3592
3767
|
this.addLine(`if (vnode${id}) {`);
|
|
3593
3768
|
this.target.indentLevel++;
|
|
3594
3769
|
this.addLine(`if (shallowEqual(vnode${id}.memo, memo${id})) {`);
|
|
@@ -3616,7 +3791,7 @@ class CodeGenerator {
|
|
|
3616
3791
|
}
|
|
3617
3792
|
compileTKey(ast, ctx) {
|
|
3618
3793
|
const tKeyExpr = this.generateId("tKey_");
|
|
3619
|
-
this.
|
|
3794
|
+
this.define(tKeyExpr, compileExpr(ast.expr));
|
|
3620
3795
|
ctx = createContext(ctx, {
|
|
3621
3796
|
tKeyExpr,
|
|
3622
3797
|
block: ctx.block,
|
|
@@ -3661,14 +3836,14 @@ class CodeGenerator {
|
|
|
3661
3836
|
const children = block.children.slice();
|
|
3662
3837
|
let current = children.shift();
|
|
3663
3838
|
for (let i = codeIdx; i < code.length; i++) {
|
|
3664
|
-
if (code[i].trimStart().startsWith(`
|
|
3665
|
-
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);
|
|
3666
3841
|
current = children.shift();
|
|
3667
3842
|
if (!current)
|
|
3668
3843
|
break;
|
|
3669
3844
|
}
|
|
3670
3845
|
}
|
|
3671
|
-
this.
|
|
3846
|
+
this.addLine(`let ${block.children.map((c) => c.varName)};`, codeIdx);
|
|
3672
3847
|
}
|
|
3673
3848
|
}
|
|
3674
3849
|
const args = block.children.map((c) => c.varName).join(", ");
|
|
@@ -3699,7 +3874,7 @@ class CodeGenerator {
|
|
|
3699
3874
|
const key = `key + \`${this.generateComponentKey()}\``;
|
|
3700
3875
|
if (isDynamic) {
|
|
3701
3876
|
const templateVar = this.generateId("template");
|
|
3702
|
-
this.
|
|
3877
|
+
this.define(templateVar, subTemplate);
|
|
3703
3878
|
block = this.createBlock(block, "multi", ctx);
|
|
3704
3879
|
this.helpers.add("call");
|
|
3705
3880
|
this.insertBlock(`call(this, ${templateVar}, ctx, node, ${key})`, block, {
|
|
@@ -3710,7 +3885,7 @@ class CodeGenerator {
|
|
|
3710
3885
|
else {
|
|
3711
3886
|
const id = this.generateId(`callTemplate_`);
|
|
3712
3887
|
this.helpers.add("getTemplate");
|
|
3713
|
-
this.
|
|
3888
|
+
this.staticDefs.push({ id, expr: `getTemplate(${subTemplate})` });
|
|
3714
3889
|
block = this.createBlock(block, "multi", ctx);
|
|
3715
3890
|
this.insertBlock(`${id}.call(this, ctx, node, ${key})`, block, {
|
|
3716
3891
|
...ctx,
|
|
@@ -3804,27 +3979,29 @@ class CodeGenerator {
|
|
|
3804
3979
|
compileComponent(ast, ctx) {
|
|
3805
3980
|
let { block } = ctx;
|
|
3806
3981
|
// props
|
|
3807
|
-
const hasSlotsProp = "slots" in ast.props;
|
|
3982
|
+
const hasSlotsProp = "slots" in (ast.props || {});
|
|
3808
3983
|
const props = [];
|
|
3809
|
-
const propExpr = this.formatPropObject(ast.props);
|
|
3984
|
+
const propExpr = this.formatPropObject(ast.props || {});
|
|
3810
3985
|
if (propExpr) {
|
|
3811
3986
|
props.push(propExpr);
|
|
3812
3987
|
}
|
|
3813
3988
|
// slots
|
|
3814
|
-
const hasSlot = !!Object.keys(ast.slots).length;
|
|
3815
3989
|
let slotDef = "";
|
|
3816
|
-
if (
|
|
3990
|
+
if (ast.slots) {
|
|
3817
3991
|
let ctxStr = "ctx";
|
|
3818
3992
|
if (this.target.loopLevel || !this.hasSafeContext) {
|
|
3819
3993
|
ctxStr = this.generateId("ctx");
|
|
3820
3994
|
this.helpers.add("capture");
|
|
3821
|
-
this.
|
|
3995
|
+
this.define(ctxStr, `capture(ctx)`);
|
|
3822
3996
|
}
|
|
3823
3997
|
let slotStr = [];
|
|
3824
3998
|
for (let slotName in ast.slots) {
|
|
3825
|
-
const slotAst = ast.slots[slotName]
|
|
3826
|
-
const
|
|
3827
|
-
|
|
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
|
+
}
|
|
3828
4005
|
const scope = ast.slots[slotName].scope;
|
|
3829
4006
|
if (scope) {
|
|
3830
4007
|
params.push(`__scope: "${scope}"`);
|
|
@@ -3849,7 +4026,7 @@ class CodeGenerator {
|
|
|
3849
4026
|
let propVar;
|
|
3850
4027
|
if ((slotDef && (ast.dynamicProps || hasSlotsProp)) || this.dev) {
|
|
3851
4028
|
propVar = this.generateId("props");
|
|
3852
|
-
this.
|
|
4029
|
+
this.define(propVar, propString);
|
|
3853
4030
|
propString = propVar;
|
|
3854
4031
|
}
|
|
3855
4032
|
if (slotDef && (ast.dynamicProps || hasSlotsProp)) {
|
|
@@ -3861,7 +4038,7 @@ class CodeGenerator {
|
|
|
3861
4038
|
let expr;
|
|
3862
4039
|
if (ast.isDynamic) {
|
|
3863
4040
|
expr = this.generateId("Comp");
|
|
3864
|
-
this.
|
|
4041
|
+
this.define(expr, compileExpr(ast.name));
|
|
3865
4042
|
}
|
|
3866
4043
|
else {
|
|
3867
4044
|
expr = `\`${ast.name}\``;
|
|
@@ -3882,9 +4059,28 @@ class CodeGenerator {
|
|
|
3882
4059
|
if (ast.isDynamic) {
|
|
3883
4060
|
blockExpr = `toggler(${expr}, ${blockExpr})`;
|
|
3884
4061
|
}
|
|
4062
|
+
// event handling
|
|
4063
|
+
if (ast.on) {
|
|
4064
|
+
blockExpr = this.wrapWithEventCatcher(blockExpr, ast.on);
|
|
4065
|
+
}
|
|
3885
4066
|
block = this.createBlock(block, "multi", ctx);
|
|
3886
4067
|
this.insertBlock(blockExpr, block, ctx);
|
|
3887
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
|
+
}
|
|
3888
4084
|
compileTSlot(ast, ctx) {
|
|
3889
4085
|
this.helpers.add("callSlot");
|
|
3890
4086
|
let { block } = ctx;
|
|
@@ -3906,13 +4102,17 @@ class CodeGenerator {
|
|
|
3906
4102
|
else {
|
|
3907
4103
|
if (dynamic) {
|
|
3908
4104
|
let name = this.generateId("slot");
|
|
3909
|
-
this.
|
|
3910
|
-
blockString = `toggler(${name}, callSlot(ctx, node, key, ${name}
|
|
4105
|
+
this.define(name, slotName);
|
|
4106
|
+
blockString = `toggler(${name}, callSlot(ctx, node, key, ${name}, ${dynamic}, ${scope}))`;
|
|
3911
4107
|
}
|
|
3912
4108
|
else {
|
|
3913
4109
|
blockString = `callSlot(ctx, node, key, ${slotName}, ${dynamic}, ${scope})`;
|
|
3914
4110
|
}
|
|
3915
4111
|
}
|
|
4112
|
+
// event handling
|
|
4113
|
+
if (ast.on) {
|
|
4114
|
+
blockString = this.wrapWithEventCatcher(blockString, ast.on);
|
|
4115
|
+
}
|
|
3916
4116
|
if (block) {
|
|
3917
4117
|
this.insertAnchor(block);
|
|
3918
4118
|
}
|
|
@@ -3933,7 +4133,7 @@ class CodeGenerator {
|
|
|
3933
4133
|
if (this.target.loopLevel || !this.hasSafeContext) {
|
|
3934
4134
|
ctxStr = this.generateId("ctx");
|
|
3935
4135
|
this.helpers.add("capture");
|
|
3936
|
-
this.
|
|
4136
|
+
this.define(ctxStr, `capture(ctx);`);
|
|
3937
4137
|
}
|
|
3938
4138
|
const blockString = `component(Portal, {target: ${ast.target},slots: {'default': {__render: ${name}, __ctx: ${ctxStr}}}}, key + \`${key}\`, node, ctx)`;
|
|
3939
4139
|
if (block) {
|
|
@@ -4067,8 +4267,8 @@ function parseDOMNode(node, ctx) {
|
|
|
4067
4267
|
const ref = node.getAttribute("t-ref");
|
|
4068
4268
|
node.removeAttribute("t-ref");
|
|
4069
4269
|
const nodeAttrsNames = node.getAttributeNames();
|
|
4070
|
-
|
|
4071
|
-
|
|
4270
|
+
let attrs = null;
|
|
4271
|
+
let on = null;
|
|
4072
4272
|
let model = null;
|
|
4073
4273
|
for (let attr of nodeAttrsNames) {
|
|
4074
4274
|
const value = node.getAttribute(attr);
|
|
@@ -4076,6 +4276,7 @@ function parseDOMNode(node, ctx) {
|
|
|
4076
4276
|
if (attr === "t-on") {
|
|
4077
4277
|
throw new Error("Missing event name with t-on directive");
|
|
4078
4278
|
}
|
|
4279
|
+
on = on || {};
|
|
4079
4280
|
on[attr.slice(5)] = value;
|
|
4080
4281
|
}
|
|
4081
4282
|
else if (attr.startsWith("t-model")) {
|
|
@@ -4113,6 +4314,7 @@ function parseDOMNode(node, ctx) {
|
|
|
4113
4314
|
targetAttr: isCheckboxInput ? "checked" : "value",
|
|
4114
4315
|
specialInitTargetAttr: isRadioInput ? "checked" : null,
|
|
4115
4316
|
eventType,
|
|
4317
|
+
hasDynamicChildren: false,
|
|
4116
4318
|
shouldTrim: hasTrimMod && (isOtherInput || isTextarea),
|
|
4117
4319
|
shouldNumberize: hasNumberMod && (isOtherInput || isTextarea),
|
|
4118
4320
|
};
|
|
@@ -4133,6 +4335,7 @@ function parseDOMNode(node, ctx) {
|
|
|
4133
4335
|
if (tModel && ["t-att-value", "t-attf-value"].includes(attr)) {
|
|
4134
4336
|
tModel.hasDynamicChildren = true;
|
|
4135
4337
|
}
|
|
4338
|
+
attrs = attrs || {};
|
|
4136
4339
|
attrs[attr] = value;
|
|
4137
4340
|
}
|
|
4138
4341
|
}
|
|
@@ -4283,7 +4486,7 @@ function parseTCall(node, ctx) {
|
|
|
4283
4486
|
if (ast && ast.type === 11 /* TComponent */) {
|
|
4284
4487
|
return {
|
|
4285
4488
|
...ast,
|
|
4286
|
-
slots: { default: { content: tcall } },
|
|
4489
|
+
slots: { default: { content: tcall, scope: null, on: null, attrs: null } },
|
|
4287
4490
|
};
|
|
4288
4491
|
}
|
|
4289
4492
|
}
|
|
@@ -4367,7 +4570,6 @@ function parseTSetNode(node, ctx) {
|
|
|
4367
4570
|
// -----------------------------------------------------------------------------
|
|
4368
4571
|
// Error messages when trying to use an unsupported directive on a component
|
|
4369
4572
|
const directiveErrorMap = new Map([
|
|
4370
|
-
["t-on", "t-on is no longer supported on components. Consider passing a callback in props."],
|
|
4371
4573
|
[
|
|
4372
4574
|
"t-ref",
|
|
4373
4575
|
"t-ref is no longer supported on components. Consider exposing only the public part of the component's API through a callback prop.",
|
|
@@ -4396,18 +4598,26 @@ function parseComponent(node, ctx) {
|
|
|
4396
4598
|
node.removeAttribute("t-props");
|
|
4397
4599
|
const defaultSlotScope = node.getAttribute("t-slot-scope");
|
|
4398
4600
|
node.removeAttribute("t-slot-scope");
|
|
4399
|
-
|
|
4601
|
+
let on = null;
|
|
4602
|
+
let props = null;
|
|
4400
4603
|
for (let name of node.getAttributeNames()) {
|
|
4401
4604
|
const value = node.getAttribute(name);
|
|
4402
4605
|
if (name.startsWith("t-")) {
|
|
4403
|
-
|
|
4404
|
-
|
|
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
|
+
}
|
|
4405
4614
|
}
|
|
4406
4615
|
else {
|
|
4616
|
+
props = props || {};
|
|
4407
4617
|
props[name] = value;
|
|
4408
4618
|
}
|
|
4409
4619
|
}
|
|
4410
|
-
|
|
4620
|
+
let slots = null;
|
|
4411
4621
|
if (node.hasChildNodes()) {
|
|
4412
4622
|
const clone = node.cloneNode(true);
|
|
4413
4623
|
// named slots
|
|
@@ -4434,33 +4644,35 @@ function parseComponent(node, ctx) {
|
|
|
4434
4644
|
slotNode.removeAttribute("t-set-slot");
|
|
4435
4645
|
slotNode.remove();
|
|
4436
4646
|
const slotAst = parseNode(slotNode, ctx);
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
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;
|
|
4655
|
+
}
|
|
4656
|
+
else if (attributeName.startsWith("t-on-")) {
|
|
4657
|
+
on = on || {};
|
|
4658
|
+
on[attributeName.slice(5)] = value;
|
|
4447
4659
|
}
|
|
4448
|
-
|
|
4449
|
-
|
|
4660
|
+
else {
|
|
4661
|
+
attrs = attrs || {};
|
|
4662
|
+
attrs[attributeName] = value;
|
|
4450
4663
|
}
|
|
4451
|
-
slots[name] = slotInfo;
|
|
4452
4664
|
}
|
|
4665
|
+
slots = slots || {};
|
|
4666
|
+
slots[name] = { content: slotAst, on, attrs, scope };
|
|
4453
4667
|
}
|
|
4454
4668
|
// default slot
|
|
4455
4669
|
const defaultContent = parseChildNodes(clone, ctx);
|
|
4456
4670
|
if (defaultContent) {
|
|
4457
|
-
slots
|
|
4458
|
-
|
|
4459
|
-
slots.default.scope = defaultSlotScope;
|
|
4460
|
-
}
|
|
4671
|
+
slots = slots || {};
|
|
4672
|
+
slots.default = { content: defaultContent, on, attrs: null, scope: defaultSlotScope };
|
|
4461
4673
|
}
|
|
4462
4674
|
}
|
|
4463
|
-
return { type: 11 /* TComponent */, name, isDynamic, dynamicProps, props, slots };
|
|
4675
|
+
return { type: 11 /* TComponent */, name, isDynamic, dynamicProps, props, slots, on };
|
|
4464
4676
|
}
|
|
4465
4677
|
// -----------------------------------------------------------------------------
|
|
4466
4678
|
// Slots
|
|
@@ -4471,15 +4683,24 @@ function parseTSlot(node, ctx) {
|
|
|
4471
4683
|
}
|
|
4472
4684
|
const name = node.getAttribute("t-slot");
|
|
4473
4685
|
node.removeAttribute("t-slot");
|
|
4474
|
-
|
|
4686
|
+
let attrs = null;
|
|
4687
|
+
let on = null;
|
|
4475
4688
|
for (let attributeName of node.getAttributeNames()) {
|
|
4476
4689
|
const value = node.getAttribute(attributeName);
|
|
4477
|
-
|
|
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
|
+
}
|
|
4478
4698
|
}
|
|
4479
4699
|
return {
|
|
4480
4700
|
type: 14 /* TSlot */,
|
|
4481
4701
|
name,
|
|
4482
4702
|
attrs,
|
|
4703
|
+
on,
|
|
4483
4704
|
defaultContent: parseChildNodes(node, ctx),
|
|
4484
4705
|
};
|
|
4485
4706
|
}
|
|
@@ -4613,68 +4834,423 @@ function normalizeTEsc(el) {
|
|
|
4613
4834
|
el.appendChild(t);
|
|
4614
4835
|
}
|
|
4615
4836
|
}
|
|
4616
|
-
/**
|
|
4617
|
-
* Normalizes the tree inside a given element and do some preliminary validation
|
|
4618
|
-
* on it. This function modifies the Element in place.
|
|
4619
|
-
*
|
|
4620
|
-
* @param el the element containing the tree that should be normalized
|
|
4621
|
-
*/
|
|
4622
|
-
function normalizeXML(el) {
|
|
4623
|
-
normalizeTIf(el);
|
|
4624
|
-
normalizeTEsc(el);
|
|
4837
|
+
/**
|
|
4838
|
+
* Normalizes the tree inside a given element and do some preliminary validation
|
|
4839
|
+
* on it. This function modifies the Element in place.
|
|
4840
|
+
*
|
|
4841
|
+
* @param el the element containing the tree that should be normalized
|
|
4842
|
+
*/
|
|
4843
|
+
function normalizeXML(el) {
|
|
4844
|
+
normalizeTIf(el);
|
|
4845
|
+
normalizeTEsc(el);
|
|
4846
|
+
}
|
|
4847
|
+
/**
|
|
4848
|
+
* Parses an XML string into an XML document, throwing errors on parser errors
|
|
4849
|
+
* instead of returning an XML document containing the parseerror.
|
|
4850
|
+
*
|
|
4851
|
+
* @param xml the string to parse
|
|
4852
|
+
* @returns an XML document corresponding to the content of the string
|
|
4853
|
+
*/
|
|
4854
|
+
function parseXML$1(xml) {
|
|
4855
|
+
const parser = new DOMParser();
|
|
4856
|
+
const doc = parser.parseFromString(xml, "text/xml");
|
|
4857
|
+
if (doc.getElementsByTagName("parsererror").length) {
|
|
4858
|
+
let msg = "Invalid XML in template.";
|
|
4859
|
+
const parsererrorText = doc.getElementsByTagName("parsererror")[0].textContent;
|
|
4860
|
+
if (parsererrorText) {
|
|
4861
|
+
msg += "\nThe parser has produced the following error message:\n" + parsererrorText;
|
|
4862
|
+
const re = /\d+/g;
|
|
4863
|
+
const firstMatch = re.exec(parsererrorText);
|
|
4864
|
+
if (firstMatch) {
|
|
4865
|
+
const lineNumber = Number(firstMatch[0]);
|
|
4866
|
+
const line = xml.split("\n")[lineNumber - 1];
|
|
4867
|
+
const secondMatch = re.exec(parsererrorText);
|
|
4868
|
+
if (line && secondMatch) {
|
|
4869
|
+
const columnIndex = Number(secondMatch[0]) - 1;
|
|
4870
|
+
if (line[columnIndex]) {
|
|
4871
|
+
msg +=
|
|
4872
|
+
`\nThe error might be located at xml line ${lineNumber} column ${columnIndex}\n` +
|
|
4873
|
+
`${line}\n${"-".repeat(columnIndex - 1)}^`;
|
|
4874
|
+
}
|
|
4875
|
+
}
|
|
4876
|
+
}
|
|
4877
|
+
}
|
|
4878
|
+
throw new Error(msg);
|
|
4879
|
+
}
|
|
4880
|
+
return doc;
|
|
4881
|
+
}
|
|
4882
|
+
|
|
4883
|
+
function compile(template, options = {}) {
|
|
4884
|
+
// parsing
|
|
4885
|
+
const ast = parse(template);
|
|
4886
|
+
// some work
|
|
4887
|
+
const hasSafeContext = template instanceof Node
|
|
4888
|
+
? !(template instanceof Element) || template.querySelector("[t-set], [t-call]") === null
|
|
4889
|
+
: !template.includes("t-set") && !template.includes("t-call");
|
|
4890
|
+
// code generation
|
|
4891
|
+
const codeGenerator = new CodeGenerator(ast, { ...options, hasSafeContext });
|
|
4892
|
+
const code = codeGenerator.generateCode();
|
|
4893
|
+
// template function
|
|
4894
|
+
return new Function("bdom, helpers", code);
|
|
4895
|
+
}
|
|
4896
|
+
|
|
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
|
+
});
|
|
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
|
+
});
|
|
4924
|
+
}
|
|
4925
|
+
return result;
|
|
4926
|
+
}
|
|
4927
|
+
catch (cause) {
|
|
4928
|
+
if (cause instanceof Error) {
|
|
4929
|
+
error.message += `"${cause.message}"`;
|
|
4930
|
+
}
|
|
4931
|
+
throw error;
|
|
4932
|
+
}
|
|
4933
|
+
};
|
|
4934
|
+
}
|
|
4935
|
+
// -----------------------------------------------------------------------------
|
|
4936
|
+
// hooks
|
|
4937
|
+
// -----------------------------------------------------------------------------
|
|
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"));
|
|
4942
|
+
}
|
|
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
|
+
}
|
|
5003
|
+
|
|
5004
|
+
class Component {
|
|
5005
|
+
constructor(props, env, node) {
|
|
5006
|
+
this.props = props;
|
|
5007
|
+
this.env = env;
|
|
5008
|
+
this.__owl__ = node;
|
|
5009
|
+
}
|
|
5010
|
+
setup() { }
|
|
5011
|
+
render(deep = false) {
|
|
5012
|
+
this.__owl__.render(deep === true);
|
|
5013
|
+
}
|
|
5014
|
+
}
|
|
5015
|
+
Component.template = "";
|
|
5016
|
+
|
|
5017
|
+
const VText = text("").constructor;
|
|
5018
|
+
class VPortal extends VText {
|
|
5019
|
+
constructor(selector, realBDom) {
|
|
5020
|
+
super("");
|
|
5021
|
+
this.target = null;
|
|
5022
|
+
this.selector = selector;
|
|
5023
|
+
this.realBDom = realBDom;
|
|
5024
|
+
}
|
|
5025
|
+
mount(parent, anchor) {
|
|
5026
|
+
super.mount(parent, anchor);
|
|
5027
|
+
this.target = document.querySelector(this.selector);
|
|
5028
|
+
if (!this.target) {
|
|
5029
|
+
let el = this.el;
|
|
5030
|
+
while (el && el.parentElement instanceof HTMLElement) {
|
|
5031
|
+
el = el.parentElement;
|
|
5032
|
+
}
|
|
5033
|
+
this.target = el && el.querySelector(this.selector);
|
|
5034
|
+
if (!this.target) {
|
|
5035
|
+
throw new Error("invalid portal target");
|
|
5036
|
+
}
|
|
5037
|
+
}
|
|
5038
|
+
this.realBDom.mount(this.target, null);
|
|
5039
|
+
}
|
|
5040
|
+
beforeRemove() {
|
|
5041
|
+
this.realBDom.beforeRemove();
|
|
5042
|
+
}
|
|
5043
|
+
remove() {
|
|
5044
|
+
if (this.realBDom) {
|
|
5045
|
+
super.remove();
|
|
5046
|
+
this.realBDom.remove();
|
|
5047
|
+
this.realBDom = null;
|
|
5048
|
+
}
|
|
5049
|
+
}
|
|
5050
|
+
patch(other) {
|
|
5051
|
+
super.patch(other);
|
|
5052
|
+
if (this.realBDom) {
|
|
5053
|
+
this.realBDom.patch(other.realBDom, true);
|
|
5054
|
+
}
|
|
5055
|
+
else {
|
|
5056
|
+
this.realBDom = other.realBDom;
|
|
5057
|
+
this.realBDom.mount(this.target, null);
|
|
5058
|
+
}
|
|
5059
|
+
}
|
|
5060
|
+
}
|
|
5061
|
+
class Portal extends Component {
|
|
5062
|
+
setup() {
|
|
5063
|
+
const node = this.__owl__;
|
|
5064
|
+
const renderFn = node.renderFn;
|
|
5065
|
+
node.renderFn = () => new VPortal(this.props.target, renderFn());
|
|
5066
|
+
onWillUnmount(() => {
|
|
5067
|
+
if (node.bdom) {
|
|
5068
|
+
node.bdom.remove();
|
|
5069
|
+
}
|
|
5070
|
+
});
|
|
5071
|
+
}
|
|
5072
|
+
}
|
|
5073
|
+
Portal.template = xml `<t t-slot="default"/>`;
|
|
5074
|
+
Portal.props = {
|
|
5075
|
+
target: {
|
|
5076
|
+
type: String,
|
|
5077
|
+
},
|
|
5078
|
+
slots: true,
|
|
5079
|
+
};
|
|
5080
|
+
|
|
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);
|
|
5132
|
+
}
|
|
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;
|
|
4625
5219
|
}
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
function parseXML$1(xml) {
|
|
4634
|
-
const parser = new DOMParser();
|
|
4635
|
-
const doc = parser.parseFromString(xml, "text/xml");
|
|
4636
|
-
if (doc.getElementsByTagName("parsererror").length) {
|
|
4637
|
-
let msg = "Invalid XML in template.";
|
|
4638
|
-
const parsererrorText = doc.getElementsByTagName("parsererror")[0].textContent;
|
|
4639
|
-
if (parsererrorText) {
|
|
4640
|
-
msg += "\nThe parser has produced the following error message:\n" + parsererrorText;
|
|
4641
|
-
const re = /\d+/g;
|
|
4642
|
-
const firstMatch = re.exec(parsererrorText);
|
|
4643
|
-
if (firstMatch) {
|
|
4644
|
-
const lineNumber = Number(firstMatch[0]);
|
|
4645
|
-
const line = xml.split("\n")[lineNumber - 1];
|
|
4646
|
-
const secondMatch = re.exec(parsererrorText);
|
|
4647
|
-
if (line && secondMatch) {
|
|
4648
|
-
const columnIndex = Number(secondMatch[0]) - 1;
|
|
4649
|
-
if (line[columnIndex]) {
|
|
4650
|
-
msg +=
|
|
4651
|
-
`\nThe error might be located at xml line ${lineNumber} column ${columnIndex}\n` +
|
|
4652
|
-
`${line}\n${"-".repeat(columnIndex - 1)}^`;
|
|
4653
|
-
}
|
|
4654
|
-
}
|
|
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");
|
|
4655
5227
|
}
|
|
4656
5228
|
}
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
}
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
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
|
+
};
|
|
4675
5252
|
|
|
4676
5253
|
const bdom = { text, createBlock, list, multi, html, toggler, component, comment };
|
|
4677
|
-
const globalTemplates = {};
|
|
4678
5254
|
function parseXML(xml) {
|
|
4679
5255
|
const parser = new DOMParser();
|
|
4680
5256
|
const doc = parser.parseFromString(xml, "text/xml");
|
|
@@ -4703,23 +5279,32 @@ function parseXML(xml) {
|
|
|
4703
5279
|
}
|
|
4704
5280
|
return doc;
|
|
4705
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
|
+
}
|
|
4706
5297
|
class TemplateSet {
|
|
4707
5298
|
constructor(config = {}) {
|
|
4708
5299
|
this.rawTemplates = Object.create(globalTemplates);
|
|
4709
5300
|
this.templates = {};
|
|
4710
|
-
this.utils = Object.assign({}, UTILS, {
|
|
4711
|
-
call: (owner, subTemplate, ctx, parent, key) => {
|
|
4712
|
-
const template = this.getTemplate(subTemplate);
|
|
4713
|
-
return toggler(subTemplate, template.call(owner, ctx, parent, key));
|
|
4714
|
-
},
|
|
4715
|
-
getTemplate: (name) => this.getTemplate(name),
|
|
4716
|
-
});
|
|
4717
5301
|
this.dev = config.dev || false;
|
|
4718
5302
|
this.translateFn = config.translateFn;
|
|
4719
5303
|
this.translatableAttributes = config.translatableAttributes;
|
|
4720
5304
|
if (config.templates) {
|
|
4721
5305
|
this.addTemplates(config.templates);
|
|
4722
5306
|
}
|
|
5307
|
+
this.helpers = makeHelpers(this.getTemplate.bind(this));
|
|
4723
5308
|
}
|
|
4724
5309
|
addTemplate(name, template, options = {}) {
|
|
4725
5310
|
if (name in this.rawTemplates && !options.allowDuplicate) {
|
|
@@ -4757,7 +5342,7 @@ class TemplateSet {
|
|
|
4757
5342
|
this.templates[name] = function (context, parent) {
|
|
4758
5343
|
return templates[name].call(this, context, parent);
|
|
4759
5344
|
};
|
|
4760
|
-
const template = templateFn(bdom, this.
|
|
5345
|
+
const template = templateFn(bdom, this.helpers);
|
|
4761
5346
|
this.templates[name] = template;
|
|
4762
5347
|
}
|
|
4763
5348
|
return this.templates[name];
|
|
@@ -4770,160 +5355,9 @@ class TemplateSet {
|
|
|
4770
5355
|
translatableAttributes: this.translatableAttributes,
|
|
4771
5356
|
});
|
|
4772
5357
|
}
|
|
4773
|
-
}
|
|
4774
|
-
// -----------------------------------------------------------------------------
|
|
4775
|
-
// xml tag helper
|
|
4776
|
-
// -----------------------------------------------------------------------------
|
|
4777
|
-
function xml(...args) {
|
|
4778
|
-
const name = `__template__${xml.nextId++}`;
|
|
4779
|
-
const value = String.raw(...args);
|
|
4780
|
-
globalTemplates[name] = value;
|
|
4781
|
-
return name;
|
|
4782
|
-
}
|
|
4783
|
-
xml.nextId = 1;
|
|
4784
|
-
|
|
4785
|
-
class Component {
|
|
4786
|
-
constructor(props, env, node) {
|
|
4787
|
-
this.props = props;
|
|
4788
|
-
this.env = env;
|
|
4789
|
-
this.__owl__ = node;
|
|
4790
|
-
}
|
|
4791
|
-
setup() { }
|
|
4792
|
-
render(deep = false) {
|
|
4793
|
-
this.__owl__.render(deep);
|
|
4794
|
-
}
|
|
4795
|
-
}
|
|
4796
|
-
Component.template = "";
|
|
4797
|
-
|
|
4798
|
-
const VText = text("").constructor;
|
|
4799
|
-
class VPortal extends VText {
|
|
4800
|
-
constructor(selector, realBDom) {
|
|
4801
|
-
super("");
|
|
4802
|
-
this.target = null;
|
|
4803
|
-
this.selector = selector;
|
|
4804
|
-
this.realBDom = realBDom;
|
|
4805
|
-
}
|
|
4806
|
-
mount(parent, anchor) {
|
|
4807
|
-
super.mount(parent, anchor);
|
|
4808
|
-
this.target = document.querySelector(this.selector);
|
|
4809
|
-
if (!this.target) {
|
|
4810
|
-
let el = this.el;
|
|
4811
|
-
while (el && el.parentElement instanceof HTMLElement) {
|
|
4812
|
-
el = el.parentElement;
|
|
4813
|
-
}
|
|
4814
|
-
this.target = el && el.querySelector(this.selector);
|
|
4815
|
-
if (!this.target) {
|
|
4816
|
-
throw new Error("invalid portal target");
|
|
4817
|
-
}
|
|
4818
|
-
}
|
|
4819
|
-
this.realBDom.mount(this.target, null);
|
|
4820
|
-
}
|
|
4821
|
-
beforeRemove() {
|
|
4822
|
-
this.realBDom.beforeRemove();
|
|
4823
|
-
}
|
|
4824
|
-
remove() {
|
|
4825
|
-
if (this.realBDom) {
|
|
4826
|
-
super.remove();
|
|
4827
|
-
this.realBDom.remove();
|
|
4828
|
-
this.realBDom = null;
|
|
4829
|
-
}
|
|
4830
|
-
}
|
|
4831
|
-
patch(other) {
|
|
4832
|
-
super.patch(other);
|
|
4833
|
-
if (this.realBDom) {
|
|
4834
|
-
this.realBDom.patch(other.realBDom, true);
|
|
4835
|
-
}
|
|
4836
|
-
else {
|
|
4837
|
-
this.realBDom = other.realBDom;
|
|
4838
|
-
this.realBDom.mount(this.target, null);
|
|
4839
|
-
}
|
|
4840
|
-
}
|
|
4841
|
-
}
|
|
4842
|
-
class Portal extends Component {
|
|
4843
|
-
setup() {
|
|
4844
|
-
const node = this.__owl__;
|
|
4845
|
-
const renderFn = node.renderFn;
|
|
4846
|
-
node.renderFn = () => new VPortal(this.props.target, renderFn());
|
|
4847
|
-
onWillUnmount(() => {
|
|
4848
|
-
if (node.bdom) {
|
|
4849
|
-
node.bdom.remove();
|
|
4850
|
-
}
|
|
4851
|
-
});
|
|
4852
|
-
}
|
|
4853
|
-
}
|
|
4854
|
-
Portal.template = xml `<t t-slot="default"/>`;
|
|
4855
|
-
Portal.props = {
|
|
4856
|
-
target: {
|
|
4857
|
-
type: String,
|
|
4858
|
-
},
|
|
4859
|
-
slots: true,
|
|
4860
|
-
};
|
|
4861
|
-
|
|
4862
|
-
// -----------------------------------------------------------------------------
|
|
4863
|
-
// Scheduler
|
|
4864
|
-
// -----------------------------------------------------------------------------
|
|
4865
|
-
class Scheduler {
|
|
4866
|
-
constructor() {
|
|
4867
|
-
this.tasks = new Set();
|
|
4868
|
-
this.isRunning = false;
|
|
4869
|
-
this.requestAnimationFrame = Scheduler.requestAnimationFrame;
|
|
4870
|
-
}
|
|
4871
|
-
start() {
|
|
4872
|
-
this.isRunning = true;
|
|
4873
|
-
this.scheduleTasks();
|
|
4874
|
-
}
|
|
4875
|
-
stop() {
|
|
4876
|
-
this.isRunning = false;
|
|
4877
|
-
}
|
|
4878
|
-
addFiber(fiber) {
|
|
4879
|
-
this.tasks.add(fiber.root);
|
|
4880
|
-
if (!this.isRunning) {
|
|
4881
|
-
this.start();
|
|
4882
|
-
}
|
|
4883
|
-
}
|
|
4884
|
-
/**
|
|
4885
|
-
* Process all current tasks. This only applies to the fibers that are ready.
|
|
4886
|
-
* Other tasks are left unchanged.
|
|
4887
|
-
*/
|
|
4888
|
-
flush() {
|
|
4889
|
-
this.tasks.forEach((fiber) => {
|
|
4890
|
-
if (fiber.root !== fiber) {
|
|
4891
|
-
this.tasks.delete(fiber);
|
|
4892
|
-
return;
|
|
4893
|
-
}
|
|
4894
|
-
const hasError = fibersInError.has(fiber);
|
|
4895
|
-
if (hasError && fiber.counter !== 0) {
|
|
4896
|
-
this.tasks.delete(fiber);
|
|
4897
|
-
return;
|
|
4898
|
-
}
|
|
4899
|
-
if (fiber.node.status === 2 /* DESTROYED */) {
|
|
4900
|
-
this.tasks.delete(fiber);
|
|
4901
|
-
return;
|
|
4902
|
-
}
|
|
4903
|
-
if (fiber.counter === 0) {
|
|
4904
|
-
if (!hasError) {
|
|
4905
|
-
fiber.complete();
|
|
4906
|
-
}
|
|
4907
|
-
this.tasks.delete(fiber);
|
|
4908
|
-
}
|
|
4909
|
-
});
|
|
4910
|
-
if (this.tasks.size === 0) {
|
|
4911
|
-
this.stop();
|
|
4912
|
-
}
|
|
4913
|
-
}
|
|
4914
|
-
scheduleTasks() {
|
|
4915
|
-
this.requestAnimationFrame(() => {
|
|
4916
|
-
this.flush();
|
|
4917
|
-
if (this.isRunning) {
|
|
4918
|
-
this.scheduleTasks();
|
|
4919
|
-
}
|
|
4920
|
-
});
|
|
4921
|
-
}
|
|
4922
|
-
}
|
|
4923
|
-
// capture the value of requestAnimationFrame as soon as possible, to avoid
|
|
4924
|
-
// interactions with other code, such as test frameworks that override them
|
|
4925
|
-
Scheduler.requestAnimationFrame = window.requestAnimationFrame.bind(window);
|
|
5358
|
+
}
|
|
4926
5359
|
|
|
5360
|
+
let hasBeenLogged = false;
|
|
4927
5361
|
const DEV_MSG = () => {
|
|
4928
5362
|
const hash = window.owl ? window.owl.__info__.hash : "master";
|
|
4929
5363
|
return `Owl is running in 'dev' mode.
|
|
@@ -4940,11 +5374,13 @@ class App extends TemplateSet {
|
|
|
4940
5374
|
if (config.test) {
|
|
4941
5375
|
this.dev = true;
|
|
4942
5376
|
}
|
|
4943
|
-
if (this.dev && !config.test) {
|
|
5377
|
+
if (this.dev && !config.test && !hasBeenLogged) {
|
|
4944
5378
|
console.info(DEV_MSG());
|
|
5379
|
+
hasBeenLogged = true;
|
|
4945
5380
|
}
|
|
4946
|
-
const
|
|
4947
|
-
|
|
5381
|
+
const env = config.env || {};
|
|
5382
|
+
const descrs = Object.getOwnPropertyDescriptors(env);
|
|
5383
|
+
this.env = Object.freeze(Object.create(Object.getPrototypeOf(env), descrs));
|
|
4948
5384
|
this.props = config.props || {};
|
|
4949
5385
|
}
|
|
4950
5386
|
mount(target, options) {
|
|
@@ -4955,7 +5391,7 @@ class App extends TemplateSet {
|
|
|
4955
5391
|
return prom;
|
|
4956
5392
|
}
|
|
4957
5393
|
makeNode(Component, props) {
|
|
4958
|
-
return new ComponentNode(Component, props, this);
|
|
5394
|
+
return new ComponentNode(Component, props, this, null, null);
|
|
4959
5395
|
}
|
|
4960
5396
|
mountNode(node, target, options) {
|
|
4961
5397
|
const promise = new Promise((resolve, reject) => {
|
|
@@ -4987,6 +5423,7 @@ class App extends TemplateSet {
|
|
|
4987
5423
|
}
|
|
4988
5424
|
destroy() {
|
|
4989
5425
|
if (this.root) {
|
|
5426
|
+
this.scheduler.flush();
|
|
4990
5427
|
this.root.destroy();
|
|
4991
5428
|
}
|
|
4992
5429
|
}
|
|
@@ -5007,45 +5444,6 @@ function status(component) {
|
|
|
5007
5444
|
}
|
|
5008
5445
|
}
|
|
5009
5446
|
|
|
5010
|
-
class Memo extends Component {
|
|
5011
|
-
constructor(props, env, node) {
|
|
5012
|
-
super(props, env, node);
|
|
5013
|
-
// prevent patching process conditionally
|
|
5014
|
-
let applyPatch = false;
|
|
5015
|
-
const patchFn = node.patch;
|
|
5016
|
-
node.patch = () => {
|
|
5017
|
-
if (applyPatch) {
|
|
5018
|
-
patchFn.call(node);
|
|
5019
|
-
applyPatch = false;
|
|
5020
|
-
}
|
|
5021
|
-
};
|
|
5022
|
-
// check props change, and render/apply patch if it changed
|
|
5023
|
-
let prevProps = props;
|
|
5024
|
-
const updateAndRender = node.updateAndRender;
|
|
5025
|
-
node.updateAndRender = function (props, parentFiber) {
|
|
5026
|
-
const shouldUpdate = !shallowEqual(prevProps, props);
|
|
5027
|
-
if (shouldUpdate) {
|
|
5028
|
-
prevProps = props;
|
|
5029
|
-
updateAndRender.call(node, props, parentFiber);
|
|
5030
|
-
applyPatch = true;
|
|
5031
|
-
}
|
|
5032
|
-
return Promise.resolve();
|
|
5033
|
-
};
|
|
5034
|
-
}
|
|
5035
|
-
}
|
|
5036
|
-
Memo.template = xml `<t t-slot="default"/>`;
|
|
5037
|
-
/**
|
|
5038
|
-
* we assume that each object have the same set of keys
|
|
5039
|
-
*/
|
|
5040
|
-
function shallowEqual(p1, p2) {
|
|
5041
|
-
for (let k in p1) {
|
|
5042
|
-
if (k !== "slots" && p1[k] !== p2[k]) {
|
|
5043
|
-
return false;
|
|
5044
|
-
}
|
|
5045
|
-
}
|
|
5046
|
-
return true;
|
|
5047
|
-
}
|
|
5048
|
-
|
|
5049
5447
|
// -----------------------------------------------------------------------------
|
|
5050
5448
|
// useRef
|
|
5051
5449
|
// -----------------------------------------------------------------------------
|
|
@@ -5091,10 +5489,6 @@ function useChildSubEnv(envExtension) {
|
|
|
5091
5489
|
const node = getCurrent();
|
|
5092
5490
|
node.childEnv = extendEnv(node.childEnv, envExtension);
|
|
5093
5491
|
}
|
|
5094
|
-
// -----------------------------------------------------------------------------
|
|
5095
|
-
// useEffect
|
|
5096
|
-
// -----------------------------------------------------------------------------
|
|
5097
|
-
const NO_OP = () => { };
|
|
5098
5492
|
/**
|
|
5099
5493
|
* This hook will run a callback when a component is mounted and patched, and
|
|
5100
5494
|
* will run a cleanup function before patching and before unmounting the
|
|
@@ -5112,18 +5506,20 @@ function useEffect(effect, computeDependencies = () => [NaN]) {
|
|
|
5112
5506
|
let dependencies;
|
|
5113
5507
|
onMounted(() => {
|
|
5114
5508
|
dependencies = computeDependencies();
|
|
5115
|
-
cleanup = effect(...dependencies)
|
|
5509
|
+
cleanup = effect(...dependencies);
|
|
5116
5510
|
});
|
|
5117
5511
|
onPatched(() => {
|
|
5118
5512
|
const newDeps = computeDependencies();
|
|
5119
5513
|
const shouldReapply = newDeps.some((val, i) => val !== dependencies[i]);
|
|
5120
5514
|
if (shouldReapply) {
|
|
5121
5515
|
dependencies = newDeps;
|
|
5122
|
-
cleanup
|
|
5123
|
-
|
|
5516
|
+
if (cleanup) {
|
|
5517
|
+
cleanup();
|
|
5518
|
+
}
|
|
5519
|
+
cleanup = effect(...dependencies);
|
|
5124
5520
|
}
|
|
5125
5521
|
});
|
|
5126
|
-
onWillUnmount(() => cleanup());
|
|
5522
|
+
onWillUnmount(() => cleanup && cleanup());
|
|
5127
5523
|
}
|
|
5128
5524
|
// -----------------------------------------------------------------------------
|
|
5129
5525
|
// useExternalListener
|
|
@@ -5150,8 +5546,6 @@ function useExternalListener(target, eventName, handler, eventParams) {
|
|
|
5150
5546
|
|
|
5151
5547
|
config.shouldNormalizeDom = false;
|
|
5152
5548
|
config.mainEventHandler = mainEventHandler;
|
|
5153
|
-
UTILS.Portal = Portal;
|
|
5154
|
-
UTILS.markRaw = markRaw;
|
|
5155
5549
|
const blockDom = {
|
|
5156
5550
|
config,
|
|
5157
5551
|
// bdom entry points
|
|
@@ -5169,10 +5563,10 @@ const blockDom = {
|
|
|
5169
5563
|
};
|
|
5170
5564
|
const __info__ = {};
|
|
5171
5565
|
|
|
5172
|
-
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 };
|
|
5173
5567
|
|
|
5174
5568
|
|
|
5175
|
-
__info__.version = '2.0.0-
|
|
5176
|
-
__info__.date = '2022-
|
|
5177
|
-
__info__.hash = '
|
|
5569
|
+
__info__.version = '2.0.0-beta-6';
|
|
5570
|
+
__info__.date = '2022-04-11T09:01:42.605Z';
|
|
5571
|
+
__info__.hash = '41344ef';
|
|
5178
5572
|
__info__.url = 'https://github.com/odoo/owl';
|