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