@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.cjs.js
CHANGED
|
@@ -213,7 +213,8 @@ function updateClass(val, oldVal) {
|
|
|
213
213
|
}
|
|
214
214
|
function makePropSetter(name) {
|
|
215
215
|
return function setProp(value) {
|
|
216
|
-
|
|
216
|
+
// support 0, fallback to empty string for other falsy values
|
|
217
|
+
this[name] = value === 0 ? 0 : value || "";
|
|
217
218
|
};
|
|
218
219
|
}
|
|
219
220
|
function isProp(tag, key) {
|
|
@@ -267,10 +268,14 @@ function createElementHandler(evName, capture = false) {
|
|
|
267
268
|
this[eventKey] = data;
|
|
268
269
|
this.addEventListener(evName, listener, { capture });
|
|
269
270
|
}
|
|
271
|
+
function remove() {
|
|
272
|
+
delete this[eventKey];
|
|
273
|
+
this.removeEventListener(evName, listener, { capture });
|
|
274
|
+
}
|
|
270
275
|
function update(data) {
|
|
271
276
|
this[eventKey] = data;
|
|
272
277
|
}
|
|
273
|
-
return { setup, update };
|
|
278
|
+
return { setup, update, remove };
|
|
274
279
|
}
|
|
275
280
|
// Synthetic handler: a form of event delegation that allows placing only one
|
|
276
281
|
// listener per event type.
|
|
@@ -287,7 +292,10 @@ function createSyntheticHandler(evName, capture = false) {
|
|
|
287
292
|
_data[currentId] = data;
|
|
288
293
|
this[eventKey] = _data;
|
|
289
294
|
}
|
|
290
|
-
|
|
295
|
+
function remove() {
|
|
296
|
+
delete this[eventKey];
|
|
297
|
+
}
|
|
298
|
+
return { setup, update: setup, remove };
|
|
291
299
|
}
|
|
292
300
|
function nativeToSyntheticEvent(eventKey, event) {
|
|
293
301
|
let dom = event.target;
|
|
@@ -512,7 +520,7 @@ const characterDataProto = CharacterData.prototype;
|
|
|
512
520
|
const characterDataSetData = getDescriptor$1(characterDataProto, "data").set;
|
|
513
521
|
const nodeGetFirstChild = getDescriptor$1(nodeProto$2, "firstChild").get;
|
|
514
522
|
const nodeGetNextSibling = getDescriptor$1(nodeProto$2, "nextSibling").get;
|
|
515
|
-
const NO_OP
|
|
523
|
+
const NO_OP = () => { };
|
|
516
524
|
const cache$1 = {};
|
|
517
525
|
/**
|
|
518
526
|
* Compiling blocks is a multi-step process:
|
|
@@ -816,7 +824,7 @@ function updateCtx(ctx, tree) {
|
|
|
816
824
|
idx: info.idx,
|
|
817
825
|
refIdx: info.refIdx,
|
|
818
826
|
setData: makeRefSetter(index, ctx.refList),
|
|
819
|
-
updateData: NO_OP
|
|
827
|
+
updateData: NO_OP,
|
|
820
828
|
});
|
|
821
829
|
}
|
|
822
830
|
}
|
|
@@ -1282,6 +1290,75 @@ function html(str) {
|
|
|
1282
1290
|
return new VHtml(str);
|
|
1283
1291
|
}
|
|
1284
1292
|
|
|
1293
|
+
function createCatcher(eventsSpec) {
|
|
1294
|
+
let setupFns = [];
|
|
1295
|
+
let removeFns = [];
|
|
1296
|
+
for (let name in eventsSpec) {
|
|
1297
|
+
let index = eventsSpec[name];
|
|
1298
|
+
let { setup, remove } = createEventHandler(name);
|
|
1299
|
+
setupFns[index] = setup;
|
|
1300
|
+
removeFns[index] = remove;
|
|
1301
|
+
}
|
|
1302
|
+
let n = setupFns.length;
|
|
1303
|
+
class VCatcher {
|
|
1304
|
+
constructor(child, handlers) {
|
|
1305
|
+
this.afterNode = null;
|
|
1306
|
+
this.child = child;
|
|
1307
|
+
this.handlers = handlers;
|
|
1308
|
+
}
|
|
1309
|
+
mount(parent, afterNode) {
|
|
1310
|
+
this.parentEl = parent;
|
|
1311
|
+
this.afterNode = afterNode;
|
|
1312
|
+
this.child.mount(parent, afterNode);
|
|
1313
|
+
for (let i = 0; i < n; i++) {
|
|
1314
|
+
let origFn = this.handlers[i][0];
|
|
1315
|
+
const self = this;
|
|
1316
|
+
this.handlers[i][0] = function (ev) {
|
|
1317
|
+
const target = ev.target;
|
|
1318
|
+
let currentNode = self.child.firstNode();
|
|
1319
|
+
const afterNode = self.afterNode;
|
|
1320
|
+
while (currentNode !== afterNode) {
|
|
1321
|
+
if (currentNode.contains(target)) {
|
|
1322
|
+
return origFn.call(this, ev);
|
|
1323
|
+
}
|
|
1324
|
+
currentNode = currentNode.nextSibling;
|
|
1325
|
+
}
|
|
1326
|
+
};
|
|
1327
|
+
setupFns[i].call(parent, this.handlers[i]);
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
moveBefore(other, afterNode) {
|
|
1331
|
+
this.afterNode = null;
|
|
1332
|
+
this.child.moveBefore(other ? other.child : null, afterNode);
|
|
1333
|
+
}
|
|
1334
|
+
patch(other, withBeforeRemove) {
|
|
1335
|
+
if (this === other) {
|
|
1336
|
+
return;
|
|
1337
|
+
}
|
|
1338
|
+
this.handlers = other.handlers;
|
|
1339
|
+
this.child.patch(other.child, withBeforeRemove);
|
|
1340
|
+
}
|
|
1341
|
+
beforeRemove() {
|
|
1342
|
+
this.child.beforeRemove();
|
|
1343
|
+
}
|
|
1344
|
+
remove() {
|
|
1345
|
+
for (let i = 0; i < n; i++) {
|
|
1346
|
+
removeFns[i].call(this.parentEl);
|
|
1347
|
+
}
|
|
1348
|
+
this.child.remove();
|
|
1349
|
+
}
|
|
1350
|
+
firstNode() {
|
|
1351
|
+
return this.child.firstNode();
|
|
1352
|
+
}
|
|
1353
|
+
toString() {
|
|
1354
|
+
return this.child.toString();
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
return function (child, handlers) {
|
|
1358
|
+
return new VCatcher(child, handlers);
|
|
1359
|
+
};
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1285
1362
|
function mount$1(vnode, fixture, afterNode = null) {
|
|
1286
1363
|
vnode.mount(fixture, afterNode);
|
|
1287
1364
|
}
|
|
@@ -1295,143 +1372,464 @@ function remove(vnode, withBeforeRemove = false) {
|
|
|
1295
1372
|
vnode.remove();
|
|
1296
1373
|
}
|
|
1297
1374
|
|
|
1375
|
+
const mainEventHandler = (data, ev, currentTarget) => {
|
|
1376
|
+
const { data: _data, modifiers } = filterOutModifiersFromData(data);
|
|
1377
|
+
data = _data;
|
|
1378
|
+
let stopped = false;
|
|
1379
|
+
if (modifiers.length) {
|
|
1380
|
+
let selfMode = false;
|
|
1381
|
+
const isSelf = ev.target === currentTarget;
|
|
1382
|
+
for (const mod of modifiers) {
|
|
1383
|
+
switch (mod) {
|
|
1384
|
+
case "self":
|
|
1385
|
+
selfMode = true;
|
|
1386
|
+
if (isSelf) {
|
|
1387
|
+
continue;
|
|
1388
|
+
}
|
|
1389
|
+
else {
|
|
1390
|
+
return stopped;
|
|
1391
|
+
}
|
|
1392
|
+
case "prevent":
|
|
1393
|
+
if ((selfMode && isSelf) || !selfMode)
|
|
1394
|
+
ev.preventDefault();
|
|
1395
|
+
continue;
|
|
1396
|
+
case "stop":
|
|
1397
|
+
if ((selfMode && isSelf) || !selfMode)
|
|
1398
|
+
ev.stopPropagation();
|
|
1399
|
+
stopped = true;
|
|
1400
|
+
continue;
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
// If handler is empty, the array slot 0 will also be empty, and data will not have the property 0
|
|
1405
|
+
// We check this rather than data[0] being truthy (or typeof function) so that it crashes
|
|
1406
|
+
// as expected when there is a handler expression that evaluates to a falsy value
|
|
1407
|
+
if (Object.hasOwnProperty.call(data, 0)) {
|
|
1408
|
+
const handler = data[0];
|
|
1409
|
+
if (typeof handler !== "function") {
|
|
1410
|
+
throw new Error(`Invalid handler (expected a function, received: '${handler}')`);
|
|
1411
|
+
}
|
|
1412
|
+
let node = data[1] ? data[1].__owl__ : null;
|
|
1413
|
+
if (node ? node.status === 1 /* MOUNTED */ : true) {
|
|
1414
|
+
handler.call(node ? node.component : null, ev);
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
return stopped;
|
|
1418
|
+
};
|
|
1419
|
+
|
|
1420
|
+
// Allows to get the target of a Reactive (used for making a new Reactive from the underlying object)
|
|
1421
|
+
const TARGET = Symbol("Target");
|
|
1422
|
+
// Escape hatch to prevent reactivity system to turn something into a reactive
|
|
1423
|
+
const SKIP = Symbol("Skip");
|
|
1424
|
+
// Special key to subscribe to, to be notified of key creation/deletion
|
|
1425
|
+
const KEYCHANGES = Symbol("Key changes");
|
|
1426
|
+
const objectToString = Object.prototype.toString;
|
|
1427
|
+
const objectHasOwnProperty = Object.prototype.hasOwnProperty;
|
|
1428
|
+
const SUPPORTED_RAW_TYPES = new Set(["Object", "Array", "Set", "Map", "WeakMap"]);
|
|
1429
|
+
const COLLECTION_RAWTYPES = new Set(["Set", "Map", "WeakMap"]);
|
|
1298
1430
|
/**
|
|
1299
|
-
*
|
|
1431
|
+
* extract "RawType" from strings like "[object RawType]" => this lets us ignore
|
|
1432
|
+
* many native objects such as Promise (whose toString is [object Promise])
|
|
1433
|
+
* or Date ([object Date]), while also supporting collections without using
|
|
1434
|
+
* instanceof in a loop
|
|
1300
1435
|
*
|
|
1301
|
-
*
|
|
1436
|
+
* @param obj the object to check
|
|
1437
|
+
* @returns the raw type of the object
|
|
1302
1438
|
*/
|
|
1303
|
-
function
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1439
|
+
function rawType(obj) {
|
|
1440
|
+
return objectToString.call(obj).slice(8, -1);
|
|
1441
|
+
}
|
|
1442
|
+
/**
|
|
1443
|
+
* Checks whether a given value can be made into a reactive object.
|
|
1444
|
+
*
|
|
1445
|
+
* @param value the value to check
|
|
1446
|
+
* @returns whether the value can be made reactive
|
|
1447
|
+
*/
|
|
1448
|
+
function canBeMadeReactive(value) {
|
|
1449
|
+
if (typeof value !== "object") {
|
|
1450
|
+
return false;
|
|
1311
1451
|
}
|
|
1452
|
+
return SUPPORTED_RAW_TYPES.has(rawType(value));
|
|
1312
1453
|
}
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1454
|
+
/**
|
|
1455
|
+
* Creates a reactive from the given object/callback if possible and returns it,
|
|
1456
|
+
* returns the original object otherwise.
|
|
1457
|
+
*
|
|
1458
|
+
* @param value the value make reactive
|
|
1459
|
+
* @returns a reactive for the given object when possible, the original otherwise
|
|
1460
|
+
*/
|
|
1461
|
+
function possiblyReactive(val, cb) {
|
|
1462
|
+
return canBeMadeReactive(val) ? reactive(val, cb) : val;
|
|
1463
|
+
}
|
|
1464
|
+
/**
|
|
1465
|
+
* Mark an object or array so that it is ignored by the reactivity system
|
|
1466
|
+
*
|
|
1467
|
+
* @param value the value to mark
|
|
1468
|
+
* @returns the object itself
|
|
1469
|
+
*/
|
|
1470
|
+
function markRaw(value) {
|
|
1471
|
+
value[SKIP] = true;
|
|
1472
|
+
return value;
|
|
1473
|
+
}
|
|
1474
|
+
/**
|
|
1475
|
+
* Given a reactive objet, return the raw (non reactive) underlying object
|
|
1476
|
+
*
|
|
1477
|
+
* @param value a reactive value
|
|
1478
|
+
* @returns the underlying value
|
|
1479
|
+
*/
|
|
1480
|
+
function toRaw(value) {
|
|
1481
|
+
return value[TARGET] || value;
|
|
1482
|
+
}
|
|
1483
|
+
const targetToKeysToCallbacks = new WeakMap();
|
|
1484
|
+
/**
|
|
1485
|
+
* Observes a given key on a target with an callback. The callback will be
|
|
1486
|
+
* called when the given key changes on the target.
|
|
1487
|
+
*
|
|
1488
|
+
* @param target the target whose key should be observed
|
|
1489
|
+
* @param key the key to observe (or Symbol(KEYCHANGES) for key creation
|
|
1490
|
+
* or deletion)
|
|
1491
|
+
* @param callback the function to call when the key changes
|
|
1492
|
+
*/
|
|
1493
|
+
function observeTargetKey(target, key, callback) {
|
|
1494
|
+
if (!targetToKeysToCallbacks.get(target)) {
|
|
1495
|
+
targetToKeysToCallbacks.set(target, new Map());
|
|
1319
1496
|
}
|
|
1320
|
-
|
|
1497
|
+
const keyToCallbacks = targetToKeysToCallbacks.get(target);
|
|
1498
|
+
if (!keyToCallbacks.get(key)) {
|
|
1499
|
+
keyToCallbacks.set(key, new Set());
|
|
1500
|
+
}
|
|
1501
|
+
keyToCallbacks.get(key).add(callback);
|
|
1502
|
+
if (!callbacksToTargets.has(callback)) {
|
|
1503
|
+
callbacksToTargets.set(callback, new Set());
|
|
1504
|
+
}
|
|
1505
|
+
callbacksToTargets.get(callback).add(target);
|
|
1321
1506
|
}
|
|
1322
1507
|
/**
|
|
1323
|
-
*
|
|
1324
|
-
*
|
|
1325
|
-
*
|
|
1326
|
-
*
|
|
1508
|
+
* Notify Reactives that are observing a given target that a key has changed on
|
|
1509
|
+
* the target.
|
|
1510
|
+
*
|
|
1511
|
+
* @param target target whose Reactives should be notified that the target was
|
|
1512
|
+
* changed.
|
|
1513
|
+
* @param key the key that changed (or Symbol `KEYCHANGES` if a key was created
|
|
1514
|
+
* or deleted)
|
|
1327
1515
|
*/
|
|
1328
|
-
function
|
|
1329
|
-
const
|
|
1330
|
-
|
|
1331
|
-
: parent.constructor.components[name];
|
|
1332
|
-
if (!ComponentClass) {
|
|
1333
|
-
// this is an error, wrong component. We silently return here instead so the
|
|
1334
|
-
// error is triggered by the usual path ('component' function)
|
|
1516
|
+
function notifyReactives(target, key) {
|
|
1517
|
+
const keyToCallbacks = targetToKeysToCallbacks.get(target);
|
|
1518
|
+
if (!keyToCallbacks) {
|
|
1335
1519
|
return;
|
|
1336
1520
|
}
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
const allowAdditionalProps = "*" in propsDef;
|
|
1341
|
-
for (let propName in propsDef) {
|
|
1342
|
-
if (propName === "*") {
|
|
1343
|
-
continue;
|
|
1344
|
-
}
|
|
1345
|
-
const propDef = propsDef[propName];
|
|
1346
|
-
let isMandatory = !!propDef;
|
|
1347
|
-
if (typeof propDef === "object" && "optional" in propDef) {
|
|
1348
|
-
isMandatory = !propDef.optional;
|
|
1349
|
-
}
|
|
1350
|
-
if (isMandatory && propName in defaultProps) {
|
|
1351
|
-
throw new Error(`A default value cannot be defined for a mandatory prop (name: '${propName}', component: ${ComponentClass.name})`);
|
|
1352
|
-
}
|
|
1353
|
-
if (props[propName] === undefined) {
|
|
1354
|
-
if (isMandatory) {
|
|
1355
|
-
throw new Error(`Missing props '${propName}' (component '${ComponentClass.name}')`);
|
|
1356
|
-
}
|
|
1357
|
-
else {
|
|
1358
|
-
continue;
|
|
1359
|
-
}
|
|
1360
|
-
}
|
|
1361
|
-
let isValid;
|
|
1362
|
-
try {
|
|
1363
|
-
isValid = isValidProp(props[propName], propDef);
|
|
1364
|
-
}
|
|
1365
|
-
catch (e) {
|
|
1366
|
-
e.message = `Invalid prop '${propName}' in component ${ComponentClass.name} (${e.message})`;
|
|
1367
|
-
throw e;
|
|
1368
|
-
}
|
|
1369
|
-
if (!isValid) {
|
|
1370
|
-
throw new Error(`Invalid Prop '${propName}' in component '${ComponentClass.name}'`);
|
|
1371
|
-
}
|
|
1521
|
+
const callbacks = keyToCallbacks.get(key);
|
|
1522
|
+
if (!callbacks) {
|
|
1523
|
+
return;
|
|
1372
1524
|
}
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
}
|
|
1378
|
-
}
|
|
1525
|
+
// Loop on copy because clearReactivesForCallback will modify the set in place
|
|
1526
|
+
for (const callback of [...callbacks]) {
|
|
1527
|
+
clearReactivesForCallback(callback);
|
|
1528
|
+
callback();
|
|
1379
1529
|
}
|
|
1380
1530
|
}
|
|
1531
|
+
const callbacksToTargets = new WeakMap();
|
|
1381
1532
|
/**
|
|
1382
|
-
*
|
|
1533
|
+
* Clears all subscriptions of the Reactives associated with a given callback.
|
|
1534
|
+
*
|
|
1535
|
+
* @param callback the callback for which the reactives need to be cleared
|
|
1383
1536
|
*/
|
|
1384
|
-
function
|
|
1385
|
-
|
|
1386
|
-
|
|
1537
|
+
function clearReactivesForCallback(callback) {
|
|
1538
|
+
const targetsToClear = callbacksToTargets.get(callback);
|
|
1539
|
+
if (!targetsToClear) {
|
|
1540
|
+
return;
|
|
1387
1541
|
}
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
// So, even though 1 is not an instance of Number, we want to consider that
|
|
1393
|
-
// it is valid.
|
|
1394
|
-
if (typeof prop === "object") {
|
|
1395
|
-
return prop instanceof propDef;
|
|
1542
|
+
for (const target of targetsToClear) {
|
|
1543
|
+
const observedKeys = targetToKeysToCallbacks.get(target);
|
|
1544
|
+
if (!observedKeys) {
|
|
1545
|
+
continue;
|
|
1396
1546
|
}
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
else if (propDef instanceof Array) {
|
|
1400
|
-
// If this code is executed, this means that we want to check if a prop
|
|
1401
|
-
// matches at least one of its descriptor.
|
|
1402
|
-
let result = false;
|
|
1403
|
-
for (let i = 0, iLen = propDef.length; i < iLen; i++) {
|
|
1404
|
-
result = result || isValidProp(prop, propDef[i]);
|
|
1547
|
+
for (const callbacks of observedKeys.values()) {
|
|
1548
|
+
callbacks.delete(callback);
|
|
1405
1549
|
}
|
|
1406
|
-
return result;
|
|
1407
1550
|
}
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1551
|
+
targetsToClear.clear();
|
|
1552
|
+
}
|
|
1553
|
+
function getSubscriptions(callback) {
|
|
1554
|
+
const targets = callbacksToTargets.get(callback) || [];
|
|
1555
|
+
return [...targets].map((target) => {
|
|
1556
|
+
const keysToCallbacks = targetToKeysToCallbacks.get(target);
|
|
1557
|
+
return {
|
|
1558
|
+
target,
|
|
1559
|
+
keys: keysToCallbacks ? [...keysToCallbacks.keys()] : [],
|
|
1560
|
+
};
|
|
1561
|
+
});
|
|
1562
|
+
}
|
|
1563
|
+
const reactiveCache = new WeakMap();
|
|
1564
|
+
/**
|
|
1565
|
+
* Creates a reactive proxy for an object. Reading data on the reactive object
|
|
1566
|
+
* subscribes to changes to the data. Writing data on the object will cause the
|
|
1567
|
+
* notify callback to be called if there are suscriptions to that data. Nested
|
|
1568
|
+
* objects and arrays are automatically made reactive as well.
|
|
1569
|
+
*
|
|
1570
|
+
* Whenever you are notified of a change, all subscriptions are cleared, and if
|
|
1571
|
+
* you would like to be notified of any further changes, you should go read
|
|
1572
|
+
* the underlying data again. We assume that if you don't go read it again after
|
|
1573
|
+
* being notified, it means that you are no longer interested in that data.
|
|
1574
|
+
*
|
|
1575
|
+
* Subscriptions:
|
|
1576
|
+
* + Reading a property on an object will subscribe you to changes in the value
|
|
1577
|
+
* of that property.
|
|
1578
|
+
* + Accessing an object keys (eg with Object.keys or with `for..in`) will
|
|
1579
|
+
* subscribe you to the creation/deletion of keys. Checking the presence of a
|
|
1580
|
+
* key on the object with 'in' has the same effect.
|
|
1581
|
+
* - getOwnPropertyDescriptor does not currently subscribe you to the property.
|
|
1582
|
+
* This is a choice that was made because changing a key's value will trigger
|
|
1583
|
+
* this trap and we do not want to subscribe by writes. This also means that
|
|
1584
|
+
* Object.hasOwnProperty doesn't subscribe as it goes through this trap.
|
|
1585
|
+
*
|
|
1586
|
+
* @param target the object for which to create a reactive proxy
|
|
1587
|
+
* @param callback the function to call when an observed property of the
|
|
1588
|
+
* reactive has changed
|
|
1589
|
+
* @returns a proxy that tracks changes to it
|
|
1590
|
+
*/
|
|
1591
|
+
function reactive(target, callback = () => { }) {
|
|
1592
|
+
if (!canBeMadeReactive(target)) {
|
|
1593
|
+
throw new Error(`Cannot make the given value reactive`);
|
|
1411
1594
|
}
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
result = result && propDef.validate(prop);
|
|
1595
|
+
if (SKIP in target) {
|
|
1596
|
+
return target;
|
|
1415
1597
|
}
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
}
|
|
1598
|
+
const originalTarget = target[TARGET];
|
|
1599
|
+
if (originalTarget) {
|
|
1600
|
+
return reactive(originalTarget, callback);
|
|
1420
1601
|
}
|
|
1421
|
-
if (
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1602
|
+
if (!reactiveCache.has(target)) {
|
|
1603
|
+
reactiveCache.set(target, new Map());
|
|
1604
|
+
}
|
|
1605
|
+
const reactivesForTarget = reactiveCache.get(target);
|
|
1606
|
+
if (!reactivesForTarget.has(callback)) {
|
|
1607
|
+
const targetRawType = rawType(target);
|
|
1608
|
+
const handler = COLLECTION_RAWTYPES.has(targetRawType)
|
|
1609
|
+
? collectionsProxyHandler(target, callback, targetRawType)
|
|
1610
|
+
: basicProxyHandler(callback);
|
|
1611
|
+
const proxy = new Proxy(target, handler);
|
|
1612
|
+
reactivesForTarget.set(callback, proxy);
|
|
1613
|
+
}
|
|
1614
|
+
return reactivesForTarget.get(callback);
|
|
1615
|
+
}
|
|
1616
|
+
/**
|
|
1617
|
+
* Creates a basic proxy handler for regular objects and arrays.
|
|
1618
|
+
*
|
|
1619
|
+
* @param callback @see reactive
|
|
1620
|
+
* @returns a proxy handler object
|
|
1621
|
+
*/
|
|
1622
|
+
function basicProxyHandler(callback) {
|
|
1623
|
+
return {
|
|
1624
|
+
get(target, key, proxy) {
|
|
1625
|
+
if (key === TARGET) {
|
|
1626
|
+
return target;
|
|
1627
|
+
}
|
|
1628
|
+
// non-writable non-configurable properties cannot be made reactive
|
|
1629
|
+
const desc = Object.getOwnPropertyDescriptor(target, key);
|
|
1630
|
+
if (desc && !desc.writable && !desc.configurable) {
|
|
1631
|
+
return Reflect.get(target, key, proxy);
|
|
1632
|
+
}
|
|
1633
|
+
observeTargetKey(target, key, callback);
|
|
1634
|
+
return possiblyReactive(Reflect.get(target, key, proxy), callback);
|
|
1635
|
+
},
|
|
1636
|
+
set(target, key, value, proxy) {
|
|
1637
|
+
const isNewKey = !objectHasOwnProperty.call(target, key);
|
|
1638
|
+
const originalValue = Reflect.get(target, key, proxy);
|
|
1639
|
+
const ret = Reflect.set(target, key, value, proxy);
|
|
1640
|
+
if (isNewKey) {
|
|
1641
|
+
notifyReactives(target, KEYCHANGES);
|
|
1642
|
+
}
|
|
1643
|
+
// While Array length may trigger the set trap, it's not actually set by this
|
|
1644
|
+
// method but is updated behind the scenes, and the trap is not called with the
|
|
1645
|
+
// new value. We disable the "same-value-optimization" for it because of that.
|
|
1646
|
+
if (originalValue !== value || (Array.isArray(target) && key === "length")) {
|
|
1647
|
+
notifyReactives(target, key);
|
|
1431
1648
|
}
|
|
1649
|
+
return ret;
|
|
1650
|
+
},
|
|
1651
|
+
deleteProperty(target, key) {
|
|
1652
|
+
const ret = Reflect.deleteProperty(target, key);
|
|
1653
|
+
// TODO: only notify when something was actually deleted
|
|
1654
|
+
notifyReactives(target, KEYCHANGES);
|
|
1655
|
+
notifyReactives(target, key);
|
|
1656
|
+
return ret;
|
|
1657
|
+
},
|
|
1658
|
+
ownKeys(target) {
|
|
1659
|
+
observeTargetKey(target, KEYCHANGES, callback);
|
|
1660
|
+
return Reflect.ownKeys(target);
|
|
1661
|
+
},
|
|
1662
|
+
has(target, key) {
|
|
1663
|
+
// TODO: this observes all key changes instead of only the presence of the argument key
|
|
1664
|
+
// observing the key itself would observe value changes instead of presence changes
|
|
1665
|
+
// so we may need a finer grained system to distinguish observing value vs presence.
|
|
1666
|
+
observeTargetKey(target, KEYCHANGES, callback);
|
|
1667
|
+
return Reflect.has(target, key);
|
|
1668
|
+
},
|
|
1669
|
+
};
|
|
1670
|
+
}
|
|
1671
|
+
/**
|
|
1672
|
+
* Creates a function that will observe the key that is passed to it when called
|
|
1673
|
+
* and delegates to the underlying method.
|
|
1674
|
+
*
|
|
1675
|
+
* @param methodName name of the method to delegate to
|
|
1676
|
+
* @param target @see reactive
|
|
1677
|
+
* @param callback @see reactive
|
|
1678
|
+
*/
|
|
1679
|
+
function makeKeyObserver(methodName, target, callback) {
|
|
1680
|
+
return (key) => {
|
|
1681
|
+
key = toRaw(key);
|
|
1682
|
+
observeTargetKey(target, key, callback);
|
|
1683
|
+
return possiblyReactive(target[methodName](key), callback);
|
|
1684
|
+
};
|
|
1685
|
+
}
|
|
1686
|
+
/**
|
|
1687
|
+
* Creates an iterable that will delegate to the underlying iteration method and
|
|
1688
|
+
* observe keys as necessary.
|
|
1689
|
+
*
|
|
1690
|
+
* @param methodName name of the method to delegate to
|
|
1691
|
+
* @param target @see reactive
|
|
1692
|
+
* @param callback @see reactive
|
|
1693
|
+
*/
|
|
1694
|
+
function makeIteratorObserver(methodName, target, callback) {
|
|
1695
|
+
return function* () {
|
|
1696
|
+
observeTargetKey(target, KEYCHANGES, callback);
|
|
1697
|
+
const keys = target.keys();
|
|
1698
|
+
for (const item of target[methodName]()) {
|
|
1699
|
+
const key = keys.next().value;
|
|
1700
|
+
observeTargetKey(target, key, callback);
|
|
1701
|
+
yield possiblyReactive(item, callback);
|
|
1432
1702
|
}
|
|
1433
|
-
}
|
|
1434
|
-
|
|
1703
|
+
};
|
|
1704
|
+
}
|
|
1705
|
+
/**
|
|
1706
|
+
* Creates a forEach function that will delegate to forEach on the underlying
|
|
1707
|
+
* collection while observing key changes, and keys as they're iterated over,
|
|
1708
|
+
* and making the passed keys/values reactive.
|
|
1709
|
+
*
|
|
1710
|
+
* @param target @see reactive
|
|
1711
|
+
* @param callback @see reactive
|
|
1712
|
+
*/
|
|
1713
|
+
function makeForEachObserver(target, callback) {
|
|
1714
|
+
return function forEach(forEachCb, thisArg) {
|
|
1715
|
+
observeTargetKey(target, KEYCHANGES, callback);
|
|
1716
|
+
target.forEach(function (val, key, targetObj) {
|
|
1717
|
+
observeTargetKey(target, key, callback);
|
|
1718
|
+
forEachCb.call(thisArg, possiblyReactive(val, callback), possiblyReactive(key, callback), possiblyReactive(targetObj, callback));
|
|
1719
|
+
}, thisArg);
|
|
1720
|
+
};
|
|
1721
|
+
}
|
|
1722
|
+
/**
|
|
1723
|
+
* Creates a function that will delegate to an underlying method, and check if
|
|
1724
|
+
* that method has modified the presence or value of a key, and notify the
|
|
1725
|
+
* reactives appropriately.
|
|
1726
|
+
*
|
|
1727
|
+
* @param setterName name of the method to delegate to
|
|
1728
|
+
* @param getterName name of the method which should be used to retrieve the
|
|
1729
|
+
* value before calling the delegate method for comparison purposes
|
|
1730
|
+
* @param target @see reactive
|
|
1731
|
+
*/
|
|
1732
|
+
function delegateAndNotify(setterName, getterName, target) {
|
|
1733
|
+
return (key, value) => {
|
|
1734
|
+
key = toRaw(key);
|
|
1735
|
+
const hadKey = target.has(key);
|
|
1736
|
+
const originalValue = target[getterName](key);
|
|
1737
|
+
const ret = target[setterName](key, value);
|
|
1738
|
+
const hasKey = target.has(key);
|
|
1739
|
+
if (hadKey !== hasKey) {
|
|
1740
|
+
notifyReactives(target, KEYCHANGES);
|
|
1741
|
+
}
|
|
1742
|
+
if (originalValue !== value) {
|
|
1743
|
+
notifyReactives(target, key);
|
|
1744
|
+
}
|
|
1745
|
+
return ret;
|
|
1746
|
+
};
|
|
1747
|
+
}
|
|
1748
|
+
/**
|
|
1749
|
+
* Creates a function that will clear the underlying collection and notify that
|
|
1750
|
+
* the keys of the collection have changed.
|
|
1751
|
+
*
|
|
1752
|
+
* @param target @see reactive
|
|
1753
|
+
*/
|
|
1754
|
+
function makeClearNotifier(target) {
|
|
1755
|
+
return () => {
|
|
1756
|
+
const allKeys = [...target.keys()];
|
|
1757
|
+
target.clear();
|
|
1758
|
+
notifyReactives(target, KEYCHANGES);
|
|
1759
|
+
for (const key of allKeys) {
|
|
1760
|
+
notifyReactives(target, key);
|
|
1761
|
+
}
|
|
1762
|
+
};
|
|
1763
|
+
}
|
|
1764
|
+
/**
|
|
1765
|
+
* Maps raw type of an object to an object containing functions that can be used
|
|
1766
|
+
* to build an appropritate proxy handler for that raw type. Eg: when making a
|
|
1767
|
+
* reactive set, calling the has method should mark the key that is being
|
|
1768
|
+
* retrieved as observed, and calling the add or delete method should notify the
|
|
1769
|
+
* reactives that the key which is being added or deleted has been modified.
|
|
1770
|
+
*/
|
|
1771
|
+
const rawTypeToFuncHandlers = {
|
|
1772
|
+
Set: (target, callback) => ({
|
|
1773
|
+
has: makeKeyObserver("has", target, callback),
|
|
1774
|
+
add: delegateAndNotify("add", "has", target),
|
|
1775
|
+
delete: delegateAndNotify("delete", "has", target),
|
|
1776
|
+
keys: makeIteratorObserver("keys", target, callback),
|
|
1777
|
+
values: makeIteratorObserver("values", target, callback),
|
|
1778
|
+
entries: makeIteratorObserver("entries", target, callback),
|
|
1779
|
+
[Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
|
|
1780
|
+
forEach: makeForEachObserver(target, callback),
|
|
1781
|
+
clear: makeClearNotifier(target),
|
|
1782
|
+
get size() {
|
|
1783
|
+
observeTargetKey(target, KEYCHANGES, callback);
|
|
1784
|
+
return target.size;
|
|
1785
|
+
},
|
|
1786
|
+
}),
|
|
1787
|
+
Map: (target, callback) => ({
|
|
1788
|
+
has: makeKeyObserver("has", target, callback),
|
|
1789
|
+
get: makeKeyObserver("get", target, callback),
|
|
1790
|
+
set: delegateAndNotify("set", "get", target),
|
|
1791
|
+
delete: delegateAndNotify("delete", "has", target),
|
|
1792
|
+
keys: makeIteratorObserver("keys", target, callback),
|
|
1793
|
+
values: makeIteratorObserver("values", target, callback),
|
|
1794
|
+
entries: makeIteratorObserver("entries", target, callback),
|
|
1795
|
+
[Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
|
|
1796
|
+
forEach: makeForEachObserver(target, callback),
|
|
1797
|
+
clear: makeClearNotifier(target),
|
|
1798
|
+
get size() {
|
|
1799
|
+
observeTargetKey(target, KEYCHANGES, callback);
|
|
1800
|
+
return target.size;
|
|
1801
|
+
},
|
|
1802
|
+
}),
|
|
1803
|
+
WeakMap: (target, callback) => ({
|
|
1804
|
+
has: makeKeyObserver("has", target, callback),
|
|
1805
|
+
get: makeKeyObserver("get", target, callback),
|
|
1806
|
+
set: delegateAndNotify("set", "get", target),
|
|
1807
|
+
delete: delegateAndNotify("delete", "has", target),
|
|
1808
|
+
}),
|
|
1809
|
+
};
|
|
1810
|
+
/**
|
|
1811
|
+
* Creates a proxy handler for collections (Set/Map/WeakMap)
|
|
1812
|
+
*
|
|
1813
|
+
* @param callback @see reactive
|
|
1814
|
+
* @param target @see reactive
|
|
1815
|
+
* @returns a proxy handler object
|
|
1816
|
+
*/
|
|
1817
|
+
function collectionsProxyHandler(target, callback, targetRawType) {
|
|
1818
|
+
// TODO: if performance is an issue we can create the special handlers lazily when each
|
|
1819
|
+
// property is read.
|
|
1820
|
+
const specialHandlers = rawTypeToFuncHandlers[targetRawType](target, callback);
|
|
1821
|
+
return Object.assign(basicProxyHandler(callback), {
|
|
1822
|
+
get(target, key) {
|
|
1823
|
+
if (key === TARGET) {
|
|
1824
|
+
return target;
|
|
1825
|
+
}
|
|
1826
|
+
if (objectHasOwnProperty.call(specialHandlers, key)) {
|
|
1827
|
+
return specialHandlers[key];
|
|
1828
|
+
}
|
|
1829
|
+
observeTargetKey(target, key, callback);
|
|
1830
|
+
return possiblyReactive(target[key], callback);
|
|
1831
|
+
},
|
|
1832
|
+
});
|
|
1435
1833
|
}
|
|
1436
1834
|
|
|
1437
1835
|
/**
|
|
@@ -1449,11 +1847,13 @@ function batched(callback) {
|
|
|
1449
1847
|
await Promise.resolve();
|
|
1450
1848
|
if (!called) {
|
|
1451
1849
|
called = true;
|
|
1452
|
-
callback();
|
|
1453
1850
|
// wait for all calls in this microtick to fall through before resetting "called"
|
|
1454
|
-
// so that only the first call to the batched function calls the original callback
|
|
1455
|
-
|
|
1456
|
-
called
|
|
1851
|
+
// so that only the first call to the batched function calls the original callback.
|
|
1852
|
+
// Schedule this before calling the callback so that calls to the batched function
|
|
1853
|
+
// within the callback will proceed only after resetting called to false, and have
|
|
1854
|
+
// a chance to execute the callback again
|
|
1855
|
+
Promise.resolve().then(() => (called = false));
|
|
1856
|
+
callback();
|
|
1457
1857
|
}
|
|
1458
1858
|
};
|
|
1459
1859
|
}
|
|
@@ -1500,562 +1900,206 @@ class Markup extends String {
|
|
|
1500
1900
|
*/
|
|
1501
1901
|
function markup(value) {
|
|
1502
1902
|
return new Markup(value);
|
|
1503
|
-
}
|
|
1504
|
-
|
|
1505
|
-
// Allows to get the target of a Reactive (used for making a new Reactive from the underlying object)
|
|
1506
|
-
const TARGET = Symbol("Target");
|
|
1507
|
-
// Escape hatch to prevent reactivity system to turn something into a reactive
|
|
1508
|
-
const SKIP = Symbol("Skip");
|
|
1509
|
-
// Special key to subscribe to, to be notified of key creation/deletion
|
|
1510
|
-
const KEYCHANGES = Symbol("Key changes");
|
|
1511
|
-
const objectToString = Object.prototype.toString;
|
|
1512
|
-
/**
|
|
1513
|
-
* Checks whether a given value can be made into a reactive object.
|
|
1514
|
-
*
|
|
1515
|
-
* @param value the value to check
|
|
1516
|
-
* @returns whether the value can be made reactive
|
|
1517
|
-
*/
|
|
1518
|
-
function canBeMadeReactive(value) {
|
|
1519
|
-
if (typeof value !== "object") {
|
|
1520
|
-
return false;
|
|
1521
|
-
}
|
|
1522
|
-
// extract "RawType" from strings like "[object RawType]" => this lets us
|
|
1523
|
-
// ignore many native objects such as Promise (whose toString is [object Promise])
|
|
1524
|
-
// or Date ([object Date]).
|
|
1525
|
-
const rawType = objectToString.call(value).slice(8, -1);
|
|
1526
|
-
return rawType === "Object" || rawType === "Array";
|
|
1527
|
-
}
|
|
1528
|
-
/**
|
|
1529
|
-
* Mark an object or array so that it is ignored by the reactivity system
|
|
1530
|
-
*
|
|
1531
|
-
* @param value the value to mark
|
|
1532
|
-
* @returns the object itself
|
|
1533
|
-
*/
|
|
1534
|
-
function markRaw(value) {
|
|
1535
|
-
value[SKIP] = true;
|
|
1536
|
-
return value;
|
|
1537
1903
|
}
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1904
|
+
// -----------------------------------------------------------------------------
|
|
1905
|
+
// xml tag helper
|
|
1906
|
+
// -----------------------------------------------------------------------------
|
|
1907
|
+
const globalTemplates = {};
|
|
1908
|
+
function xml(...args) {
|
|
1909
|
+
const name = `__template__${xml.nextId++}`;
|
|
1910
|
+
const value = String.raw(...args);
|
|
1911
|
+
globalTemplates[name] = value;
|
|
1912
|
+
return name;
|
|
1546
1913
|
}
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
* @param callback the function to call when the key changes
|
|
1556
|
-
*/
|
|
1557
|
-
function observeTargetKey(target, key, callback) {
|
|
1558
|
-
if (!targetToKeysToCallbacks.get(target)) {
|
|
1559
|
-
targetToKeysToCallbacks.set(target, new Map());
|
|
1914
|
+
xml.nextId = 1;
|
|
1915
|
+
|
|
1916
|
+
// Maps fibers to thrown errors
|
|
1917
|
+
const fibersInError = new WeakMap();
|
|
1918
|
+
const nodeErrorHandlers = new WeakMap();
|
|
1919
|
+
function _handleError(node, error) {
|
|
1920
|
+
if (!node) {
|
|
1921
|
+
return false;
|
|
1560
1922
|
}
|
|
1561
|
-
const
|
|
1562
|
-
if (
|
|
1563
|
-
|
|
1923
|
+
const fiber = node.fiber;
|
|
1924
|
+
if (fiber) {
|
|
1925
|
+
fibersInError.set(fiber, error);
|
|
1564
1926
|
}
|
|
1565
|
-
|
|
1566
|
-
if (
|
|
1567
|
-
|
|
1927
|
+
const errorHandlers = nodeErrorHandlers.get(node);
|
|
1928
|
+
if (errorHandlers) {
|
|
1929
|
+
let handled = false;
|
|
1930
|
+
// execute in the opposite order
|
|
1931
|
+
for (let i = errorHandlers.length - 1; i >= 0; i--) {
|
|
1932
|
+
try {
|
|
1933
|
+
errorHandlers[i](error);
|
|
1934
|
+
handled = true;
|
|
1935
|
+
break;
|
|
1936
|
+
}
|
|
1937
|
+
catch (e) {
|
|
1938
|
+
error = e;
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
if (handled) {
|
|
1942
|
+
return true;
|
|
1943
|
+
}
|
|
1568
1944
|
}
|
|
1569
|
-
|
|
1945
|
+
return _handleError(node.parent, error);
|
|
1570
1946
|
}
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1947
|
+
function handleError(params) {
|
|
1948
|
+
const error = params.error;
|
|
1949
|
+
const node = "node" in params ? params.node : params.fiber.node;
|
|
1950
|
+
const fiber = "fiber" in params ? params.fiber : node.fiber;
|
|
1951
|
+
// resets the fibers on components if possible. This is important so that
|
|
1952
|
+
// new renderings can be properly included in the initial one, if any.
|
|
1953
|
+
let current = fiber;
|
|
1954
|
+
do {
|
|
1955
|
+
current.node.fiber = current;
|
|
1956
|
+
current = current.parent;
|
|
1957
|
+
} while (current);
|
|
1958
|
+
fibersInError.set(fiber.root, error);
|
|
1959
|
+
const handled = _handleError(node, error);
|
|
1960
|
+
if (!handled) {
|
|
1961
|
+
console.warn(`[Owl] Unhandled error. Destroying the root component`);
|
|
1962
|
+
try {
|
|
1963
|
+
node.app.destroy();
|
|
1964
|
+
}
|
|
1965
|
+
catch (e) {
|
|
1966
|
+
console.error(e);
|
|
1967
|
+
}
|
|
1588
1968
|
}
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
function makeChildFiber(node, parent) {
|
|
1972
|
+
let current = node.fiber;
|
|
1973
|
+
if (current) {
|
|
1974
|
+
cancelFibers(current.children);
|
|
1975
|
+
current.root = null;
|
|
1593
1976
|
}
|
|
1977
|
+
return new Fiber(node, parent);
|
|
1594
1978
|
}
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
if (
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
callbacks.delete(callback);
|
|
1979
|
+
function makeRootFiber(node) {
|
|
1980
|
+
let current = node.fiber;
|
|
1981
|
+
if (current) {
|
|
1982
|
+
let root = current.root;
|
|
1983
|
+
// lock root fiber because canceling children fibers may destroy components,
|
|
1984
|
+
// which means any arbitrary code can be run in onWillDestroy, which may
|
|
1985
|
+
// trigger new renderings
|
|
1986
|
+
root.locked = true;
|
|
1987
|
+
root.setCounter(root.counter + 1 - cancelFibers(current.children));
|
|
1988
|
+
root.locked = false;
|
|
1989
|
+
current.children = [];
|
|
1990
|
+
current.childrenMap = {};
|
|
1991
|
+
current.bdom = null;
|
|
1992
|
+
if (fibersInError.has(current)) {
|
|
1993
|
+
fibersInError.delete(current);
|
|
1994
|
+
fibersInError.delete(root);
|
|
1995
|
+
current.appliedToDom = false;
|
|
1613
1996
|
}
|
|
1997
|
+
return current;
|
|
1614
1998
|
}
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
/**
|
|
1619
|
-
* Creates a reactive proxy for an object. Reading data on the reactive object
|
|
1620
|
-
* subscribes to changes to the data. Writing data on the object will cause the
|
|
1621
|
-
* notify callback to be called if there are suscriptions to that data. Nested
|
|
1622
|
-
* objects and arrays are automatically made reactive as well.
|
|
1623
|
-
*
|
|
1624
|
-
* Whenever you are notified of a change, all subscriptions are cleared, and if
|
|
1625
|
-
* you would like to be notified of any further changes, you should go read
|
|
1626
|
-
* the underlying data again. We assume that if you don't go read it again after
|
|
1627
|
-
* being notified, it means that you are no longer interested in that data.
|
|
1628
|
-
*
|
|
1629
|
-
* Subscriptions:
|
|
1630
|
-
* + Reading a property on an object will subscribe you to changes in the value
|
|
1631
|
-
* of that property.
|
|
1632
|
-
* + Accessing an object keys (eg with Object.keys or with `for..in`) will
|
|
1633
|
-
* subscribe you to the creation/deletion of keys. Checking the presence of a
|
|
1634
|
-
* key on the object with 'in' has the same effect.
|
|
1635
|
-
* - getOwnPropertyDescriptor does not currently subscribe you to the property.
|
|
1636
|
-
* This is a choice that was made because changing a key's value will trigger
|
|
1637
|
-
* this trap and we do not want to subscribe by writes. This also means that
|
|
1638
|
-
* Object.hasOwnProperty doesn't subscribe as it goes through this trap.
|
|
1639
|
-
*
|
|
1640
|
-
* @param target the object for which to create a reactive proxy
|
|
1641
|
-
* @param callback the function to call when an observed property of the
|
|
1642
|
-
* reactive has changed
|
|
1643
|
-
* @returns a proxy that tracks changes to it
|
|
1644
|
-
*/
|
|
1645
|
-
function reactive(target, callback = () => { }) {
|
|
1646
|
-
if (!canBeMadeReactive(target)) {
|
|
1647
|
-
throw new Error(`Cannot make the given value reactive`);
|
|
1648
|
-
}
|
|
1649
|
-
if (SKIP in target) {
|
|
1650
|
-
return target;
|
|
1651
|
-
}
|
|
1652
|
-
const originalTarget = target[TARGET];
|
|
1653
|
-
if (originalTarget) {
|
|
1654
|
-
return reactive(originalTarget, callback);
|
|
1655
|
-
}
|
|
1656
|
-
if (!reactiveCache.has(target)) {
|
|
1657
|
-
reactiveCache.set(target, new Map());
|
|
1999
|
+
const fiber = new RootFiber(node, null);
|
|
2000
|
+
if (node.willPatch.length) {
|
|
2001
|
+
fiber.willPatch.push(fiber);
|
|
1658
2002
|
}
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
const proxy = new Proxy(target, {
|
|
1662
|
-
get(target, key, proxy) {
|
|
1663
|
-
if (key === TARGET) {
|
|
1664
|
-
return target;
|
|
1665
|
-
}
|
|
1666
|
-
observeTargetKey(target, key, callback);
|
|
1667
|
-
const value = Reflect.get(target, key, proxy);
|
|
1668
|
-
if (!canBeMadeReactive(value)) {
|
|
1669
|
-
return value;
|
|
1670
|
-
}
|
|
1671
|
-
return reactive(value, callback);
|
|
1672
|
-
},
|
|
1673
|
-
set(target, key, value, proxy) {
|
|
1674
|
-
const isNewKey = !Object.hasOwnProperty.call(target, key);
|
|
1675
|
-
const originalValue = Reflect.get(target, key, proxy);
|
|
1676
|
-
const ret = Reflect.set(target, key, value, proxy);
|
|
1677
|
-
if (isNewKey) {
|
|
1678
|
-
notifyReactives(target, KEYCHANGES);
|
|
1679
|
-
}
|
|
1680
|
-
// While Array length may trigger the set trap, it's not actually set by this
|
|
1681
|
-
// method but is updated behind the scenes, and the trap is not called with the
|
|
1682
|
-
// new value. We disable the "same-value-optimization" for it because of that.
|
|
1683
|
-
if (originalValue !== value || (Array.isArray(target) && key === "length")) {
|
|
1684
|
-
notifyReactives(target, key);
|
|
1685
|
-
}
|
|
1686
|
-
return ret;
|
|
1687
|
-
},
|
|
1688
|
-
deleteProperty(target, key) {
|
|
1689
|
-
const ret = Reflect.deleteProperty(target, key);
|
|
1690
|
-
notifyReactives(target, KEYCHANGES);
|
|
1691
|
-
notifyReactives(target, key);
|
|
1692
|
-
return ret;
|
|
1693
|
-
},
|
|
1694
|
-
ownKeys(target) {
|
|
1695
|
-
observeTargetKey(target, KEYCHANGES, callback);
|
|
1696
|
-
return Reflect.ownKeys(target);
|
|
1697
|
-
},
|
|
1698
|
-
has(target, key) {
|
|
1699
|
-
// TODO: this observes all key changes instead of only the presence of the argument key
|
|
1700
|
-
observeTargetKey(target, KEYCHANGES, callback);
|
|
1701
|
-
return Reflect.has(target, key);
|
|
1702
|
-
},
|
|
1703
|
-
});
|
|
1704
|
-
reactivesForTarget.set(callback, proxy);
|
|
2003
|
+
if (node.patched.length) {
|
|
2004
|
+
fiber.patched.push(fiber);
|
|
1705
2005
|
}
|
|
1706
|
-
return
|
|
1707
|
-
}
|
|
1708
|
-
|
|
2006
|
+
return fiber;
|
|
2007
|
+
}
|
|
1709
2008
|
/**
|
|
1710
|
-
*
|
|
1711
|
-
* to perform various useful tasks in the compiled code.
|
|
2009
|
+
* @returns number of not-yet rendered fibers cancelled
|
|
1712
2010
|
*/
|
|
1713
|
-
function
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
child1 = dynamic ? toggler(name, slotBDom) : slotBDom;
|
|
2011
|
+
function cancelFibers(fibers) {
|
|
2012
|
+
let result = 0;
|
|
2013
|
+
for (let fiber of fibers) {
|
|
2014
|
+
let node = fiber.node;
|
|
2015
|
+
if (node.status === 0 /* NEW */) {
|
|
2016
|
+
node.destroy();
|
|
2017
|
+
}
|
|
2018
|
+
node.fiber = null;
|
|
2019
|
+
if (fiber.bdom) {
|
|
2020
|
+
// if fiber has been rendered, this means that the component props have
|
|
2021
|
+
// been updated. however, this fiber will not be patched to the dom, so
|
|
2022
|
+
// it could happen that the next render compare the current props with
|
|
2023
|
+
// the same props, and skip the render completely. With the next line,
|
|
2024
|
+
// we kindly request the component code to force a render, so it works as
|
|
2025
|
+
// expected.
|
|
2026
|
+
node.forceNextRender = true;
|
|
1730
2027
|
}
|
|
1731
2028
|
else {
|
|
1732
|
-
|
|
2029
|
+
result++;
|
|
1733
2030
|
}
|
|
1734
|
-
|
|
1735
|
-
}
|
|
1736
|
-
return slotBDom || text("");
|
|
1737
|
-
}
|
|
1738
|
-
function capture(ctx) {
|
|
1739
|
-
const component = ctx.__owl__.component;
|
|
1740
|
-
const result = Object.create(component);
|
|
1741
|
-
for (let k in ctx) {
|
|
1742
|
-
result[k] = ctx[k];
|
|
2031
|
+
result += cancelFibers(fiber.children);
|
|
1743
2032
|
}
|
|
1744
2033
|
return result;
|
|
1745
2034
|
}
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
else {
|
|
1762
|
-
throw new Error("Invalid loop expression");
|
|
1763
|
-
}
|
|
1764
|
-
const n = values.length;
|
|
1765
|
-
return [keys, values, n, new Array(n)];
|
|
1766
|
-
}
|
|
1767
|
-
const isBoundary = Symbol("isBoundary");
|
|
1768
|
-
function setContextValue(ctx, key, value) {
|
|
1769
|
-
const ctx0 = ctx;
|
|
1770
|
-
while (!ctx.hasOwnProperty(key) && !ctx.hasOwnProperty(isBoundary)) {
|
|
1771
|
-
const newCtx = ctx.__proto__;
|
|
1772
|
-
if (!newCtx) {
|
|
1773
|
-
ctx = ctx0;
|
|
1774
|
-
break;
|
|
2035
|
+
class Fiber {
|
|
2036
|
+
constructor(node, parent) {
|
|
2037
|
+
this.bdom = null;
|
|
2038
|
+
this.children = [];
|
|
2039
|
+
this.appliedToDom = false;
|
|
2040
|
+
this.deep = false;
|
|
2041
|
+
this.childrenMap = {};
|
|
2042
|
+
this.node = node;
|
|
2043
|
+
this.parent = parent;
|
|
2044
|
+
if (parent) {
|
|
2045
|
+
this.deep = parent.deep;
|
|
2046
|
+
const root = parent.root;
|
|
2047
|
+
root.setCounter(root.counter + 1);
|
|
2048
|
+
this.root = root;
|
|
2049
|
+
parent.children.push(this);
|
|
1775
2050
|
}
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
ctx[key] = value;
|
|
1779
|
-
}
|
|
1780
|
-
function toNumber(val) {
|
|
1781
|
-
const n = parseFloat(val);
|
|
1782
|
-
return isNaN(n) ? val : n;
|
|
1783
|
-
}
|
|
1784
|
-
function shallowEqual$1(l1, l2) {
|
|
1785
|
-
for (let i = 0, l = l1.length; i < l; i++) {
|
|
1786
|
-
if (l1[i] !== l2[i]) {
|
|
1787
|
-
return false;
|
|
2051
|
+
else {
|
|
2052
|
+
this.root = this;
|
|
1788
2053
|
}
|
|
1789
2054
|
}
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
2055
|
+
render() {
|
|
2056
|
+
// if some parent has a fiber => register in followup
|
|
2057
|
+
let prev = this.root.node;
|
|
2058
|
+
let scheduler = prev.app.scheduler;
|
|
2059
|
+
let current = prev.parent;
|
|
2060
|
+
while (current) {
|
|
2061
|
+
if (current.fiber) {
|
|
2062
|
+
let root = current.fiber.root;
|
|
2063
|
+
if (root.counter === 0 && prev.parentKey in current.fiber.childrenMap) {
|
|
2064
|
+
current = root.node;
|
|
2065
|
+
}
|
|
2066
|
+
else {
|
|
2067
|
+
scheduler.delayedRenders.push(this);
|
|
2068
|
+
return;
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
prev = current;
|
|
2072
|
+
current = current.parent;
|
|
2073
|
+
}
|
|
2074
|
+
// there are no current rendering from above => we can render
|
|
2075
|
+
this._render();
|
|
1800
2076
|
}
|
|
1801
|
-
|
|
1802
|
-
|
|
2077
|
+
_render() {
|
|
2078
|
+
const node = this.node;
|
|
2079
|
+
const root = this.root;
|
|
2080
|
+
if (root) {
|
|
2081
|
+
try {
|
|
2082
|
+
this.bdom = true;
|
|
2083
|
+
this.bdom = node.renderFn();
|
|
2084
|
+
}
|
|
2085
|
+
catch (e) {
|
|
2086
|
+
handleError({ node, error: e });
|
|
2087
|
+
}
|
|
2088
|
+
root.setCounter(root.counter - 1);
|
|
2089
|
+
}
|
|
1803
2090
|
}
|
|
1804
2091
|
}
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
block = html(value);
|
|
1817
|
-
}
|
|
1818
|
-
else if (value instanceof LazyValue) {
|
|
1819
|
-
safeKey = `lazy_value`;
|
|
1820
|
-
block = value.evaluate();
|
|
1821
|
-
}
|
|
1822
|
-
else if (value instanceof String || typeof value === "string") {
|
|
1823
|
-
safeKey = "string_unsafe";
|
|
1824
|
-
block = text(value);
|
|
1825
|
-
}
|
|
1826
|
-
else {
|
|
1827
|
-
// Assuming it is a block
|
|
1828
|
-
safeKey = "block_safe";
|
|
1829
|
-
block = value;
|
|
1830
|
-
}
|
|
1831
|
-
return toggler(safeKey, block);
|
|
1832
|
-
}
|
|
1833
|
-
let boundFunctions = new WeakMap();
|
|
1834
|
-
function bind(ctx, fn) {
|
|
1835
|
-
let component = ctx.__owl__.component;
|
|
1836
|
-
let boundFnMap = boundFunctions.get(component);
|
|
1837
|
-
if (!boundFnMap) {
|
|
1838
|
-
boundFnMap = new WeakMap();
|
|
1839
|
-
boundFunctions.set(component, boundFnMap);
|
|
1840
|
-
}
|
|
1841
|
-
let boundFn = boundFnMap.get(fn);
|
|
1842
|
-
if (!boundFn) {
|
|
1843
|
-
boundFn = fn.bind(component);
|
|
1844
|
-
boundFnMap.set(fn, boundFn);
|
|
1845
|
-
}
|
|
1846
|
-
return boundFn;
|
|
1847
|
-
}
|
|
1848
|
-
function multiRefSetter(refs, name) {
|
|
1849
|
-
let count = 0;
|
|
1850
|
-
return (el) => {
|
|
1851
|
-
if (el) {
|
|
1852
|
-
count++;
|
|
1853
|
-
if (count > 1) {
|
|
1854
|
-
throw new Error("Cannot have 2 elements with same ref name at the same time");
|
|
1855
|
-
}
|
|
1856
|
-
}
|
|
1857
|
-
if (count === 0 || el) {
|
|
1858
|
-
refs[name] = el;
|
|
1859
|
-
}
|
|
1860
|
-
};
|
|
1861
|
-
}
|
|
1862
|
-
const UTILS = {
|
|
1863
|
-
withDefault,
|
|
1864
|
-
zero: Symbol("zero"),
|
|
1865
|
-
isBoundary,
|
|
1866
|
-
callSlot,
|
|
1867
|
-
capture,
|
|
1868
|
-
withKey,
|
|
1869
|
-
prepareList,
|
|
1870
|
-
setContextValue,
|
|
1871
|
-
multiRefSetter,
|
|
1872
|
-
shallowEqual: shallowEqual$1,
|
|
1873
|
-
toNumber,
|
|
1874
|
-
validateProps,
|
|
1875
|
-
LazyValue,
|
|
1876
|
-
safeOutput,
|
|
1877
|
-
bind,
|
|
1878
|
-
};
|
|
1879
|
-
|
|
1880
|
-
const mainEventHandler = (data, ev, currentTarget) => {
|
|
1881
|
-
const { data: _data, modifiers } = filterOutModifiersFromData(data);
|
|
1882
|
-
data = _data;
|
|
1883
|
-
let stopped = false;
|
|
1884
|
-
if (modifiers.length) {
|
|
1885
|
-
let selfMode = false;
|
|
1886
|
-
const isSelf = ev.target === currentTarget;
|
|
1887
|
-
for (const mod of modifiers) {
|
|
1888
|
-
switch (mod) {
|
|
1889
|
-
case "self":
|
|
1890
|
-
selfMode = true;
|
|
1891
|
-
if (isSelf) {
|
|
1892
|
-
continue;
|
|
1893
|
-
}
|
|
1894
|
-
else {
|
|
1895
|
-
return stopped;
|
|
1896
|
-
}
|
|
1897
|
-
case "prevent":
|
|
1898
|
-
if ((selfMode && isSelf) || !selfMode)
|
|
1899
|
-
ev.preventDefault();
|
|
1900
|
-
continue;
|
|
1901
|
-
case "stop":
|
|
1902
|
-
if ((selfMode && isSelf) || !selfMode)
|
|
1903
|
-
ev.stopPropagation();
|
|
1904
|
-
stopped = true;
|
|
1905
|
-
continue;
|
|
1906
|
-
}
|
|
1907
|
-
}
|
|
1908
|
-
}
|
|
1909
|
-
// If handler is empty, the array slot 0 will also be empty, and data will not have the property 0
|
|
1910
|
-
// We check this rather than data[0] being truthy (or typeof function) so that it crashes
|
|
1911
|
-
// as expected when there is a handler expression that evaluates to a falsy value
|
|
1912
|
-
if (Object.hasOwnProperty.call(data, 0)) {
|
|
1913
|
-
const handler = data[0];
|
|
1914
|
-
if (typeof handler !== "function") {
|
|
1915
|
-
throw new Error(`Invalid handler (expected a function, received: '${handler}')`);
|
|
1916
|
-
}
|
|
1917
|
-
let node = data[1] ? data[1].__owl__ : null;
|
|
1918
|
-
if (node ? node.status === 1 /* MOUNTED */ : true) {
|
|
1919
|
-
handler.call(node ? node.component : null, ev);
|
|
1920
|
-
}
|
|
1921
|
-
}
|
|
1922
|
-
return stopped;
|
|
1923
|
-
};
|
|
1924
|
-
|
|
1925
|
-
// Maps fibers to thrown errors
|
|
1926
|
-
const fibersInError = new WeakMap();
|
|
1927
|
-
const nodeErrorHandlers = new WeakMap();
|
|
1928
|
-
function _handleError(node, error, isFirstRound = false) {
|
|
1929
|
-
if (!node) {
|
|
1930
|
-
return false;
|
|
1931
|
-
}
|
|
1932
|
-
const fiber = node.fiber;
|
|
1933
|
-
if (fiber) {
|
|
1934
|
-
fibersInError.set(fiber, error);
|
|
1935
|
-
}
|
|
1936
|
-
const errorHandlers = nodeErrorHandlers.get(node);
|
|
1937
|
-
if (errorHandlers) {
|
|
1938
|
-
let stopped = false;
|
|
1939
|
-
// execute in the opposite order
|
|
1940
|
-
for (let i = errorHandlers.length - 1; i >= 0; i--) {
|
|
1941
|
-
try {
|
|
1942
|
-
errorHandlers[i](error);
|
|
1943
|
-
stopped = true;
|
|
1944
|
-
break;
|
|
1945
|
-
}
|
|
1946
|
-
catch (e) {
|
|
1947
|
-
error = e;
|
|
1948
|
-
}
|
|
1949
|
-
}
|
|
1950
|
-
if (stopped) {
|
|
1951
|
-
if (isFirstRound && fiber && fiber.node.fiber) {
|
|
1952
|
-
fiber.root.counter--;
|
|
1953
|
-
}
|
|
1954
|
-
return true;
|
|
1955
|
-
}
|
|
1956
|
-
}
|
|
1957
|
-
return _handleError(node.parent, error);
|
|
1958
|
-
}
|
|
1959
|
-
function handleError(params) {
|
|
1960
|
-
const error = params.error;
|
|
1961
|
-
const node = "node" in params ? params.node : params.fiber.node;
|
|
1962
|
-
const fiber = "fiber" in params ? params.fiber : node.fiber;
|
|
1963
|
-
// resets the fibers on components if possible. This is important so that
|
|
1964
|
-
// new renderings can be properly included in the initial one, if any.
|
|
1965
|
-
let current = fiber;
|
|
1966
|
-
do {
|
|
1967
|
-
current.node.fiber = current;
|
|
1968
|
-
current = current.parent;
|
|
1969
|
-
} while (current);
|
|
1970
|
-
fibersInError.set(fiber.root, error);
|
|
1971
|
-
const handled = _handleError(node, error, true);
|
|
1972
|
-
if (!handled) {
|
|
1973
|
-
console.warn(`[Owl] Unhandled error. Destroying the root component`);
|
|
1974
|
-
try {
|
|
1975
|
-
node.app.destroy();
|
|
1976
|
-
}
|
|
1977
|
-
catch (e) {
|
|
1978
|
-
console.error(e);
|
|
1979
|
-
}
|
|
1980
|
-
}
|
|
1981
|
-
}
|
|
1982
|
-
|
|
1983
|
-
function makeChildFiber(node, parent) {
|
|
1984
|
-
let current = node.fiber;
|
|
1985
|
-
if (current) {
|
|
1986
|
-
cancelFibers(current.children);
|
|
1987
|
-
current.root = null;
|
|
1988
|
-
}
|
|
1989
|
-
return new Fiber(node, parent);
|
|
1990
|
-
}
|
|
1991
|
-
function makeRootFiber(node) {
|
|
1992
|
-
let current = node.fiber;
|
|
1993
|
-
if (current) {
|
|
1994
|
-
let root = current.root;
|
|
1995
|
-
root.counter = root.counter + 1 - cancelFibers(current.children);
|
|
1996
|
-
current.children = [];
|
|
1997
|
-
current.bdom = null;
|
|
1998
|
-
if (fibersInError.has(current)) {
|
|
1999
|
-
fibersInError.delete(current);
|
|
2000
|
-
fibersInError.delete(root);
|
|
2001
|
-
current.appliedToDom = false;
|
|
2002
|
-
}
|
|
2003
|
-
return current;
|
|
2004
|
-
}
|
|
2005
|
-
const fiber = new RootFiber(node, null);
|
|
2006
|
-
if (node.willPatch.length) {
|
|
2007
|
-
fiber.willPatch.push(fiber);
|
|
2008
|
-
}
|
|
2009
|
-
if (node.patched.length) {
|
|
2010
|
-
fiber.patched.push(fiber);
|
|
2011
|
-
}
|
|
2012
|
-
return fiber;
|
|
2013
|
-
}
|
|
2014
|
-
/**
|
|
2015
|
-
* @returns number of not-yet rendered fibers cancelled
|
|
2016
|
-
*/
|
|
2017
|
-
function cancelFibers(fibers) {
|
|
2018
|
-
let result = 0;
|
|
2019
|
-
for (let fiber of fibers) {
|
|
2020
|
-
fiber.node.fiber = null;
|
|
2021
|
-
if (!fiber.bdom) {
|
|
2022
|
-
result++;
|
|
2023
|
-
}
|
|
2024
|
-
result += cancelFibers(fiber.children);
|
|
2025
|
-
}
|
|
2026
|
-
return result;
|
|
2027
|
-
}
|
|
2028
|
-
class Fiber {
|
|
2029
|
-
constructor(node, parent) {
|
|
2030
|
-
this.bdom = null;
|
|
2031
|
-
this.children = [];
|
|
2032
|
-
this.appliedToDom = false;
|
|
2033
|
-
this.deep = false;
|
|
2034
|
-
this.node = node;
|
|
2035
|
-
this.parent = parent;
|
|
2036
|
-
if (parent) {
|
|
2037
|
-
this.deep = parent.deep;
|
|
2038
|
-
const root = parent.root;
|
|
2039
|
-
root.counter++;
|
|
2040
|
-
this.root = root;
|
|
2041
|
-
parent.children.push(this);
|
|
2042
|
-
}
|
|
2043
|
-
else {
|
|
2044
|
-
this.root = this;
|
|
2045
|
-
}
|
|
2046
|
-
}
|
|
2047
|
-
}
|
|
2048
|
-
class RootFiber extends Fiber {
|
|
2049
|
-
constructor() {
|
|
2050
|
-
super(...arguments);
|
|
2051
|
-
this.counter = 1;
|
|
2052
|
-
// only add stuff in this if they have registered some hooks
|
|
2053
|
-
this.willPatch = [];
|
|
2054
|
-
this.patched = [];
|
|
2055
|
-
this.mounted = [];
|
|
2056
|
-
// A fiber is typically locked when it is completing and the patch has not, or is being applied.
|
|
2057
|
-
// i.e.: render triggered in onWillUnmount or in willPatch will be delayed
|
|
2058
|
-
this.locked = false;
|
|
2092
|
+
class RootFiber extends Fiber {
|
|
2093
|
+
constructor() {
|
|
2094
|
+
super(...arguments);
|
|
2095
|
+
this.counter = 1;
|
|
2096
|
+
// only add stuff in this if they have registered some hooks
|
|
2097
|
+
this.willPatch = [];
|
|
2098
|
+
this.patched = [];
|
|
2099
|
+
this.mounted = [];
|
|
2100
|
+
// A fiber is typically locked when it is completing and the patch has not, or is being applied.
|
|
2101
|
+
// i.e.: render triggered in onWillUnmount or in willPatch will be delayed
|
|
2102
|
+
this.locked = false;
|
|
2059
2103
|
}
|
|
2060
2104
|
complete() {
|
|
2061
2105
|
const node = this.node;
|
|
@@ -2105,6 +2149,12 @@ class RootFiber extends Fiber {
|
|
|
2105
2149
|
handleError({ fiber: current || this, error: e });
|
|
2106
2150
|
}
|
|
2107
2151
|
}
|
|
2152
|
+
setCounter(newValue) {
|
|
2153
|
+
this.counter = newValue;
|
|
2154
|
+
if (newValue === 0) {
|
|
2155
|
+
this.node.app.scheduler.flush();
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2108
2158
|
}
|
|
2109
2159
|
class MountFiber extends RootFiber {
|
|
2110
2160
|
constructor(node, target, options = {}) {
|
|
@@ -2116,6 +2166,7 @@ class MountFiber extends RootFiber {
|
|
|
2116
2166
|
let current = this;
|
|
2117
2167
|
try {
|
|
2118
2168
|
const node = this.node;
|
|
2169
|
+
node.children = this.childrenMap;
|
|
2119
2170
|
node.app.constructor.validateTarget(this.target);
|
|
2120
2171
|
if (node.bdom) {
|
|
2121
2172
|
// this is a complicated situation: if we mount a fiber with an existing
|
|
@@ -2154,23 +2205,162 @@ class MountFiber extends RootFiber {
|
|
|
2154
2205
|
}
|
|
2155
2206
|
}
|
|
2156
2207
|
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2208
|
+
/**
|
|
2209
|
+
* Apply default props (only top level).
|
|
2210
|
+
*
|
|
2211
|
+
* Note that this method does modify in place the props
|
|
2212
|
+
*/
|
|
2213
|
+
function applyDefaultProps(props, ComponentClass) {
|
|
2214
|
+
const defaultProps = ComponentClass.defaultProps;
|
|
2215
|
+
if (defaultProps) {
|
|
2216
|
+
for (let propName in defaultProps) {
|
|
2217
|
+
if (props[propName] === undefined) {
|
|
2218
|
+
props[propName] = defaultProps[propName];
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2161
2221
|
}
|
|
2162
|
-
return currentNode;
|
|
2163
2222
|
}
|
|
2164
|
-
|
|
2165
|
-
|
|
2223
|
+
//------------------------------------------------------------------------------
|
|
2224
|
+
// Prop validation helper
|
|
2225
|
+
//------------------------------------------------------------------------------
|
|
2226
|
+
function getPropDescription(staticProps) {
|
|
2227
|
+
if (staticProps instanceof Array) {
|
|
2228
|
+
return Object.fromEntries(staticProps.map((p) => (p.endsWith("?") ? [p.slice(0, -1), false] : [p, true])));
|
|
2229
|
+
}
|
|
2230
|
+
return staticProps || { "*": true };
|
|
2166
2231
|
}
|
|
2167
|
-
// -----------------------------------------------------------------------------
|
|
2168
|
-
// Integration with reactivity system (useState)
|
|
2169
|
-
// -----------------------------------------------------------------------------
|
|
2170
|
-
const batchedRenderFunctions = new WeakMap();
|
|
2171
2232
|
/**
|
|
2172
|
-
*
|
|
2173
|
-
*
|
|
2233
|
+
* Validate the component props (or next props) against the (static) props
|
|
2234
|
+
* description. This is potentially an expensive operation: it may needs to
|
|
2235
|
+
* visit recursively the props and all the children to check if they are valid.
|
|
2236
|
+
* This is why it is only done in 'dev' mode.
|
|
2237
|
+
*/
|
|
2238
|
+
function validateProps(name, props, parent) {
|
|
2239
|
+
const ComponentClass = typeof name !== "string"
|
|
2240
|
+
? name
|
|
2241
|
+
: parent.constructor.components[name];
|
|
2242
|
+
if (!ComponentClass) {
|
|
2243
|
+
// this is an error, wrong component. We silently return here instead so the
|
|
2244
|
+
// error is triggered by the usual path ('component' function)
|
|
2245
|
+
return;
|
|
2246
|
+
}
|
|
2247
|
+
applyDefaultProps(props, ComponentClass);
|
|
2248
|
+
const defaultProps = ComponentClass.defaultProps || {};
|
|
2249
|
+
let propsDef = getPropDescription(ComponentClass.props);
|
|
2250
|
+
const allowAdditionalProps = "*" in propsDef;
|
|
2251
|
+
for (let propName in propsDef) {
|
|
2252
|
+
if (propName === "*") {
|
|
2253
|
+
continue;
|
|
2254
|
+
}
|
|
2255
|
+
const propDef = propsDef[propName];
|
|
2256
|
+
let isMandatory = !!propDef;
|
|
2257
|
+
if (typeof propDef === "object" && "optional" in propDef) {
|
|
2258
|
+
isMandatory = !propDef.optional;
|
|
2259
|
+
}
|
|
2260
|
+
if (isMandatory && propName in defaultProps) {
|
|
2261
|
+
throw new Error(`A default value cannot be defined for a mandatory prop (name: '${propName}', component: ${ComponentClass.name})`);
|
|
2262
|
+
}
|
|
2263
|
+
if (props[propName] === undefined) {
|
|
2264
|
+
if (isMandatory) {
|
|
2265
|
+
throw new Error(`Missing props '${propName}' (component '${ComponentClass.name}')`);
|
|
2266
|
+
}
|
|
2267
|
+
else {
|
|
2268
|
+
continue;
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
let isValid;
|
|
2272
|
+
try {
|
|
2273
|
+
isValid = isValidProp(props[propName], propDef);
|
|
2274
|
+
}
|
|
2275
|
+
catch (e) {
|
|
2276
|
+
e.message = `Invalid prop '${propName}' in component ${ComponentClass.name} (${e.message})`;
|
|
2277
|
+
throw e;
|
|
2278
|
+
}
|
|
2279
|
+
if (!isValid) {
|
|
2280
|
+
throw new Error(`Invalid Prop '${propName}' in component '${ComponentClass.name}'`);
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
if (!allowAdditionalProps) {
|
|
2284
|
+
for (let propName in props) {
|
|
2285
|
+
if (!(propName in propsDef)) {
|
|
2286
|
+
throw new Error(`Unknown prop '${propName}' given to component '${ComponentClass.name}'`);
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
/**
|
|
2292
|
+
* Check if an invidual prop value matches its (static) prop definition
|
|
2293
|
+
*/
|
|
2294
|
+
function isValidProp(prop, propDef) {
|
|
2295
|
+
if (propDef === true) {
|
|
2296
|
+
return true;
|
|
2297
|
+
}
|
|
2298
|
+
if (typeof propDef === "function") {
|
|
2299
|
+
// Check if a value is constructed by some Constructor. Note that there is a
|
|
2300
|
+
// slight abuse of language: we want to consider primitive values as well.
|
|
2301
|
+
//
|
|
2302
|
+
// So, even though 1 is not an instance of Number, we want to consider that
|
|
2303
|
+
// it is valid.
|
|
2304
|
+
if (typeof prop === "object") {
|
|
2305
|
+
return prop instanceof propDef;
|
|
2306
|
+
}
|
|
2307
|
+
return typeof prop === propDef.name.toLowerCase();
|
|
2308
|
+
}
|
|
2309
|
+
else if (propDef instanceof Array) {
|
|
2310
|
+
// If this code is executed, this means that we want to check if a prop
|
|
2311
|
+
// matches at least one of its descriptor.
|
|
2312
|
+
let result = false;
|
|
2313
|
+
for (let i = 0, iLen = propDef.length; i < iLen; i++) {
|
|
2314
|
+
result = result || isValidProp(prop, propDef[i]);
|
|
2315
|
+
}
|
|
2316
|
+
return result;
|
|
2317
|
+
}
|
|
2318
|
+
// propsDef is an object
|
|
2319
|
+
if (propDef.optional && prop === undefined) {
|
|
2320
|
+
return true;
|
|
2321
|
+
}
|
|
2322
|
+
let result = propDef.type ? isValidProp(prop, propDef.type) : true;
|
|
2323
|
+
if (propDef.validate) {
|
|
2324
|
+
result = result && propDef.validate(prop);
|
|
2325
|
+
}
|
|
2326
|
+
if (propDef.type === Array && propDef.element) {
|
|
2327
|
+
for (let i = 0, iLen = prop.length; i < iLen; i++) {
|
|
2328
|
+
result = result && isValidProp(prop[i], propDef.element);
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
if (propDef.type === Object && propDef.shape) {
|
|
2332
|
+
const shape = propDef.shape;
|
|
2333
|
+
for (let key in shape) {
|
|
2334
|
+
result = result && isValidProp(prop[key], shape[key]);
|
|
2335
|
+
}
|
|
2336
|
+
if (result) {
|
|
2337
|
+
for (let propName in prop) {
|
|
2338
|
+
if (!(propName in shape)) {
|
|
2339
|
+
throw new Error(`unknown prop '${propName}'`);
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
return result;
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
let currentNode = null;
|
|
2348
|
+
function getCurrent() {
|
|
2349
|
+
if (!currentNode) {
|
|
2350
|
+
throw new Error("No active component (a hook function should only be called in 'setup')");
|
|
2351
|
+
}
|
|
2352
|
+
return currentNode;
|
|
2353
|
+
}
|
|
2354
|
+
function useComponent() {
|
|
2355
|
+
return currentNode.component;
|
|
2356
|
+
}
|
|
2357
|
+
// -----------------------------------------------------------------------------
|
|
2358
|
+
// Integration with reactivity system (useState)
|
|
2359
|
+
// -----------------------------------------------------------------------------
|
|
2360
|
+
const batchedRenderFunctions = new WeakMap();
|
|
2361
|
+
/**
|
|
2362
|
+
* Creates a reactive object that will be observed by the current component.
|
|
2363
|
+
* Reading data from the returned object (eg during rendering) will cause the
|
|
2174
2364
|
* component to subscribe to that data and be rerendered when it changes.
|
|
2175
2365
|
*
|
|
2176
2366
|
* @param state the state to observe
|
|
@@ -2182,7 +2372,7 @@ function useState(state) {
|
|
|
2182
2372
|
const node = getCurrent();
|
|
2183
2373
|
let render = batchedRenderFunctions.get(node);
|
|
2184
2374
|
if (!render) {
|
|
2185
|
-
render = batched(node.render.bind(node));
|
|
2375
|
+
render = batched(node.render.bind(node, false));
|
|
2186
2376
|
batchedRenderFunctions.set(node, render);
|
|
2187
2377
|
// manual implementation of onWillDestroy to break cyclic dependency
|
|
2188
2378
|
node.willDestroy.push(clearReactivesForCallback.bind(null, render));
|
|
@@ -2200,22 +2390,23 @@ function arePropsDifferent(props1, props2) {
|
|
|
2200
2390
|
function component(name, props, key, ctx, parent) {
|
|
2201
2391
|
let node = ctx.children[key];
|
|
2202
2392
|
let isDynamic = typeof name !== "string";
|
|
2203
|
-
if (node) {
|
|
2204
|
-
|
|
2205
|
-
node.destroy();
|
|
2206
|
-
node = undefined;
|
|
2207
|
-
}
|
|
2208
|
-
else if (node.status === 2 /* DESTROYED */) {
|
|
2209
|
-
node = undefined;
|
|
2210
|
-
}
|
|
2393
|
+
if (node && node.status === 2 /* DESTROYED */) {
|
|
2394
|
+
node = undefined;
|
|
2211
2395
|
}
|
|
2212
2396
|
if (isDynamic && node && node.component.constructor !== name) {
|
|
2213
2397
|
node = undefined;
|
|
2214
2398
|
}
|
|
2215
2399
|
const parentFiber = ctx.fiber;
|
|
2216
2400
|
if (node) {
|
|
2217
|
-
|
|
2218
|
-
if (
|
|
2401
|
+
let shouldRender = node.forceNextRender;
|
|
2402
|
+
if (shouldRender) {
|
|
2403
|
+
node.forceNextRender = false;
|
|
2404
|
+
}
|
|
2405
|
+
else {
|
|
2406
|
+
const currentProps = node.component.props[TARGET];
|
|
2407
|
+
shouldRender = parentFiber.deep || arePropsDifferent(currentProps, props);
|
|
2408
|
+
}
|
|
2409
|
+
if (shouldRender) {
|
|
2219
2410
|
node.updateAndRender(props, parentFiber);
|
|
2220
2411
|
}
|
|
2221
2412
|
}
|
|
@@ -2231,17 +2422,19 @@ function component(name, props, key, ctx, parent) {
|
|
|
2231
2422
|
throw new Error(`Cannot find the definition of component "${name}"`);
|
|
2232
2423
|
}
|
|
2233
2424
|
}
|
|
2234
|
-
node = new ComponentNode(C, props, ctx.app, ctx);
|
|
2425
|
+
node = new ComponentNode(C, props, ctx.app, ctx, key);
|
|
2235
2426
|
ctx.children[key] = node;
|
|
2236
2427
|
node.initiateRender(new Fiber(node, parentFiber));
|
|
2237
2428
|
}
|
|
2429
|
+
parentFiber.childrenMap[key] = node;
|
|
2238
2430
|
return node;
|
|
2239
2431
|
}
|
|
2240
2432
|
class ComponentNode {
|
|
2241
|
-
constructor(C, props, app, parent) {
|
|
2433
|
+
constructor(C, props, app, parent, parentKey) {
|
|
2242
2434
|
this.fiber = null;
|
|
2243
2435
|
this.bdom = null;
|
|
2244
2436
|
this.status = 0 /* NEW */;
|
|
2437
|
+
this.forceNextRender = false;
|
|
2245
2438
|
this.children = Object.create(null);
|
|
2246
2439
|
this.refs = {};
|
|
2247
2440
|
this.willStart = [];
|
|
@@ -2253,7 +2446,8 @@ class ComponentNode {
|
|
|
2253
2446
|
this.willDestroy = [];
|
|
2254
2447
|
currentNode = this;
|
|
2255
2448
|
this.app = app;
|
|
2256
|
-
this.parent = parent
|
|
2449
|
+
this.parent = parent;
|
|
2450
|
+
this.parentKey = parentKey;
|
|
2257
2451
|
this.level = parent ? parent.level + 1 : 0;
|
|
2258
2452
|
applyDefaultProps(props, C);
|
|
2259
2453
|
const env = (parent && parent.childEnv) || app.env;
|
|
@@ -2283,12 +2477,12 @@ class ComponentNode {
|
|
|
2283
2477
|
return;
|
|
2284
2478
|
}
|
|
2285
2479
|
if (this.status === 0 /* NEW */ && this.fiber === fiber) {
|
|
2286
|
-
|
|
2480
|
+
fiber.render();
|
|
2287
2481
|
}
|
|
2288
2482
|
}
|
|
2289
|
-
async render(deep
|
|
2483
|
+
async render(deep) {
|
|
2290
2484
|
let current = this.fiber;
|
|
2291
|
-
if (current && current.root.locked) {
|
|
2485
|
+
if (current && (current.root.locked || current.bdom === true)) {
|
|
2292
2486
|
await Promise.resolve();
|
|
2293
2487
|
// situation may have changed after the microtask tick
|
|
2294
2488
|
current = this.fiber;
|
|
@@ -2327,16 +2521,7 @@ class ComponentNode {
|
|
|
2327
2521
|
// embedded in a rendering coming from above, so the fiber will be rendered
|
|
2328
2522
|
// in the next microtick anyway, so we should not render it again.
|
|
2329
2523
|
if (this.fiber === fiber && (current || !fiber.parent)) {
|
|
2330
|
-
|
|
2331
|
-
}
|
|
2332
|
-
}
|
|
2333
|
-
_render(fiber) {
|
|
2334
|
-
try {
|
|
2335
|
-
fiber.bdom = this.renderFn();
|
|
2336
|
-
fiber.root.counter--;
|
|
2337
|
-
}
|
|
2338
|
-
catch (e) {
|
|
2339
|
-
handleError({ node: this, error: e });
|
|
2524
|
+
fiber.render();
|
|
2340
2525
|
}
|
|
2341
2526
|
}
|
|
2342
2527
|
destroy() {
|
|
@@ -2356,8 +2541,15 @@ class ComponentNode {
|
|
|
2356
2541
|
for (let child of Object.values(this.children)) {
|
|
2357
2542
|
child._destroy();
|
|
2358
2543
|
}
|
|
2359
|
-
|
|
2360
|
-
|
|
2544
|
+
if (this.willDestroy.length) {
|
|
2545
|
+
try {
|
|
2546
|
+
for (let cb of this.willDestroy) {
|
|
2547
|
+
cb.call(component);
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
catch (e) {
|
|
2551
|
+
handleError({ error: e, node: this });
|
|
2552
|
+
}
|
|
2361
2553
|
}
|
|
2362
2554
|
this.status = 2 /* DESTROYED */;
|
|
2363
2555
|
}
|
|
@@ -2376,7 +2568,7 @@ class ComponentNode {
|
|
|
2376
2568
|
return;
|
|
2377
2569
|
}
|
|
2378
2570
|
component.props = props;
|
|
2379
|
-
|
|
2571
|
+
fiber.render();
|
|
2380
2572
|
const parentRoot = parentFiber.root;
|
|
2381
2573
|
if (this.willPatch.length) {
|
|
2382
2574
|
parentRoot.willPatch.push(fiber);
|
|
@@ -2423,6 +2615,7 @@ class ComponentNode {
|
|
|
2423
2615
|
bdom.mount(parent, anchor);
|
|
2424
2616
|
this.status = 1 /* MOUNTED */;
|
|
2425
2617
|
this.fiber.appliedToDom = true;
|
|
2618
|
+
this.children = this.fiber.childrenMap;
|
|
2426
2619
|
this.fiber = null;
|
|
2427
2620
|
}
|
|
2428
2621
|
moveBefore(other, afterNode) {
|
|
@@ -2438,10 +2631,8 @@ class ComponentNode {
|
|
|
2438
2631
|
}
|
|
2439
2632
|
_patch() {
|
|
2440
2633
|
const hasChildren = Object.keys(this.children).length > 0;
|
|
2634
|
+
this.children = this.fiber.childrenMap;
|
|
2441
2635
|
this.bdom.patch(this.fiber.bdom, hasChildren);
|
|
2442
|
-
if (hasChildren) {
|
|
2443
|
-
this.cleanOutdatedChildren();
|
|
2444
|
-
}
|
|
2445
2636
|
this.fiber.appliedToDom = true;
|
|
2446
2637
|
this.fiber = null;
|
|
2447
2638
|
}
|
|
@@ -2451,111 +2642,82 @@ class ComponentNode {
|
|
|
2451
2642
|
remove() {
|
|
2452
2643
|
this.bdom.remove();
|
|
2453
2644
|
}
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
}
|
|
2464
|
-
}
|
|
2465
|
-
}
|
|
2645
|
+
// ---------------------------------------------------------------------------
|
|
2646
|
+
// Some debug helpers
|
|
2647
|
+
// ---------------------------------------------------------------------------
|
|
2648
|
+
get name() {
|
|
2649
|
+
return this.component.constructor.name;
|
|
2650
|
+
}
|
|
2651
|
+
get subscriptions() {
|
|
2652
|
+
const render = batchedRenderFunctions.get(this);
|
|
2653
|
+
return render ? getSubscriptions(render) : [];
|
|
2466
2654
|
}
|
|
2467
2655
|
}
|
|
2468
2656
|
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2657
|
+
// -----------------------------------------------------------------------------
|
|
2658
|
+
// Scheduler
|
|
2659
|
+
// -----------------------------------------------------------------------------
|
|
2660
|
+
class Scheduler {
|
|
2661
|
+
constructor() {
|
|
2662
|
+
this.tasks = new Set();
|
|
2663
|
+
this.frame = 0;
|
|
2664
|
+
this.delayedRenders = [];
|
|
2665
|
+
this.requestAnimationFrame = Scheduler.requestAnimationFrame;
|
|
2666
|
+
}
|
|
2667
|
+
addFiber(fiber) {
|
|
2668
|
+
this.tasks.add(fiber.root);
|
|
2669
|
+
}
|
|
2670
|
+
/**
|
|
2671
|
+
* Process all current tasks. This only applies to the fibers that are ready.
|
|
2672
|
+
* Other tasks are left unchanged.
|
|
2673
|
+
*/
|
|
2674
|
+
flush() {
|
|
2675
|
+
if (this.delayedRenders.length) {
|
|
2676
|
+
let renders = this.delayedRenders;
|
|
2677
|
+
this.delayedRenders = [];
|
|
2678
|
+
for (let f of renders) {
|
|
2679
|
+
if (f.root && f.node.status !== 2 /* DESTROYED */) {
|
|
2680
|
+
f.render();
|
|
2681
|
+
}
|
|
2482
2682
|
}
|
|
2483
|
-
return result;
|
|
2484
2683
|
}
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2684
|
+
if (this.frame === 0) {
|
|
2685
|
+
this.frame = this.requestAnimationFrame(() => {
|
|
2686
|
+
this.frame = 0;
|
|
2687
|
+
this.tasks.forEach((fiber) => this.processFiber(fiber));
|
|
2688
|
+
for (let task of this.tasks) {
|
|
2689
|
+
if (task.node.status === 2 /* DESTROYED */) {
|
|
2690
|
+
this.tasks.delete(task);
|
|
2691
|
+
}
|
|
2692
|
+
}
|
|
2693
|
+
});
|
|
2694
|
+
}
|
|
2695
|
+
}
|
|
2696
|
+
processFiber(fiber) {
|
|
2697
|
+
if (fiber.root !== fiber) {
|
|
2698
|
+
this.tasks.delete(fiber);
|
|
2699
|
+
return;
|
|
2700
|
+
}
|
|
2701
|
+
const hasError = fibersInError.has(fiber);
|
|
2702
|
+
if (hasError && fiber.counter !== 0) {
|
|
2703
|
+
this.tasks.delete(fiber);
|
|
2704
|
+
return;
|
|
2705
|
+
}
|
|
2706
|
+
if (fiber.node.status === 2 /* DESTROYED */) {
|
|
2707
|
+
this.tasks.delete(fiber);
|
|
2708
|
+
return;
|
|
2709
|
+
}
|
|
2710
|
+
if (fiber.counter === 0) {
|
|
2711
|
+
if (!hasError) {
|
|
2712
|
+
fiber.complete();
|
|
2488
2713
|
}
|
|
2489
|
-
|
|
2714
|
+
this.tasks.delete(fiber);
|
|
2490
2715
|
}
|
|
2491
|
-
}
|
|
2492
|
-
}
|
|
2493
|
-
// -----------------------------------------------------------------------------
|
|
2494
|
-
// hooks
|
|
2495
|
-
// -----------------------------------------------------------------------------
|
|
2496
|
-
function onWillStart(fn) {
|
|
2497
|
-
const node = getCurrent();
|
|
2498
|
-
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
2499
|
-
node.willStart.push(decorate(fn.bind(node.component), "onWillStart"));
|
|
2500
|
-
}
|
|
2501
|
-
function onWillUpdateProps(fn) {
|
|
2502
|
-
const node = getCurrent();
|
|
2503
|
-
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
2504
|
-
node.willUpdateProps.push(decorate(fn.bind(node.component), "onWillUpdateProps"));
|
|
2505
|
-
}
|
|
2506
|
-
function onMounted(fn) {
|
|
2507
|
-
const node = getCurrent();
|
|
2508
|
-
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
2509
|
-
node.mounted.push(decorate(fn.bind(node.component), "onMounted"));
|
|
2510
|
-
}
|
|
2511
|
-
function onWillPatch(fn) {
|
|
2512
|
-
const node = getCurrent();
|
|
2513
|
-
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
2514
|
-
node.willPatch.unshift(decorate(fn.bind(node.component), "onWillPatch"));
|
|
2515
|
-
}
|
|
2516
|
-
function onPatched(fn) {
|
|
2517
|
-
const node = getCurrent();
|
|
2518
|
-
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
2519
|
-
node.patched.push(decorate(fn.bind(node.component), "onPatched"));
|
|
2520
|
-
}
|
|
2521
|
-
function onWillUnmount(fn) {
|
|
2522
|
-
const node = getCurrent();
|
|
2523
|
-
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
2524
|
-
node.willUnmount.unshift(decorate(fn.bind(node.component), "onWillUnmount"));
|
|
2525
|
-
}
|
|
2526
|
-
function onWillDestroy(fn) {
|
|
2527
|
-
const node = getCurrent();
|
|
2528
|
-
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
2529
|
-
node.willDestroy.push(decorate(fn.bind(node.component), "onWillDestroy"));
|
|
2530
|
-
}
|
|
2531
|
-
function onWillRender(fn) {
|
|
2532
|
-
const node = getCurrent();
|
|
2533
|
-
const renderFn = node.renderFn;
|
|
2534
|
-
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
2535
|
-
node.renderFn = decorate(() => {
|
|
2536
|
-
fn.call(node.component);
|
|
2537
|
-
return renderFn();
|
|
2538
|
-
}, "onWillRender");
|
|
2716
|
+
}
|
|
2539
2717
|
}
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
2544
|
-
node.renderFn = decorate(() => {
|
|
2545
|
-
const result = renderFn();
|
|
2546
|
-
fn.call(node.component);
|
|
2547
|
-
return result;
|
|
2548
|
-
}, "onRendered");
|
|
2549
|
-
}
|
|
2550
|
-
function onError(callback) {
|
|
2551
|
-
const node = getCurrent();
|
|
2552
|
-
let handlers = nodeErrorHandlers.get(node);
|
|
2553
|
-
if (!handlers) {
|
|
2554
|
-
handlers = [];
|
|
2555
|
-
nodeErrorHandlers.set(node, handlers);
|
|
2556
|
-
}
|
|
2557
|
-
handlers.push(callback.bind(node.component));
|
|
2558
|
-
}
|
|
2718
|
+
// capture the value of requestAnimationFrame as soon as possible, to avoid
|
|
2719
|
+
// interactions with other code, such as test frameworks that override them
|
|
2720
|
+
Scheduler.requestAnimationFrame = window.requestAnimationFrame.bind(window);
|
|
2559
2721
|
|
|
2560
2722
|
/**
|
|
2561
2723
|
* Owl QWeb Expression Parser
|
|
@@ -2709,24 +2871,31 @@ const TOKENIZERS = [
|
|
|
2709
2871
|
function tokenize(expr) {
|
|
2710
2872
|
const result = [];
|
|
2711
2873
|
let token = true;
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2874
|
+
let error;
|
|
2875
|
+
let current = expr;
|
|
2876
|
+
try {
|
|
2877
|
+
while (token) {
|
|
2878
|
+
current = current.trim();
|
|
2879
|
+
if (current) {
|
|
2880
|
+
for (let tokenizer of TOKENIZERS) {
|
|
2881
|
+
token = tokenizer(current);
|
|
2882
|
+
if (token) {
|
|
2883
|
+
result.push(token);
|
|
2884
|
+
current = current.slice(token.size || token.value.length);
|
|
2885
|
+
break;
|
|
2886
|
+
}
|
|
2721
2887
|
}
|
|
2722
2888
|
}
|
|
2889
|
+
else {
|
|
2890
|
+
token = false;
|
|
2891
|
+
}
|
|
2723
2892
|
}
|
|
2724
|
-
else {
|
|
2725
|
-
token = false;
|
|
2726
|
-
}
|
|
2727
2893
|
}
|
|
2728
|
-
|
|
2729
|
-
|
|
2894
|
+
catch (e) {
|
|
2895
|
+
error = e; // Silence all errors and throw a generic error below
|
|
2896
|
+
}
|
|
2897
|
+
if (current.length || error) {
|
|
2898
|
+
throw new Error(`Tokenizer error: could not tokenize \`${expr}\``);
|
|
2730
2899
|
}
|
|
2731
2900
|
return result;
|
|
2732
2901
|
}
|
|
@@ -2931,7 +3100,7 @@ function createContext(parentCtx, params) {
|
|
|
2931
3100
|
}, params);
|
|
2932
3101
|
}
|
|
2933
3102
|
class CodeTarget {
|
|
2934
|
-
constructor(name) {
|
|
3103
|
+
constructor(name, on) {
|
|
2935
3104
|
this.indentLevel = 0;
|
|
2936
3105
|
this.loopLevel = 0;
|
|
2937
3106
|
this.code = [];
|
|
@@ -2942,6 +3111,7 @@ class CodeTarget {
|
|
|
2942
3111
|
this.refInfo = {};
|
|
2943
3112
|
this.shouldProtectScope = false;
|
|
2944
3113
|
this.name = name;
|
|
3114
|
+
this.on = on || null;
|
|
2945
3115
|
}
|
|
2946
3116
|
addLine(line, idx) {
|
|
2947
3117
|
const prefix = new Array(this.indentLevel + 2).join(" ");
|
|
@@ -2991,7 +3161,7 @@ class CodeGenerator {
|
|
|
2991
3161
|
this.targets = [];
|
|
2992
3162
|
this.target = new CodeTarget("template");
|
|
2993
3163
|
this.translatableAttributes = TRANSLATABLE_ATTRS;
|
|
2994
|
-
this.
|
|
3164
|
+
this.staticDefs = [];
|
|
2995
3165
|
this.helpers = new Set();
|
|
2996
3166
|
this.translateFn = options.translateFn || ((s) => s);
|
|
2997
3167
|
if (options.translatableAttributes) {
|
|
@@ -3034,8 +3204,8 @@ class CodeGenerator {
|
|
|
3034
3204
|
if (this.templateName) {
|
|
3035
3205
|
mainCode.push(`// Template name: "${this.templateName}"`);
|
|
3036
3206
|
}
|
|
3037
|
-
for (let { id,
|
|
3038
|
-
mainCode.push(`const ${id} =
|
|
3207
|
+
for (let { id, expr } of this.staticDefs) {
|
|
3208
|
+
mainCode.push(`const ${id} = ${expr};`);
|
|
3039
3209
|
}
|
|
3040
3210
|
// define all blocks
|
|
3041
3211
|
if (this.blocks.length) {
|
|
@@ -3071,19 +3241,21 @@ class CodeGenerator {
|
|
|
3071
3241
|
}
|
|
3072
3242
|
return code;
|
|
3073
3243
|
}
|
|
3074
|
-
compileInNewTarget(prefix, ast, ctx) {
|
|
3244
|
+
compileInNewTarget(prefix, ast, ctx, on) {
|
|
3075
3245
|
const name = this.generateId(prefix);
|
|
3076
3246
|
const initialTarget = this.target;
|
|
3077
|
-
const target = new CodeTarget(name);
|
|
3247
|
+
const target = new CodeTarget(name, on);
|
|
3078
3248
|
this.targets.push(target);
|
|
3079
3249
|
this.target = target;
|
|
3080
|
-
|
|
3081
|
-
this.compileAST(ast, subCtx);
|
|
3250
|
+
this.compileAST(ast, createContext(ctx));
|
|
3082
3251
|
this.target = initialTarget;
|
|
3083
3252
|
return name;
|
|
3084
3253
|
}
|
|
3085
|
-
addLine(line) {
|
|
3086
|
-
this.target.addLine(line);
|
|
3254
|
+
addLine(line, idx) {
|
|
3255
|
+
this.target.addLine(line, idx);
|
|
3256
|
+
}
|
|
3257
|
+
define(varName, expr) {
|
|
3258
|
+
this.addLine(`const ${varName} = ${expr};`);
|
|
3087
3259
|
}
|
|
3088
3260
|
generateId(prefix = "") {
|
|
3089
3261
|
this.ids[prefix] = (this.ids[prefix] || 0) + 1;
|
|
@@ -3125,10 +3297,13 @@ class CodeGenerator {
|
|
|
3125
3297
|
blockExpr = `toggler(${tKeyExpr}, ${blockExpr})`;
|
|
3126
3298
|
}
|
|
3127
3299
|
if (block.isRoot && !ctx.preventRoot) {
|
|
3300
|
+
if (this.target.on) {
|
|
3301
|
+
blockExpr = this.wrapWithEventCatcher(blockExpr, this.target.on);
|
|
3302
|
+
}
|
|
3128
3303
|
this.addLine(`return ${blockExpr};`);
|
|
3129
3304
|
}
|
|
3130
3305
|
else {
|
|
3131
|
-
this.
|
|
3306
|
+
this.define(block.varName, blockExpr);
|
|
3132
3307
|
}
|
|
3133
3308
|
}
|
|
3134
3309
|
/**
|
|
@@ -3156,7 +3331,7 @@ class CodeGenerator {
|
|
|
3156
3331
|
if (!mapping.has(tok.varName)) {
|
|
3157
3332
|
const varId = this.generateId("v");
|
|
3158
3333
|
mapping.set(tok.varName, varId);
|
|
3159
|
-
this.
|
|
3334
|
+
this.define(varId, tok.value);
|
|
3160
3335
|
}
|
|
3161
3336
|
tok.value = mapping.get(tok.varName);
|
|
3162
3337
|
}
|
|
@@ -3295,7 +3470,7 @@ class CodeGenerator {
|
|
|
3295
3470
|
this.blocks.push(block);
|
|
3296
3471
|
if (ast.dynamicTag) {
|
|
3297
3472
|
const tagExpr = this.generateId("tag");
|
|
3298
|
-
this.
|
|
3473
|
+
this.define(tagExpr, compileExpr(ast.dynamicTag));
|
|
3299
3474
|
block.dynamicTagName = tagExpr;
|
|
3300
3475
|
}
|
|
3301
3476
|
}
|
|
@@ -3377,10 +3552,10 @@ class CodeGenerator {
|
|
|
3377
3552
|
const { hasDynamicChildren, baseExpr, expr, eventType, shouldNumberize, shouldTrim, targetAttr, specialInitTargetAttr, } = ast.model;
|
|
3378
3553
|
const baseExpression = compileExpr(baseExpr);
|
|
3379
3554
|
const bExprId = this.generateId("bExpr");
|
|
3380
|
-
this.
|
|
3555
|
+
this.define(bExprId, baseExpression);
|
|
3381
3556
|
const expression = compileExpr(expr);
|
|
3382
3557
|
const exprId = this.generateId("expr");
|
|
3383
|
-
this.
|
|
3558
|
+
this.define(exprId, expression);
|
|
3384
3559
|
const fullExpression = `${bExprId}[${exprId}]`;
|
|
3385
3560
|
let idx;
|
|
3386
3561
|
if (specialInitTargetAttr) {
|
|
@@ -3390,7 +3565,7 @@ class CodeGenerator {
|
|
|
3390
3565
|
else if (hasDynamicChildren) {
|
|
3391
3566
|
const bValueId = this.generateId("bValue");
|
|
3392
3567
|
tModelSelectedExpr = `${bValueId}`;
|
|
3393
|
-
this.
|
|
3568
|
+
this.define(tModelSelectedExpr, fullExpression);
|
|
3394
3569
|
}
|
|
3395
3570
|
else {
|
|
3396
3571
|
idx = block.insertData(`${fullExpression}`, "attr");
|
|
@@ -3438,14 +3613,14 @@ class CodeGenerator {
|
|
|
3438
3613
|
const children = block.children.slice();
|
|
3439
3614
|
let current = children.shift();
|
|
3440
3615
|
for (let i = codeIdx; i < code.length; i++) {
|
|
3441
|
-
if (code[i].trimStart().startsWith(`
|
|
3442
|
-
code[i] = code[i].replace(`
|
|
3616
|
+
if (code[i].trimStart().startsWith(`const ${current.varName} `)) {
|
|
3617
|
+
code[i] = code[i].replace(`const ${current.varName}`, current.varName);
|
|
3443
3618
|
current = children.shift();
|
|
3444
3619
|
if (!current)
|
|
3445
3620
|
break;
|
|
3446
3621
|
}
|
|
3447
3622
|
}
|
|
3448
|
-
this.
|
|
3623
|
+
this.addLine(`let ${block.children.map((c) => c.varName)};`, codeIdx);
|
|
3449
3624
|
}
|
|
3450
3625
|
}
|
|
3451
3626
|
}
|
|
@@ -3533,14 +3708,14 @@ class CodeGenerator {
|
|
|
3533
3708
|
const children = block.children.slice();
|
|
3534
3709
|
let current = children.shift();
|
|
3535
3710
|
for (let i = codeIdx; i < code.length; i++) {
|
|
3536
|
-
if (code[i].trimStart().startsWith(`
|
|
3537
|
-
code[i] = code[i].replace(`
|
|
3711
|
+
if (code[i].trimStart().startsWith(`const ${current.varName} `)) {
|
|
3712
|
+
code[i] = code[i].replace(`const ${current.varName}`, current.varName);
|
|
3538
3713
|
current = children.shift();
|
|
3539
3714
|
if (!current)
|
|
3540
3715
|
break;
|
|
3541
3716
|
}
|
|
3542
3717
|
}
|
|
3543
|
-
this.
|
|
3718
|
+
this.addLine(`let ${block.children.map((c) => c.varName)};`, codeIdx);
|
|
3544
3719
|
}
|
|
3545
3720
|
// note: this part is duplicated from end of compilemulti:
|
|
3546
3721
|
const args = block.children.map((c) => c.varName).join(", ");
|
|
@@ -3561,10 +3736,10 @@ class CodeGenerator {
|
|
|
3561
3736
|
const l = `l_block${block.id}`;
|
|
3562
3737
|
const c = `c_block${block.id}`;
|
|
3563
3738
|
this.helpers.add("prepareList");
|
|
3564
|
-
this.
|
|
3739
|
+
this.define(`[${keys}, ${vals}, ${l}, ${c}]`, `prepareList(${compileExpr(ast.collection)});`);
|
|
3565
3740
|
// Throw errors on duplicate keys in dev mode
|
|
3566
3741
|
if (this.dev) {
|
|
3567
|
-
this.
|
|
3742
|
+
this.define(`keys${block.id}`, `new Set()`);
|
|
3568
3743
|
}
|
|
3569
3744
|
this.addLine(`for (let ${loopVar} = 0; ${loopVar} < ${l}; ${loopVar}++) {`);
|
|
3570
3745
|
this.target.indentLevel++;
|
|
@@ -3581,7 +3756,7 @@ class CodeGenerator {
|
|
|
3581
3756
|
if (!ast.hasNoValue) {
|
|
3582
3757
|
this.addLine(`ctx[\`${ast.elem}_value\`] = ${keys}[${loopVar}];`);
|
|
3583
3758
|
}
|
|
3584
|
-
this.
|
|
3759
|
+
this.define(`key${this.target.loopLevel}`, ast.key ? compileExpr(ast.key) : loopVar);
|
|
3585
3760
|
if (this.dev) {
|
|
3586
3761
|
// Throw error on duplicate keys in dev mode
|
|
3587
3762
|
this.addLine(`if (keys${block.id}.has(key${this.target.loopLevel})) { throw new Error(\`Got duplicate key in t-foreach: \${key${this.target.loopLevel}}\`)}`);
|
|
@@ -3591,8 +3766,8 @@ class CodeGenerator {
|
|
|
3591
3766
|
if (ast.memo) {
|
|
3592
3767
|
this.target.hasCache = true;
|
|
3593
3768
|
id = this.generateId();
|
|
3594
|
-
this.
|
|
3595
|
-
this.
|
|
3769
|
+
this.define(`memo${id}`, compileExpr(ast.memo));
|
|
3770
|
+
this.define(`vnode${id}`, `cache[key${this.target.loopLevel}];`);
|
|
3596
3771
|
this.addLine(`if (vnode${id}) {`);
|
|
3597
3772
|
this.target.indentLevel++;
|
|
3598
3773
|
this.addLine(`if (shallowEqual(vnode${id}.memo, memo${id})) {`);
|
|
@@ -3620,7 +3795,7 @@ class CodeGenerator {
|
|
|
3620
3795
|
}
|
|
3621
3796
|
compileTKey(ast, ctx) {
|
|
3622
3797
|
const tKeyExpr = this.generateId("tKey_");
|
|
3623
|
-
this.
|
|
3798
|
+
this.define(tKeyExpr, compileExpr(ast.expr));
|
|
3624
3799
|
ctx = createContext(ctx, {
|
|
3625
3800
|
tKeyExpr,
|
|
3626
3801
|
block: ctx.block,
|
|
@@ -3665,14 +3840,14 @@ class CodeGenerator {
|
|
|
3665
3840
|
const children = block.children.slice();
|
|
3666
3841
|
let current = children.shift();
|
|
3667
3842
|
for (let i = codeIdx; i < code.length; i++) {
|
|
3668
|
-
if (code[i].trimStart().startsWith(`
|
|
3669
|
-
code[i] = code[i].replace(`
|
|
3843
|
+
if (code[i].trimStart().startsWith(`const ${current.varName} `)) {
|
|
3844
|
+
code[i] = code[i].replace(`const ${current.varName}`, current.varName);
|
|
3670
3845
|
current = children.shift();
|
|
3671
3846
|
if (!current)
|
|
3672
3847
|
break;
|
|
3673
3848
|
}
|
|
3674
3849
|
}
|
|
3675
|
-
this.
|
|
3850
|
+
this.addLine(`let ${block.children.map((c) => c.varName)};`, codeIdx);
|
|
3676
3851
|
}
|
|
3677
3852
|
}
|
|
3678
3853
|
const args = block.children.map((c) => c.varName).join(", ");
|
|
@@ -3703,7 +3878,7 @@ class CodeGenerator {
|
|
|
3703
3878
|
const key = `key + \`${this.generateComponentKey()}\``;
|
|
3704
3879
|
if (isDynamic) {
|
|
3705
3880
|
const templateVar = this.generateId("template");
|
|
3706
|
-
this.
|
|
3881
|
+
this.define(templateVar, subTemplate);
|
|
3707
3882
|
block = this.createBlock(block, "multi", ctx);
|
|
3708
3883
|
this.helpers.add("call");
|
|
3709
3884
|
this.insertBlock(`call(this, ${templateVar}, ctx, node, ${key})`, block, {
|
|
@@ -3714,7 +3889,7 @@ class CodeGenerator {
|
|
|
3714
3889
|
else {
|
|
3715
3890
|
const id = this.generateId(`callTemplate_`);
|
|
3716
3891
|
this.helpers.add("getTemplate");
|
|
3717
|
-
this.
|
|
3892
|
+
this.staticDefs.push({ id, expr: `getTemplate(${subTemplate})` });
|
|
3718
3893
|
block = this.createBlock(block, "multi", ctx);
|
|
3719
3894
|
this.insertBlock(`${id}.call(this, ctx, node, ${key})`, block, {
|
|
3720
3895
|
...ctx,
|
|
@@ -3808,27 +3983,29 @@ class CodeGenerator {
|
|
|
3808
3983
|
compileComponent(ast, ctx) {
|
|
3809
3984
|
let { block } = ctx;
|
|
3810
3985
|
// props
|
|
3811
|
-
const hasSlotsProp = "slots" in ast.props;
|
|
3986
|
+
const hasSlotsProp = "slots" in (ast.props || {});
|
|
3812
3987
|
const props = [];
|
|
3813
|
-
const propExpr = this.formatPropObject(ast.props);
|
|
3988
|
+
const propExpr = this.formatPropObject(ast.props || {});
|
|
3814
3989
|
if (propExpr) {
|
|
3815
3990
|
props.push(propExpr);
|
|
3816
3991
|
}
|
|
3817
3992
|
// slots
|
|
3818
|
-
const hasSlot = !!Object.keys(ast.slots).length;
|
|
3819
3993
|
let slotDef = "";
|
|
3820
|
-
if (
|
|
3994
|
+
if (ast.slots) {
|
|
3821
3995
|
let ctxStr = "ctx";
|
|
3822
3996
|
if (this.target.loopLevel || !this.hasSafeContext) {
|
|
3823
3997
|
ctxStr = this.generateId("ctx");
|
|
3824
3998
|
this.helpers.add("capture");
|
|
3825
|
-
this.
|
|
3999
|
+
this.define(ctxStr, `capture(ctx)`);
|
|
3826
4000
|
}
|
|
3827
4001
|
let slotStr = [];
|
|
3828
4002
|
for (let slotName in ast.slots) {
|
|
3829
|
-
const slotAst = ast.slots[slotName]
|
|
3830
|
-
const
|
|
3831
|
-
|
|
4003
|
+
const slotAst = ast.slots[slotName];
|
|
4004
|
+
const params = [];
|
|
4005
|
+
if (slotAst.content) {
|
|
4006
|
+
const name = this.compileInNewTarget("slot", slotAst.content, ctx, slotAst.on);
|
|
4007
|
+
params.push(`__render: ${name}, __ctx: ${ctxStr}`);
|
|
4008
|
+
}
|
|
3832
4009
|
const scope = ast.slots[slotName].scope;
|
|
3833
4010
|
if (scope) {
|
|
3834
4011
|
params.push(`__scope: "${scope}"`);
|
|
@@ -3853,7 +4030,7 @@ class CodeGenerator {
|
|
|
3853
4030
|
let propVar;
|
|
3854
4031
|
if ((slotDef && (ast.dynamicProps || hasSlotsProp)) || this.dev) {
|
|
3855
4032
|
propVar = this.generateId("props");
|
|
3856
|
-
this.
|
|
4033
|
+
this.define(propVar, propString);
|
|
3857
4034
|
propString = propVar;
|
|
3858
4035
|
}
|
|
3859
4036
|
if (slotDef && (ast.dynamicProps || hasSlotsProp)) {
|
|
@@ -3865,7 +4042,7 @@ class CodeGenerator {
|
|
|
3865
4042
|
let expr;
|
|
3866
4043
|
if (ast.isDynamic) {
|
|
3867
4044
|
expr = this.generateId("Comp");
|
|
3868
|
-
this.
|
|
4045
|
+
this.define(expr, compileExpr(ast.name));
|
|
3869
4046
|
}
|
|
3870
4047
|
else {
|
|
3871
4048
|
expr = `\`${ast.name}\``;
|
|
@@ -3886,9 +4063,28 @@ class CodeGenerator {
|
|
|
3886
4063
|
if (ast.isDynamic) {
|
|
3887
4064
|
blockExpr = `toggler(${expr}, ${blockExpr})`;
|
|
3888
4065
|
}
|
|
4066
|
+
// event handling
|
|
4067
|
+
if (ast.on) {
|
|
4068
|
+
blockExpr = this.wrapWithEventCatcher(blockExpr, ast.on);
|
|
4069
|
+
}
|
|
3889
4070
|
block = this.createBlock(block, "multi", ctx);
|
|
3890
4071
|
this.insertBlock(blockExpr, block, ctx);
|
|
3891
4072
|
}
|
|
4073
|
+
wrapWithEventCatcher(expr, on) {
|
|
4074
|
+
this.helpers.add("createCatcher");
|
|
4075
|
+
let name = this.generateId("catcher");
|
|
4076
|
+
let spec = {};
|
|
4077
|
+
let handlers = [];
|
|
4078
|
+
for (let ev in on) {
|
|
4079
|
+
let handlerId = this.generateId("hdlr");
|
|
4080
|
+
let idx = handlers.push(handlerId) - 1;
|
|
4081
|
+
spec[ev] = idx;
|
|
4082
|
+
const handler = this.generateHandlerCode(ev, on[ev]);
|
|
4083
|
+
this.define(handlerId, handler);
|
|
4084
|
+
}
|
|
4085
|
+
this.staticDefs.push({ id: name, expr: `createCatcher(${JSON.stringify(spec)})` });
|
|
4086
|
+
return `${name}(${expr}, [${handlers.join(",")}])`;
|
|
4087
|
+
}
|
|
3892
4088
|
compileTSlot(ast, ctx) {
|
|
3893
4089
|
this.helpers.add("callSlot");
|
|
3894
4090
|
let { block } = ctx;
|
|
@@ -3910,13 +4106,17 @@ class CodeGenerator {
|
|
|
3910
4106
|
else {
|
|
3911
4107
|
if (dynamic) {
|
|
3912
4108
|
let name = this.generateId("slot");
|
|
3913
|
-
this.
|
|
3914
|
-
blockString = `toggler(${name}, callSlot(ctx, node, key, ${name}
|
|
4109
|
+
this.define(name, slotName);
|
|
4110
|
+
blockString = `toggler(${name}, callSlot(ctx, node, key, ${name}, ${dynamic}, ${scope}))`;
|
|
3915
4111
|
}
|
|
3916
4112
|
else {
|
|
3917
4113
|
blockString = `callSlot(ctx, node, key, ${slotName}, ${dynamic}, ${scope})`;
|
|
3918
4114
|
}
|
|
3919
4115
|
}
|
|
4116
|
+
// event handling
|
|
4117
|
+
if (ast.on) {
|
|
4118
|
+
blockString = this.wrapWithEventCatcher(blockString, ast.on);
|
|
4119
|
+
}
|
|
3920
4120
|
if (block) {
|
|
3921
4121
|
this.insertAnchor(block);
|
|
3922
4122
|
}
|
|
@@ -3937,7 +4137,7 @@ class CodeGenerator {
|
|
|
3937
4137
|
if (this.target.loopLevel || !this.hasSafeContext) {
|
|
3938
4138
|
ctxStr = this.generateId("ctx");
|
|
3939
4139
|
this.helpers.add("capture");
|
|
3940
|
-
this.
|
|
4140
|
+
this.define(ctxStr, `capture(ctx);`);
|
|
3941
4141
|
}
|
|
3942
4142
|
const blockString = `component(Portal, {target: ${ast.target},slots: {'default': {__render: ${name}, __ctx: ${ctxStr}}}}, key + \`${key}\`, node, ctx)`;
|
|
3943
4143
|
if (block) {
|
|
@@ -4071,8 +4271,8 @@ function parseDOMNode(node, ctx) {
|
|
|
4071
4271
|
const ref = node.getAttribute("t-ref");
|
|
4072
4272
|
node.removeAttribute("t-ref");
|
|
4073
4273
|
const nodeAttrsNames = node.getAttributeNames();
|
|
4074
|
-
|
|
4075
|
-
|
|
4274
|
+
let attrs = null;
|
|
4275
|
+
let on = null;
|
|
4076
4276
|
let model = null;
|
|
4077
4277
|
for (let attr of nodeAttrsNames) {
|
|
4078
4278
|
const value = node.getAttribute(attr);
|
|
@@ -4080,6 +4280,7 @@ function parseDOMNode(node, ctx) {
|
|
|
4080
4280
|
if (attr === "t-on") {
|
|
4081
4281
|
throw new Error("Missing event name with t-on directive");
|
|
4082
4282
|
}
|
|
4283
|
+
on = on || {};
|
|
4083
4284
|
on[attr.slice(5)] = value;
|
|
4084
4285
|
}
|
|
4085
4286
|
else if (attr.startsWith("t-model")) {
|
|
@@ -4117,6 +4318,7 @@ function parseDOMNode(node, ctx) {
|
|
|
4117
4318
|
targetAttr: isCheckboxInput ? "checked" : "value",
|
|
4118
4319
|
specialInitTargetAttr: isRadioInput ? "checked" : null,
|
|
4119
4320
|
eventType,
|
|
4321
|
+
hasDynamicChildren: false,
|
|
4120
4322
|
shouldTrim: hasTrimMod && (isOtherInput || isTextarea),
|
|
4121
4323
|
shouldNumberize: hasNumberMod && (isOtherInput || isTextarea),
|
|
4122
4324
|
};
|
|
@@ -4137,6 +4339,7 @@ function parseDOMNode(node, ctx) {
|
|
|
4137
4339
|
if (tModel && ["t-att-value", "t-attf-value"].includes(attr)) {
|
|
4138
4340
|
tModel.hasDynamicChildren = true;
|
|
4139
4341
|
}
|
|
4342
|
+
attrs = attrs || {};
|
|
4140
4343
|
attrs[attr] = value;
|
|
4141
4344
|
}
|
|
4142
4345
|
}
|
|
@@ -4287,7 +4490,7 @@ function parseTCall(node, ctx) {
|
|
|
4287
4490
|
if (ast && ast.type === 11 /* TComponent */) {
|
|
4288
4491
|
return {
|
|
4289
4492
|
...ast,
|
|
4290
|
-
slots: { default: { content: tcall } },
|
|
4493
|
+
slots: { default: { content: tcall, scope: null, on: null, attrs: null } },
|
|
4291
4494
|
};
|
|
4292
4495
|
}
|
|
4293
4496
|
}
|
|
@@ -4371,7 +4574,6 @@ function parseTSetNode(node, ctx) {
|
|
|
4371
4574
|
// -----------------------------------------------------------------------------
|
|
4372
4575
|
// Error messages when trying to use an unsupported directive on a component
|
|
4373
4576
|
const directiveErrorMap = new Map([
|
|
4374
|
-
["t-on", "t-on is no longer supported on components. Consider passing a callback in props."],
|
|
4375
4577
|
[
|
|
4376
4578
|
"t-ref",
|
|
4377
4579
|
"t-ref is no longer supported on components. Consider exposing only the public part of the component's API through a callback prop.",
|
|
@@ -4400,18 +4602,26 @@ function parseComponent(node, ctx) {
|
|
|
4400
4602
|
node.removeAttribute("t-props");
|
|
4401
4603
|
const defaultSlotScope = node.getAttribute("t-slot-scope");
|
|
4402
4604
|
node.removeAttribute("t-slot-scope");
|
|
4403
|
-
|
|
4605
|
+
let on = null;
|
|
4606
|
+
let props = null;
|
|
4404
4607
|
for (let name of node.getAttributeNames()) {
|
|
4405
4608
|
const value = node.getAttribute(name);
|
|
4406
4609
|
if (name.startsWith("t-")) {
|
|
4407
|
-
|
|
4408
|
-
|
|
4610
|
+
if (name.startsWith("t-on-")) {
|
|
4611
|
+
on = on || {};
|
|
4612
|
+
on[name.slice(5)] = value;
|
|
4613
|
+
}
|
|
4614
|
+
else {
|
|
4615
|
+
const message = directiveErrorMap.get(name.split("-").slice(0, 2).join("-"));
|
|
4616
|
+
throw new Error(message || `unsupported directive on Component: ${name}`);
|
|
4617
|
+
}
|
|
4409
4618
|
}
|
|
4410
4619
|
else {
|
|
4620
|
+
props = props || {};
|
|
4411
4621
|
props[name] = value;
|
|
4412
4622
|
}
|
|
4413
4623
|
}
|
|
4414
|
-
|
|
4624
|
+
let slots = null;
|
|
4415
4625
|
if (node.hasChildNodes()) {
|
|
4416
4626
|
const clone = node.cloneNode(true);
|
|
4417
4627
|
// named slots
|
|
@@ -4438,33 +4648,35 @@ function parseComponent(node, ctx) {
|
|
|
4438
4648
|
slotNode.removeAttribute("t-set-slot");
|
|
4439
4649
|
slotNode.remove();
|
|
4440
4650
|
const slotAst = parseNode(slotNode, ctx);
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4651
|
+
let on = null;
|
|
4652
|
+
let attrs = null;
|
|
4653
|
+
let scope = null;
|
|
4654
|
+
for (let attributeName of slotNode.getAttributeNames()) {
|
|
4655
|
+
const value = slotNode.getAttribute(attributeName);
|
|
4656
|
+
if (attributeName === "t-slot-scope") {
|
|
4657
|
+
scope = value;
|
|
4658
|
+
continue;
|
|
4659
|
+
}
|
|
4660
|
+
else if (attributeName.startsWith("t-on-")) {
|
|
4661
|
+
on = on || {};
|
|
4662
|
+
on[attributeName.slice(5)] = value;
|
|
4451
4663
|
}
|
|
4452
|
-
|
|
4453
|
-
|
|
4664
|
+
else {
|
|
4665
|
+
attrs = attrs || {};
|
|
4666
|
+
attrs[attributeName] = value;
|
|
4454
4667
|
}
|
|
4455
|
-
slots[name] = slotInfo;
|
|
4456
4668
|
}
|
|
4669
|
+
slots = slots || {};
|
|
4670
|
+
slots[name] = { content: slotAst, on, attrs, scope };
|
|
4457
4671
|
}
|
|
4458
4672
|
// default slot
|
|
4459
4673
|
const defaultContent = parseChildNodes(clone, ctx);
|
|
4460
4674
|
if (defaultContent) {
|
|
4461
|
-
slots
|
|
4462
|
-
|
|
4463
|
-
slots.default.scope = defaultSlotScope;
|
|
4464
|
-
}
|
|
4675
|
+
slots = slots || {};
|
|
4676
|
+
slots.default = { content: defaultContent, on, attrs: null, scope: defaultSlotScope };
|
|
4465
4677
|
}
|
|
4466
4678
|
}
|
|
4467
|
-
return { type: 11 /* TComponent */, name, isDynamic, dynamicProps, props, slots };
|
|
4679
|
+
return { type: 11 /* TComponent */, name, isDynamic, dynamicProps, props, slots, on };
|
|
4468
4680
|
}
|
|
4469
4681
|
// -----------------------------------------------------------------------------
|
|
4470
4682
|
// Slots
|
|
@@ -4475,15 +4687,24 @@ function parseTSlot(node, ctx) {
|
|
|
4475
4687
|
}
|
|
4476
4688
|
const name = node.getAttribute("t-slot");
|
|
4477
4689
|
node.removeAttribute("t-slot");
|
|
4478
|
-
|
|
4690
|
+
let attrs = null;
|
|
4691
|
+
let on = null;
|
|
4479
4692
|
for (let attributeName of node.getAttributeNames()) {
|
|
4480
4693
|
const value = node.getAttribute(attributeName);
|
|
4481
|
-
|
|
4694
|
+
if (attributeName.startsWith("t-on-")) {
|
|
4695
|
+
on = on || {};
|
|
4696
|
+
on[attributeName.slice(5)] = value;
|
|
4697
|
+
}
|
|
4698
|
+
else {
|
|
4699
|
+
attrs = attrs || {};
|
|
4700
|
+
attrs[attributeName] = value;
|
|
4701
|
+
}
|
|
4482
4702
|
}
|
|
4483
4703
|
return {
|
|
4484
4704
|
type: 14 /* TSlot */,
|
|
4485
4705
|
name,
|
|
4486
4706
|
attrs,
|
|
4707
|
+
on,
|
|
4487
4708
|
defaultContent: parseChildNodes(node, ctx),
|
|
4488
4709
|
};
|
|
4489
4710
|
}
|
|
@@ -4617,68 +4838,423 @@ function normalizeTEsc(el) {
|
|
|
4617
4838
|
el.appendChild(t);
|
|
4618
4839
|
}
|
|
4619
4840
|
}
|
|
4620
|
-
/**
|
|
4621
|
-
* Normalizes the tree inside a given element and do some preliminary validation
|
|
4622
|
-
* on it. This function modifies the Element in place.
|
|
4623
|
-
*
|
|
4624
|
-
* @param el the element containing the tree that should be normalized
|
|
4625
|
-
*/
|
|
4626
|
-
function normalizeXML(el) {
|
|
4627
|
-
normalizeTIf(el);
|
|
4628
|
-
normalizeTEsc(el);
|
|
4841
|
+
/**
|
|
4842
|
+
* Normalizes the tree inside a given element and do some preliminary validation
|
|
4843
|
+
* on it. This function modifies the Element in place.
|
|
4844
|
+
*
|
|
4845
|
+
* @param el the element containing the tree that should be normalized
|
|
4846
|
+
*/
|
|
4847
|
+
function normalizeXML(el) {
|
|
4848
|
+
normalizeTIf(el);
|
|
4849
|
+
normalizeTEsc(el);
|
|
4850
|
+
}
|
|
4851
|
+
/**
|
|
4852
|
+
* Parses an XML string into an XML document, throwing errors on parser errors
|
|
4853
|
+
* instead of returning an XML document containing the parseerror.
|
|
4854
|
+
*
|
|
4855
|
+
* @param xml the string to parse
|
|
4856
|
+
* @returns an XML document corresponding to the content of the string
|
|
4857
|
+
*/
|
|
4858
|
+
function parseXML$1(xml) {
|
|
4859
|
+
const parser = new DOMParser();
|
|
4860
|
+
const doc = parser.parseFromString(xml, "text/xml");
|
|
4861
|
+
if (doc.getElementsByTagName("parsererror").length) {
|
|
4862
|
+
let msg = "Invalid XML in template.";
|
|
4863
|
+
const parsererrorText = doc.getElementsByTagName("parsererror")[0].textContent;
|
|
4864
|
+
if (parsererrorText) {
|
|
4865
|
+
msg += "\nThe parser has produced the following error message:\n" + parsererrorText;
|
|
4866
|
+
const re = /\d+/g;
|
|
4867
|
+
const firstMatch = re.exec(parsererrorText);
|
|
4868
|
+
if (firstMatch) {
|
|
4869
|
+
const lineNumber = Number(firstMatch[0]);
|
|
4870
|
+
const line = xml.split("\n")[lineNumber - 1];
|
|
4871
|
+
const secondMatch = re.exec(parsererrorText);
|
|
4872
|
+
if (line && secondMatch) {
|
|
4873
|
+
const columnIndex = Number(secondMatch[0]) - 1;
|
|
4874
|
+
if (line[columnIndex]) {
|
|
4875
|
+
msg +=
|
|
4876
|
+
`\nThe error might be located at xml line ${lineNumber} column ${columnIndex}\n` +
|
|
4877
|
+
`${line}\n${"-".repeat(columnIndex - 1)}^`;
|
|
4878
|
+
}
|
|
4879
|
+
}
|
|
4880
|
+
}
|
|
4881
|
+
}
|
|
4882
|
+
throw new Error(msg);
|
|
4883
|
+
}
|
|
4884
|
+
return doc;
|
|
4885
|
+
}
|
|
4886
|
+
|
|
4887
|
+
function compile(template, options = {}) {
|
|
4888
|
+
// parsing
|
|
4889
|
+
const ast = parse(template);
|
|
4890
|
+
// some work
|
|
4891
|
+
const hasSafeContext = template instanceof Node
|
|
4892
|
+
? !(template instanceof Element) || template.querySelector("[t-set], [t-call]") === null
|
|
4893
|
+
: !template.includes("t-set") && !template.includes("t-call");
|
|
4894
|
+
// code generation
|
|
4895
|
+
const codeGenerator = new CodeGenerator(ast, { ...options, hasSafeContext });
|
|
4896
|
+
const code = codeGenerator.generateCode();
|
|
4897
|
+
// template function
|
|
4898
|
+
return new Function("bdom, helpers", code);
|
|
4899
|
+
}
|
|
4900
|
+
|
|
4901
|
+
const TIMEOUT = Symbol("timeout");
|
|
4902
|
+
function wrapError(fn, hookName) {
|
|
4903
|
+
const error = new Error(`The following error occurred in ${hookName}: `);
|
|
4904
|
+
const timeoutError = new Error(`${hookName}'s promise hasn't resolved after 3 seconds`);
|
|
4905
|
+
const node = getCurrent();
|
|
4906
|
+
return (...args) => {
|
|
4907
|
+
try {
|
|
4908
|
+
const result = fn(...args);
|
|
4909
|
+
if (result instanceof Promise) {
|
|
4910
|
+
if (hookName === "onWillStart" || hookName === "onWillUpdateProps") {
|
|
4911
|
+
const fiber = node.fiber;
|
|
4912
|
+
Promise.race([
|
|
4913
|
+
result,
|
|
4914
|
+
new Promise((resolve) => setTimeout(() => resolve(TIMEOUT), 3000)),
|
|
4915
|
+
]).then((res) => {
|
|
4916
|
+
if (res === TIMEOUT && node.fiber === fiber) {
|
|
4917
|
+
console.warn(timeoutError);
|
|
4918
|
+
}
|
|
4919
|
+
});
|
|
4920
|
+
}
|
|
4921
|
+
return result.catch((cause) => {
|
|
4922
|
+
error.cause = cause;
|
|
4923
|
+
if (cause instanceof Error) {
|
|
4924
|
+
error.message += `"${cause.message}"`;
|
|
4925
|
+
}
|
|
4926
|
+
throw error;
|
|
4927
|
+
});
|
|
4928
|
+
}
|
|
4929
|
+
return result;
|
|
4930
|
+
}
|
|
4931
|
+
catch (cause) {
|
|
4932
|
+
if (cause instanceof Error) {
|
|
4933
|
+
error.message += `"${cause.message}"`;
|
|
4934
|
+
}
|
|
4935
|
+
throw error;
|
|
4936
|
+
}
|
|
4937
|
+
};
|
|
4938
|
+
}
|
|
4939
|
+
// -----------------------------------------------------------------------------
|
|
4940
|
+
// hooks
|
|
4941
|
+
// -----------------------------------------------------------------------------
|
|
4942
|
+
function onWillStart(fn) {
|
|
4943
|
+
const node = getCurrent();
|
|
4944
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4945
|
+
node.willStart.push(decorate(fn.bind(node.component), "onWillStart"));
|
|
4946
|
+
}
|
|
4947
|
+
function onWillUpdateProps(fn) {
|
|
4948
|
+
const node = getCurrent();
|
|
4949
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4950
|
+
node.willUpdateProps.push(decorate(fn.bind(node.component), "onWillUpdateProps"));
|
|
4951
|
+
}
|
|
4952
|
+
function onMounted(fn) {
|
|
4953
|
+
const node = getCurrent();
|
|
4954
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4955
|
+
node.mounted.push(decorate(fn.bind(node.component), "onMounted"));
|
|
4956
|
+
}
|
|
4957
|
+
function onWillPatch(fn) {
|
|
4958
|
+
const node = getCurrent();
|
|
4959
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4960
|
+
node.willPatch.unshift(decorate(fn.bind(node.component), "onWillPatch"));
|
|
4961
|
+
}
|
|
4962
|
+
function onPatched(fn) {
|
|
4963
|
+
const node = getCurrent();
|
|
4964
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4965
|
+
node.patched.push(decorate(fn.bind(node.component), "onPatched"));
|
|
4966
|
+
}
|
|
4967
|
+
function onWillUnmount(fn) {
|
|
4968
|
+
const node = getCurrent();
|
|
4969
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4970
|
+
node.willUnmount.unshift(decorate(fn.bind(node.component), "onWillUnmount"));
|
|
4971
|
+
}
|
|
4972
|
+
function onWillDestroy(fn) {
|
|
4973
|
+
const node = getCurrent();
|
|
4974
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4975
|
+
node.willDestroy.push(decorate(fn.bind(node.component), "onWillDestroy"));
|
|
4976
|
+
}
|
|
4977
|
+
function onWillRender(fn) {
|
|
4978
|
+
const node = getCurrent();
|
|
4979
|
+
const renderFn = node.renderFn;
|
|
4980
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4981
|
+
fn = decorate(fn.bind(node.component), "onWillRender");
|
|
4982
|
+
node.renderFn = () => {
|
|
4983
|
+
fn();
|
|
4984
|
+
return renderFn();
|
|
4985
|
+
};
|
|
4986
|
+
}
|
|
4987
|
+
function onRendered(fn) {
|
|
4988
|
+
const node = getCurrent();
|
|
4989
|
+
const renderFn = node.renderFn;
|
|
4990
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4991
|
+
fn = decorate(fn.bind(node.component), "onRendered");
|
|
4992
|
+
node.renderFn = () => {
|
|
4993
|
+
const result = renderFn();
|
|
4994
|
+
fn();
|
|
4995
|
+
return result;
|
|
4996
|
+
};
|
|
4997
|
+
}
|
|
4998
|
+
function onError(callback) {
|
|
4999
|
+
const node = getCurrent();
|
|
5000
|
+
let handlers = nodeErrorHandlers.get(node);
|
|
5001
|
+
if (!handlers) {
|
|
5002
|
+
handlers = [];
|
|
5003
|
+
nodeErrorHandlers.set(node, handlers);
|
|
5004
|
+
}
|
|
5005
|
+
handlers.push(callback.bind(node.component));
|
|
5006
|
+
}
|
|
5007
|
+
|
|
5008
|
+
class Component {
|
|
5009
|
+
constructor(props, env, node) {
|
|
5010
|
+
this.props = props;
|
|
5011
|
+
this.env = env;
|
|
5012
|
+
this.__owl__ = node;
|
|
5013
|
+
}
|
|
5014
|
+
setup() { }
|
|
5015
|
+
render(deep = false) {
|
|
5016
|
+
this.__owl__.render(deep === true);
|
|
5017
|
+
}
|
|
5018
|
+
}
|
|
5019
|
+
Component.template = "";
|
|
5020
|
+
|
|
5021
|
+
const VText = text("").constructor;
|
|
5022
|
+
class VPortal extends VText {
|
|
5023
|
+
constructor(selector, realBDom) {
|
|
5024
|
+
super("");
|
|
5025
|
+
this.target = null;
|
|
5026
|
+
this.selector = selector;
|
|
5027
|
+
this.realBDom = realBDom;
|
|
5028
|
+
}
|
|
5029
|
+
mount(parent, anchor) {
|
|
5030
|
+
super.mount(parent, anchor);
|
|
5031
|
+
this.target = document.querySelector(this.selector);
|
|
5032
|
+
if (!this.target) {
|
|
5033
|
+
let el = this.el;
|
|
5034
|
+
while (el && el.parentElement instanceof HTMLElement) {
|
|
5035
|
+
el = el.parentElement;
|
|
5036
|
+
}
|
|
5037
|
+
this.target = el && el.querySelector(this.selector);
|
|
5038
|
+
if (!this.target) {
|
|
5039
|
+
throw new Error("invalid portal target");
|
|
5040
|
+
}
|
|
5041
|
+
}
|
|
5042
|
+
this.realBDom.mount(this.target, null);
|
|
5043
|
+
}
|
|
5044
|
+
beforeRemove() {
|
|
5045
|
+
this.realBDom.beforeRemove();
|
|
5046
|
+
}
|
|
5047
|
+
remove() {
|
|
5048
|
+
if (this.realBDom) {
|
|
5049
|
+
super.remove();
|
|
5050
|
+
this.realBDom.remove();
|
|
5051
|
+
this.realBDom = null;
|
|
5052
|
+
}
|
|
5053
|
+
}
|
|
5054
|
+
patch(other) {
|
|
5055
|
+
super.patch(other);
|
|
5056
|
+
if (this.realBDom) {
|
|
5057
|
+
this.realBDom.patch(other.realBDom, true);
|
|
5058
|
+
}
|
|
5059
|
+
else {
|
|
5060
|
+
this.realBDom = other.realBDom;
|
|
5061
|
+
this.realBDom.mount(this.target, null);
|
|
5062
|
+
}
|
|
5063
|
+
}
|
|
5064
|
+
}
|
|
5065
|
+
class Portal extends Component {
|
|
5066
|
+
setup() {
|
|
5067
|
+
const node = this.__owl__;
|
|
5068
|
+
const renderFn = node.renderFn;
|
|
5069
|
+
node.renderFn = () => new VPortal(this.props.target, renderFn());
|
|
5070
|
+
onWillUnmount(() => {
|
|
5071
|
+
if (node.bdom) {
|
|
5072
|
+
node.bdom.remove();
|
|
5073
|
+
}
|
|
5074
|
+
});
|
|
5075
|
+
}
|
|
5076
|
+
}
|
|
5077
|
+
Portal.template = xml `<t t-slot="default"/>`;
|
|
5078
|
+
Portal.props = {
|
|
5079
|
+
target: {
|
|
5080
|
+
type: String,
|
|
5081
|
+
},
|
|
5082
|
+
slots: true,
|
|
5083
|
+
};
|
|
5084
|
+
|
|
5085
|
+
/**
|
|
5086
|
+
* This file contains utility functions that will be injected in each template,
|
|
5087
|
+
* to perform various useful tasks in the compiled code.
|
|
5088
|
+
*/
|
|
5089
|
+
function withDefault(value, defaultValue) {
|
|
5090
|
+
return value === undefined || value === null || value === false ? defaultValue : value;
|
|
5091
|
+
}
|
|
5092
|
+
function callSlot(ctx, parent, key, name, dynamic, extra, defaultContent) {
|
|
5093
|
+
key = key + "__slot_" + name;
|
|
5094
|
+
const slots = ctx.props[TARGET].slots || {};
|
|
5095
|
+
const { __render, __ctx, __scope } = slots[name] || {};
|
|
5096
|
+
const slotScope = Object.create(__ctx || {});
|
|
5097
|
+
if (__scope) {
|
|
5098
|
+
slotScope[__scope] = extra;
|
|
5099
|
+
}
|
|
5100
|
+
const slotBDom = __render ? __render.call(__ctx.__owl__.component, slotScope, parent, key) : null;
|
|
5101
|
+
if (defaultContent) {
|
|
5102
|
+
let child1 = undefined;
|
|
5103
|
+
let child2 = undefined;
|
|
5104
|
+
if (slotBDom) {
|
|
5105
|
+
child1 = dynamic ? toggler(name, slotBDom) : slotBDom;
|
|
5106
|
+
}
|
|
5107
|
+
else {
|
|
5108
|
+
child2 = defaultContent.call(ctx.__owl__.component, ctx, parent, key);
|
|
5109
|
+
}
|
|
5110
|
+
return multi([child1, child2]);
|
|
5111
|
+
}
|
|
5112
|
+
return slotBDom || text("");
|
|
5113
|
+
}
|
|
5114
|
+
function capture(ctx) {
|
|
5115
|
+
const component = ctx.__owl__.component;
|
|
5116
|
+
const result = Object.create(component);
|
|
5117
|
+
for (let k in ctx) {
|
|
5118
|
+
result[k] = ctx[k];
|
|
5119
|
+
}
|
|
5120
|
+
return result;
|
|
5121
|
+
}
|
|
5122
|
+
function withKey(elem, k) {
|
|
5123
|
+
elem.key = k;
|
|
5124
|
+
return elem;
|
|
5125
|
+
}
|
|
5126
|
+
function prepareList(collection) {
|
|
5127
|
+
let keys;
|
|
5128
|
+
let values;
|
|
5129
|
+
if (Array.isArray(collection)) {
|
|
5130
|
+
keys = collection;
|
|
5131
|
+
values = collection;
|
|
5132
|
+
}
|
|
5133
|
+
else if (collection) {
|
|
5134
|
+
values = Object.keys(collection);
|
|
5135
|
+
keys = Object.values(collection);
|
|
5136
|
+
}
|
|
5137
|
+
else {
|
|
5138
|
+
throw new Error("Invalid loop expression");
|
|
5139
|
+
}
|
|
5140
|
+
const n = values.length;
|
|
5141
|
+
return [keys, values, n, new Array(n)];
|
|
5142
|
+
}
|
|
5143
|
+
const isBoundary = Symbol("isBoundary");
|
|
5144
|
+
function setContextValue(ctx, key, value) {
|
|
5145
|
+
const ctx0 = ctx;
|
|
5146
|
+
while (!ctx.hasOwnProperty(key) && !ctx.hasOwnProperty(isBoundary)) {
|
|
5147
|
+
const newCtx = ctx.__proto__;
|
|
5148
|
+
if (!newCtx) {
|
|
5149
|
+
ctx = ctx0;
|
|
5150
|
+
break;
|
|
5151
|
+
}
|
|
5152
|
+
ctx = newCtx;
|
|
5153
|
+
}
|
|
5154
|
+
ctx[key] = value;
|
|
5155
|
+
}
|
|
5156
|
+
function toNumber(val) {
|
|
5157
|
+
const n = parseFloat(val);
|
|
5158
|
+
return isNaN(n) ? val : n;
|
|
5159
|
+
}
|
|
5160
|
+
function shallowEqual(l1, l2) {
|
|
5161
|
+
for (let i = 0, l = l1.length; i < l; i++) {
|
|
5162
|
+
if (l1[i] !== l2[i]) {
|
|
5163
|
+
return false;
|
|
5164
|
+
}
|
|
5165
|
+
}
|
|
5166
|
+
return true;
|
|
5167
|
+
}
|
|
5168
|
+
class LazyValue {
|
|
5169
|
+
constructor(fn, ctx, node) {
|
|
5170
|
+
this.fn = fn;
|
|
5171
|
+
this.ctx = capture(ctx);
|
|
5172
|
+
this.node = node;
|
|
5173
|
+
}
|
|
5174
|
+
evaluate() {
|
|
5175
|
+
return this.fn(this.ctx, this.node);
|
|
5176
|
+
}
|
|
5177
|
+
toString() {
|
|
5178
|
+
return this.evaluate().toString();
|
|
5179
|
+
}
|
|
5180
|
+
}
|
|
5181
|
+
/*
|
|
5182
|
+
* Safely outputs `value` as a block depending on the nature of `value`
|
|
5183
|
+
*/
|
|
5184
|
+
function safeOutput(value) {
|
|
5185
|
+
if (!value) {
|
|
5186
|
+
return value;
|
|
5187
|
+
}
|
|
5188
|
+
let safeKey;
|
|
5189
|
+
let block;
|
|
5190
|
+
if (value instanceof Markup) {
|
|
5191
|
+
safeKey = `string_safe`;
|
|
5192
|
+
block = html(value);
|
|
5193
|
+
}
|
|
5194
|
+
else if (value instanceof LazyValue) {
|
|
5195
|
+
safeKey = `lazy_value`;
|
|
5196
|
+
block = value.evaluate();
|
|
5197
|
+
}
|
|
5198
|
+
else if (value instanceof String || typeof value === "string") {
|
|
5199
|
+
safeKey = "string_unsafe";
|
|
5200
|
+
block = text(value);
|
|
5201
|
+
}
|
|
5202
|
+
else {
|
|
5203
|
+
// Assuming it is a block
|
|
5204
|
+
safeKey = "block_safe";
|
|
5205
|
+
block = value;
|
|
5206
|
+
}
|
|
5207
|
+
return toggler(safeKey, block);
|
|
5208
|
+
}
|
|
5209
|
+
let boundFunctions = new WeakMap();
|
|
5210
|
+
function bind(ctx, fn) {
|
|
5211
|
+
let component = ctx.__owl__.component;
|
|
5212
|
+
let boundFnMap = boundFunctions.get(component);
|
|
5213
|
+
if (!boundFnMap) {
|
|
5214
|
+
boundFnMap = new WeakMap();
|
|
5215
|
+
boundFunctions.set(component, boundFnMap);
|
|
5216
|
+
}
|
|
5217
|
+
let boundFn = boundFnMap.get(fn);
|
|
5218
|
+
if (!boundFn) {
|
|
5219
|
+
boundFn = fn.bind(component);
|
|
5220
|
+
boundFnMap.set(fn, boundFn);
|
|
5221
|
+
}
|
|
5222
|
+
return boundFn;
|
|
4629
5223
|
}
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
function parseXML$1(xml) {
|
|
4638
|
-
const parser = new DOMParser();
|
|
4639
|
-
const doc = parser.parseFromString(xml, "text/xml");
|
|
4640
|
-
if (doc.getElementsByTagName("parsererror").length) {
|
|
4641
|
-
let msg = "Invalid XML in template.";
|
|
4642
|
-
const parsererrorText = doc.getElementsByTagName("parsererror")[0].textContent;
|
|
4643
|
-
if (parsererrorText) {
|
|
4644
|
-
msg += "\nThe parser has produced the following error message:\n" + parsererrorText;
|
|
4645
|
-
const re = /\d+/g;
|
|
4646
|
-
const firstMatch = re.exec(parsererrorText);
|
|
4647
|
-
if (firstMatch) {
|
|
4648
|
-
const lineNumber = Number(firstMatch[0]);
|
|
4649
|
-
const line = xml.split("\n")[lineNumber - 1];
|
|
4650
|
-
const secondMatch = re.exec(parsererrorText);
|
|
4651
|
-
if (line && secondMatch) {
|
|
4652
|
-
const columnIndex = Number(secondMatch[0]) - 1;
|
|
4653
|
-
if (line[columnIndex]) {
|
|
4654
|
-
msg +=
|
|
4655
|
-
`\nThe error might be located at xml line ${lineNumber} column ${columnIndex}\n` +
|
|
4656
|
-
`${line}\n${"-".repeat(columnIndex - 1)}^`;
|
|
4657
|
-
}
|
|
4658
|
-
}
|
|
5224
|
+
function multiRefSetter(refs, name) {
|
|
5225
|
+
let count = 0;
|
|
5226
|
+
return (el) => {
|
|
5227
|
+
if (el) {
|
|
5228
|
+
count++;
|
|
5229
|
+
if (count > 1) {
|
|
5230
|
+
throw new Error("Cannot have 2 elements with same ref name at the same time");
|
|
4659
5231
|
}
|
|
4660
5232
|
}
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
}
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
5233
|
+
if (count === 0 || el) {
|
|
5234
|
+
refs[name] = el;
|
|
5235
|
+
}
|
|
5236
|
+
};
|
|
5237
|
+
}
|
|
5238
|
+
const helpers = {
|
|
5239
|
+
withDefault,
|
|
5240
|
+
zero: Symbol("zero"),
|
|
5241
|
+
isBoundary,
|
|
5242
|
+
callSlot,
|
|
5243
|
+
capture,
|
|
5244
|
+
withKey,
|
|
5245
|
+
prepareList,
|
|
5246
|
+
setContextValue,
|
|
5247
|
+
multiRefSetter,
|
|
5248
|
+
shallowEqual,
|
|
5249
|
+
toNumber,
|
|
5250
|
+
validateProps,
|
|
5251
|
+
LazyValue,
|
|
5252
|
+
safeOutput,
|
|
5253
|
+
bind,
|
|
5254
|
+
createCatcher,
|
|
5255
|
+
};
|
|
4679
5256
|
|
|
4680
5257
|
const bdom = { text, createBlock, list, multi, html, toggler, component, comment };
|
|
4681
|
-
const globalTemplates = {};
|
|
4682
5258
|
function parseXML(xml) {
|
|
4683
5259
|
const parser = new DOMParser();
|
|
4684
5260
|
const doc = parser.parseFromString(xml, "text/xml");
|
|
@@ -4707,23 +5283,32 @@ function parseXML(xml) {
|
|
|
4707
5283
|
}
|
|
4708
5284
|
return doc;
|
|
4709
5285
|
}
|
|
5286
|
+
/**
|
|
5287
|
+
* Returns the helpers object that will be injected in each template closure
|
|
5288
|
+
* function
|
|
5289
|
+
*/
|
|
5290
|
+
function makeHelpers(getTemplate) {
|
|
5291
|
+
return Object.assign({}, helpers, {
|
|
5292
|
+
Portal,
|
|
5293
|
+
markRaw,
|
|
5294
|
+
getTemplate,
|
|
5295
|
+
call: (owner, subTemplate, ctx, parent, key) => {
|
|
5296
|
+
const template = getTemplate(subTemplate);
|
|
5297
|
+
return toggler(subTemplate, template.call(owner, ctx, parent, key));
|
|
5298
|
+
},
|
|
5299
|
+
});
|
|
5300
|
+
}
|
|
4710
5301
|
class TemplateSet {
|
|
4711
5302
|
constructor(config = {}) {
|
|
4712
5303
|
this.rawTemplates = Object.create(globalTemplates);
|
|
4713
5304
|
this.templates = {};
|
|
4714
|
-
this.utils = Object.assign({}, UTILS, {
|
|
4715
|
-
call: (owner, subTemplate, ctx, parent, key) => {
|
|
4716
|
-
const template = this.getTemplate(subTemplate);
|
|
4717
|
-
return toggler(subTemplate, template.call(owner, ctx, parent, key));
|
|
4718
|
-
},
|
|
4719
|
-
getTemplate: (name) => this.getTemplate(name),
|
|
4720
|
-
});
|
|
4721
5305
|
this.dev = config.dev || false;
|
|
4722
5306
|
this.translateFn = config.translateFn;
|
|
4723
5307
|
this.translatableAttributes = config.translatableAttributes;
|
|
4724
5308
|
if (config.templates) {
|
|
4725
5309
|
this.addTemplates(config.templates);
|
|
4726
5310
|
}
|
|
5311
|
+
this.helpers = makeHelpers(this.getTemplate.bind(this));
|
|
4727
5312
|
}
|
|
4728
5313
|
addTemplate(name, template, options = {}) {
|
|
4729
5314
|
if (name in this.rawTemplates && !options.allowDuplicate) {
|
|
@@ -4761,7 +5346,7 @@ class TemplateSet {
|
|
|
4761
5346
|
this.templates[name] = function (context, parent) {
|
|
4762
5347
|
return templates[name].call(this, context, parent);
|
|
4763
5348
|
};
|
|
4764
|
-
const template = templateFn(bdom, this.
|
|
5349
|
+
const template = templateFn(bdom, this.helpers);
|
|
4765
5350
|
this.templates[name] = template;
|
|
4766
5351
|
}
|
|
4767
5352
|
return this.templates[name];
|
|
@@ -4774,160 +5359,9 @@ class TemplateSet {
|
|
|
4774
5359
|
translatableAttributes: this.translatableAttributes,
|
|
4775
5360
|
});
|
|
4776
5361
|
}
|
|
4777
|
-
}
|
|
4778
|
-
// -----------------------------------------------------------------------------
|
|
4779
|
-
// xml tag helper
|
|
4780
|
-
// -----------------------------------------------------------------------------
|
|
4781
|
-
function xml(...args) {
|
|
4782
|
-
const name = `__template__${xml.nextId++}`;
|
|
4783
|
-
const value = String.raw(...args);
|
|
4784
|
-
globalTemplates[name] = value;
|
|
4785
|
-
return name;
|
|
4786
|
-
}
|
|
4787
|
-
xml.nextId = 1;
|
|
4788
|
-
|
|
4789
|
-
class Component {
|
|
4790
|
-
constructor(props, env, node) {
|
|
4791
|
-
this.props = props;
|
|
4792
|
-
this.env = env;
|
|
4793
|
-
this.__owl__ = node;
|
|
4794
|
-
}
|
|
4795
|
-
setup() { }
|
|
4796
|
-
render(deep = false) {
|
|
4797
|
-
this.__owl__.render(deep);
|
|
4798
|
-
}
|
|
4799
|
-
}
|
|
4800
|
-
Component.template = "";
|
|
4801
|
-
|
|
4802
|
-
const VText = text("").constructor;
|
|
4803
|
-
class VPortal extends VText {
|
|
4804
|
-
constructor(selector, realBDom) {
|
|
4805
|
-
super("");
|
|
4806
|
-
this.target = null;
|
|
4807
|
-
this.selector = selector;
|
|
4808
|
-
this.realBDom = realBDom;
|
|
4809
|
-
}
|
|
4810
|
-
mount(parent, anchor) {
|
|
4811
|
-
super.mount(parent, anchor);
|
|
4812
|
-
this.target = document.querySelector(this.selector);
|
|
4813
|
-
if (!this.target) {
|
|
4814
|
-
let el = this.el;
|
|
4815
|
-
while (el && el.parentElement instanceof HTMLElement) {
|
|
4816
|
-
el = el.parentElement;
|
|
4817
|
-
}
|
|
4818
|
-
this.target = el && el.querySelector(this.selector);
|
|
4819
|
-
if (!this.target) {
|
|
4820
|
-
throw new Error("invalid portal target");
|
|
4821
|
-
}
|
|
4822
|
-
}
|
|
4823
|
-
this.realBDom.mount(this.target, null);
|
|
4824
|
-
}
|
|
4825
|
-
beforeRemove() {
|
|
4826
|
-
this.realBDom.beforeRemove();
|
|
4827
|
-
}
|
|
4828
|
-
remove() {
|
|
4829
|
-
if (this.realBDom) {
|
|
4830
|
-
super.remove();
|
|
4831
|
-
this.realBDom.remove();
|
|
4832
|
-
this.realBDom = null;
|
|
4833
|
-
}
|
|
4834
|
-
}
|
|
4835
|
-
patch(other) {
|
|
4836
|
-
super.patch(other);
|
|
4837
|
-
if (this.realBDom) {
|
|
4838
|
-
this.realBDom.patch(other.realBDom, true);
|
|
4839
|
-
}
|
|
4840
|
-
else {
|
|
4841
|
-
this.realBDom = other.realBDom;
|
|
4842
|
-
this.realBDom.mount(this.target, null);
|
|
4843
|
-
}
|
|
4844
|
-
}
|
|
4845
|
-
}
|
|
4846
|
-
class Portal extends Component {
|
|
4847
|
-
setup() {
|
|
4848
|
-
const node = this.__owl__;
|
|
4849
|
-
const renderFn = node.renderFn;
|
|
4850
|
-
node.renderFn = () => new VPortal(this.props.target, renderFn());
|
|
4851
|
-
onWillUnmount(() => {
|
|
4852
|
-
if (node.bdom) {
|
|
4853
|
-
node.bdom.remove();
|
|
4854
|
-
}
|
|
4855
|
-
});
|
|
4856
|
-
}
|
|
4857
|
-
}
|
|
4858
|
-
Portal.template = xml `<t t-slot="default"/>`;
|
|
4859
|
-
Portal.props = {
|
|
4860
|
-
target: {
|
|
4861
|
-
type: String,
|
|
4862
|
-
},
|
|
4863
|
-
slots: true,
|
|
4864
|
-
};
|
|
4865
|
-
|
|
4866
|
-
// -----------------------------------------------------------------------------
|
|
4867
|
-
// Scheduler
|
|
4868
|
-
// -----------------------------------------------------------------------------
|
|
4869
|
-
class Scheduler {
|
|
4870
|
-
constructor() {
|
|
4871
|
-
this.tasks = new Set();
|
|
4872
|
-
this.isRunning = false;
|
|
4873
|
-
this.requestAnimationFrame = Scheduler.requestAnimationFrame;
|
|
4874
|
-
}
|
|
4875
|
-
start() {
|
|
4876
|
-
this.isRunning = true;
|
|
4877
|
-
this.scheduleTasks();
|
|
4878
|
-
}
|
|
4879
|
-
stop() {
|
|
4880
|
-
this.isRunning = false;
|
|
4881
|
-
}
|
|
4882
|
-
addFiber(fiber) {
|
|
4883
|
-
this.tasks.add(fiber.root);
|
|
4884
|
-
if (!this.isRunning) {
|
|
4885
|
-
this.start();
|
|
4886
|
-
}
|
|
4887
|
-
}
|
|
4888
|
-
/**
|
|
4889
|
-
* Process all current tasks. This only applies to the fibers that are ready.
|
|
4890
|
-
* Other tasks are left unchanged.
|
|
4891
|
-
*/
|
|
4892
|
-
flush() {
|
|
4893
|
-
this.tasks.forEach((fiber) => {
|
|
4894
|
-
if (fiber.root !== fiber) {
|
|
4895
|
-
this.tasks.delete(fiber);
|
|
4896
|
-
return;
|
|
4897
|
-
}
|
|
4898
|
-
const hasError = fibersInError.has(fiber);
|
|
4899
|
-
if (hasError && fiber.counter !== 0) {
|
|
4900
|
-
this.tasks.delete(fiber);
|
|
4901
|
-
return;
|
|
4902
|
-
}
|
|
4903
|
-
if (fiber.node.status === 2 /* DESTROYED */) {
|
|
4904
|
-
this.tasks.delete(fiber);
|
|
4905
|
-
return;
|
|
4906
|
-
}
|
|
4907
|
-
if (fiber.counter === 0) {
|
|
4908
|
-
if (!hasError) {
|
|
4909
|
-
fiber.complete();
|
|
4910
|
-
}
|
|
4911
|
-
this.tasks.delete(fiber);
|
|
4912
|
-
}
|
|
4913
|
-
});
|
|
4914
|
-
if (this.tasks.size === 0) {
|
|
4915
|
-
this.stop();
|
|
4916
|
-
}
|
|
4917
|
-
}
|
|
4918
|
-
scheduleTasks() {
|
|
4919
|
-
this.requestAnimationFrame(() => {
|
|
4920
|
-
this.flush();
|
|
4921
|
-
if (this.isRunning) {
|
|
4922
|
-
this.scheduleTasks();
|
|
4923
|
-
}
|
|
4924
|
-
});
|
|
4925
|
-
}
|
|
4926
|
-
}
|
|
4927
|
-
// capture the value of requestAnimationFrame as soon as possible, to avoid
|
|
4928
|
-
// interactions with other code, such as test frameworks that override them
|
|
4929
|
-
Scheduler.requestAnimationFrame = window.requestAnimationFrame.bind(window);
|
|
5362
|
+
}
|
|
4930
5363
|
|
|
5364
|
+
let hasBeenLogged = false;
|
|
4931
5365
|
const DEV_MSG = () => {
|
|
4932
5366
|
const hash = window.owl ? window.owl.__info__.hash : "master";
|
|
4933
5367
|
return `Owl is running in 'dev' mode.
|
|
@@ -4944,11 +5378,13 @@ class App extends TemplateSet {
|
|
|
4944
5378
|
if (config.test) {
|
|
4945
5379
|
this.dev = true;
|
|
4946
5380
|
}
|
|
4947
|
-
if (this.dev && !config.test) {
|
|
5381
|
+
if (this.dev && !config.test && !hasBeenLogged) {
|
|
4948
5382
|
console.info(DEV_MSG());
|
|
5383
|
+
hasBeenLogged = true;
|
|
4949
5384
|
}
|
|
4950
|
-
const
|
|
4951
|
-
|
|
5385
|
+
const env = config.env || {};
|
|
5386
|
+
const descrs = Object.getOwnPropertyDescriptors(env);
|
|
5387
|
+
this.env = Object.freeze(Object.create(Object.getPrototypeOf(env), descrs));
|
|
4952
5388
|
this.props = config.props || {};
|
|
4953
5389
|
}
|
|
4954
5390
|
mount(target, options) {
|
|
@@ -4959,7 +5395,7 @@ class App extends TemplateSet {
|
|
|
4959
5395
|
return prom;
|
|
4960
5396
|
}
|
|
4961
5397
|
makeNode(Component, props) {
|
|
4962
|
-
return new ComponentNode(Component, props, this);
|
|
5398
|
+
return new ComponentNode(Component, props, this, null, null);
|
|
4963
5399
|
}
|
|
4964
5400
|
mountNode(node, target, options) {
|
|
4965
5401
|
const promise = new Promise((resolve, reject) => {
|
|
@@ -4991,6 +5427,7 @@ class App extends TemplateSet {
|
|
|
4991
5427
|
}
|
|
4992
5428
|
destroy() {
|
|
4993
5429
|
if (this.root) {
|
|
5430
|
+
this.scheduler.flush();
|
|
4994
5431
|
this.root.destroy();
|
|
4995
5432
|
}
|
|
4996
5433
|
}
|
|
@@ -5011,45 +5448,6 @@ function status(component) {
|
|
|
5011
5448
|
}
|
|
5012
5449
|
}
|
|
5013
5450
|
|
|
5014
|
-
class Memo extends Component {
|
|
5015
|
-
constructor(props, env, node) {
|
|
5016
|
-
super(props, env, node);
|
|
5017
|
-
// prevent patching process conditionally
|
|
5018
|
-
let applyPatch = false;
|
|
5019
|
-
const patchFn = node.patch;
|
|
5020
|
-
node.patch = () => {
|
|
5021
|
-
if (applyPatch) {
|
|
5022
|
-
patchFn.call(node);
|
|
5023
|
-
applyPatch = false;
|
|
5024
|
-
}
|
|
5025
|
-
};
|
|
5026
|
-
// check props change, and render/apply patch if it changed
|
|
5027
|
-
let prevProps = props;
|
|
5028
|
-
const updateAndRender = node.updateAndRender;
|
|
5029
|
-
node.updateAndRender = function (props, parentFiber) {
|
|
5030
|
-
const shouldUpdate = !shallowEqual(prevProps, props);
|
|
5031
|
-
if (shouldUpdate) {
|
|
5032
|
-
prevProps = props;
|
|
5033
|
-
updateAndRender.call(node, props, parentFiber);
|
|
5034
|
-
applyPatch = true;
|
|
5035
|
-
}
|
|
5036
|
-
return Promise.resolve();
|
|
5037
|
-
};
|
|
5038
|
-
}
|
|
5039
|
-
}
|
|
5040
|
-
Memo.template = xml `<t t-slot="default"/>`;
|
|
5041
|
-
/**
|
|
5042
|
-
* we assume that each object have the same set of keys
|
|
5043
|
-
*/
|
|
5044
|
-
function shallowEqual(p1, p2) {
|
|
5045
|
-
for (let k in p1) {
|
|
5046
|
-
if (k !== "slots" && p1[k] !== p2[k]) {
|
|
5047
|
-
return false;
|
|
5048
|
-
}
|
|
5049
|
-
}
|
|
5050
|
-
return true;
|
|
5051
|
-
}
|
|
5052
|
-
|
|
5053
5451
|
// -----------------------------------------------------------------------------
|
|
5054
5452
|
// useRef
|
|
5055
5453
|
// -----------------------------------------------------------------------------
|
|
@@ -5095,10 +5493,6 @@ function useChildSubEnv(envExtension) {
|
|
|
5095
5493
|
const node = getCurrent();
|
|
5096
5494
|
node.childEnv = extendEnv(node.childEnv, envExtension);
|
|
5097
5495
|
}
|
|
5098
|
-
// -----------------------------------------------------------------------------
|
|
5099
|
-
// useEffect
|
|
5100
|
-
// -----------------------------------------------------------------------------
|
|
5101
|
-
const NO_OP = () => { };
|
|
5102
5496
|
/**
|
|
5103
5497
|
* This hook will run a callback when a component is mounted and patched, and
|
|
5104
5498
|
* will run a cleanup function before patching and before unmounting the
|
|
@@ -5116,18 +5510,20 @@ function useEffect(effect, computeDependencies = () => [NaN]) {
|
|
|
5116
5510
|
let dependencies;
|
|
5117
5511
|
onMounted(() => {
|
|
5118
5512
|
dependencies = computeDependencies();
|
|
5119
|
-
cleanup = effect(...dependencies)
|
|
5513
|
+
cleanup = effect(...dependencies);
|
|
5120
5514
|
});
|
|
5121
5515
|
onPatched(() => {
|
|
5122
5516
|
const newDeps = computeDependencies();
|
|
5123
5517
|
const shouldReapply = newDeps.some((val, i) => val !== dependencies[i]);
|
|
5124
5518
|
if (shouldReapply) {
|
|
5125
5519
|
dependencies = newDeps;
|
|
5126
|
-
cleanup
|
|
5127
|
-
|
|
5520
|
+
if (cleanup) {
|
|
5521
|
+
cleanup();
|
|
5522
|
+
}
|
|
5523
|
+
cleanup = effect(...dependencies);
|
|
5128
5524
|
}
|
|
5129
5525
|
});
|
|
5130
|
-
onWillUnmount(() => cleanup());
|
|
5526
|
+
onWillUnmount(() => cleanup && cleanup());
|
|
5131
5527
|
}
|
|
5132
5528
|
// -----------------------------------------------------------------------------
|
|
5133
5529
|
// useExternalListener
|
|
@@ -5154,8 +5550,6 @@ function useExternalListener(target, eventName, handler, eventParams) {
|
|
|
5154
5550
|
|
|
5155
5551
|
config.shouldNormalizeDom = false;
|
|
5156
5552
|
config.mainEventHandler = mainEventHandler;
|
|
5157
|
-
UTILS.Portal = Portal;
|
|
5158
|
-
UTILS.markRaw = markRaw;
|
|
5159
5553
|
const blockDom = {
|
|
5160
5554
|
config,
|
|
5161
5555
|
// bdom entry points
|
|
@@ -5176,7 +5570,6 @@ const __info__ = {};
|
|
|
5176
5570
|
exports.App = App;
|
|
5177
5571
|
exports.Component = Component;
|
|
5178
5572
|
exports.EventBus = EventBus;
|
|
5179
|
-
exports.Memo = Memo;
|
|
5180
5573
|
exports.__info__ = __info__;
|
|
5181
5574
|
exports.blockDom = blockDom;
|
|
5182
5575
|
exports.loadFile = loadFile;
|
|
@@ -5208,7 +5601,7 @@ exports.whenReady = whenReady;
|
|
|
5208
5601
|
exports.xml = xml;
|
|
5209
5602
|
|
|
5210
5603
|
|
|
5211
|
-
__info__.version = '2.0.0-
|
|
5212
|
-
__info__.date = '2022-
|
|
5213
|
-
__info__.hash = '
|
|
5604
|
+
__info__.version = '2.0.0-beta-6';
|
|
5605
|
+
__info__.date = '2022-04-11T09:01:42.605Z';
|
|
5606
|
+
__info__.hash = '41344ef';
|
|
5214
5607
|
__info__.url = 'https://github.com/odoo/owl';
|