@odoo/owl 2.0.0-beta-8 → 2.0.0-beta-11
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/dist/owl.cjs.js +928 -863
- package/dist/owl.es.js +928 -863
- package/dist/owl.iife.js +928 -863
- package/dist/owl.iife.min.js +1 -1
- package/dist/types/compiler/code_generator.d.ts +3 -1
- package/dist/types/compiler/inline_expressions.d.ts +1 -0
- package/dist/types/compiler/parser.d.ts +1 -0
- package/dist/types/owl.d.ts +520 -0
- package/dist/types/runtime/app.d.ts +2 -1
- package/dist/types/runtime/component.d.ts +1 -1
- package/dist/types/runtime/component_node.d.ts +3 -6
- package/dist/types/runtime/template_helpers.d.ts +3 -2
- package/dist/types/runtime/validation.d.ts +4 -2
- package/package.json +3 -2
package/dist/owl.cjs.js
CHANGED
|
@@ -171,6 +171,10 @@ function toClassObj(expr) {
|
|
|
171
171
|
for (let key in expr) {
|
|
172
172
|
const value = expr[key];
|
|
173
173
|
if (value) {
|
|
174
|
+
key = trim.call(key);
|
|
175
|
+
if (!key) {
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
174
178
|
const words = split.call(key, wordRegexp);
|
|
175
179
|
for (let word of words) {
|
|
176
180
|
result[word] = value;
|
|
@@ -1291,29 +1295,35 @@ function html(str) {
|
|
|
1291
1295
|
}
|
|
1292
1296
|
|
|
1293
1297
|
function createCatcher(eventsSpec) {
|
|
1294
|
-
|
|
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;
|
|
1298
|
+
const n = Object.keys(eventsSpec).length;
|
|
1303
1299
|
class VCatcher {
|
|
1304
1300
|
constructor(child, handlers) {
|
|
1301
|
+
this.handlerFns = [];
|
|
1305
1302
|
this.afterNode = null;
|
|
1306
1303
|
this.child = child;
|
|
1307
|
-
this.
|
|
1304
|
+
this.handlerData = handlers;
|
|
1308
1305
|
}
|
|
1309
1306
|
mount(parent, afterNode) {
|
|
1310
1307
|
this.parentEl = parent;
|
|
1311
|
-
this.afterNode = afterNode;
|
|
1312
1308
|
this.child.mount(parent, afterNode);
|
|
1309
|
+
this.afterNode = document.createTextNode("");
|
|
1310
|
+
parent.insertBefore(this.afterNode, afterNode);
|
|
1311
|
+
this.wrapHandlerData();
|
|
1312
|
+
for (let name in eventsSpec) {
|
|
1313
|
+
const index = eventsSpec[name];
|
|
1314
|
+
const handler = createEventHandler(name);
|
|
1315
|
+
this.handlerFns[index] = handler;
|
|
1316
|
+
handler.setup.call(parent, this.handlerData[index]);
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
wrapHandlerData() {
|
|
1313
1320
|
for (let i = 0; i < n; i++) {
|
|
1314
|
-
let
|
|
1321
|
+
let handler = this.handlerData[i];
|
|
1322
|
+
// handler = [...mods, fn, comp], so we need to replace second to last elem
|
|
1323
|
+
let idx = handler.length - 2;
|
|
1324
|
+
let origFn = handler[idx];
|
|
1315
1325
|
const self = this;
|
|
1316
|
-
|
|
1326
|
+
handler[idx] = function (ev) {
|
|
1317
1327
|
const target = ev.target;
|
|
1318
1328
|
let currentNode = self.child.firstNode();
|
|
1319
1329
|
const afterNode = self.afterNode;
|
|
@@ -1324,18 +1334,21 @@ function createCatcher(eventsSpec) {
|
|
|
1324
1334
|
currentNode = currentNode.nextSibling;
|
|
1325
1335
|
}
|
|
1326
1336
|
};
|
|
1327
|
-
setupFns[i].call(parent, this.handlers[i]);
|
|
1328
1337
|
}
|
|
1329
1338
|
}
|
|
1330
1339
|
moveBefore(other, afterNode) {
|
|
1331
|
-
this.afterNode = null;
|
|
1332
1340
|
this.child.moveBefore(other ? other.child : null, afterNode);
|
|
1341
|
+
this.parentEl.insertBefore(this.afterNode, afterNode);
|
|
1333
1342
|
}
|
|
1334
1343
|
patch(other, withBeforeRemove) {
|
|
1335
1344
|
if (this === other) {
|
|
1336
1345
|
return;
|
|
1337
1346
|
}
|
|
1338
|
-
this.
|
|
1347
|
+
this.handlerData = other.handlerData;
|
|
1348
|
+
this.wrapHandlerData();
|
|
1349
|
+
for (let i = 0; i < n; i++) {
|
|
1350
|
+
this.handlerFns[i].update.call(this.parentEl, this.handlerData[i]);
|
|
1351
|
+
}
|
|
1339
1352
|
this.child.patch(other.child, withBeforeRemove);
|
|
1340
1353
|
}
|
|
1341
1354
|
beforeRemove() {
|
|
@@ -1343,9 +1356,10 @@ function createCatcher(eventsSpec) {
|
|
|
1343
1356
|
}
|
|
1344
1357
|
remove() {
|
|
1345
1358
|
for (let i = 0; i < n; i++) {
|
|
1346
|
-
|
|
1359
|
+
this.handlerFns[i].remove.call(this.parentEl);
|
|
1347
1360
|
}
|
|
1348
1361
|
this.child.remove();
|
|
1362
|
+
this.afterNode.remove();
|
|
1349
1363
|
}
|
|
1350
1364
|
firstNode() {
|
|
1351
1365
|
return this.child.firstNode();
|
|
@@ -1372,798 +1386,786 @@ function remove(vnode, withBeforeRemove = false) {
|
|
|
1372
1386
|
vnode.remove();
|
|
1373
1387
|
}
|
|
1374
1388
|
|
|
1375
|
-
//
|
|
1376
|
-
const
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
const KEYCHANGES = Symbol("Key changes");
|
|
1381
|
-
const objectToString = Object.prototype.toString;
|
|
1382
|
-
const objectHasOwnProperty = Object.prototype.hasOwnProperty;
|
|
1383
|
-
const SUPPORTED_RAW_TYPES = new Set(["Object", "Array", "Set", "Map", "WeakMap"]);
|
|
1384
|
-
const COLLECTION_RAWTYPES = new Set(["Set", "Map", "WeakMap"]);
|
|
1385
|
-
/**
|
|
1386
|
-
* extract "RawType" from strings like "[object RawType]" => this lets us ignore
|
|
1387
|
-
* many native objects such as Promise (whose toString is [object Promise])
|
|
1388
|
-
* or Date ([object Date]), while also supporting collections without using
|
|
1389
|
-
* instanceof in a loop
|
|
1390
|
-
*
|
|
1391
|
-
* @param obj the object to check
|
|
1392
|
-
* @returns the raw type of the object
|
|
1393
|
-
*/
|
|
1394
|
-
function rawType(obj) {
|
|
1395
|
-
return objectToString.call(obj).slice(8, -1);
|
|
1396
|
-
}
|
|
1397
|
-
/**
|
|
1398
|
-
* Checks whether a given value can be made into a reactive object.
|
|
1399
|
-
*
|
|
1400
|
-
* @param value the value to check
|
|
1401
|
-
* @returns whether the value can be made reactive
|
|
1402
|
-
*/
|
|
1403
|
-
function canBeMadeReactive(value) {
|
|
1404
|
-
if (typeof value !== "object") {
|
|
1389
|
+
// Maps fibers to thrown errors
|
|
1390
|
+
const fibersInError = new WeakMap();
|
|
1391
|
+
const nodeErrorHandlers = new WeakMap();
|
|
1392
|
+
function _handleError(node, error) {
|
|
1393
|
+
if (!node) {
|
|
1405
1394
|
return false;
|
|
1406
1395
|
}
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
* Creates a reactive from the given object/callback if possible and returns it,
|
|
1411
|
-
* returns the original object otherwise.
|
|
1412
|
-
*
|
|
1413
|
-
* @param value the value make reactive
|
|
1414
|
-
* @returns a reactive for the given object when possible, the original otherwise
|
|
1415
|
-
*/
|
|
1416
|
-
function possiblyReactive(val, cb) {
|
|
1417
|
-
return canBeMadeReactive(val) ? reactive(val, cb) : val;
|
|
1418
|
-
}
|
|
1419
|
-
/**
|
|
1420
|
-
* Mark an object or array so that it is ignored by the reactivity system
|
|
1421
|
-
*
|
|
1422
|
-
* @param value the value to mark
|
|
1423
|
-
* @returns the object itself
|
|
1424
|
-
*/
|
|
1425
|
-
function markRaw(value) {
|
|
1426
|
-
value[SKIP] = true;
|
|
1427
|
-
return value;
|
|
1428
|
-
}
|
|
1429
|
-
/**
|
|
1430
|
-
* Given a reactive objet, return the raw (non reactive) underlying object
|
|
1431
|
-
*
|
|
1432
|
-
* @param value a reactive value
|
|
1433
|
-
* @returns the underlying value
|
|
1434
|
-
*/
|
|
1435
|
-
function toRaw(value) {
|
|
1436
|
-
return value[TARGET] || value;
|
|
1437
|
-
}
|
|
1438
|
-
const targetToKeysToCallbacks = new WeakMap();
|
|
1439
|
-
/**
|
|
1440
|
-
* Observes a given key on a target with an callback. The callback will be
|
|
1441
|
-
* called when the given key changes on the target.
|
|
1442
|
-
*
|
|
1443
|
-
* @param target the target whose key should be observed
|
|
1444
|
-
* @param key the key to observe (or Symbol(KEYCHANGES) for key creation
|
|
1445
|
-
* or deletion)
|
|
1446
|
-
* @param callback the function to call when the key changes
|
|
1447
|
-
*/
|
|
1448
|
-
function observeTargetKey(target, key, callback) {
|
|
1449
|
-
if (!targetToKeysToCallbacks.get(target)) {
|
|
1450
|
-
targetToKeysToCallbacks.set(target, new Map());
|
|
1396
|
+
const fiber = node.fiber;
|
|
1397
|
+
if (fiber) {
|
|
1398
|
+
fibersInError.set(fiber, error);
|
|
1451
1399
|
}
|
|
1452
|
-
const
|
|
1453
|
-
if (
|
|
1454
|
-
|
|
1400
|
+
const errorHandlers = nodeErrorHandlers.get(node);
|
|
1401
|
+
if (errorHandlers) {
|
|
1402
|
+
let handled = false;
|
|
1403
|
+
// execute in the opposite order
|
|
1404
|
+
for (let i = errorHandlers.length - 1; i >= 0; i--) {
|
|
1405
|
+
try {
|
|
1406
|
+
errorHandlers[i](error);
|
|
1407
|
+
handled = true;
|
|
1408
|
+
break;
|
|
1409
|
+
}
|
|
1410
|
+
catch (e) {
|
|
1411
|
+
error = e;
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
if (handled) {
|
|
1415
|
+
return true;
|
|
1416
|
+
}
|
|
1455
1417
|
}
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1418
|
+
return _handleError(node.parent, error);
|
|
1419
|
+
}
|
|
1420
|
+
function handleError(params) {
|
|
1421
|
+
const error = params.error;
|
|
1422
|
+
const node = "node" in params ? params.node : params.fiber.node;
|
|
1423
|
+
const fiber = "fiber" in params ? params.fiber : node.fiber;
|
|
1424
|
+
// resets the fibers on components if possible. This is important so that
|
|
1425
|
+
// new renderings can be properly included in the initial one, if any.
|
|
1426
|
+
let current = fiber;
|
|
1427
|
+
do {
|
|
1428
|
+
current.node.fiber = current;
|
|
1429
|
+
current = current.parent;
|
|
1430
|
+
} while (current);
|
|
1431
|
+
fibersInError.set(fiber.root, error);
|
|
1432
|
+
const handled = _handleError(node, error);
|
|
1433
|
+
if (!handled) {
|
|
1434
|
+
console.warn(`[Owl] Unhandled error. Destroying the root component`);
|
|
1435
|
+
try {
|
|
1436
|
+
node.app.destroy();
|
|
1437
|
+
}
|
|
1438
|
+
catch (e) {
|
|
1439
|
+
console.error(e);
|
|
1440
|
+
}
|
|
1459
1441
|
}
|
|
1460
|
-
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
function makeChildFiber(node, parent) {
|
|
1445
|
+
let current = node.fiber;
|
|
1446
|
+
if (current) {
|
|
1447
|
+
cancelFibers(current.children);
|
|
1448
|
+
current.root = null;
|
|
1449
|
+
}
|
|
1450
|
+
return new Fiber(node, parent);
|
|
1461
1451
|
}
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1452
|
+
function makeRootFiber(node) {
|
|
1453
|
+
let current = node.fiber;
|
|
1454
|
+
if (current) {
|
|
1455
|
+
let root = current.root;
|
|
1456
|
+
// lock root fiber because canceling children fibers may destroy components,
|
|
1457
|
+
// which means any arbitrary code can be run in onWillDestroy, which may
|
|
1458
|
+
// trigger new renderings
|
|
1459
|
+
root.locked = true;
|
|
1460
|
+
root.setCounter(root.counter + 1 - cancelFibers(current.children));
|
|
1461
|
+
root.locked = false;
|
|
1462
|
+
current.children = [];
|
|
1463
|
+
current.childrenMap = {};
|
|
1464
|
+
current.bdom = null;
|
|
1465
|
+
if (fibersInError.has(current)) {
|
|
1466
|
+
fibersInError.delete(current);
|
|
1467
|
+
fibersInError.delete(root);
|
|
1468
|
+
current.appliedToDom = false;
|
|
1469
|
+
}
|
|
1470
|
+
return current;
|
|
1475
1471
|
}
|
|
1476
|
-
const
|
|
1477
|
-
if (
|
|
1478
|
-
|
|
1472
|
+
const fiber = new RootFiber(node, null);
|
|
1473
|
+
if (node.willPatch.length) {
|
|
1474
|
+
fiber.willPatch.push(fiber);
|
|
1479
1475
|
}
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
clearReactivesForCallback(callback);
|
|
1483
|
-
callback();
|
|
1476
|
+
if (node.patched.length) {
|
|
1477
|
+
fiber.patched.push(fiber);
|
|
1484
1478
|
}
|
|
1479
|
+
return fiber;
|
|
1480
|
+
}
|
|
1481
|
+
function throwOnRender() {
|
|
1482
|
+
throw new Error("Attempted to render cancelled fiber");
|
|
1485
1483
|
}
|
|
1486
|
-
const callbacksToTargets = new WeakMap();
|
|
1487
1484
|
/**
|
|
1488
|
-
*
|
|
1489
|
-
*
|
|
1490
|
-
* @param callback the callback for which the reactives need to be cleared
|
|
1485
|
+
* @returns number of not-yet rendered fibers cancelled
|
|
1491
1486
|
*/
|
|
1492
|
-
function
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
continue;
|
|
1487
|
+
function cancelFibers(fibers) {
|
|
1488
|
+
let result = 0;
|
|
1489
|
+
for (let fiber of fibers) {
|
|
1490
|
+
let node = fiber.node;
|
|
1491
|
+
fiber.render = throwOnRender;
|
|
1492
|
+
if (node.status === 0 /* NEW */) {
|
|
1493
|
+
node.destroy();
|
|
1494
|
+
delete node.parent.children[node.parentKey];
|
|
1501
1495
|
}
|
|
1502
|
-
|
|
1503
|
-
|
|
1496
|
+
node.fiber = null;
|
|
1497
|
+
if (fiber.bdom) {
|
|
1498
|
+
// if fiber has been rendered, this means that the component props have
|
|
1499
|
+
// been updated. however, this fiber will not be patched to the dom, so
|
|
1500
|
+
// it could happen that the next render compare the current props with
|
|
1501
|
+
// the same props, and skip the render completely. With the next line,
|
|
1502
|
+
// we kindly request the component code to force a render, so it works as
|
|
1503
|
+
// expected.
|
|
1504
|
+
node.forceNextRender = true;
|
|
1505
|
+
}
|
|
1506
|
+
else {
|
|
1507
|
+
result++;
|
|
1504
1508
|
}
|
|
1509
|
+
result += cancelFibers(fiber.children);
|
|
1505
1510
|
}
|
|
1506
|
-
|
|
1507
|
-
}
|
|
1508
|
-
function getSubscriptions(callback) {
|
|
1509
|
-
const targets = callbacksToTargets.get(callback) || [];
|
|
1510
|
-
return [...targets].map((target) => {
|
|
1511
|
-
const keysToCallbacks = targetToKeysToCallbacks.get(target);
|
|
1512
|
-
return {
|
|
1513
|
-
target,
|
|
1514
|
-
keys: keysToCallbacks ? [...keysToCallbacks.keys()] : [],
|
|
1515
|
-
};
|
|
1516
|
-
});
|
|
1511
|
+
return result;
|
|
1517
1512
|
}
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
* This is a choice that was made because changing a key's value will trigger
|
|
1538
|
-
* this trap and we do not want to subscribe by writes. This also means that
|
|
1539
|
-
* Object.hasOwnProperty doesn't subscribe as it goes through this trap.
|
|
1540
|
-
*
|
|
1541
|
-
* @param target the object for which to create a reactive proxy
|
|
1542
|
-
* @param callback the function to call when an observed property of the
|
|
1543
|
-
* reactive has changed
|
|
1544
|
-
* @returns a proxy that tracks changes to it
|
|
1545
|
-
*/
|
|
1546
|
-
function reactive(target, callback = () => { }) {
|
|
1547
|
-
if (!canBeMadeReactive(target)) {
|
|
1548
|
-
throw new Error(`Cannot make the given value reactive`);
|
|
1513
|
+
class Fiber {
|
|
1514
|
+
constructor(node, parent) {
|
|
1515
|
+
this.bdom = null;
|
|
1516
|
+
this.children = [];
|
|
1517
|
+
this.appliedToDom = false;
|
|
1518
|
+
this.deep = false;
|
|
1519
|
+
this.childrenMap = {};
|
|
1520
|
+
this.node = node;
|
|
1521
|
+
this.parent = parent;
|
|
1522
|
+
if (parent) {
|
|
1523
|
+
this.deep = parent.deep;
|
|
1524
|
+
const root = parent.root;
|
|
1525
|
+
root.setCounter(root.counter + 1);
|
|
1526
|
+
this.root = root;
|
|
1527
|
+
parent.children.push(this);
|
|
1528
|
+
}
|
|
1529
|
+
else {
|
|
1530
|
+
this.root = this;
|
|
1531
|
+
}
|
|
1549
1532
|
}
|
|
1550
|
-
|
|
1551
|
-
|
|
1533
|
+
render() {
|
|
1534
|
+
// if some parent has a fiber => register in followup
|
|
1535
|
+
let prev = this.root.node;
|
|
1536
|
+
let scheduler = prev.app.scheduler;
|
|
1537
|
+
let current = prev.parent;
|
|
1538
|
+
while (current) {
|
|
1539
|
+
if (current.fiber) {
|
|
1540
|
+
let root = current.fiber.root;
|
|
1541
|
+
if (root.counter === 0 && prev.parentKey in current.fiber.childrenMap) {
|
|
1542
|
+
current = root.node;
|
|
1543
|
+
}
|
|
1544
|
+
else {
|
|
1545
|
+
scheduler.delayedRenders.push(this);
|
|
1546
|
+
return;
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
prev = current;
|
|
1550
|
+
current = current.parent;
|
|
1551
|
+
}
|
|
1552
|
+
// there are no current rendering from above => we can render
|
|
1553
|
+
this._render();
|
|
1552
1554
|
}
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1555
|
+
_render() {
|
|
1556
|
+
const node = this.node;
|
|
1557
|
+
const root = this.root;
|
|
1558
|
+
if (root) {
|
|
1559
|
+
try {
|
|
1560
|
+
this.bdom = true;
|
|
1561
|
+
this.bdom = node.renderFn();
|
|
1562
|
+
}
|
|
1563
|
+
catch (e) {
|
|
1564
|
+
handleError({ node, error: e });
|
|
1565
|
+
}
|
|
1566
|
+
root.setCounter(root.counter - 1);
|
|
1567
|
+
}
|
|
1556
1568
|
}
|
|
1557
|
-
|
|
1558
|
-
|
|
1569
|
+
}
|
|
1570
|
+
class RootFiber extends Fiber {
|
|
1571
|
+
constructor() {
|
|
1572
|
+
super(...arguments);
|
|
1573
|
+
this.counter = 1;
|
|
1574
|
+
// only add stuff in this if they have registered some hooks
|
|
1575
|
+
this.willPatch = [];
|
|
1576
|
+
this.patched = [];
|
|
1577
|
+
this.mounted = [];
|
|
1578
|
+
// A fiber is typically locked when it is completing and the patch has not, or is being applied.
|
|
1579
|
+
// i.e.: render triggered in onWillUnmount or in willPatch will be delayed
|
|
1580
|
+
this.locked = false;
|
|
1559
1581
|
}
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
:
|
|
1566
|
-
|
|
1567
|
-
|
|
1582
|
+
complete() {
|
|
1583
|
+
const node = this.node;
|
|
1584
|
+
this.locked = true;
|
|
1585
|
+
let current = undefined;
|
|
1586
|
+
try {
|
|
1587
|
+
// Step 1: calling all willPatch lifecycle hooks
|
|
1588
|
+
for (current of this.willPatch) {
|
|
1589
|
+
// because of the asynchronous nature of the rendering, some parts of the
|
|
1590
|
+
// UI may have been rendered, then deleted in a followup rendering, and we
|
|
1591
|
+
// do not want to call onWillPatch in that case.
|
|
1592
|
+
let node = current.node;
|
|
1593
|
+
if (node.fiber === current) {
|
|
1594
|
+
const component = node.component;
|
|
1595
|
+
for (let cb of node.willPatch) {
|
|
1596
|
+
cb.call(component);
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
current = undefined;
|
|
1601
|
+
// Step 2: patching the dom
|
|
1602
|
+
node._patch();
|
|
1603
|
+
this.locked = false;
|
|
1604
|
+
// Step 4: calling all mounted lifecycle hooks
|
|
1605
|
+
let mountedFibers = this.mounted;
|
|
1606
|
+
while ((current = mountedFibers.pop())) {
|
|
1607
|
+
current = current;
|
|
1608
|
+
if (current.appliedToDom) {
|
|
1609
|
+
for (let cb of current.node.mounted) {
|
|
1610
|
+
cb();
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
// Step 5: calling all patched hooks
|
|
1615
|
+
let patchedFibers = this.patched;
|
|
1616
|
+
while ((current = patchedFibers.pop())) {
|
|
1617
|
+
current = current;
|
|
1618
|
+
if (current.appliedToDom) {
|
|
1619
|
+
for (let cb of current.node.patched) {
|
|
1620
|
+
cb();
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
catch (e) {
|
|
1626
|
+
this.locked = false;
|
|
1627
|
+
handleError({ fiber: current || this, error: e });
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
setCounter(newValue) {
|
|
1631
|
+
this.counter = newValue;
|
|
1632
|
+
if (newValue === 0) {
|
|
1633
|
+
this.node.app.scheduler.flush();
|
|
1634
|
+
}
|
|
1568
1635
|
}
|
|
1569
|
-
return reactivesForTarget.get(callback);
|
|
1570
1636
|
}
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1637
|
+
class MountFiber extends RootFiber {
|
|
1638
|
+
constructor(node, target, options = {}) {
|
|
1639
|
+
super(node, null);
|
|
1640
|
+
this.target = target;
|
|
1641
|
+
this.position = options.position || "last-child";
|
|
1642
|
+
}
|
|
1643
|
+
complete() {
|
|
1644
|
+
let current = this;
|
|
1645
|
+
try {
|
|
1646
|
+
const node = this.node;
|
|
1647
|
+
node.children = this.childrenMap;
|
|
1648
|
+
node.app.constructor.validateTarget(this.target);
|
|
1649
|
+
if (node.bdom) {
|
|
1650
|
+
// this is a complicated situation: if we mount a fiber with an existing
|
|
1651
|
+
// bdom, this means that this same fiber was already completed, mounted,
|
|
1652
|
+
// but a crash occurred in some mounted hook. Then, it was handled and
|
|
1653
|
+
// the new rendering is being applied.
|
|
1654
|
+
node.updateDom();
|
|
1587
1655
|
}
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1656
|
+
else {
|
|
1657
|
+
node.bdom = this.bdom;
|
|
1658
|
+
if (this.position === "last-child" || this.target.childNodes.length === 0) {
|
|
1659
|
+
mount$1(node.bdom, this.target);
|
|
1660
|
+
}
|
|
1661
|
+
else {
|
|
1662
|
+
const firstChild = this.target.childNodes[0];
|
|
1663
|
+
mount$1(node.bdom, this.target, firstChild);
|
|
1664
|
+
}
|
|
1597
1665
|
}
|
|
1598
|
-
//
|
|
1599
|
-
//
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1666
|
+
// unregistering the fiber before mounted since it can do another render
|
|
1667
|
+
// and that the current rendering is obviously completed
|
|
1668
|
+
node.fiber = null;
|
|
1669
|
+
node.status = 1 /* MOUNTED */;
|
|
1670
|
+
this.appliedToDom = true;
|
|
1671
|
+
let mountedFibers = this.mounted;
|
|
1672
|
+
while ((current = mountedFibers.pop())) {
|
|
1673
|
+
if (current.appliedToDom) {
|
|
1674
|
+
for (let cb of current.node.mounted) {
|
|
1675
|
+
cb();
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1603
1678
|
}
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
observeTargetKey(target, KEYCHANGES, callback);
|
|
1622
|
-
return Reflect.has(target, key);
|
|
1623
|
-
},
|
|
1624
|
-
};
|
|
1625
|
-
}
|
|
1679
|
+
}
|
|
1680
|
+
catch (e) {
|
|
1681
|
+
handleError({ fiber: current, error: e });
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
// Allows to get the target of a Reactive (used for making a new Reactive from the underlying object)
|
|
1687
|
+
const TARGET = Symbol("Target");
|
|
1688
|
+
// Escape hatch to prevent reactivity system to turn something into a reactive
|
|
1689
|
+
const SKIP = Symbol("Skip");
|
|
1690
|
+
// Special key to subscribe to, to be notified of key creation/deletion
|
|
1691
|
+
const KEYCHANGES = Symbol("Key changes");
|
|
1692
|
+
const objectToString = Object.prototype.toString;
|
|
1693
|
+
const objectHasOwnProperty = Object.prototype.hasOwnProperty;
|
|
1694
|
+
const SUPPORTED_RAW_TYPES = new Set(["Object", "Array", "Set", "Map", "WeakMap"]);
|
|
1695
|
+
const COLLECTION_RAWTYPES = new Set(["Set", "Map", "WeakMap"]);
|
|
1626
1696
|
/**
|
|
1627
|
-
*
|
|
1628
|
-
*
|
|
1697
|
+
* extract "RawType" from strings like "[object RawType]" => this lets us ignore
|
|
1698
|
+
* many native objects such as Promise (whose toString is [object Promise])
|
|
1699
|
+
* or Date ([object Date]), while also supporting collections without using
|
|
1700
|
+
* instanceof in a loop
|
|
1629
1701
|
*
|
|
1630
|
-
* @param
|
|
1631
|
-
* @
|
|
1632
|
-
* @param callback @see reactive
|
|
1702
|
+
* @param obj the object to check
|
|
1703
|
+
* @returns the raw type of the object
|
|
1633
1704
|
*/
|
|
1634
|
-
function
|
|
1635
|
-
return (
|
|
1636
|
-
key = toRaw(key);
|
|
1637
|
-
observeTargetKey(target, key, callback);
|
|
1638
|
-
return possiblyReactive(target[methodName](key), callback);
|
|
1639
|
-
};
|
|
1705
|
+
function rawType(obj) {
|
|
1706
|
+
return objectToString.call(obj).slice(8, -1);
|
|
1640
1707
|
}
|
|
1641
1708
|
/**
|
|
1642
|
-
*
|
|
1643
|
-
* observe keys as necessary.
|
|
1709
|
+
* Checks whether a given value can be made into a reactive object.
|
|
1644
1710
|
*
|
|
1645
|
-
* @param
|
|
1646
|
-
* @
|
|
1647
|
-
* @param callback @see reactive
|
|
1711
|
+
* @param value the value to check
|
|
1712
|
+
* @returns whether the value can be made reactive
|
|
1648
1713
|
*/
|
|
1649
|
-
function
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
const key = keys.next().value;
|
|
1655
|
-
observeTargetKey(target, key, callback);
|
|
1656
|
-
yield possiblyReactive(item, callback);
|
|
1657
|
-
}
|
|
1658
|
-
};
|
|
1714
|
+
function canBeMadeReactive(value) {
|
|
1715
|
+
if (typeof value !== "object") {
|
|
1716
|
+
return false;
|
|
1717
|
+
}
|
|
1718
|
+
return SUPPORTED_RAW_TYPES.has(rawType(value));
|
|
1659
1719
|
}
|
|
1660
1720
|
/**
|
|
1661
|
-
* Creates a
|
|
1662
|
-
*
|
|
1663
|
-
* and making the passed keys/values reactive.
|
|
1721
|
+
* Creates a reactive from the given object/callback if possible and returns it,
|
|
1722
|
+
* returns the original object otherwise.
|
|
1664
1723
|
*
|
|
1665
|
-
* @param
|
|
1666
|
-
* @
|
|
1724
|
+
* @param value the value make reactive
|
|
1725
|
+
* @returns a reactive for the given object when possible, the original otherwise
|
|
1667
1726
|
*/
|
|
1668
|
-
function
|
|
1669
|
-
return
|
|
1670
|
-
observeTargetKey(target, KEYCHANGES, callback);
|
|
1671
|
-
target.forEach(function (val, key, targetObj) {
|
|
1672
|
-
observeTargetKey(target, key, callback);
|
|
1673
|
-
forEachCb.call(thisArg, possiblyReactive(val, callback), possiblyReactive(key, callback), possiblyReactive(targetObj, callback));
|
|
1674
|
-
}, thisArg);
|
|
1675
|
-
};
|
|
1727
|
+
function possiblyReactive(val, cb) {
|
|
1728
|
+
return canBeMadeReactive(val) ? reactive(val, cb) : val;
|
|
1676
1729
|
}
|
|
1677
1730
|
/**
|
|
1678
|
-
*
|
|
1679
|
-
* that method has modified the presence or value of a key, and notify the
|
|
1680
|
-
* reactives appropriately.
|
|
1731
|
+
* Mark an object or array so that it is ignored by the reactivity system
|
|
1681
1732
|
*
|
|
1682
|
-
* @param
|
|
1683
|
-
* @
|
|
1684
|
-
* value before calling the delegate method for comparison purposes
|
|
1685
|
-
* @param target @see reactive
|
|
1733
|
+
* @param value the value to mark
|
|
1734
|
+
* @returns the object itself
|
|
1686
1735
|
*/
|
|
1687
|
-
function
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
const hadKey = target.has(key);
|
|
1691
|
-
const originalValue = target[getterName](key);
|
|
1692
|
-
const ret = target[setterName](key, value);
|
|
1693
|
-
const hasKey = target.has(key);
|
|
1694
|
-
if (hadKey !== hasKey) {
|
|
1695
|
-
notifyReactives(target, KEYCHANGES);
|
|
1696
|
-
}
|
|
1697
|
-
if (originalValue !== value) {
|
|
1698
|
-
notifyReactives(target, key);
|
|
1699
|
-
}
|
|
1700
|
-
return ret;
|
|
1701
|
-
};
|
|
1736
|
+
function markRaw(value) {
|
|
1737
|
+
value[SKIP] = true;
|
|
1738
|
+
return value;
|
|
1702
1739
|
}
|
|
1703
1740
|
/**
|
|
1704
|
-
*
|
|
1705
|
-
* the keys of the collection have changed.
|
|
1741
|
+
* Given a reactive objet, return the raw (non reactive) underlying object
|
|
1706
1742
|
*
|
|
1707
|
-
* @param
|
|
1743
|
+
* @param value a reactive value
|
|
1744
|
+
* @returns the underlying value
|
|
1708
1745
|
*/
|
|
1709
|
-
function
|
|
1710
|
-
return
|
|
1711
|
-
const allKeys = [...target.keys()];
|
|
1712
|
-
target.clear();
|
|
1713
|
-
notifyReactives(target, KEYCHANGES);
|
|
1714
|
-
for (const key of allKeys) {
|
|
1715
|
-
notifyReactives(target, key);
|
|
1716
|
-
}
|
|
1717
|
-
};
|
|
1746
|
+
function toRaw(value) {
|
|
1747
|
+
return value[TARGET] || value;
|
|
1718
1748
|
}
|
|
1749
|
+
const targetToKeysToCallbacks = new WeakMap();
|
|
1719
1750
|
/**
|
|
1720
|
-
*
|
|
1721
|
-
*
|
|
1722
|
-
* reactive set, calling the has method should mark the key that is being
|
|
1723
|
-
* retrieved as observed, and calling the add or delete method should notify the
|
|
1724
|
-
* reactives that the key which is being added or deleted has been modified.
|
|
1725
|
-
*/
|
|
1726
|
-
const rawTypeToFuncHandlers = {
|
|
1727
|
-
Set: (target, callback) => ({
|
|
1728
|
-
has: makeKeyObserver("has", target, callback),
|
|
1729
|
-
add: delegateAndNotify("add", "has", target),
|
|
1730
|
-
delete: delegateAndNotify("delete", "has", target),
|
|
1731
|
-
keys: makeIteratorObserver("keys", target, callback),
|
|
1732
|
-
values: makeIteratorObserver("values", target, callback),
|
|
1733
|
-
entries: makeIteratorObserver("entries", target, callback),
|
|
1734
|
-
[Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
|
|
1735
|
-
forEach: makeForEachObserver(target, callback),
|
|
1736
|
-
clear: makeClearNotifier(target),
|
|
1737
|
-
get size() {
|
|
1738
|
-
observeTargetKey(target, KEYCHANGES, callback);
|
|
1739
|
-
return target.size;
|
|
1740
|
-
},
|
|
1741
|
-
}),
|
|
1742
|
-
Map: (target, callback) => ({
|
|
1743
|
-
has: makeKeyObserver("has", target, callback),
|
|
1744
|
-
get: makeKeyObserver("get", target, callback),
|
|
1745
|
-
set: delegateAndNotify("set", "get", target),
|
|
1746
|
-
delete: delegateAndNotify("delete", "has", target),
|
|
1747
|
-
keys: makeIteratorObserver("keys", target, callback),
|
|
1748
|
-
values: makeIteratorObserver("values", target, callback),
|
|
1749
|
-
entries: makeIteratorObserver("entries", target, callback),
|
|
1750
|
-
[Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
|
|
1751
|
-
forEach: makeForEachObserver(target, callback),
|
|
1752
|
-
clear: makeClearNotifier(target),
|
|
1753
|
-
get size() {
|
|
1754
|
-
observeTargetKey(target, KEYCHANGES, callback);
|
|
1755
|
-
return target.size;
|
|
1756
|
-
},
|
|
1757
|
-
}),
|
|
1758
|
-
WeakMap: (target, callback) => ({
|
|
1759
|
-
has: makeKeyObserver("has", target, callback),
|
|
1760
|
-
get: makeKeyObserver("get", target, callback),
|
|
1761
|
-
set: delegateAndNotify("set", "get", target),
|
|
1762
|
-
delete: delegateAndNotify("delete", "has", target),
|
|
1763
|
-
}),
|
|
1764
|
-
};
|
|
1765
|
-
/**
|
|
1766
|
-
* Creates a proxy handler for collections (Set/Map/WeakMap)
|
|
1767
|
-
*
|
|
1768
|
-
* @param callback @see reactive
|
|
1769
|
-
* @param target @see reactive
|
|
1770
|
-
* @returns a proxy handler object
|
|
1771
|
-
*/
|
|
1772
|
-
function collectionsProxyHandler(target, callback, targetRawType) {
|
|
1773
|
-
// TODO: if performance is an issue we can create the special handlers lazily when each
|
|
1774
|
-
// property is read.
|
|
1775
|
-
const specialHandlers = rawTypeToFuncHandlers[targetRawType](target, callback);
|
|
1776
|
-
return Object.assign(basicProxyHandler(callback), {
|
|
1777
|
-
get(target, key) {
|
|
1778
|
-
if (key === TARGET) {
|
|
1779
|
-
return target;
|
|
1780
|
-
}
|
|
1781
|
-
if (objectHasOwnProperty.call(specialHandlers, key)) {
|
|
1782
|
-
return specialHandlers[key];
|
|
1783
|
-
}
|
|
1784
|
-
observeTargetKey(target, key, callback);
|
|
1785
|
-
return possiblyReactive(target[key], callback);
|
|
1786
|
-
},
|
|
1787
|
-
});
|
|
1788
|
-
}
|
|
1789
|
-
|
|
1790
|
-
/**
|
|
1791
|
-
* Creates a batched version of a callback so that all calls to it in the same
|
|
1792
|
-
* microtick will only call the original callback once.
|
|
1751
|
+
* Observes a given key on a target with an callback. The callback will be
|
|
1752
|
+
* called when the given key changes on the target.
|
|
1793
1753
|
*
|
|
1794
|
-
* @param
|
|
1795
|
-
* @
|
|
1754
|
+
* @param target the target whose key should be observed
|
|
1755
|
+
* @param key the key to observe (or Symbol(KEYCHANGES) for key creation
|
|
1756
|
+
* or deletion)
|
|
1757
|
+
* @param callback the function to call when the key changes
|
|
1796
1758
|
*/
|
|
1797
|
-
function
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
// This await blocks all calls to the callback here, then releases them sequentially
|
|
1801
|
-
// in the next microtick. This line decides the granularity of the batch.
|
|
1802
|
-
await Promise.resolve();
|
|
1803
|
-
if (!called) {
|
|
1804
|
-
called = true;
|
|
1805
|
-
// wait for all calls in this microtick to fall through before resetting "called"
|
|
1806
|
-
// so that only the first call to the batched function calls the original callback.
|
|
1807
|
-
// Schedule this before calling the callback so that calls to the batched function
|
|
1808
|
-
// within the callback will proceed only after resetting called to false, and have
|
|
1809
|
-
// a chance to execute the callback again
|
|
1810
|
-
Promise.resolve().then(() => (called = false));
|
|
1811
|
-
callback();
|
|
1812
|
-
}
|
|
1813
|
-
};
|
|
1814
|
-
}
|
|
1815
|
-
function validateTarget(target) {
|
|
1816
|
-
if (!(target instanceof HTMLElement)) {
|
|
1817
|
-
throw new Error("Cannot mount component: the target is not a valid DOM element");
|
|
1759
|
+
function observeTargetKey(target, key, callback) {
|
|
1760
|
+
if (!targetToKeysToCallbacks.get(target)) {
|
|
1761
|
+
targetToKeysToCallbacks.set(target, new Map());
|
|
1818
1762
|
}
|
|
1819
|
-
|
|
1820
|
-
|
|
1763
|
+
const keyToCallbacks = targetToKeysToCallbacks.get(target);
|
|
1764
|
+
if (!keyToCallbacks.get(key)) {
|
|
1765
|
+
keyToCallbacks.set(key, new Set());
|
|
1766
|
+
}
|
|
1767
|
+
keyToCallbacks.get(key).add(callback);
|
|
1768
|
+
if (!callbacksToTargets.has(callback)) {
|
|
1769
|
+
callbacksToTargets.set(callback, new Set());
|
|
1821
1770
|
}
|
|
1771
|
+
callbacksToTargets.get(callback).add(target);
|
|
1822
1772
|
}
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1773
|
+
/**
|
|
1774
|
+
* Notify Reactives that are observing a given target that a key has changed on
|
|
1775
|
+
* the target.
|
|
1776
|
+
*
|
|
1777
|
+
* @param target target whose Reactives should be notified that the target was
|
|
1778
|
+
* changed.
|
|
1779
|
+
* @param key the key that changed (or Symbol `KEYCHANGES` if a key was created
|
|
1780
|
+
* or deleted)
|
|
1781
|
+
*/
|
|
1782
|
+
function notifyReactives(target, key) {
|
|
1783
|
+
const keyToCallbacks = targetToKeysToCallbacks.get(target);
|
|
1784
|
+
if (!keyToCallbacks) {
|
|
1785
|
+
return;
|
|
1786
|
+
}
|
|
1787
|
+
const callbacks = keyToCallbacks.get(key);
|
|
1788
|
+
if (!callbacks) {
|
|
1789
|
+
return;
|
|
1790
|
+
}
|
|
1791
|
+
// Loop on copy because clearReactivesForCallback will modify the set in place
|
|
1792
|
+
for (const callback of [...callbacks]) {
|
|
1793
|
+
clearReactivesForCallback(callback);
|
|
1794
|
+
callback();
|
|
1826
1795
|
}
|
|
1827
1796
|
}
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1797
|
+
const callbacksToTargets = new WeakMap();
|
|
1798
|
+
/**
|
|
1799
|
+
* Clears all subscriptions of the Reactives associated with a given callback.
|
|
1800
|
+
*
|
|
1801
|
+
* @param callback the callback for which the reactives need to be cleared
|
|
1802
|
+
*/
|
|
1803
|
+
function clearReactivesForCallback(callback) {
|
|
1804
|
+
const targetsToClear = callbacksToTargets.get(callback);
|
|
1805
|
+
if (!targetsToClear) {
|
|
1806
|
+
return;
|
|
1807
|
+
}
|
|
1808
|
+
for (const target of targetsToClear) {
|
|
1809
|
+
const observedKeys = targetToKeysToCallbacks.get(target);
|
|
1810
|
+
if (!observedKeys) {
|
|
1811
|
+
continue;
|
|
1832
1812
|
}
|
|
1833
|
-
|
|
1834
|
-
|
|
1813
|
+
for (const callbacks of observedKeys.values()) {
|
|
1814
|
+
callbacks.delete(callback);
|
|
1835
1815
|
}
|
|
1836
|
-
}).then(fn || function () { });
|
|
1837
|
-
}
|
|
1838
|
-
async function loadFile(url) {
|
|
1839
|
-
const result = await fetch(url);
|
|
1840
|
-
if (!result.ok) {
|
|
1841
|
-
throw new Error("Error while fetching xml templates");
|
|
1842
1816
|
}
|
|
1843
|
-
|
|
1817
|
+
targetsToClear.clear();
|
|
1844
1818
|
}
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1819
|
+
function getSubscriptions(callback) {
|
|
1820
|
+
const targets = callbacksToTargets.get(callback) || [];
|
|
1821
|
+
return [...targets].map((target) => {
|
|
1822
|
+
const keysToCallbacks = targetToKeysToCallbacks.get(target);
|
|
1823
|
+
return {
|
|
1824
|
+
target,
|
|
1825
|
+
keys: keysToCallbacks ? [...keysToCallbacks.keys()] : [],
|
|
1826
|
+
};
|
|
1827
|
+
});
|
|
1851
1828
|
}
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
*
|
|
1829
|
+
const reactiveCache = new WeakMap();
|
|
1830
|
+
/**
|
|
1831
|
+
* Creates a reactive proxy for an object. Reading data on the reactive object
|
|
1832
|
+
* subscribes to changes to the data. Writing data on the object will cause the
|
|
1833
|
+
* notify callback to be called if there are suscriptions to that data. Nested
|
|
1834
|
+
* objects and arrays are automatically made reactive as well.
|
|
1835
|
+
*
|
|
1836
|
+
* Whenever you are notified of a change, all subscriptions are cleared, and if
|
|
1837
|
+
* you would like to be notified of any further changes, you should go read
|
|
1838
|
+
* the underlying data again. We assume that if you don't go read it again after
|
|
1839
|
+
* being notified, it means that you are no longer interested in that data.
|
|
1840
|
+
*
|
|
1841
|
+
* Subscriptions:
|
|
1842
|
+
* + Reading a property on an object will subscribe you to changes in the value
|
|
1843
|
+
* of that property.
|
|
1844
|
+
* + Accessing an object keys (eg with Object.keys or with `for..in`) will
|
|
1845
|
+
* subscribe you to the creation/deletion of keys. Checking the presence of a
|
|
1846
|
+
* key on the object with 'in' has the same effect.
|
|
1847
|
+
* - getOwnPropertyDescriptor does not currently subscribe you to the property.
|
|
1848
|
+
* This is a choice that was made because changing a key's value will trigger
|
|
1849
|
+
* this trap and we do not want to subscribe by writes. This also means that
|
|
1850
|
+
* Object.hasOwnProperty doesn't subscribe as it goes through this trap.
|
|
1851
|
+
*
|
|
1852
|
+
* @param target the object for which to create a reactive proxy
|
|
1853
|
+
* @param callback the function to call when an observed property of the
|
|
1854
|
+
* reactive has changed
|
|
1855
|
+
* @returns a proxy that tracks changes to it
|
|
1855
1856
|
*/
|
|
1856
|
-
function
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
class Component {
|
|
1861
|
-
constructor(props, env, node) {
|
|
1862
|
-
this.props = props;
|
|
1863
|
-
this.env = env;
|
|
1864
|
-
this.__owl__ = node;
|
|
1857
|
+
function reactive(target, callback = () => { }) {
|
|
1858
|
+
if (!canBeMadeReactive(target)) {
|
|
1859
|
+
throw new Error(`Cannot make the given value reactive`);
|
|
1865
1860
|
}
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
this.__owl__.render(deep === true);
|
|
1861
|
+
if (SKIP in target) {
|
|
1862
|
+
return target;
|
|
1869
1863
|
}
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
// Maps fibers to thrown errors
|
|
1874
|
-
const fibersInError = new WeakMap();
|
|
1875
|
-
const nodeErrorHandlers = new WeakMap();
|
|
1876
|
-
function _handleError(node, error) {
|
|
1877
|
-
if (!node) {
|
|
1878
|
-
return false;
|
|
1864
|
+
const originalTarget = target[TARGET];
|
|
1865
|
+
if (originalTarget) {
|
|
1866
|
+
return reactive(originalTarget, callback);
|
|
1879
1867
|
}
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
fibersInError.set(fiber, error);
|
|
1868
|
+
if (!reactiveCache.has(target)) {
|
|
1869
|
+
reactiveCache.set(target, new WeakMap());
|
|
1883
1870
|
}
|
|
1884
|
-
const
|
|
1885
|
-
if (
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
break;
|
|
1893
|
-
}
|
|
1894
|
-
catch (e) {
|
|
1895
|
-
error = e;
|
|
1896
|
-
}
|
|
1897
|
-
}
|
|
1898
|
-
if (handled) {
|
|
1899
|
-
return true;
|
|
1900
|
-
}
|
|
1871
|
+
const reactivesForTarget = reactiveCache.get(target);
|
|
1872
|
+
if (!reactivesForTarget.has(callback)) {
|
|
1873
|
+
const targetRawType = rawType(target);
|
|
1874
|
+
const handler = COLLECTION_RAWTYPES.has(targetRawType)
|
|
1875
|
+
? collectionsProxyHandler(target, callback, targetRawType)
|
|
1876
|
+
: basicProxyHandler(callback);
|
|
1877
|
+
const proxy = new Proxy(target, handler);
|
|
1878
|
+
reactivesForTarget.set(callback, proxy);
|
|
1901
1879
|
}
|
|
1902
|
-
return
|
|
1880
|
+
return reactivesForTarget.get(callback);
|
|
1903
1881
|
}
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1882
|
+
/**
|
|
1883
|
+
* Creates a basic proxy handler for regular objects and arrays.
|
|
1884
|
+
*
|
|
1885
|
+
* @param callback @see reactive
|
|
1886
|
+
* @returns a proxy handler object
|
|
1887
|
+
*/
|
|
1888
|
+
function basicProxyHandler(callback) {
|
|
1889
|
+
return {
|
|
1890
|
+
get(target, key, proxy) {
|
|
1891
|
+
if (key === TARGET) {
|
|
1892
|
+
return target;
|
|
1893
|
+
}
|
|
1894
|
+
// non-writable non-configurable properties cannot be made reactive
|
|
1895
|
+
const desc = Object.getOwnPropertyDescriptor(target, key);
|
|
1896
|
+
if (desc && !desc.writable && !desc.configurable) {
|
|
1897
|
+
return Reflect.get(target, key, proxy);
|
|
1898
|
+
}
|
|
1899
|
+
observeTargetKey(target, key, callback);
|
|
1900
|
+
return possiblyReactive(Reflect.get(target, key, proxy), callback);
|
|
1901
|
+
},
|
|
1902
|
+
set(target, key, value, proxy) {
|
|
1903
|
+
const isNewKey = !objectHasOwnProperty.call(target, key);
|
|
1904
|
+
const originalValue = Reflect.get(target, key, proxy);
|
|
1905
|
+
const ret = Reflect.set(target, key, value, proxy);
|
|
1906
|
+
if (isNewKey) {
|
|
1907
|
+
notifyReactives(target, KEYCHANGES);
|
|
1908
|
+
}
|
|
1909
|
+
// While Array length may trigger the set trap, it's not actually set by this
|
|
1910
|
+
// method but is updated behind the scenes, and the trap is not called with the
|
|
1911
|
+
// new value. We disable the "same-value-optimization" for it because of that.
|
|
1912
|
+
if (originalValue !== value || (Array.isArray(target) && key === "length")) {
|
|
1913
|
+
notifyReactives(target, key);
|
|
1914
|
+
}
|
|
1915
|
+
return ret;
|
|
1916
|
+
},
|
|
1917
|
+
deleteProperty(target, key) {
|
|
1918
|
+
const ret = Reflect.deleteProperty(target, key);
|
|
1919
|
+
// TODO: only notify when something was actually deleted
|
|
1920
|
+
notifyReactives(target, KEYCHANGES);
|
|
1921
|
+
notifyReactives(target, key);
|
|
1922
|
+
return ret;
|
|
1923
|
+
},
|
|
1924
|
+
ownKeys(target) {
|
|
1925
|
+
observeTargetKey(target, KEYCHANGES, callback);
|
|
1926
|
+
return Reflect.ownKeys(target);
|
|
1927
|
+
},
|
|
1928
|
+
has(target, key) {
|
|
1929
|
+
// TODO: this observes all key changes instead of only the presence of the argument key
|
|
1930
|
+
// observing the key itself would observe value changes instead of presence changes
|
|
1931
|
+
// so we may need a finer grained system to distinguish observing value vs presence.
|
|
1932
|
+
observeTargetKey(target, KEYCHANGES, callback);
|
|
1933
|
+
return Reflect.has(target, key);
|
|
1934
|
+
},
|
|
1935
|
+
};
|
|
1935
1936
|
}
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1937
|
+
/**
|
|
1938
|
+
* Creates a function that will observe the key that is passed to it when called
|
|
1939
|
+
* and delegates to the underlying method.
|
|
1940
|
+
*
|
|
1941
|
+
* @param methodName name of the method to delegate to
|
|
1942
|
+
* @param target @see reactive
|
|
1943
|
+
* @param callback @see reactive
|
|
1944
|
+
*/
|
|
1945
|
+
function makeKeyObserver(methodName, target, callback) {
|
|
1946
|
+
return (key) => {
|
|
1947
|
+
key = toRaw(key);
|
|
1948
|
+
observeTargetKey(target, key, callback);
|
|
1949
|
+
return possiblyReactive(target[methodName](key), callback);
|
|
1950
|
+
};
|
|
1951
|
+
}
|
|
1952
|
+
/**
|
|
1953
|
+
* Creates an iterable that will delegate to the underlying iteration method and
|
|
1954
|
+
* observe keys as necessary.
|
|
1955
|
+
*
|
|
1956
|
+
* @param methodName name of the method to delegate to
|
|
1957
|
+
* @param target @see reactive
|
|
1958
|
+
* @param callback @see reactive
|
|
1959
|
+
*/
|
|
1960
|
+
function makeIteratorObserver(methodName, target, callback) {
|
|
1961
|
+
return function* () {
|
|
1962
|
+
observeTargetKey(target, KEYCHANGES, callback);
|
|
1963
|
+
const keys = target.keys();
|
|
1964
|
+
for (const item of target[methodName]()) {
|
|
1965
|
+
const key = keys.next().value;
|
|
1966
|
+
observeTargetKey(target, key, callback);
|
|
1967
|
+
yield possiblyReactive(item, callback);
|
|
1953
1968
|
}
|
|
1954
|
-
|
|
1955
|
-
}
|
|
1956
|
-
const fiber = new RootFiber(node, null);
|
|
1957
|
-
if (node.willPatch.length) {
|
|
1958
|
-
fiber.willPatch.push(fiber);
|
|
1959
|
-
}
|
|
1960
|
-
if (node.patched.length) {
|
|
1961
|
-
fiber.patched.push(fiber);
|
|
1962
|
-
}
|
|
1963
|
-
return fiber;
|
|
1969
|
+
};
|
|
1964
1970
|
}
|
|
1965
|
-
|
|
1966
|
-
|
|
1971
|
+
/**
|
|
1972
|
+
* Creates a forEach function that will delegate to forEach on the underlying
|
|
1973
|
+
* collection while observing key changes, and keys as they're iterated over,
|
|
1974
|
+
* and making the passed keys/values reactive.
|
|
1975
|
+
*
|
|
1976
|
+
* @param target @see reactive
|
|
1977
|
+
* @param callback @see reactive
|
|
1978
|
+
*/
|
|
1979
|
+
function makeForEachObserver(target, callback) {
|
|
1980
|
+
return function forEach(forEachCb, thisArg) {
|
|
1981
|
+
observeTargetKey(target, KEYCHANGES, callback);
|
|
1982
|
+
target.forEach(function (val, key, targetObj) {
|
|
1983
|
+
observeTargetKey(target, key, callback);
|
|
1984
|
+
forEachCb.call(thisArg, possiblyReactive(val, callback), possiblyReactive(key, callback), possiblyReactive(targetObj, callback));
|
|
1985
|
+
}, thisArg);
|
|
1986
|
+
};
|
|
1967
1987
|
}
|
|
1968
1988
|
/**
|
|
1969
|
-
*
|
|
1989
|
+
* Creates a function that will delegate to an underlying method, and check if
|
|
1990
|
+
* that method has modified the presence or value of a key, and notify the
|
|
1991
|
+
* reactives appropriately.
|
|
1992
|
+
*
|
|
1993
|
+
* @param setterName name of the method to delegate to
|
|
1994
|
+
* @param getterName name of the method which should be used to retrieve the
|
|
1995
|
+
* value before calling the delegate method for comparison purposes
|
|
1996
|
+
* @param target @see reactive
|
|
1970
1997
|
*/
|
|
1971
|
-
function
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
if (fiber.bdom) {
|
|
1981
|
-
// if fiber has been rendered, this means that the component props have
|
|
1982
|
-
// been updated. however, this fiber will not be patched to the dom, so
|
|
1983
|
-
// it could happen that the next render compare the current props with
|
|
1984
|
-
// the same props, and skip the render completely. With the next line,
|
|
1985
|
-
// we kindly request the component code to force a render, so it works as
|
|
1986
|
-
// expected.
|
|
1987
|
-
node.forceNextRender = true;
|
|
1998
|
+
function delegateAndNotify(setterName, getterName, target) {
|
|
1999
|
+
return (key, value) => {
|
|
2000
|
+
key = toRaw(key);
|
|
2001
|
+
const hadKey = target.has(key);
|
|
2002
|
+
const originalValue = target[getterName](key);
|
|
2003
|
+
const ret = target[setterName](key, value);
|
|
2004
|
+
const hasKey = target.has(key);
|
|
2005
|
+
if (hadKey !== hasKey) {
|
|
2006
|
+
notifyReactives(target, KEYCHANGES);
|
|
1988
2007
|
}
|
|
1989
|
-
|
|
1990
|
-
|
|
2008
|
+
if (originalValue !== value) {
|
|
2009
|
+
notifyReactives(target, key);
|
|
1991
2010
|
}
|
|
1992
|
-
|
|
1993
|
-
}
|
|
1994
|
-
return result;
|
|
2011
|
+
return ret;
|
|
2012
|
+
};
|
|
1995
2013
|
}
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
this.root = root;
|
|
2010
|
-
parent.children.push(this);
|
|
2011
|
-
}
|
|
2012
|
-
else {
|
|
2013
|
-
this.root = this;
|
|
2014
|
-
}
|
|
2015
|
-
}
|
|
2016
|
-
render() {
|
|
2017
|
-
// if some parent has a fiber => register in followup
|
|
2018
|
-
let prev = this.root.node;
|
|
2019
|
-
let scheduler = prev.app.scheduler;
|
|
2020
|
-
let current = prev.parent;
|
|
2021
|
-
while (current) {
|
|
2022
|
-
if (current.fiber) {
|
|
2023
|
-
let root = current.fiber.root;
|
|
2024
|
-
if (root.counter === 0 && prev.parentKey in current.fiber.childrenMap) {
|
|
2025
|
-
current = root.node;
|
|
2026
|
-
}
|
|
2027
|
-
else {
|
|
2028
|
-
scheduler.delayedRenders.push(this);
|
|
2029
|
-
return;
|
|
2030
|
-
}
|
|
2031
|
-
}
|
|
2032
|
-
prev = current;
|
|
2033
|
-
current = current.parent;
|
|
2034
|
-
}
|
|
2035
|
-
// there are no current rendering from above => we can render
|
|
2036
|
-
this._render();
|
|
2037
|
-
}
|
|
2038
|
-
_render() {
|
|
2039
|
-
const node = this.node;
|
|
2040
|
-
const root = this.root;
|
|
2041
|
-
if (root) {
|
|
2042
|
-
try {
|
|
2043
|
-
this.bdom = true;
|
|
2044
|
-
this.bdom = node.renderFn();
|
|
2045
|
-
}
|
|
2046
|
-
catch (e) {
|
|
2047
|
-
handleError({ node, error: e });
|
|
2048
|
-
}
|
|
2049
|
-
root.setCounter(root.counter - 1);
|
|
2014
|
+
/**
|
|
2015
|
+
* Creates a function that will clear the underlying collection and notify that
|
|
2016
|
+
* the keys of the collection have changed.
|
|
2017
|
+
*
|
|
2018
|
+
* @param target @see reactive
|
|
2019
|
+
*/
|
|
2020
|
+
function makeClearNotifier(target) {
|
|
2021
|
+
return () => {
|
|
2022
|
+
const allKeys = [...target.keys()];
|
|
2023
|
+
target.clear();
|
|
2024
|
+
notifyReactives(target, KEYCHANGES);
|
|
2025
|
+
for (const key of allKeys) {
|
|
2026
|
+
notifyReactives(target, key);
|
|
2050
2027
|
}
|
|
2051
|
-
}
|
|
2028
|
+
};
|
|
2052
2029
|
}
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2030
|
+
/**
|
|
2031
|
+
* Maps raw type of an object to an object containing functions that can be used
|
|
2032
|
+
* to build an appropritate proxy handler for that raw type. Eg: when making a
|
|
2033
|
+
* reactive set, calling the has method should mark the key that is being
|
|
2034
|
+
* retrieved as observed, and calling the add or delete method should notify the
|
|
2035
|
+
* reactives that the key which is being added or deleted has been modified.
|
|
2036
|
+
*/
|
|
2037
|
+
const rawTypeToFuncHandlers = {
|
|
2038
|
+
Set: (target, callback) => ({
|
|
2039
|
+
has: makeKeyObserver("has", target, callback),
|
|
2040
|
+
add: delegateAndNotify("add", "has", target),
|
|
2041
|
+
delete: delegateAndNotify("delete", "has", target),
|
|
2042
|
+
keys: makeIteratorObserver("keys", target, callback),
|
|
2043
|
+
values: makeIteratorObserver("values", target, callback),
|
|
2044
|
+
entries: makeIteratorObserver("entries", target, callback),
|
|
2045
|
+
[Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
|
|
2046
|
+
forEach: makeForEachObserver(target, callback),
|
|
2047
|
+
clear: makeClearNotifier(target),
|
|
2048
|
+
get size() {
|
|
2049
|
+
observeTargetKey(target, KEYCHANGES, callback);
|
|
2050
|
+
return target.size;
|
|
2051
|
+
},
|
|
2052
|
+
}),
|
|
2053
|
+
Map: (target, callback) => ({
|
|
2054
|
+
has: makeKeyObserver("has", target, callback),
|
|
2055
|
+
get: makeKeyObserver("get", target, callback),
|
|
2056
|
+
set: delegateAndNotify("set", "get", target),
|
|
2057
|
+
delete: delegateAndNotify("delete", "has", target),
|
|
2058
|
+
keys: makeIteratorObserver("keys", target, callback),
|
|
2059
|
+
values: makeIteratorObserver("values", target, callback),
|
|
2060
|
+
entries: makeIteratorObserver("entries", target, callback),
|
|
2061
|
+
[Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
|
|
2062
|
+
forEach: makeForEachObserver(target, callback),
|
|
2063
|
+
clear: makeClearNotifier(target),
|
|
2064
|
+
get size() {
|
|
2065
|
+
observeTargetKey(target, KEYCHANGES, callback);
|
|
2066
|
+
return target.size;
|
|
2067
|
+
},
|
|
2068
|
+
}),
|
|
2069
|
+
WeakMap: (target, callback) => ({
|
|
2070
|
+
has: makeKeyObserver("has", target, callback),
|
|
2071
|
+
get: makeKeyObserver("get", target, callback),
|
|
2072
|
+
set: delegateAndNotify("set", "get", target),
|
|
2073
|
+
delete: delegateAndNotify("delete", "has", target),
|
|
2074
|
+
}),
|
|
2075
|
+
};
|
|
2076
|
+
/**
|
|
2077
|
+
* Creates a proxy handler for collections (Set/Map/WeakMap)
|
|
2078
|
+
*
|
|
2079
|
+
* @param callback @see reactive
|
|
2080
|
+
* @param target @see reactive
|
|
2081
|
+
* @returns a proxy handler object
|
|
2082
|
+
*/
|
|
2083
|
+
function collectionsProxyHandler(target, callback, targetRawType) {
|
|
2084
|
+
// TODO: if performance is an issue we can create the special handlers lazily when each
|
|
2085
|
+
// property is read.
|
|
2086
|
+
const specialHandlers = rawTypeToFuncHandlers[targetRawType](target, callback);
|
|
2087
|
+
return Object.assign(basicProxyHandler(callback), {
|
|
2088
|
+
get(target, key) {
|
|
2089
|
+
if (key === TARGET) {
|
|
2090
|
+
return target;
|
|
2096
2091
|
}
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
while ((current = patchedFibers.pop())) {
|
|
2100
|
-
current = current;
|
|
2101
|
-
if (current.appliedToDom) {
|
|
2102
|
-
for (let cb of current.node.patched) {
|
|
2103
|
-
cb();
|
|
2104
|
-
}
|
|
2105
|
-
}
|
|
2092
|
+
if (objectHasOwnProperty.call(specialHandlers, key)) {
|
|
2093
|
+
return specialHandlers[key];
|
|
2106
2094
|
}
|
|
2095
|
+
observeTargetKey(target, key, callback);
|
|
2096
|
+
return possiblyReactive(target[key], callback);
|
|
2097
|
+
},
|
|
2098
|
+
});
|
|
2099
|
+
}
|
|
2100
|
+
|
|
2101
|
+
/**
|
|
2102
|
+
* Creates a batched version of a callback so that all calls to it in the same
|
|
2103
|
+
* microtick will only call the original callback once.
|
|
2104
|
+
*
|
|
2105
|
+
* @param callback the callback to batch
|
|
2106
|
+
* @returns a batched version of the original callback
|
|
2107
|
+
*/
|
|
2108
|
+
function batched(callback) {
|
|
2109
|
+
let called = false;
|
|
2110
|
+
return async () => {
|
|
2111
|
+
// This await blocks all calls to the callback here, then releases them sequentially
|
|
2112
|
+
// in the next microtick. This line decides the granularity of the batch.
|
|
2113
|
+
await Promise.resolve();
|
|
2114
|
+
if (!called) {
|
|
2115
|
+
called = true;
|
|
2116
|
+
// wait for all calls in this microtick to fall through before resetting "called"
|
|
2117
|
+
// so that only the first call to the batched function calls the original callback.
|
|
2118
|
+
// Schedule this before calling the callback so that calls to the batched function
|
|
2119
|
+
// within the callback will proceed only after resetting called to false, and have
|
|
2120
|
+
// a chance to execute the callback again
|
|
2121
|
+
Promise.resolve().then(() => (called = false));
|
|
2122
|
+
callback();
|
|
2107
2123
|
}
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2124
|
+
};
|
|
2125
|
+
}
|
|
2126
|
+
function validateTarget(target) {
|
|
2127
|
+
if (!(target instanceof HTMLElement)) {
|
|
2128
|
+
throw new Error("Cannot mount component: the target is not a valid DOM element");
|
|
2112
2129
|
}
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
if (newValue === 0) {
|
|
2116
|
-
this.node.app.scheduler.flush();
|
|
2117
|
-
}
|
|
2130
|
+
if (!document.body.contains(target)) {
|
|
2131
|
+
throw new Error("Cannot mount a component on a detached dom node");
|
|
2118
2132
|
}
|
|
2119
2133
|
}
|
|
2120
|
-
class
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
this.target = target;
|
|
2124
|
-
this.position = options.position || "last-child";
|
|
2134
|
+
class EventBus extends EventTarget {
|
|
2135
|
+
trigger(name, payload) {
|
|
2136
|
+
this.dispatchEvent(new CustomEvent(name, { detail: payload }));
|
|
2125
2137
|
}
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
node.app.constructor.validateTarget(this.target);
|
|
2132
|
-
if (node.bdom) {
|
|
2133
|
-
// this is a complicated situation: if we mount a fiber with an existing
|
|
2134
|
-
// bdom, this means that this same fiber was already completed, mounted,
|
|
2135
|
-
// but a crash occurred in some mounted hook. Then, it was handled and
|
|
2136
|
-
// the new rendering is being applied.
|
|
2137
|
-
node.updateDom();
|
|
2138
|
-
}
|
|
2139
|
-
else {
|
|
2140
|
-
node.bdom = this.bdom;
|
|
2141
|
-
if (this.position === "last-child" || this.target.childNodes.length === 0) {
|
|
2142
|
-
mount$1(node.bdom, this.target);
|
|
2143
|
-
}
|
|
2144
|
-
else {
|
|
2145
|
-
const firstChild = this.target.childNodes[0];
|
|
2146
|
-
mount$1(node.bdom, this.target, firstChild);
|
|
2147
|
-
}
|
|
2148
|
-
}
|
|
2149
|
-
// unregistering the fiber before mounted since it can do another render
|
|
2150
|
-
// and that the current rendering is obviously completed
|
|
2151
|
-
node.fiber = null;
|
|
2152
|
-
node.status = 1 /* MOUNTED */;
|
|
2153
|
-
this.appliedToDom = true;
|
|
2154
|
-
let mountedFibers = this.mounted;
|
|
2155
|
-
while ((current = mountedFibers.pop())) {
|
|
2156
|
-
if (current.appliedToDom) {
|
|
2157
|
-
for (let cb of current.node.mounted) {
|
|
2158
|
-
cb();
|
|
2159
|
-
}
|
|
2160
|
-
}
|
|
2161
|
-
}
|
|
2138
|
+
}
|
|
2139
|
+
function whenReady(fn) {
|
|
2140
|
+
return new Promise(function (resolve) {
|
|
2141
|
+
if (document.readyState !== "loading") {
|
|
2142
|
+
resolve(true);
|
|
2162
2143
|
}
|
|
2163
|
-
|
|
2164
|
-
|
|
2144
|
+
else {
|
|
2145
|
+
document.addEventListener("DOMContentLoaded", resolve, false);
|
|
2165
2146
|
}
|
|
2147
|
+
}).then(fn || function () { });
|
|
2148
|
+
}
|
|
2149
|
+
async function loadFile(url) {
|
|
2150
|
+
const result = await fetch(url);
|
|
2151
|
+
if (!result.ok) {
|
|
2152
|
+
throw new Error("Error while fetching xml templates");
|
|
2166
2153
|
}
|
|
2154
|
+
return await result.text();
|
|
2155
|
+
}
|
|
2156
|
+
/*
|
|
2157
|
+
* This class just transports the fact that a string is safe
|
|
2158
|
+
* to be injected as HTML. Overriding a JS primitive is quite painful though
|
|
2159
|
+
* so we need to redfine toString and valueOf.
|
|
2160
|
+
*/
|
|
2161
|
+
class Markup extends String {
|
|
2162
|
+
}
|
|
2163
|
+
/*
|
|
2164
|
+
* Marks a value as safe, that is, a value that can be injected as HTML directly.
|
|
2165
|
+
* It should be used to wrap the value passed to a t-out directive to allow a raw rendering.
|
|
2166
|
+
*/
|
|
2167
|
+
function markup(value) {
|
|
2168
|
+
return new Markup(value);
|
|
2167
2169
|
}
|
|
2168
2170
|
|
|
2169
2171
|
let currentNode = null;
|
|
@@ -2178,8 +2180,6 @@ function useComponent() {
|
|
|
2178
2180
|
}
|
|
2179
2181
|
/**
|
|
2180
2182
|
* Apply default props (only top level).
|
|
2181
|
-
*
|
|
2182
|
-
* Note that this method does modify in place the props
|
|
2183
2183
|
*/
|
|
2184
2184
|
function applyDefaultProps(props, defaultProps) {
|
|
2185
2185
|
for (let propName in defaultProps) {
|
|
@@ -2213,61 +2213,6 @@ function useState(state) {
|
|
|
2213
2213
|
}
|
|
2214
2214
|
return reactive(state, render);
|
|
2215
2215
|
}
|
|
2216
|
-
function arePropsDifferent(props1, props2) {
|
|
2217
|
-
for (let k in props1) {
|
|
2218
|
-
const prop1 = props1[k] && typeof props1[k] === "object" ? toRaw(props1[k]) : props1[k];
|
|
2219
|
-
const prop2 = props2[k] && typeof props2[k] === "object" ? toRaw(props2[k]) : props2[k];
|
|
2220
|
-
if (prop1 !== prop2) {
|
|
2221
|
-
return true;
|
|
2222
|
-
}
|
|
2223
|
-
}
|
|
2224
|
-
return Object.keys(props1).length !== Object.keys(props2).length;
|
|
2225
|
-
}
|
|
2226
|
-
function component(name, props, key, ctx, parent) {
|
|
2227
|
-
let node = ctx.children[key];
|
|
2228
|
-
let isDynamic = typeof name !== "string";
|
|
2229
|
-
if (node && node.status === 2 /* DESTROYED */) {
|
|
2230
|
-
node = undefined;
|
|
2231
|
-
}
|
|
2232
|
-
if (isDynamic && node && node.component.constructor !== name) {
|
|
2233
|
-
node = undefined;
|
|
2234
|
-
}
|
|
2235
|
-
const parentFiber = ctx.fiber;
|
|
2236
|
-
if (node) {
|
|
2237
|
-
let shouldRender = node.forceNextRender;
|
|
2238
|
-
if (shouldRender) {
|
|
2239
|
-
node.forceNextRender = false;
|
|
2240
|
-
}
|
|
2241
|
-
else {
|
|
2242
|
-
const currentProps = node.component.props;
|
|
2243
|
-
shouldRender = parentFiber.deep || arePropsDifferent(currentProps, props);
|
|
2244
|
-
}
|
|
2245
|
-
if (shouldRender) {
|
|
2246
|
-
node.updateAndRender(props, parentFiber);
|
|
2247
|
-
}
|
|
2248
|
-
}
|
|
2249
|
-
else {
|
|
2250
|
-
// new component
|
|
2251
|
-
let C;
|
|
2252
|
-
if (isDynamic) {
|
|
2253
|
-
C = name;
|
|
2254
|
-
}
|
|
2255
|
-
else {
|
|
2256
|
-
C = parent.constructor.components[name];
|
|
2257
|
-
if (!C) {
|
|
2258
|
-
throw new Error(`Cannot find the definition of component "${name}"`);
|
|
2259
|
-
}
|
|
2260
|
-
else if (!(C.prototype instanceof Component)) {
|
|
2261
|
-
throw new Error(`"${name}" is not a Component. It must inherit from the Component class`);
|
|
2262
|
-
}
|
|
2263
|
-
}
|
|
2264
|
-
node = new ComponentNode(C, props, ctx.app, ctx, key);
|
|
2265
|
-
ctx.children[key] = node;
|
|
2266
|
-
node.initiateRender(new Fiber(node, parentFiber));
|
|
2267
|
-
}
|
|
2268
|
-
parentFiber.childrenMap[key] = node;
|
|
2269
|
-
return node;
|
|
2270
|
-
}
|
|
2271
2216
|
class ComponentNode {
|
|
2272
2217
|
constructor(C, props, app, parent, parentKey) {
|
|
2273
2218
|
this.fiber = null;
|
|
@@ -2286,9 +2231,11 @@ class ComponentNode {
|
|
|
2286
2231
|
currentNode = this;
|
|
2287
2232
|
this.app = app;
|
|
2288
2233
|
this.parent = parent;
|
|
2234
|
+
this.props = props;
|
|
2289
2235
|
this.parentKey = parentKey;
|
|
2290
2236
|
this.level = parent ? parent.level + 1 : 0;
|
|
2291
2237
|
const defaultProps = C.defaultProps;
|
|
2238
|
+
props = Object.assign({}, props);
|
|
2292
2239
|
if (defaultProps) {
|
|
2293
2240
|
applyDefaultProps(props, defaultProps);
|
|
2294
2241
|
}
|
|
@@ -2401,6 +2348,8 @@ class ComponentNode {
|
|
|
2401
2348
|
this.status = 2 /* DESTROYED */;
|
|
2402
2349
|
}
|
|
2403
2350
|
async updateAndRender(props, parentFiber) {
|
|
2351
|
+
const rawProps = props;
|
|
2352
|
+
props = Object.assign({}, props);
|
|
2404
2353
|
// update
|
|
2405
2354
|
const fiber = makeChildFiber(this, parentFiber);
|
|
2406
2355
|
this.fiber = fiber;
|
|
@@ -2423,6 +2372,7 @@ class ComponentNode {
|
|
|
2423
2372
|
return;
|
|
2424
2373
|
}
|
|
2425
2374
|
component.props = props;
|
|
2375
|
+
this.props = rawProps;
|
|
2426
2376
|
fiber.render();
|
|
2427
2377
|
const parentRoot = parentFiber.root;
|
|
2428
2378
|
if (this.willPatch.length) {
|
|
@@ -2485,10 +2435,15 @@ class ComponentNode {
|
|
|
2485
2435
|
}
|
|
2486
2436
|
}
|
|
2487
2437
|
_patch() {
|
|
2488
|
-
|
|
2489
|
-
this.children
|
|
2490
|
-
|
|
2491
|
-
|
|
2438
|
+
let hasChildren = false;
|
|
2439
|
+
for (let _k in this.children) {
|
|
2440
|
+
hasChildren = true;
|
|
2441
|
+
break;
|
|
2442
|
+
}
|
|
2443
|
+
const fiber = this.fiber;
|
|
2444
|
+
this.children = fiber.childrenMap;
|
|
2445
|
+
this.bdom.patch(fiber.bdom, hasChildren);
|
|
2446
|
+
fiber.appliedToDom = true;
|
|
2492
2447
|
this.fiber = null;
|
|
2493
2448
|
}
|
|
2494
2449
|
beforeRemove() {
|
|
@@ -2616,6 +2571,19 @@ function onError(callback) {
|
|
|
2616
2571
|
handlers.push(callback.bind(node.component));
|
|
2617
2572
|
}
|
|
2618
2573
|
|
|
2574
|
+
class Component {
|
|
2575
|
+
constructor(props, env, node) {
|
|
2576
|
+
this.props = props;
|
|
2577
|
+
this.env = env;
|
|
2578
|
+
this.__owl__ = node;
|
|
2579
|
+
}
|
|
2580
|
+
setup() { }
|
|
2581
|
+
render(deep = false) {
|
|
2582
|
+
this.__owl__.render(deep === true);
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
Component.template = "";
|
|
2586
|
+
|
|
2619
2587
|
const VText = text("").constructor;
|
|
2620
2588
|
class VPortal extends VText {
|
|
2621
2589
|
constructor(selector, realBDom) {
|
|
@@ -2694,6 +2662,7 @@ Portal.props = {
|
|
|
2694
2662
|
// -----------------------------------------------------------------------------
|
|
2695
2663
|
const isUnionType = (t) => Array.isArray(t);
|
|
2696
2664
|
const isBaseType = (t) => typeof t !== "object";
|
|
2665
|
+
const isValueType = (t) => typeof t === "object" && t && "value" in t;
|
|
2697
2666
|
function isOptional(t) {
|
|
2698
2667
|
return typeof t === "object" && "optional" in t ? t.optional || false : false;
|
|
2699
2668
|
}
|
|
@@ -2707,6 +2676,9 @@ function describe(info) {
|
|
|
2707
2676
|
else if (isUnionType(info)) {
|
|
2708
2677
|
return info.map(describe).join(" or ");
|
|
2709
2678
|
}
|
|
2679
|
+
else if (isValueType(info)) {
|
|
2680
|
+
return String(info.value);
|
|
2681
|
+
}
|
|
2710
2682
|
if ("element" in info) {
|
|
2711
2683
|
return `list of ${describe({ type: info.element, optional: false })}s`;
|
|
2712
2684
|
}
|
|
@@ -2792,6 +2764,9 @@ function validateType(key, value, descr) {
|
|
|
2792
2764
|
else if (isBaseType(descr)) {
|
|
2793
2765
|
return validateBaseType(key, value, descr);
|
|
2794
2766
|
}
|
|
2767
|
+
else if (isValueType(descr)) {
|
|
2768
|
+
return value === descr.value ? null : `'${key}' is not equal to '${descr.value}'`;
|
|
2769
|
+
}
|
|
2795
2770
|
else if (isUnionType(descr)) {
|
|
2796
2771
|
let validDescr = descr.find((p) => !validateType(key, value, p));
|
|
2797
2772
|
return validDescr ? null : `'${key}' is not a ${describe(descr)}`;
|
|
@@ -2820,6 +2795,7 @@ function validateType(key, value, descr) {
|
|
|
2820
2795
|
return result;
|
|
2821
2796
|
}
|
|
2822
2797
|
|
|
2798
|
+
const ObjectCreate = Object.create;
|
|
2823
2799
|
/**
|
|
2824
2800
|
* This file contains utility functions that will be injected in each template,
|
|
2825
2801
|
* to perform various useful tasks in the compiled code.
|
|
@@ -2831,7 +2807,7 @@ function callSlot(ctx, parent, key, name, dynamic, extra, defaultContent) {
|
|
|
2831
2807
|
key = key + "__slot_" + name;
|
|
2832
2808
|
const slots = ctx.props.slots || {};
|
|
2833
2809
|
const { __render, __ctx, __scope } = slots[name] || {};
|
|
2834
|
-
const slotScope =
|
|
2810
|
+
const slotScope = ObjectCreate(__ctx || {});
|
|
2835
2811
|
if (__scope) {
|
|
2836
2812
|
slotScope[__scope] = extra;
|
|
2837
2813
|
}
|
|
@@ -2851,7 +2827,7 @@ function callSlot(ctx, parent, key, name, dynamic, extra, defaultContent) {
|
|
|
2851
2827
|
}
|
|
2852
2828
|
function capture(ctx) {
|
|
2853
2829
|
const component = ctx.__owl__.component;
|
|
2854
|
-
const result =
|
|
2830
|
+
const result = ObjectCreate(component);
|
|
2855
2831
|
for (let k in ctx) {
|
|
2856
2832
|
result[k] = ctx[k];
|
|
2857
2833
|
}
|
|
@@ -2904,13 +2880,14 @@ function shallowEqual(l1, l2) {
|
|
|
2904
2880
|
return true;
|
|
2905
2881
|
}
|
|
2906
2882
|
class LazyValue {
|
|
2907
|
-
constructor(fn, ctx, node) {
|
|
2883
|
+
constructor(fn, ctx, component, node) {
|
|
2908
2884
|
this.fn = fn;
|
|
2909
2885
|
this.ctx = capture(ctx);
|
|
2886
|
+
this.component = component;
|
|
2910
2887
|
this.node = node;
|
|
2911
2888
|
}
|
|
2912
2889
|
evaluate() {
|
|
2913
|
-
return this.fn(this.ctx, this.node);
|
|
2890
|
+
return this.fn.call(this.component, this.ctx, this.node);
|
|
2914
2891
|
}
|
|
2915
2892
|
toString() {
|
|
2916
2893
|
return this.evaluate().toString();
|
|
@@ -2919,43 +2896,56 @@ class LazyValue {
|
|
|
2919
2896
|
/*
|
|
2920
2897
|
* Safely outputs `value` as a block depending on the nature of `value`
|
|
2921
2898
|
*/
|
|
2922
|
-
function safeOutput(value) {
|
|
2923
|
-
if (
|
|
2924
|
-
return
|
|
2899
|
+
function safeOutput(value, defaultValue) {
|
|
2900
|
+
if (value === undefined) {
|
|
2901
|
+
return defaultValue ? toggler("default", defaultValue) : toggler("undefined", text(""));
|
|
2925
2902
|
}
|
|
2926
2903
|
let safeKey;
|
|
2927
2904
|
let block;
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2905
|
+
switch (typeof value) {
|
|
2906
|
+
case "object":
|
|
2907
|
+
if (value instanceof Markup) {
|
|
2908
|
+
safeKey = `string_safe`;
|
|
2909
|
+
block = html(value);
|
|
2910
|
+
}
|
|
2911
|
+
else if (value instanceof LazyValue) {
|
|
2912
|
+
safeKey = `lazy_value`;
|
|
2913
|
+
block = value.evaluate();
|
|
2914
|
+
}
|
|
2915
|
+
else if (value instanceof String) {
|
|
2916
|
+
safeKey = "string_unsafe";
|
|
2917
|
+
block = text(value);
|
|
2918
|
+
}
|
|
2919
|
+
else {
|
|
2920
|
+
// Assuming it is a block
|
|
2921
|
+
safeKey = "block_safe";
|
|
2922
|
+
block = value;
|
|
2923
|
+
}
|
|
2924
|
+
break;
|
|
2925
|
+
case "string":
|
|
2926
|
+
safeKey = "string_unsafe";
|
|
2927
|
+
block = text(value);
|
|
2928
|
+
break;
|
|
2929
|
+
default:
|
|
2930
|
+
safeKey = "string_unsafe";
|
|
2931
|
+
block = text(String(value));
|
|
2944
2932
|
}
|
|
2945
2933
|
return toggler(safeKey, block);
|
|
2946
2934
|
}
|
|
2947
2935
|
let boundFunctions = new WeakMap();
|
|
2936
|
+
const WeakMapGet = WeakMap.prototype.get;
|
|
2937
|
+
const WeakMapSet = WeakMap.prototype.set;
|
|
2948
2938
|
function bind(ctx, fn) {
|
|
2949
2939
|
let component = ctx.__owl__.component;
|
|
2950
|
-
let boundFnMap =
|
|
2940
|
+
let boundFnMap = WeakMapGet.call(boundFunctions, component);
|
|
2951
2941
|
if (!boundFnMap) {
|
|
2952
2942
|
boundFnMap = new WeakMap();
|
|
2953
|
-
|
|
2943
|
+
WeakMapSet.call(boundFunctions, component, boundFnMap);
|
|
2954
2944
|
}
|
|
2955
|
-
let boundFn =
|
|
2945
|
+
let boundFn = WeakMapGet.call(boundFnMap, fn);
|
|
2956
2946
|
if (!boundFn) {
|
|
2957
2947
|
boundFn = fn.bind(component);
|
|
2958
|
-
|
|
2948
|
+
WeakMapSet.call(boundFnMap, fn, boundFn);
|
|
2959
2949
|
}
|
|
2960
2950
|
return boundFn;
|
|
2961
2951
|
}
|
|
@@ -3031,7 +3021,7 @@ const helpers = {
|
|
|
3031
3021
|
markRaw,
|
|
3032
3022
|
};
|
|
3033
3023
|
|
|
3034
|
-
const bdom = { text, createBlock, list, multi, html, toggler,
|
|
3024
|
+
const bdom = { text, createBlock, list, multi, html, toggler, comment };
|
|
3035
3025
|
function parseXML$1(xml) {
|
|
3036
3026
|
const parser = new DOMParser();
|
|
3037
3027
|
const doc = parser.parseFromString(xml, "text/xml");
|
|
@@ -3194,7 +3184,7 @@ const STATIC_TOKEN_MAP = Object.assign(Object.create(null), {
|
|
|
3194
3184
|
});
|
|
3195
3185
|
// note that the space after typeof is relevant. It makes sure that the formatted
|
|
3196
3186
|
// expression has a space after typeof. Currently we don't support delete and void
|
|
3197
|
-
const OPERATORS = "...,.,===,==,+,!==,!=,!,||,&&,>=,>,<=,<,?,-,*,/,%,typeof ,=>,=,;,in ,new ".split(",");
|
|
3187
|
+
const OPERATORS = "...,.,===,==,+,!==,!=,!,||,&&,>=,>,<=,<,?,-,*,/,%,typeof ,=>,=,;,in ,new ,|,&,^,~".split(",");
|
|
3198
3188
|
let tokenizeString = function (expr) {
|
|
3199
3189
|
let s = expr[0];
|
|
3200
3190
|
let start = s;
|
|
@@ -3442,15 +3432,17 @@ function compileExpr(expr) {
|
|
|
3442
3432
|
.map((t) => paddedValues.get(t.value) || t.value)
|
|
3443
3433
|
.join("");
|
|
3444
3434
|
}
|
|
3445
|
-
const INTERP_REGEXP = /\{\{.*?\}\}/g;
|
|
3446
|
-
|
|
3447
|
-
function interpolate(s) {
|
|
3435
|
+
const INTERP_REGEXP = /\{\{.*?\}\}|\#\{.*?\}/g;
|
|
3436
|
+
function replaceDynamicParts(s, replacer) {
|
|
3448
3437
|
let matches = s.match(INTERP_REGEXP);
|
|
3449
3438
|
if (matches && matches[0].length === s.length) {
|
|
3450
|
-
return `(${
|
|
3439
|
+
return `(${replacer(s.slice(2, matches[0][0] === "{" ? -2 : -1))})`;
|
|
3451
3440
|
}
|
|
3452
|
-
let r = s.replace(
|
|
3441
|
+
let r = s.replace(INTERP_REGEXP, (s) => "${" + replacer(s.slice(2, s[0] === "{" ? -2 : -1)) + "}");
|
|
3453
3442
|
return "`" + r + "`";
|
|
3443
|
+
}
|
|
3444
|
+
function interpolate(s) {
|
|
3445
|
+
return replaceDynamicParts(s, compileExpr);
|
|
3454
3446
|
}
|
|
3455
3447
|
|
|
3456
3448
|
// using a non-html document so that <inner/outer>HTML serializes as XML instead
|
|
@@ -3625,9 +3617,7 @@ class CodeGenerator {
|
|
|
3625
3617
|
tKeyExpr: null,
|
|
3626
3618
|
});
|
|
3627
3619
|
// define blocks and utility functions
|
|
3628
|
-
let mainCode = [
|
|
3629
|
-
` let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;`,
|
|
3630
|
-
];
|
|
3620
|
+
let mainCode = [` let { text, createBlock, list, multi, html, toggler, comment } = bdom;`];
|
|
3631
3621
|
if (this.helpers.size) {
|
|
3632
3622
|
mainCode.push(`let { ${[...this.helpers].join(", ")} } = helpers;`);
|
|
3633
3623
|
}
|
|
@@ -3643,6 +3633,7 @@ class CodeGenerator {
|
|
|
3643
3633
|
for (let block of this.blocks) {
|
|
3644
3634
|
if (block.dom) {
|
|
3645
3635
|
let xmlString = block.asXmlString();
|
|
3636
|
+
xmlString = xmlString.replace(/`/g, "\\`");
|
|
3646
3637
|
if (block.dynamicTagName) {
|
|
3647
3638
|
xmlString = xmlString.replace(/^<\w+/, `<\${tag || '${block.dom.nodeName}'}`);
|
|
3648
3639
|
xmlString = xmlString.replace(/\w+>$/, `\${tag || '${block.dom.nodeName}'}>`);
|
|
@@ -3950,8 +3941,8 @@ class CodeGenerator {
|
|
|
3950
3941
|
this.target.hasRef = true;
|
|
3951
3942
|
const isDynamic = INTERP_REGEXP.test(ast.ref);
|
|
3952
3943
|
if (isDynamic) {
|
|
3953
|
-
const str = ast.ref
|
|
3954
|
-
const idx = block.insertData(`(el) => refs[
|
|
3944
|
+
const str = replaceDynamicParts(ast.ref, (expr) => this.captureExpression(expr, true));
|
|
3945
|
+
const idx = block.insertData(`(el) => refs[${str}] = el`, "ref");
|
|
3955
3946
|
attrs["block-ref"] = String(idx);
|
|
3956
3947
|
}
|
|
3957
3948
|
else {
|
|
@@ -4080,16 +4071,24 @@ class CodeGenerator {
|
|
|
4080
4071
|
this.insertAnchor(block);
|
|
4081
4072
|
}
|
|
4082
4073
|
block = this.createBlock(block, "html", ctx);
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4074
|
+
let blockStr;
|
|
4075
|
+
if (ast.expr === "0") {
|
|
4076
|
+
this.helpers.add("zero");
|
|
4077
|
+
blockStr = `ctx[zero]`;
|
|
4078
|
+
}
|
|
4079
|
+
else if (ast.body) {
|
|
4080
|
+
let bodyValue = null;
|
|
4081
|
+
bodyValue = BlockDescription.nextBlockId;
|
|
4087
4082
|
const subCtx = createContext(ctx);
|
|
4088
4083
|
this.compileAST({ type: 3 /* Multi */, content: ast.body }, subCtx);
|
|
4089
|
-
this.helpers.add("
|
|
4090
|
-
|
|
4084
|
+
this.helpers.add("safeOutput");
|
|
4085
|
+
blockStr = `safeOutput(${compileExpr(ast.expr)}, b${bodyValue})`;
|
|
4086
|
+
}
|
|
4087
|
+
else {
|
|
4088
|
+
this.helpers.add("safeOutput");
|
|
4089
|
+
blockStr = `safeOutput(${compileExpr(ast.expr)})`;
|
|
4091
4090
|
}
|
|
4092
|
-
this.insertBlock(
|
|
4091
|
+
this.insertBlock(blockStr, block, ctx);
|
|
4093
4092
|
}
|
|
4094
4093
|
compileTIf(ast, ctx, nextNode) {
|
|
4095
4094
|
let { block, forceNewBlock, index } = ctx;
|
|
@@ -4282,16 +4281,21 @@ class CodeGenerator {
|
|
|
4282
4281
|
}
|
|
4283
4282
|
compileTCall(ast, ctx) {
|
|
4284
4283
|
let { block, forceNewBlock } = ctx;
|
|
4284
|
+
let ctxVar = ctx.ctxVar || "ctx";
|
|
4285
|
+
if (ast.context) {
|
|
4286
|
+
ctxVar = generateId("ctx");
|
|
4287
|
+
this.addLine(`let ${ctxVar} = ${compileExpr(ast.context)};`);
|
|
4288
|
+
}
|
|
4285
4289
|
if (ast.body) {
|
|
4286
|
-
this.addLine(
|
|
4287
|
-
this.addLine(
|
|
4290
|
+
this.addLine(`${ctxVar} = Object.create(${ctxVar});`);
|
|
4291
|
+
this.addLine(`${ctxVar}[isBoundary] = 1;`);
|
|
4288
4292
|
this.helpers.add("isBoundary");
|
|
4289
4293
|
const nextId = BlockDescription.nextBlockId;
|
|
4290
|
-
const subCtx = createContext(ctx, { preventRoot: true });
|
|
4294
|
+
const subCtx = createContext(ctx, { preventRoot: true, ctxVar });
|
|
4291
4295
|
this.compileAST({ type: 3 /* Multi */, content: ast.body }, subCtx);
|
|
4292
4296
|
if (nextId !== BlockDescription.nextBlockId) {
|
|
4293
4297
|
this.helpers.add("zero");
|
|
4294
|
-
this.addLine(
|
|
4298
|
+
this.addLine(`${ctxVar}[zero] = b${nextId};`);
|
|
4295
4299
|
}
|
|
4296
4300
|
}
|
|
4297
4301
|
const isDynamic = INTERP_REGEXP.test(ast.name);
|
|
@@ -4309,7 +4313,7 @@ class CodeGenerator {
|
|
|
4309
4313
|
}
|
|
4310
4314
|
this.define(templateVar, subTemplate);
|
|
4311
4315
|
block = this.createBlock(block, "multi", ctx);
|
|
4312
|
-
this.insertBlock(`call(this, ${templateVar},
|
|
4316
|
+
this.insertBlock(`call(this, ${templateVar}, ${ctxVar}, node, ${key})`, block, {
|
|
4313
4317
|
...ctx,
|
|
4314
4318
|
forceNewBlock: !block,
|
|
4315
4319
|
});
|
|
@@ -4318,13 +4322,13 @@ class CodeGenerator {
|
|
|
4318
4322
|
const id = generateId(`callTemplate_`);
|
|
4319
4323
|
this.staticDefs.push({ id, expr: `app.getTemplate(${subTemplate})` });
|
|
4320
4324
|
block = this.createBlock(block, "multi", ctx);
|
|
4321
|
-
this.insertBlock(`${id}.call(this,
|
|
4325
|
+
this.insertBlock(`${id}.call(this, ${ctxVar}, node, ${key})`, block, {
|
|
4322
4326
|
...ctx,
|
|
4323
4327
|
forceNewBlock: !block,
|
|
4324
4328
|
});
|
|
4325
4329
|
}
|
|
4326
4330
|
if (ast.body && !ctx.isLast) {
|
|
4327
|
-
this.addLine(
|
|
4331
|
+
this.addLine(`${ctxVar} = ${ctxVar}.__proto__;`);
|
|
4328
4332
|
}
|
|
4329
4333
|
}
|
|
4330
4334
|
compileTCallBlock(ast, ctx) {
|
|
@@ -4345,7 +4349,7 @@ class CodeGenerator {
|
|
|
4345
4349
|
this.helpers.add("LazyValue");
|
|
4346
4350
|
const bodyAst = { type: 3 /* Multi */, content: ast.body };
|
|
4347
4351
|
const name = this.compileInNewTarget("value", bodyAst, ctx);
|
|
4348
|
-
let value = `new LazyValue(${name}, ctx, node)`;
|
|
4352
|
+
let value = `new LazyValue(${name}, ctx, this, node)`;
|
|
4349
4353
|
value = ast.value ? (value ? `withDefault(${expr}, ${value})` : expr) : value;
|
|
4350
4354
|
this.addLine(`ctx[\`${ast.name}\`] = ${value};`);
|
|
4351
4355
|
}
|
|
@@ -4363,7 +4367,7 @@ class CodeGenerator {
|
|
|
4363
4367
|
value = expr;
|
|
4364
4368
|
}
|
|
4365
4369
|
this.helpers.add("setContextValue");
|
|
4366
|
-
this.addLine(`setContextValue(ctx, "${ast.name}", ${value});`);
|
|
4370
|
+
this.addLine(`setContextValue(${ctx.ctxVar || "ctx"}, "${ast.name}", ${value});`);
|
|
4367
4371
|
}
|
|
4368
4372
|
}
|
|
4369
4373
|
generateComponentKey() {
|
|
@@ -4401,21 +4405,20 @@ class CodeGenerator {
|
|
|
4401
4405
|
return `${name}: ${value || undefined}`;
|
|
4402
4406
|
}
|
|
4403
4407
|
formatPropObject(obj) {
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4408
|
+
return Object.entries(obj).map(([k, v]) => this.formatProp(k, v));
|
|
4409
|
+
}
|
|
4410
|
+
getPropString(props, dynProps) {
|
|
4411
|
+
let propString = `{${props.join(",")}}`;
|
|
4412
|
+
if (dynProps) {
|
|
4413
|
+
propString = `Object.assign({}, ${compileExpr(dynProps)}${props.length ? ", " + propString : ""})`;
|
|
4407
4414
|
}
|
|
4408
|
-
return
|
|
4415
|
+
return propString;
|
|
4409
4416
|
}
|
|
4410
4417
|
compileComponent(ast, ctx) {
|
|
4411
4418
|
let { block } = ctx;
|
|
4412
4419
|
// props
|
|
4413
4420
|
const hasSlotsProp = "slots" in (ast.props || {});
|
|
4414
|
-
const props = [];
|
|
4415
|
-
const propExpr = this.formatPropObject(ast.props || {});
|
|
4416
|
-
if (propExpr) {
|
|
4417
|
-
props.push(propExpr);
|
|
4418
|
-
}
|
|
4421
|
+
const props = ast.props ? this.formatPropObject(ast.props) : [];
|
|
4419
4422
|
// slots
|
|
4420
4423
|
let slotDef = "";
|
|
4421
4424
|
if (ast.slots) {
|
|
@@ -4438,7 +4441,7 @@ class CodeGenerator {
|
|
|
4438
4441
|
params.push(`__scope: "${scope}"`);
|
|
4439
4442
|
}
|
|
4440
4443
|
if (ast.slots[slotName].attrs) {
|
|
4441
|
-
params.push(this.formatPropObject(ast.slots[slotName].attrs));
|
|
4444
|
+
params.push(...this.formatPropObject(ast.slots[slotName].attrs));
|
|
4442
4445
|
}
|
|
4443
4446
|
const slotInfo = `{${params.join(", ")}}`;
|
|
4444
4447
|
slotStr.push(`'${slotName}': ${slotInfo}`);
|
|
@@ -4449,11 +4452,7 @@ class CodeGenerator {
|
|
|
4449
4452
|
this.helpers.add("markRaw");
|
|
4450
4453
|
props.push(`slots: markRaw(${slotDef})`);
|
|
4451
4454
|
}
|
|
4452
|
-
|
|
4453
|
-
let propString = propStr;
|
|
4454
|
-
if (ast.dynamicProps) {
|
|
4455
|
-
propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)}${props.length ? ", " + propStr : ""})`;
|
|
4456
|
-
}
|
|
4455
|
+
let propString = this.getPropString(props, ast.dynamicProps);
|
|
4457
4456
|
let propVar;
|
|
4458
4457
|
if ((slotDef && (ast.dynamicProps || hasSlotsProp)) || this.dev) {
|
|
4459
4458
|
propVar = generateId("props");
|
|
@@ -4485,8 +4484,12 @@ class CodeGenerator {
|
|
|
4485
4484
|
if (ctx.tKeyExpr) {
|
|
4486
4485
|
keyArg = `${ctx.tKeyExpr} + ${keyArg}`;
|
|
4487
4486
|
}
|
|
4488
|
-
|
|
4489
|
-
|
|
4487
|
+
let id = generateId("comp");
|
|
4488
|
+
this.staticDefs.push({
|
|
4489
|
+
id,
|
|
4490
|
+
expr: `app.createComponent(${ast.isDynamic ? null : expr}, ${!ast.isDynamic}, ${!!ast.slots}, ${!!ast.dynamicProps}, ${!ast.props && !ast.dynamicProps})`,
|
|
4491
|
+
});
|
|
4492
|
+
let blockExpr = `${id}(${propString}, ${keyArg}, node, this, ${ast.isDynamic ? expr : null})`;
|
|
4490
4493
|
if (ast.isDynamic) {
|
|
4491
4494
|
blockExpr = `toggler(${expr}, ${blockExpr})`;
|
|
4492
4495
|
}
|
|
@@ -4525,7 +4528,12 @@ class CodeGenerator {
|
|
|
4525
4528
|
else {
|
|
4526
4529
|
slotName = "'" + ast.name + "'";
|
|
4527
4530
|
}
|
|
4528
|
-
const
|
|
4531
|
+
const dynProps = ast.attrs ? ast.attrs["t-props"] : null;
|
|
4532
|
+
if (ast.attrs) {
|
|
4533
|
+
delete ast.attrs["t-props"];
|
|
4534
|
+
}
|
|
4535
|
+
const props = ast.attrs ? this.formatPropObject(ast.attrs) : [];
|
|
4536
|
+
const scope = this.getPropString(props, dynProps);
|
|
4529
4537
|
if (ast.defaultContent) {
|
|
4530
4538
|
const name = this.compileInNewTarget("defaultContent", ast.defaultContent, ctx);
|
|
4531
4539
|
blockString = `callSlot(ctx, node, key, ${slotName}, ${dynamic}, ${scope}, ${name})`;
|
|
@@ -4566,10 +4574,15 @@ class CodeGenerator {
|
|
|
4566
4574
|
if (this.target.loopLevel || !this.hasSafeContext) {
|
|
4567
4575
|
ctxStr = generateId("ctx");
|
|
4568
4576
|
this.helpers.add("capture");
|
|
4569
|
-
this.define(ctxStr, `capture(ctx)
|
|
4577
|
+
this.define(ctxStr, `capture(ctx)`);
|
|
4570
4578
|
}
|
|
4579
|
+
let id = generateId("comp");
|
|
4580
|
+
this.staticDefs.push({
|
|
4581
|
+
id,
|
|
4582
|
+
expr: `app.createComponent(null, false, true, false, false)`,
|
|
4583
|
+
});
|
|
4571
4584
|
const target = compileExpr(ast.target);
|
|
4572
|
-
const blockString =
|
|
4585
|
+
const blockString = `${id}({target: ${target},slots: {'default': {__render: ${name}, __ctx: ${ctxStr}}}}, key + \`${key}\`, node, ctx, Portal)`;
|
|
4573
4586
|
if (block) {
|
|
4574
4587
|
this.insertAnchor(block);
|
|
4575
4588
|
}
|
|
@@ -4909,10 +4922,12 @@ function parseTCall(node, ctx) {
|
|
|
4909
4922
|
return null;
|
|
4910
4923
|
}
|
|
4911
4924
|
const subTemplate = node.getAttribute("t-call");
|
|
4925
|
+
const context = node.getAttribute("t-call-context");
|
|
4912
4926
|
node.removeAttribute("t-call");
|
|
4927
|
+
node.removeAttribute("t-call-context");
|
|
4913
4928
|
if (node.tagName !== "t") {
|
|
4914
4929
|
const ast = parseNode(node, ctx);
|
|
4915
|
-
const tcall = { type: 7 /* TCall */, name: subTemplate, body: null };
|
|
4930
|
+
const tcall = { type: 7 /* TCall */, name: subTemplate, body: null, context };
|
|
4916
4931
|
if (ast && ast.type === 2 /* DomNode */) {
|
|
4917
4932
|
ast.content = [tcall];
|
|
4918
4933
|
return ast;
|
|
@@ -4929,6 +4944,7 @@ function parseTCall(node, ctx) {
|
|
|
4929
4944
|
type: 7 /* TCall */,
|
|
4930
4945
|
name: subTemplate,
|
|
4931
4946
|
body: body.length ? body : null,
|
|
4947
|
+
context,
|
|
4932
4948
|
};
|
|
4933
4949
|
}
|
|
4934
4950
|
// -----------------------------------------------------------------------------
|
|
@@ -5512,6 +5528,55 @@ class App extends TemplateSet {
|
|
|
5512
5528
|
this.root.destroy();
|
|
5513
5529
|
}
|
|
5514
5530
|
}
|
|
5531
|
+
createComponent(name, isStatic, hasSlotsProp, hasDynamicPropList, hasNoProp) {
|
|
5532
|
+
const isDynamic = !isStatic;
|
|
5533
|
+
function _arePropsDifferent(props1, props2) {
|
|
5534
|
+
for (let k in props1) {
|
|
5535
|
+
if (props1[k] !== props2[k]) {
|
|
5536
|
+
return true;
|
|
5537
|
+
}
|
|
5538
|
+
}
|
|
5539
|
+
return hasDynamicPropList && Object.keys(props1).length !== Object.keys(props2).length;
|
|
5540
|
+
}
|
|
5541
|
+
const arePropsDifferent = hasSlotsProp
|
|
5542
|
+
? (_1, _2) => true
|
|
5543
|
+
: hasNoProp
|
|
5544
|
+
? (_1, _2) => false
|
|
5545
|
+
: _arePropsDifferent;
|
|
5546
|
+
const updateAndRender = ComponentNode.prototype.updateAndRender;
|
|
5547
|
+
const initiateRender = ComponentNode.prototype.initiateRender;
|
|
5548
|
+
return (props, key, ctx, parent, C) => {
|
|
5549
|
+
let children = ctx.children;
|
|
5550
|
+
let node = children[key];
|
|
5551
|
+
if (isDynamic && node && node.component.constructor !== C) {
|
|
5552
|
+
node = undefined;
|
|
5553
|
+
}
|
|
5554
|
+
const parentFiber = ctx.fiber;
|
|
5555
|
+
if (node) {
|
|
5556
|
+
if (arePropsDifferent(node.props, props) || parentFiber.deep || node.forceNextRender) {
|
|
5557
|
+
node.forceNextRender = false;
|
|
5558
|
+
updateAndRender.call(node, props, parentFiber);
|
|
5559
|
+
}
|
|
5560
|
+
}
|
|
5561
|
+
else {
|
|
5562
|
+
// new component
|
|
5563
|
+
if (isStatic) {
|
|
5564
|
+
C = parent.constructor.components[name];
|
|
5565
|
+
if (!C) {
|
|
5566
|
+
throw new Error(`Cannot find the definition of component "${name}"`);
|
|
5567
|
+
}
|
|
5568
|
+
else if (!(C.prototype instanceof Component)) {
|
|
5569
|
+
throw new Error(`"${name}" is not a Component. It must inherit from the Component class`);
|
|
5570
|
+
}
|
|
5571
|
+
}
|
|
5572
|
+
node = new ComponentNode(C, props, this, ctx, key);
|
|
5573
|
+
children[key] = node;
|
|
5574
|
+
initiateRender.call(node, new Fiber(node, parentFiber));
|
|
5575
|
+
}
|
|
5576
|
+
parentFiber.childrenMap[key] = node;
|
|
5577
|
+
return node;
|
|
5578
|
+
};
|
|
5579
|
+
}
|
|
5515
5580
|
}
|
|
5516
5581
|
App.validateTarget = validateTarget;
|
|
5517
5582
|
async function mount(C, target, config = {}) {
|
|
@@ -5692,7 +5757,7 @@ exports.whenReady = whenReady;
|
|
|
5692
5757
|
exports.xml = xml;
|
|
5693
5758
|
|
|
5694
5759
|
|
|
5695
|
-
__info__.version = '2.0.0-beta-
|
|
5696
|
-
__info__.date = '2022-
|
|
5697
|
-
__info__.hash = '
|
|
5760
|
+
__info__.version = '2.0.0-beta-11';
|
|
5761
|
+
__info__.date = '2022-06-28T13:28:26.775Z';
|
|
5762
|
+
__info__.hash = '76c389a';
|
|
5698
5763
|
__info__.url = 'https://github.com/odoo/owl';
|