@odoo/owl 2.0.0-beta-9 → 2.0.0-beta-12
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 +863 -827
- package/dist/owl.es.js +863 -827
- package/dist/owl.iife.js +863 -827
- package/dist/owl.iife.min.js +1 -1
- package/dist/types/compiler/code_generator.d.ts +3 -1
- package/dist/types/compiler/parser.d.ts +1 -0
- package/dist/types/owl.d.ts +6 -6
- 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 +2 -6
- package/dist/types/runtime/template_helpers.d.ts +3 -2
- package/dist/types/runtime/validation.d.ts +4 -2
- package/package.json +1 -1
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;
|
|
@@ -1382,798 +1386,786 @@ function remove(vnode, withBeforeRemove = false) {
|
|
|
1382
1386
|
vnode.remove();
|
|
1383
1387
|
}
|
|
1384
1388
|
|
|
1385
|
-
//
|
|
1386
|
-
const
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
const KEYCHANGES = Symbol("Key changes");
|
|
1391
|
-
const objectToString = Object.prototype.toString;
|
|
1392
|
-
const objectHasOwnProperty = Object.prototype.hasOwnProperty;
|
|
1393
|
-
const SUPPORTED_RAW_TYPES = new Set(["Object", "Array", "Set", "Map", "WeakMap"]);
|
|
1394
|
-
const COLLECTION_RAWTYPES = new Set(["Set", "Map", "WeakMap"]);
|
|
1395
|
-
/**
|
|
1396
|
-
* extract "RawType" from strings like "[object RawType]" => this lets us ignore
|
|
1397
|
-
* many native objects such as Promise (whose toString is [object Promise])
|
|
1398
|
-
* or Date ([object Date]), while also supporting collections without using
|
|
1399
|
-
* instanceof in a loop
|
|
1400
|
-
*
|
|
1401
|
-
* @param obj the object to check
|
|
1402
|
-
* @returns the raw type of the object
|
|
1403
|
-
*/
|
|
1404
|
-
function rawType(obj) {
|
|
1405
|
-
return objectToString.call(obj).slice(8, -1);
|
|
1406
|
-
}
|
|
1407
|
-
/**
|
|
1408
|
-
* Checks whether a given value can be made into a reactive object.
|
|
1409
|
-
*
|
|
1410
|
-
* @param value the value to check
|
|
1411
|
-
* @returns whether the value can be made reactive
|
|
1412
|
-
*/
|
|
1413
|
-
function canBeMadeReactive(value) {
|
|
1414
|
-
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) {
|
|
1415
1394
|
return false;
|
|
1416
1395
|
}
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
* Creates a reactive from the given object/callback if possible and returns it,
|
|
1421
|
-
* returns the original object otherwise.
|
|
1422
|
-
*
|
|
1423
|
-
* @param value the value make reactive
|
|
1424
|
-
* @returns a reactive for the given object when possible, the original otherwise
|
|
1425
|
-
*/
|
|
1426
|
-
function possiblyReactive(val, cb) {
|
|
1427
|
-
return canBeMadeReactive(val) ? reactive(val, cb) : val;
|
|
1428
|
-
}
|
|
1429
|
-
/**
|
|
1430
|
-
* Mark an object or array so that it is ignored by the reactivity system
|
|
1431
|
-
*
|
|
1432
|
-
* @param value the value to mark
|
|
1433
|
-
* @returns the object itself
|
|
1434
|
-
*/
|
|
1435
|
-
function markRaw(value) {
|
|
1436
|
-
value[SKIP] = true;
|
|
1437
|
-
return value;
|
|
1438
|
-
}
|
|
1439
|
-
/**
|
|
1440
|
-
* Given a reactive objet, return the raw (non reactive) underlying object
|
|
1441
|
-
*
|
|
1442
|
-
* @param value a reactive value
|
|
1443
|
-
* @returns the underlying value
|
|
1444
|
-
*/
|
|
1445
|
-
function toRaw(value) {
|
|
1446
|
-
return value[TARGET] || value;
|
|
1447
|
-
}
|
|
1448
|
-
const targetToKeysToCallbacks = new WeakMap();
|
|
1449
|
-
/**
|
|
1450
|
-
* Observes a given key on a target with an callback. The callback will be
|
|
1451
|
-
* called when the given key changes on the target.
|
|
1452
|
-
*
|
|
1453
|
-
* @param target the target whose key should be observed
|
|
1454
|
-
* @param key the key to observe (or Symbol(KEYCHANGES) for key creation
|
|
1455
|
-
* or deletion)
|
|
1456
|
-
* @param callback the function to call when the key changes
|
|
1457
|
-
*/
|
|
1458
|
-
function observeTargetKey(target, key, callback) {
|
|
1459
|
-
if (!targetToKeysToCallbacks.get(target)) {
|
|
1460
|
-
targetToKeysToCallbacks.set(target, new Map());
|
|
1396
|
+
const fiber = node.fiber;
|
|
1397
|
+
if (fiber) {
|
|
1398
|
+
fibersInError.set(fiber, error);
|
|
1461
1399
|
}
|
|
1462
|
-
const
|
|
1463
|
-
if (
|
|
1464
|
-
|
|
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
|
+
}
|
|
1465
1417
|
}
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
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
|
+
}
|
|
1469
1441
|
}
|
|
1470
|
-
|
|
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);
|
|
1471
1451
|
}
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
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;
|
|
1485
1471
|
}
|
|
1486
|
-
const
|
|
1487
|
-
if (
|
|
1488
|
-
|
|
1472
|
+
const fiber = new RootFiber(node, null);
|
|
1473
|
+
if (node.willPatch.length) {
|
|
1474
|
+
fiber.willPatch.push(fiber);
|
|
1489
1475
|
}
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
clearReactivesForCallback(callback);
|
|
1493
|
-
callback();
|
|
1476
|
+
if (node.patched.length) {
|
|
1477
|
+
fiber.patched.push(fiber);
|
|
1494
1478
|
}
|
|
1479
|
+
return fiber;
|
|
1480
|
+
}
|
|
1481
|
+
function throwOnRender() {
|
|
1482
|
+
throw new Error("Attempted to render cancelled fiber");
|
|
1495
1483
|
}
|
|
1496
|
-
const callbacksToTargets = new WeakMap();
|
|
1497
1484
|
/**
|
|
1498
|
-
*
|
|
1499
|
-
*
|
|
1500
|
-
* @param callback the callback for which the reactives need to be cleared
|
|
1485
|
+
* @returns number of not-yet rendered fibers cancelled
|
|
1501
1486
|
*/
|
|
1502
|
-
function
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
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];
|
|
1511
1495
|
}
|
|
1512
|
-
|
|
1513
|
-
|
|
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;
|
|
1514
1505
|
}
|
|
1506
|
+
else {
|
|
1507
|
+
result++;
|
|
1508
|
+
}
|
|
1509
|
+
result += cancelFibers(fiber.children);
|
|
1515
1510
|
}
|
|
1516
|
-
|
|
1517
|
-
}
|
|
1518
|
-
function getSubscriptions(callback) {
|
|
1519
|
-
const targets = callbacksToTargets.get(callback) || [];
|
|
1520
|
-
return [...targets].map((target) => {
|
|
1521
|
-
const keysToCallbacks = targetToKeysToCallbacks.get(target);
|
|
1522
|
-
return {
|
|
1523
|
-
target,
|
|
1524
|
-
keys: keysToCallbacks ? [...keysToCallbacks.keys()] : [],
|
|
1525
|
-
};
|
|
1526
|
-
});
|
|
1511
|
+
return result;
|
|
1527
1512
|
}
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
* This is a choice that was made because changing a key's value will trigger
|
|
1548
|
-
* this trap and we do not want to subscribe by writes. This also means that
|
|
1549
|
-
* Object.hasOwnProperty doesn't subscribe as it goes through this trap.
|
|
1550
|
-
*
|
|
1551
|
-
* @param target the object for which to create a reactive proxy
|
|
1552
|
-
* @param callback the function to call when an observed property of the
|
|
1553
|
-
* reactive has changed
|
|
1554
|
-
* @returns a proxy that tracks changes to it
|
|
1555
|
-
*/
|
|
1556
|
-
function reactive(target, callback = () => { }) {
|
|
1557
|
-
if (!canBeMadeReactive(target)) {
|
|
1558
|
-
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
|
+
}
|
|
1559
1532
|
}
|
|
1560
|
-
|
|
1561
|
-
|
|
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();
|
|
1562
1554
|
}
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
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
|
+
}
|
|
1566
1568
|
}
|
|
1567
|
-
|
|
1568
|
-
|
|
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;
|
|
1569
1581
|
}
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
:
|
|
1576
|
-
|
|
1577
|
-
|
|
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
|
+
}
|
|
1578
1635
|
}
|
|
1579
|
-
return reactivesForTarget.get(callback);
|
|
1580
1636
|
}
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
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();
|
|
1597
1655
|
}
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
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
|
+
}
|
|
1607
1665
|
}
|
|
1608
|
-
//
|
|
1609
|
-
//
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
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
|
+
}
|
|
1613
1678
|
}
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
observeTargetKey(target, KEYCHANGES, callback);
|
|
1632
|
-
return Reflect.has(target, key);
|
|
1633
|
-
},
|
|
1634
|
-
};
|
|
1635
|
-
}
|
|
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"]);
|
|
1636
1696
|
/**
|
|
1637
|
-
*
|
|
1638
|
-
*
|
|
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
|
|
1639
1701
|
*
|
|
1640
|
-
* @param
|
|
1641
|
-
* @
|
|
1642
|
-
* @param callback @see reactive
|
|
1702
|
+
* @param obj the object to check
|
|
1703
|
+
* @returns the raw type of the object
|
|
1643
1704
|
*/
|
|
1644
|
-
function
|
|
1645
|
-
return (
|
|
1646
|
-
key = toRaw(key);
|
|
1647
|
-
observeTargetKey(target, key, callback);
|
|
1648
|
-
return possiblyReactive(target[methodName](key), callback);
|
|
1649
|
-
};
|
|
1705
|
+
function rawType(obj) {
|
|
1706
|
+
return objectToString.call(obj).slice(8, -1);
|
|
1650
1707
|
}
|
|
1651
1708
|
/**
|
|
1652
|
-
*
|
|
1653
|
-
* observe keys as necessary.
|
|
1709
|
+
* Checks whether a given value can be made into a reactive object.
|
|
1654
1710
|
*
|
|
1655
|
-
* @param
|
|
1656
|
-
* @
|
|
1657
|
-
* @param callback @see reactive
|
|
1711
|
+
* @param value the value to check
|
|
1712
|
+
* @returns whether the value can be made reactive
|
|
1658
1713
|
*/
|
|
1659
|
-
function
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
const key = keys.next().value;
|
|
1665
|
-
observeTargetKey(target, key, callback);
|
|
1666
|
-
yield possiblyReactive(item, callback);
|
|
1667
|
-
}
|
|
1668
|
-
};
|
|
1714
|
+
function canBeMadeReactive(value) {
|
|
1715
|
+
if (typeof value !== "object") {
|
|
1716
|
+
return false;
|
|
1717
|
+
}
|
|
1718
|
+
return SUPPORTED_RAW_TYPES.has(rawType(value));
|
|
1669
1719
|
}
|
|
1670
1720
|
/**
|
|
1671
|
-
* Creates a
|
|
1672
|
-
*
|
|
1673
|
-
* 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.
|
|
1674
1723
|
*
|
|
1675
|
-
* @param
|
|
1676
|
-
* @
|
|
1724
|
+
* @param value the value make reactive
|
|
1725
|
+
* @returns a reactive for the given object when possible, the original otherwise
|
|
1677
1726
|
*/
|
|
1678
|
-
function
|
|
1679
|
-
return
|
|
1680
|
-
observeTargetKey(target, KEYCHANGES, callback);
|
|
1681
|
-
target.forEach(function (val, key, targetObj) {
|
|
1682
|
-
observeTargetKey(target, key, callback);
|
|
1683
|
-
forEachCb.call(thisArg, possiblyReactive(val, callback), possiblyReactive(key, callback), possiblyReactive(targetObj, callback));
|
|
1684
|
-
}, thisArg);
|
|
1685
|
-
};
|
|
1727
|
+
function possiblyReactive(val, cb) {
|
|
1728
|
+
return canBeMadeReactive(val) ? reactive(val, cb) : val;
|
|
1686
1729
|
}
|
|
1687
1730
|
/**
|
|
1688
|
-
*
|
|
1689
|
-
* that method has modified the presence or value of a key, and notify the
|
|
1690
|
-
* reactives appropriately.
|
|
1731
|
+
* Mark an object or array so that it is ignored by the reactivity system
|
|
1691
1732
|
*
|
|
1692
|
-
* @param
|
|
1693
|
-
* @
|
|
1694
|
-
* value before calling the delegate method for comparison purposes
|
|
1695
|
-
* @param target @see reactive
|
|
1733
|
+
* @param value the value to mark
|
|
1734
|
+
* @returns the object itself
|
|
1696
1735
|
*/
|
|
1697
|
-
function
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
const hadKey = target.has(key);
|
|
1701
|
-
const originalValue = target[getterName](key);
|
|
1702
|
-
const ret = target[setterName](key, value);
|
|
1703
|
-
const hasKey = target.has(key);
|
|
1704
|
-
if (hadKey !== hasKey) {
|
|
1705
|
-
notifyReactives(target, KEYCHANGES);
|
|
1706
|
-
}
|
|
1707
|
-
if (originalValue !== value) {
|
|
1708
|
-
notifyReactives(target, key);
|
|
1709
|
-
}
|
|
1710
|
-
return ret;
|
|
1711
|
-
};
|
|
1736
|
+
function markRaw(value) {
|
|
1737
|
+
value[SKIP] = true;
|
|
1738
|
+
return value;
|
|
1712
1739
|
}
|
|
1713
1740
|
/**
|
|
1714
|
-
*
|
|
1715
|
-
* the keys of the collection have changed.
|
|
1741
|
+
* Given a reactive objet, return the raw (non reactive) underlying object
|
|
1716
1742
|
*
|
|
1717
|
-
* @param
|
|
1743
|
+
* @param value a reactive value
|
|
1744
|
+
* @returns the underlying value
|
|
1718
1745
|
*/
|
|
1719
|
-
function
|
|
1720
|
-
return
|
|
1721
|
-
const allKeys = [...target.keys()];
|
|
1722
|
-
target.clear();
|
|
1723
|
-
notifyReactives(target, KEYCHANGES);
|
|
1724
|
-
for (const key of allKeys) {
|
|
1725
|
-
notifyReactives(target, key);
|
|
1726
|
-
}
|
|
1727
|
-
};
|
|
1746
|
+
function toRaw(value) {
|
|
1747
|
+
return value[TARGET] || value;
|
|
1728
1748
|
}
|
|
1749
|
+
const targetToKeysToCallbacks = new WeakMap();
|
|
1729
1750
|
/**
|
|
1730
|
-
*
|
|
1731
|
-
*
|
|
1732
|
-
* reactive set, calling the has method should mark the key that is being
|
|
1733
|
-
* retrieved as observed, and calling the add or delete method should notify the
|
|
1734
|
-
* reactives that the key which is being added or deleted has been modified.
|
|
1735
|
-
*/
|
|
1736
|
-
const rawTypeToFuncHandlers = {
|
|
1737
|
-
Set: (target, callback) => ({
|
|
1738
|
-
has: makeKeyObserver("has", target, callback),
|
|
1739
|
-
add: delegateAndNotify("add", "has", target),
|
|
1740
|
-
delete: delegateAndNotify("delete", "has", target),
|
|
1741
|
-
keys: makeIteratorObserver("keys", target, callback),
|
|
1742
|
-
values: makeIteratorObserver("values", target, callback),
|
|
1743
|
-
entries: makeIteratorObserver("entries", target, callback),
|
|
1744
|
-
[Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
|
|
1745
|
-
forEach: makeForEachObserver(target, callback),
|
|
1746
|
-
clear: makeClearNotifier(target),
|
|
1747
|
-
get size() {
|
|
1748
|
-
observeTargetKey(target, KEYCHANGES, callback);
|
|
1749
|
-
return target.size;
|
|
1750
|
-
},
|
|
1751
|
-
}),
|
|
1752
|
-
Map: (target, callback) => ({
|
|
1753
|
-
has: makeKeyObserver("has", target, callback),
|
|
1754
|
-
get: makeKeyObserver("get", target, callback),
|
|
1755
|
-
set: delegateAndNotify("set", "get", target),
|
|
1756
|
-
delete: delegateAndNotify("delete", "has", target),
|
|
1757
|
-
keys: makeIteratorObserver("keys", target, callback),
|
|
1758
|
-
values: makeIteratorObserver("values", target, callback),
|
|
1759
|
-
entries: makeIteratorObserver("entries", target, callback),
|
|
1760
|
-
[Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
|
|
1761
|
-
forEach: makeForEachObserver(target, callback),
|
|
1762
|
-
clear: makeClearNotifier(target),
|
|
1763
|
-
get size() {
|
|
1764
|
-
observeTargetKey(target, KEYCHANGES, callback);
|
|
1765
|
-
return target.size;
|
|
1766
|
-
},
|
|
1767
|
-
}),
|
|
1768
|
-
WeakMap: (target, callback) => ({
|
|
1769
|
-
has: makeKeyObserver("has", target, callback),
|
|
1770
|
-
get: makeKeyObserver("get", target, callback),
|
|
1771
|
-
set: delegateAndNotify("set", "get", target),
|
|
1772
|
-
delete: delegateAndNotify("delete", "has", target),
|
|
1773
|
-
}),
|
|
1774
|
-
};
|
|
1775
|
-
/**
|
|
1776
|
-
* Creates a proxy handler for collections (Set/Map/WeakMap)
|
|
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.
|
|
1777
1753
|
*
|
|
1778
|
-
* @param
|
|
1779
|
-
* @param
|
|
1780
|
-
*
|
|
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
|
|
1781
1758
|
*/
|
|
1782
|
-
function
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
},
|
|
1797
|
-
});
|
|
1798
|
-
}
|
|
1799
|
-
|
|
1759
|
+
function observeTargetKey(target, key, callback) {
|
|
1760
|
+
if (!targetToKeysToCallbacks.get(target)) {
|
|
1761
|
+
targetToKeysToCallbacks.set(target, new Map());
|
|
1762
|
+
}
|
|
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());
|
|
1770
|
+
}
|
|
1771
|
+
callbacksToTargets.get(callback).add(target);
|
|
1772
|
+
}
|
|
1800
1773
|
/**
|
|
1801
|
-
*
|
|
1802
|
-
*
|
|
1774
|
+
* Notify Reactives that are observing a given target that a key has changed on
|
|
1775
|
+
* the target.
|
|
1803
1776
|
*
|
|
1804
|
-
* @param
|
|
1805
|
-
*
|
|
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)
|
|
1806
1781
|
*/
|
|
1807
|
-
function
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
// in the next microtick. This line decides the granularity of the batch.
|
|
1812
|
-
await Promise.resolve();
|
|
1813
|
-
if (!called) {
|
|
1814
|
-
called = true;
|
|
1815
|
-
// wait for all calls in this microtick to fall through before resetting "called"
|
|
1816
|
-
// so that only the first call to the batched function calls the original callback.
|
|
1817
|
-
// Schedule this before calling the callback so that calls to the batched function
|
|
1818
|
-
// within the callback will proceed only after resetting called to false, and have
|
|
1819
|
-
// a chance to execute the callback again
|
|
1820
|
-
Promise.resolve().then(() => (called = false));
|
|
1821
|
-
callback();
|
|
1822
|
-
}
|
|
1823
|
-
};
|
|
1824
|
-
}
|
|
1825
|
-
function validateTarget(target) {
|
|
1826
|
-
if (!(target instanceof HTMLElement)) {
|
|
1827
|
-
throw new Error("Cannot mount component: the target is not a valid DOM element");
|
|
1782
|
+
function notifyReactives(target, key) {
|
|
1783
|
+
const keyToCallbacks = targetToKeysToCallbacks.get(target);
|
|
1784
|
+
if (!keyToCallbacks) {
|
|
1785
|
+
return;
|
|
1828
1786
|
}
|
|
1829
|
-
|
|
1830
|
-
|
|
1787
|
+
const callbacks = keyToCallbacks.get(key);
|
|
1788
|
+
if (!callbacks) {
|
|
1789
|
+
return;
|
|
1831
1790
|
}
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1791
|
+
// Loop on copy because clearReactivesForCallback will modify the set in place
|
|
1792
|
+
for (const callback of [...callbacks]) {
|
|
1793
|
+
clearReactivesForCallback(callback);
|
|
1794
|
+
callback();
|
|
1836
1795
|
}
|
|
1837
1796
|
}
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
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;
|
|
1842
1812
|
}
|
|
1843
|
-
|
|
1844
|
-
|
|
1813
|
+
for (const callbacks of observedKeys.values()) {
|
|
1814
|
+
callbacks.delete(callback);
|
|
1845
1815
|
}
|
|
1846
|
-
}).then(fn || function () { });
|
|
1847
|
-
}
|
|
1848
|
-
async function loadFile(url) {
|
|
1849
|
-
const result = await fetch(url);
|
|
1850
|
-
if (!result.ok) {
|
|
1851
|
-
throw new Error("Error while fetching xml templates");
|
|
1852
1816
|
}
|
|
1853
|
-
|
|
1817
|
+
targetsToClear.clear();
|
|
1854
1818
|
}
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
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
|
+
});
|
|
1861
1828
|
}
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
*
|
|
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
|
|
1865
1856
|
*/
|
|
1866
|
-
function
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
class Component {
|
|
1871
|
-
constructor(props, env, node) {
|
|
1872
|
-
this.props = props;
|
|
1873
|
-
this.env = env;
|
|
1874
|
-
this.__owl__ = node;
|
|
1857
|
+
function reactive(target, callback = () => { }) {
|
|
1858
|
+
if (!canBeMadeReactive(target)) {
|
|
1859
|
+
throw new Error(`Cannot make the given value reactive`);
|
|
1875
1860
|
}
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
this.__owl__.render(deep === true);
|
|
1861
|
+
if (SKIP in target) {
|
|
1862
|
+
return target;
|
|
1879
1863
|
}
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
// Maps fibers to thrown errors
|
|
1884
|
-
const fibersInError = new WeakMap();
|
|
1885
|
-
const nodeErrorHandlers = new WeakMap();
|
|
1886
|
-
function _handleError(node, error) {
|
|
1887
|
-
if (!node) {
|
|
1888
|
-
return false;
|
|
1864
|
+
const originalTarget = target[TARGET];
|
|
1865
|
+
if (originalTarget) {
|
|
1866
|
+
return reactive(originalTarget, callback);
|
|
1889
1867
|
}
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
fibersInError.set(fiber, error);
|
|
1868
|
+
if (!reactiveCache.has(target)) {
|
|
1869
|
+
reactiveCache.set(target, new WeakMap());
|
|
1893
1870
|
}
|
|
1894
|
-
const
|
|
1895
|
-
if (
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
break;
|
|
1903
|
-
}
|
|
1904
|
-
catch (e) {
|
|
1905
|
-
error = e;
|
|
1906
|
-
}
|
|
1907
|
-
}
|
|
1908
|
-
if (handled) {
|
|
1909
|
-
return true;
|
|
1910
|
-
}
|
|
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);
|
|
1911
1879
|
}
|
|
1912
|
-
return
|
|
1880
|
+
return reactivesForTarget.get(callback);
|
|
1913
1881
|
}
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
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
|
+
};
|
|
1945
1936
|
}
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
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);
|
|
1963
1968
|
}
|
|
1964
|
-
|
|
1965
|
-
}
|
|
1966
|
-
const fiber = new RootFiber(node, null);
|
|
1967
|
-
if (node.willPatch.length) {
|
|
1968
|
-
fiber.willPatch.push(fiber);
|
|
1969
|
-
}
|
|
1970
|
-
if (node.patched.length) {
|
|
1971
|
-
fiber.patched.push(fiber);
|
|
1972
|
-
}
|
|
1973
|
-
return fiber;
|
|
1969
|
+
};
|
|
1974
1970
|
}
|
|
1975
|
-
|
|
1976
|
-
|
|
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
|
+
};
|
|
1977
1987
|
}
|
|
1978
1988
|
/**
|
|
1979
|
-
*
|
|
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
|
|
1980
1997
|
*/
|
|
1981
|
-
function
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
if (fiber.bdom) {
|
|
1991
|
-
// if fiber has been rendered, this means that the component props have
|
|
1992
|
-
// been updated. however, this fiber will not be patched to the dom, so
|
|
1993
|
-
// it could happen that the next render compare the current props with
|
|
1994
|
-
// the same props, and skip the render completely. With the next line,
|
|
1995
|
-
// we kindly request the component code to force a render, so it works as
|
|
1996
|
-
// expected.
|
|
1997
|
-
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);
|
|
1998
2007
|
}
|
|
1999
|
-
|
|
2000
|
-
|
|
2008
|
+
if (originalValue !== value) {
|
|
2009
|
+
notifyReactives(target, key);
|
|
2001
2010
|
}
|
|
2002
|
-
|
|
2003
|
-
}
|
|
2004
|
-
return result;
|
|
2011
|
+
return ret;
|
|
2012
|
+
};
|
|
2005
2013
|
}
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
this.root = root;
|
|
2020
|
-
parent.children.push(this);
|
|
2021
|
-
}
|
|
2022
|
-
else {
|
|
2023
|
-
this.root = this;
|
|
2024
|
-
}
|
|
2025
|
-
}
|
|
2026
|
-
render() {
|
|
2027
|
-
// if some parent has a fiber => register in followup
|
|
2028
|
-
let prev = this.root.node;
|
|
2029
|
-
let scheduler = prev.app.scheduler;
|
|
2030
|
-
let current = prev.parent;
|
|
2031
|
-
while (current) {
|
|
2032
|
-
if (current.fiber) {
|
|
2033
|
-
let root = current.fiber.root;
|
|
2034
|
-
if (root.counter === 0 && prev.parentKey in current.fiber.childrenMap) {
|
|
2035
|
-
current = root.node;
|
|
2036
|
-
}
|
|
2037
|
-
else {
|
|
2038
|
-
scheduler.delayedRenders.push(this);
|
|
2039
|
-
return;
|
|
2040
|
-
}
|
|
2041
|
-
}
|
|
2042
|
-
prev = current;
|
|
2043
|
-
current = current.parent;
|
|
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);
|
|
2044
2027
|
}
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2028
|
+
};
|
|
2029
|
+
}
|
|
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;
|
|
2055
2091
|
}
|
|
2056
|
-
|
|
2057
|
-
|
|
2092
|
+
if (objectHasOwnProperty.call(specialHandlers, key)) {
|
|
2093
|
+
return specialHandlers[key];
|
|
2058
2094
|
}
|
|
2059
|
-
|
|
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();
|
|
2060
2123
|
}
|
|
2061
|
-
}
|
|
2124
|
+
};
|
|
2062
2125
|
}
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
this.counter = 1;
|
|
2067
|
-
// only add stuff in this if they have registered some hooks
|
|
2068
|
-
this.willPatch = [];
|
|
2069
|
-
this.patched = [];
|
|
2070
|
-
this.mounted = [];
|
|
2071
|
-
// A fiber is typically locked when it is completing and the patch has not, or is being applied.
|
|
2072
|
-
// i.e.: render triggered in onWillUnmount or in willPatch will be delayed
|
|
2073
|
-
this.locked = false;
|
|
2074
|
-
}
|
|
2075
|
-
complete() {
|
|
2076
|
-
const node = this.node;
|
|
2077
|
-
this.locked = true;
|
|
2078
|
-
let current = undefined;
|
|
2079
|
-
try {
|
|
2080
|
-
// Step 1: calling all willPatch lifecycle hooks
|
|
2081
|
-
for (current of this.willPatch) {
|
|
2082
|
-
// because of the asynchronous nature of the rendering, some parts of the
|
|
2083
|
-
// UI may have been rendered, then deleted in a followup rendering, and we
|
|
2084
|
-
// do not want to call onWillPatch in that case.
|
|
2085
|
-
let node = current.node;
|
|
2086
|
-
if (node.fiber === current) {
|
|
2087
|
-
const component = node.component;
|
|
2088
|
-
for (let cb of node.willPatch) {
|
|
2089
|
-
cb.call(component);
|
|
2090
|
-
}
|
|
2091
|
-
}
|
|
2092
|
-
}
|
|
2093
|
-
current = undefined;
|
|
2094
|
-
// Step 2: patching the dom
|
|
2095
|
-
node._patch();
|
|
2096
|
-
this.locked = false;
|
|
2097
|
-
// Step 4: calling all mounted lifecycle hooks
|
|
2098
|
-
let mountedFibers = this.mounted;
|
|
2099
|
-
while ((current = mountedFibers.pop())) {
|
|
2100
|
-
current = current;
|
|
2101
|
-
if (current.appliedToDom) {
|
|
2102
|
-
for (let cb of current.node.mounted) {
|
|
2103
|
-
cb();
|
|
2104
|
-
}
|
|
2105
|
-
}
|
|
2106
|
-
}
|
|
2107
|
-
// Step 5: calling all patched hooks
|
|
2108
|
-
let patchedFibers = this.patched;
|
|
2109
|
-
while ((current = patchedFibers.pop())) {
|
|
2110
|
-
current = current;
|
|
2111
|
-
if (current.appliedToDom) {
|
|
2112
|
-
for (let cb of current.node.patched) {
|
|
2113
|
-
cb();
|
|
2114
|
-
}
|
|
2115
|
-
}
|
|
2116
|
-
}
|
|
2117
|
-
}
|
|
2118
|
-
catch (e) {
|
|
2119
|
-
this.locked = false;
|
|
2120
|
-
handleError({ fiber: current || this, error: e });
|
|
2121
|
-
}
|
|
2126
|
+
function validateTarget(target) {
|
|
2127
|
+
if (!(target instanceof HTMLElement)) {
|
|
2128
|
+
throw new Error("Cannot mount component: the target is not a valid DOM element");
|
|
2122
2129
|
}
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
if (newValue === 0) {
|
|
2126
|
-
this.node.app.scheduler.flush();
|
|
2127
|
-
}
|
|
2130
|
+
if (!document.body.contains(target)) {
|
|
2131
|
+
throw new Error("Cannot mount a component on a detached dom node");
|
|
2128
2132
|
}
|
|
2129
2133
|
}
|
|
2130
|
-
class
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
this.target = target;
|
|
2134
|
-
this.position = options.position || "last-child";
|
|
2134
|
+
class EventBus extends EventTarget {
|
|
2135
|
+
trigger(name, payload) {
|
|
2136
|
+
this.dispatchEvent(new CustomEvent(name, { detail: payload }));
|
|
2135
2137
|
}
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
node.app.constructor.validateTarget(this.target);
|
|
2142
|
-
if (node.bdom) {
|
|
2143
|
-
// this is a complicated situation: if we mount a fiber with an existing
|
|
2144
|
-
// bdom, this means that this same fiber was already completed, mounted,
|
|
2145
|
-
// but a crash occurred in some mounted hook. Then, it was handled and
|
|
2146
|
-
// the new rendering is being applied.
|
|
2147
|
-
node.updateDom();
|
|
2148
|
-
}
|
|
2149
|
-
else {
|
|
2150
|
-
node.bdom = this.bdom;
|
|
2151
|
-
if (this.position === "last-child" || this.target.childNodes.length === 0) {
|
|
2152
|
-
mount$1(node.bdom, this.target);
|
|
2153
|
-
}
|
|
2154
|
-
else {
|
|
2155
|
-
const firstChild = this.target.childNodes[0];
|
|
2156
|
-
mount$1(node.bdom, this.target, firstChild);
|
|
2157
|
-
}
|
|
2158
|
-
}
|
|
2159
|
-
// unregistering the fiber before mounted since it can do another render
|
|
2160
|
-
// and that the current rendering is obviously completed
|
|
2161
|
-
node.fiber = null;
|
|
2162
|
-
node.status = 1 /* MOUNTED */;
|
|
2163
|
-
this.appliedToDom = true;
|
|
2164
|
-
let mountedFibers = this.mounted;
|
|
2165
|
-
while ((current = mountedFibers.pop())) {
|
|
2166
|
-
if (current.appliedToDom) {
|
|
2167
|
-
for (let cb of current.node.mounted) {
|
|
2168
|
-
cb();
|
|
2169
|
-
}
|
|
2170
|
-
}
|
|
2171
|
-
}
|
|
2138
|
+
}
|
|
2139
|
+
function whenReady(fn) {
|
|
2140
|
+
return new Promise(function (resolve) {
|
|
2141
|
+
if (document.readyState !== "loading") {
|
|
2142
|
+
resolve(true);
|
|
2172
2143
|
}
|
|
2173
|
-
|
|
2174
|
-
|
|
2144
|
+
else {
|
|
2145
|
+
document.addEventListener("DOMContentLoaded", resolve, false);
|
|
2175
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");
|
|
2176
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);
|
|
2177
2169
|
}
|
|
2178
2170
|
|
|
2179
2171
|
let currentNode = null;
|
|
@@ -2190,13 +2182,11 @@ function useComponent() {
|
|
|
2190
2182
|
* Apply default props (only top level).
|
|
2191
2183
|
*/
|
|
2192
2184
|
function applyDefaultProps(props, defaultProps) {
|
|
2193
|
-
const result = Object.assign({}, props);
|
|
2194
2185
|
for (let propName in defaultProps) {
|
|
2195
2186
|
if (props[propName] === undefined) {
|
|
2196
|
-
|
|
2187
|
+
props[propName] = defaultProps[propName];
|
|
2197
2188
|
}
|
|
2198
2189
|
}
|
|
2199
|
-
return result;
|
|
2200
2190
|
}
|
|
2201
2191
|
// -----------------------------------------------------------------------------
|
|
2202
2192
|
// Integration with reactivity system (useState)
|
|
@@ -2223,61 +2213,6 @@ function useState(state) {
|
|
|
2223
2213
|
}
|
|
2224
2214
|
return reactive(state, render);
|
|
2225
2215
|
}
|
|
2226
|
-
function arePropsDifferent(props1, props2) {
|
|
2227
|
-
for (let k in props1) {
|
|
2228
|
-
const prop1 = props1[k] && typeof props1[k] === "object" ? toRaw(props1[k]) : props1[k];
|
|
2229
|
-
const prop2 = props2[k] && typeof props2[k] === "object" ? toRaw(props2[k]) : props2[k];
|
|
2230
|
-
if (prop1 !== prop2) {
|
|
2231
|
-
return true;
|
|
2232
|
-
}
|
|
2233
|
-
}
|
|
2234
|
-
return Object.keys(props1).length !== Object.keys(props2).length;
|
|
2235
|
-
}
|
|
2236
|
-
function component(name, props, key, ctx, parent) {
|
|
2237
|
-
let node = ctx.children[key];
|
|
2238
|
-
let isDynamic = typeof name !== "string";
|
|
2239
|
-
if (node && node.status === 2 /* DESTROYED */) {
|
|
2240
|
-
node = undefined;
|
|
2241
|
-
}
|
|
2242
|
-
if (isDynamic && node && node.component.constructor !== name) {
|
|
2243
|
-
node = undefined;
|
|
2244
|
-
}
|
|
2245
|
-
const parentFiber = ctx.fiber;
|
|
2246
|
-
if (node) {
|
|
2247
|
-
let shouldRender = node.forceNextRender;
|
|
2248
|
-
if (shouldRender) {
|
|
2249
|
-
node.forceNextRender = false;
|
|
2250
|
-
}
|
|
2251
|
-
else {
|
|
2252
|
-
const currentProps = node.props;
|
|
2253
|
-
shouldRender = parentFiber.deep || arePropsDifferent(currentProps, props);
|
|
2254
|
-
}
|
|
2255
|
-
if (shouldRender) {
|
|
2256
|
-
node.updateAndRender(props, parentFiber);
|
|
2257
|
-
}
|
|
2258
|
-
}
|
|
2259
|
-
else {
|
|
2260
|
-
// new component
|
|
2261
|
-
let C;
|
|
2262
|
-
if (isDynamic) {
|
|
2263
|
-
C = name;
|
|
2264
|
-
}
|
|
2265
|
-
else {
|
|
2266
|
-
C = parent.constructor.components[name];
|
|
2267
|
-
if (!C) {
|
|
2268
|
-
throw new Error(`Cannot find the definition of component "${name}"`);
|
|
2269
|
-
}
|
|
2270
|
-
else if (!(C.prototype instanceof Component)) {
|
|
2271
|
-
throw new Error(`"${name}" is not a Component. It must inherit from the Component class`);
|
|
2272
|
-
}
|
|
2273
|
-
}
|
|
2274
|
-
node = new ComponentNode(C, props, ctx.app, ctx, key);
|
|
2275
|
-
ctx.children[key] = node;
|
|
2276
|
-
node.initiateRender(new Fiber(node, parentFiber));
|
|
2277
|
-
}
|
|
2278
|
-
parentFiber.childrenMap[key] = node;
|
|
2279
|
-
return node;
|
|
2280
|
-
}
|
|
2281
2216
|
class ComponentNode {
|
|
2282
2217
|
constructor(C, props, app, parent, parentKey) {
|
|
2283
2218
|
this.fiber = null;
|
|
@@ -2300,8 +2235,9 @@ class ComponentNode {
|
|
|
2300
2235
|
this.parentKey = parentKey;
|
|
2301
2236
|
this.level = parent ? parent.level + 1 : 0;
|
|
2302
2237
|
const defaultProps = C.defaultProps;
|
|
2238
|
+
props = Object.assign({}, props);
|
|
2303
2239
|
if (defaultProps) {
|
|
2304
|
-
|
|
2240
|
+
applyDefaultProps(props, defaultProps);
|
|
2305
2241
|
}
|
|
2306
2242
|
const env = (parent && parent.childEnv) || app.env;
|
|
2307
2243
|
this.childEnv = env;
|
|
@@ -2413,13 +2349,14 @@ class ComponentNode {
|
|
|
2413
2349
|
}
|
|
2414
2350
|
async updateAndRender(props, parentFiber) {
|
|
2415
2351
|
const rawProps = props;
|
|
2352
|
+
props = Object.assign({}, props);
|
|
2416
2353
|
// update
|
|
2417
2354
|
const fiber = makeChildFiber(this, parentFiber);
|
|
2418
2355
|
this.fiber = fiber;
|
|
2419
2356
|
const component = this.component;
|
|
2420
2357
|
const defaultProps = component.constructor.defaultProps;
|
|
2421
2358
|
if (defaultProps) {
|
|
2422
|
-
|
|
2359
|
+
applyDefaultProps(props, defaultProps);
|
|
2423
2360
|
}
|
|
2424
2361
|
currentNode = this;
|
|
2425
2362
|
for (const key in props) {
|
|
@@ -2498,10 +2435,15 @@ class ComponentNode {
|
|
|
2498
2435
|
}
|
|
2499
2436
|
}
|
|
2500
2437
|
_patch() {
|
|
2501
|
-
|
|
2502
|
-
this.children
|
|
2503
|
-
|
|
2504
|
-
|
|
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;
|
|
2505
2447
|
this.fiber = null;
|
|
2506
2448
|
}
|
|
2507
2449
|
beforeRemove() {
|
|
@@ -2629,6 +2571,19 @@ function onError(callback) {
|
|
|
2629
2571
|
handlers.push(callback.bind(node.component));
|
|
2630
2572
|
}
|
|
2631
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
|
+
|
|
2632
2587
|
const VText = text("").constructor;
|
|
2633
2588
|
class VPortal extends VText {
|
|
2634
2589
|
constructor(selector, realBDom) {
|
|
@@ -2707,6 +2662,7 @@ Portal.props = {
|
|
|
2707
2662
|
// -----------------------------------------------------------------------------
|
|
2708
2663
|
const isUnionType = (t) => Array.isArray(t);
|
|
2709
2664
|
const isBaseType = (t) => typeof t !== "object";
|
|
2665
|
+
const isValueType = (t) => typeof t === "object" && t && "value" in t;
|
|
2710
2666
|
function isOptional(t) {
|
|
2711
2667
|
return typeof t === "object" && "optional" in t ? t.optional || false : false;
|
|
2712
2668
|
}
|
|
@@ -2720,6 +2676,9 @@ function describe(info) {
|
|
|
2720
2676
|
else if (isUnionType(info)) {
|
|
2721
2677
|
return info.map(describe).join(" or ");
|
|
2722
2678
|
}
|
|
2679
|
+
else if (isValueType(info)) {
|
|
2680
|
+
return String(info.value);
|
|
2681
|
+
}
|
|
2723
2682
|
if ("element" in info) {
|
|
2724
2683
|
return `list of ${describe({ type: info.element, optional: false })}s`;
|
|
2725
2684
|
}
|
|
@@ -2805,6 +2764,9 @@ function validateType(key, value, descr) {
|
|
|
2805
2764
|
else if (isBaseType(descr)) {
|
|
2806
2765
|
return validateBaseType(key, value, descr);
|
|
2807
2766
|
}
|
|
2767
|
+
else if (isValueType(descr)) {
|
|
2768
|
+
return value === descr.value ? null : `'${key}' is not equal to '${descr.value}'`;
|
|
2769
|
+
}
|
|
2808
2770
|
else if (isUnionType(descr)) {
|
|
2809
2771
|
let validDescr = descr.find((p) => !validateType(key, value, p));
|
|
2810
2772
|
return validDescr ? null : `'${key}' is not a ${describe(descr)}`;
|
|
@@ -2833,6 +2795,7 @@ function validateType(key, value, descr) {
|
|
|
2833
2795
|
return result;
|
|
2834
2796
|
}
|
|
2835
2797
|
|
|
2798
|
+
const ObjectCreate = Object.create;
|
|
2836
2799
|
/**
|
|
2837
2800
|
* This file contains utility functions that will be injected in each template,
|
|
2838
2801
|
* to perform various useful tasks in the compiled code.
|
|
@@ -2844,7 +2807,7 @@ function callSlot(ctx, parent, key, name, dynamic, extra, defaultContent) {
|
|
|
2844
2807
|
key = key + "__slot_" + name;
|
|
2845
2808
|
const slots = ctx.props.slots || {};
|
|
2846
2809
|
const { __render, __ctx, __scope } = slots[name] || {};
|
|
2847
|
-
const slotScope =
|
|
2810
|
+
const slotScope = ObjectCreate(__ctx || {});
|
|
2848
2811
|
if (__scope) {
|
|
2849
2812
|
slotScope[__scope] = extra;
|
|
2850
2813
|
}
|
|
@@ -2864,7 +2827,7 @@ function callSlot(ctx, parent, key, name, dynamic, extra, defaultContent) {
|
|
|
2864
2827
|
}
|
|
2865
2828
|
function capture(ctx) {
|
|
2866
2829
|
const component = ctx.__owl__.component;
|
|
2867
|
-
const result =
|
|
2830
|
+
const result = ObjectCreate(component);
|
|
2868
2831
|
for (let k in ctx) {
|
|
2869
2832
|
result[k] = ctx[k];
|
|
2870
2833
|
}
|
|
@@ -2917,13 +2880,14 @@ function shallowEqual(l1, l2) {
|
|
|
2917
2880
|
return true;
|
|
2918
2881
|
}
|
|
2919
2882
|
class LazyValue {
|
|
2920
|
-
constructor(fn, ctx, node) {
|
|
2883
|
+
constructor(fn, ctx, component, node) {
|
|
2921
2884
|
this.fn = fn;
|
|
2922
2885
|
this.ctx = capture(ctx);
|
|
2886
|
+
this.component = component;
|
|
2923
2887
|
this.node = node;
|
|
2924
2888
|
}
|
|
2925
2889
|
evaluate() {
|
|
2926
|
-
return this.fn(this.ctx, this.node);
|
|
2890
|
+
return this.fn.call(this.component, this.ctx, this.node);
|
|
2927
2891
|
}
|
|
2928
2892
|
toString() {
|
|
2929
2893
|
return this.evaluate().toString();
|
|
@@ -2932,9 +2896,9 @@ class LazyValue {
|
|
|
2932
2896
|
/*
|
|
2933
2897
|
* Safely outputs `value` as a block depending on the nature of `value`
|
|
2934
2898
|
*/
|
|
2935
|
-
function safeOutput(value) {
|
|
2936
|
-
if (
|
|
2937
|
-
return
|
|
2899
|
+
function safeOutput(value, defaultValue) {
|
|
2900
|
+
if (value === undefined) {
|
|
2901
|
+
return defaultValue ? toggler("default", defaultValue) : toggler("undefined", text(""));
|
|
2938
2902
|
}
|
|
2939
2903
|
let safeKey;
|
|
2940
2904
|
let block;
|
|
@@ -2969,17 +2933,19 @@ function safeOutput(value) {
|
|
|
2969
2933
|
return toggler(safeKey, block);
|
|
2970
2934
|
}
|
|
2971
2935
|
let boundFunctions = new WeakMap();
|
|
2936
|
+
const WeakMapGet = WeakMap.prototype.get;
|
|
2937
|
+
const WeakMapSet = WeakMap.prototype.set;
|
|
2972
2938
|
function bind(ctx, fn) {
|
|
2973
2939
|
let component = ctx.__owl__.component;
|
|
2974
|
-
let boundFnMap =
|
|
2940
|
+
let boundFnMap = WeakMapGet.call(boundFunctions, component);
|
|
2975
2941
|
if (!boundFnMap) {
|
|
2976
2942
|
boundFnMap = new WeakMap();
|
|
2977
|
-
|
|
2943
|
+
WeakMapSet.call(boundFunctions, component, boundFnMap);
|
|
2978
2944
|
}
|
|
2979
|
-
let boundFn =
|
|
2945
|
+
let boundFn = WeakMapGet.call(boundFnMap, fn);
|
|
2980
2946
|
if (!boundFn) {
|
|
2981
2947
|
boundFn = fn.bind(component);
|
|
2982
|
-
|
|
2948
|
+
WeakMapSet.call(boundFnMap, fn, boundFn);
|
|
2983
2949
|
}
|
|
2984
2950
|
return boundFn;
|
|
2985
2951
|
}
|
|
@@ -3055,7 +3021,7 @@ const helpers = {
|
|
|
3055
3021
|
markRaw,
|
|
3056
3022
|
};
|
|
3057
3023
|
|
|
3058
|
-
const bdom = { text, createBlock, list, multi, html, toggler,
|
|
3024
|
+
const bdom = { text, createBlock, list, multi, html, toggler, comment };
|
|
3059
3025
|
function parseXML$1(xml) {
|
|
3060
3026
|
const parser = new DOMParser();
|
|
3061
3027
|
const doc = parser.parseFromString(xml, "text/xml");
|
|
@@ -3218,7 +3184,7 @@ const STATIC_TOKEN_MAP = Object.assign(Object.create(null), {
|
|
|
3218
3184
|
});
|
|
3219
3185
|
// note that the space after typeof is relevant. It makes sure that the formatted
|
|
3220
3186
|
// expression has a space after typeof. Currently we don't support delete and void
|
|
3221
|
-
const OPERATORS = "...,.,===,==,+,!==,!=,!,||,&&,>=,>,<=,<,?,-,*,/,%,typeof ,=>,=,;,in ,new ".split(",");
|
|
3187
|
+
const OPERATORS = "...,.,===,==,+,!==,!=,!,||,&&,>=,>,<=,<,?,-,*,/,%,typeof ,=>,=,;,in ,new ,|,&,^,~".split(",");
|
|
3222
3188
|
let tokenizeString = function (expr) {
|
|
3223
3189
|
let s = expr[0];
|
|
3224
3190
|
let start = s;
|
|
@@ -3651,9 +3617,7 @@ class CodeGenerator {
|
|
|
3651
3617
|
tKeyExpr: null,
|
|
3652
3618
|
});
|
|
3653
3619
|
// define blocks and utility functions
|
|
3654
|
-
let mainCode = [
|
|
3655
|
-
` let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;`,
|
|
3656
|
-
];
|
|
3620
|
+
let mainCode = [` let { text, createBlock, list, multi, html, toggler, comment } = bdom;`];
|
|
3657
3621
|
if (this.helpers.size) {
|
|
3658
3622
|
mainCode.push(`let { ${[...this.helpers].join(", ")} } = helpers;`);
|
|
3659
3623
|
}
|
|
@@ -3669,6 +3633,7 @@ class CodeGenerator {
|
|
|
3669
3633
|
for (let block of this.blocks) {
|
|
3670
3634
|
if (block.dom) {
|
|
3671
3635
|
let xmlString = block.asXmlString();
|
|
3636
|
+
xmlString = xmlString.replace(/`/g, "\\`");
|
|
3672
3637
|
if (block.dynamicTagName) {
|
|
3673
3638
|
xmlString = xmlString.replace(/^<\w+/, `<\${tag || '${block.dom.nodeName}'}`);
|
|
3674
3639
|
xmlString = xmlString.replace(/\w+>$/, `\${tag || '${block.dom.nodeName}'}>`);
|
|
@@ -3713,8 +3678,8 @@ class CodeGenerator {
|
|
|
3713
3678
|
define(varName, expr) {
|
|
3714
3679
|
this.addLine(`const ${varName} = ${expr};`);
|
|
3715
3680
|
}
|
|
3716
|
-
insertAnchor(block) {
|
|
3717
|
-
const tag = `block-child-${
|
|
3681
|
+
insertAnchor(block, index = block.children.length) {
|
|
3682
|
+
const tag = `block-child-${index}`;
|
|
3718
3683
|
const anchor = xmlDoc.createElement(tag);
|
|
3719
3684
|
block.insert(anchor);
|
|
3720
3685
|
}
|
|
@@ -4106,20 +4071,37 @@ class CodeGenerator {
|
|
|
4106
4071
|
this.insertAnchor(block);
|
|
4107
4072
|
}
|
|
4108
4073
|
block = this.createBlock(block, "html", ctx);
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
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;
|
|
4113
4082
|
const subCtx = createContext(ctx);
|
|
4114
4083
|
this.compileAST({ type: 3 /* Multi */, content: ast.body }, subCtx);
|
|
4115
|
-
this.helpers.add("
|
|
4116
|
-
|
|
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)})`;
|
|
4090
|
+
}
|
|
4091
|
+
this.insertBlock(blockStr, block, ctx);
|
|
4092
|
+
}
|
|
4093
|
+
compileTIfBranch(content, block, ctx) {
|
|
4094
|
+
this.target.indentLevel++;
|
|
4095
|
+
let childN = block.children.length;
|
|
4096
|
+
this.compileAST(content, createContext(ctx, { block, index: ctx.index }));
|
|
4097
|
+
if (block.children.length > childN) {
|
|
4098
|
+
// we have some content => need to insert an anchor at correct index
|
|
4099
|
+
this.insertAnchor(block, childN);
|
|
4117
4100
|
}
|
|
4118
|
-
this.
|
|
4101
|
+
this.target.indentLevel--;
|
|
4119
4102
|
}
|
|
4120
4103
|
compileTIf(ast, ctx, nextNode) {
|
|
4121
|
-
let { block, forceNewBlock
|
|
4122
|
-
let currentIndex = index;
|
|
4104
|
+
let { block, forceNewBlock } = ctx;
|
|
4123
4105
|
const codeIdx = this.target.code.length;
|
|
4124
4106
|
const isNewBlock = !block || (block.type !== "multi" && forceNewBlock);
|
|
4125
4107
|
if (block) {
|
|
@@ -4129,28 +4111,16 @@ class CodeGenerator {
|
|
|
4129
4111
|
block = this.createBlock(block, "multi", ctx);
|
|
4130
4112
|
}
|
|
4131
4113
|
this.addLine(`if (${compileExpr(ast.condition)}) {`);
|
|
4132
|
-
this.
|
|
4133
|
-
this.insertAnchor(block);
|
|
4134
|
-
const subCtx = createContext(ctx, { block, index: currentIndex });
|
|
4135
|
-
this.compileAST(ast.content, subCtx);
|
|
4136
|
-
this.target.indentLevel--;
|
|
4114
|
+
this.compileTIfBranch(ast.content, block, ctx);
|
|
4137
4115
|
if (ast.tElif) {
|
|
4138
4116
|
for (let clause of ast.tElif) {
|
|
4139
4117
|
this.addLine(`} else if (${compileExpr(clause.condition)}) {`);
|
|
4140
|
-
this.
|
|
4141
|
-
this.insertAnchor(block);
|
|
4142
|
-
const subCtx = createContext(ctx, { block, index: currentIndex });
|
|
4143
|
-
this.compileAST(clause.content, subCtx);
|
|
4144
|
-
this.target.indentLevel--;
|
|
4118
|
+
this.compileTIfBranch(clause.content, block, ctx);
|
|
4145
4119
|
}
|
|
4146
4120
|
}
|
|
4147
4121
|
if (ast.tElse) {
|
|
4148
4122
|
this.addLine(`} else {`);
|
|
4149
|
-
this.
|
|
4150
|
-
this.insertAnchor(block);
|
|
4151
|
-
const subCtx = createContext(ctx, { block, index: currentIndex });
|
|
4152
|
-
this.compileAST(ast.tElse, subCtx);
|
|
4153
|
-
this.target.indentLevel--;
|
|
4123
|
+
this.compileTIfBranch(ast.tElse, block, ctx);
|
|
4154
4124
|
}
|
|
4155
4125
|
this.addLine("}");
|
|
4156
4126
|
if (isNewBlock) {
|
|
@@ -4308,16 +4278,21 @@ class CodeGenerator {
|
|
|
4308
4278
|
}
|
|
4309
4279
|
compileTCall(ast, ctx) {
|
|
4310
4280
|
let { block, forceNewBlock } = ctx;
|
|
4281
|
+
let ctxVar = ctx.ctxVar || "ctx";
|
|
4282
|
+
if (ast.context) {
|
|
4283
|
+
ctxVar = generateId("ctx");
|
|
4284
|
+
this.addLine(`let ${ctxVar} = ${compileExpr(ast.context)};`);
|
|
4285
|
+
}
|
|
4311
4286
|
if (ast.body) {
|
|
4312
|
-
this.addLine(
|
|
4313
|
-
this.addLine(
|
|
4287
|
+
this.addLine(`${ctxVar} = Object.create(${ctxVar});`);
|
|
4288
|
+
this.addLine(`${ctxVar}[isBoundary] = 1;`);
|
|
4314
4289
|
this.helpers.add("isBoundary");
|
|
4315
4290
|
const nextId = BlockDescription.nextBlockId;
|
|
4316
|
-
const subCtx = createContext(ctx, { preventRoot: true });
|
|
4291
|
+
const subCtx = createContext(ctx, { preventRoot: true, ctxVar });
|
|
4317
4292
|
this.compileAST({ type: 3 /* Multi */, content: ast.body }, subCtx);
|
|
4318
4293
|
if (nextId !== BlockDescription.nextBlockId) {
|
|
4319
4294
|
this.helpers.add("zero");
|
|
4320
|
-
this.addLine(
|
|
4295
|
+
this.addLine(`${ctxVar}[zero] = b${nextId};`);
|
|
4321
4296
|
}
|
|
4322
4297
|
}
|
|
4323
4298
|
const isDynamic = INTERP_REGEXP.test(ast.name);
|
|
@@ -4335,7 +4310,7 @@ class CodeGenerator {
|
|
|
4335
4310
|
}
|
|
4336
4311
|
this.define(templateVar, subTemplate);
|
|
4337
4312
|
block = this.createBlock(block, "multi", ctx);
|
|
4338
|
-
this.insertBlock(`call(this, ${templateVar},
|
|
4313
|
+
this.insertBlock(`call(this, ${templateVar}, ${ctxVar}, node, ${key})`, block, {
|
|
4339
4314
|
...ctx,
|
|
4340
4315
|
forceNewBlock: !block,
|
|
4341
4316
|
});
|
|
@@ -4344,13 +4319,13 @@ class CodeGenerator {
|
|
|
4344
4319
|
const id = generateId(`callTemplate_`);
|
|
4345
4320
|
this.staticDefs.push({ id, expr: `app.getTemplate(${subTemplate})` });
|
|
4346
4321
|
block = this.createBlock(block, "multi", ctx);
|
|
4347
|
-
this.insertBlock(`${id}.call(this,
|
|
4322
|
+
this.insertBlock(`${id}.call(this, ${ctxVar}, node, ${key})`, block, {
|
|
4348
4323
|
...ctx,
|
|
4349
4324
|
forceNewBlock: !block,
|
|
4350
4325
|
});
|
|
4351
4326
|
}
|
|
4352
4327
|
if (ast.body && !ctx.isLast) {
|
|
4353
|
-
this.addLine(
|
|
4328
|
+
this.addLine(`${ctxVar} = ${ctxVar}.__proto__;`);
|
|
4354
4329
|
}
|
|
4355
4330
|
}
|
|
4356
4331
|
compileTCallBlock(ast, ctx) {
|
|
@@ -4371,7 +4346,7 @@ class CodeGenerator {
|
|
|
4371
4346
|
this.helpers.add("LazyValue");
|
|
4372
4347
|
const bodyAst = { type: 3 /* Multi */, content: ast.body };
|
|
4373
4348
|
const name = this.compileInNewTarget("value", bodyAst, ctx);
|
|
4374
|
-
let value = `new LazyValue(${name}, ctx, node)`;
|
|
4349
|
+
let value = `new LazyValue(${name}, ctx, this, node)`;
|
|
4375
4350
|
value = ast.value ? (value ? `withDefault(${expr}, ${value})` : expr) : value;
|
|
4376
4351
|
this.addLine(`ctx[\`${ast.name}\`] = ${value};`);
|
|
4377
4352
|
}
|
|
@@ -4389,7 +4364,7 @@ class CodeGenerator {
|
|
|
4389
4364
|
value = expr;
|
|
4390
4365
|
}
|
|
4391
4366
|
this.helpers.add("setContextValue");
|
|
4392
|
-
this.addLine(`setContextValue(ctx, "${ast.name}", ${value});`);
|
|
4367
|
+
this.addLine(`setContextValue(${ctx.ctxVar || "ctx"}, "${ast.name}", ${value});`);
|
|
4393
4368
|
}
|
|
4394
4369
|
}
|
|
4395
4370
|
generateComponentKey() {
|
|
@@ -4506,8 +4481,12 @@ class CodeGenerator {
|
|
|
4506
4481
|
if (ctx.tKeyExpr) {
|
|
4507
4482
|
keyArg = `${ctx.tKeyExpr} + ${keyArg}`;
|
|
4508
4483
|
}
|
|
4509
|
-
|
|
4510
|
-
|
|
4484
|
+
let id = generateId("comp");
|
|
4485
|
+
this.staticDefs.push({
|
|
4486
|
+
id,
|
|
4487
|
+
expr: `app.createComponent(${ast.isDynamic ? null : expr}, ${!ast.isDynamic}, ${!!ast.slots}, ${!!ast.dynamicProps}, ${!ast.props && !ast.dynamicProps})`,
|
|
4488
|
+
});
|
|
4489
|
+
let blockExpr = `${id}(${propString}, ${keyArg}, node, this, ${ast.isDynamic ? expr : null})`;
|
|
4511
4490
|
if (ast.isDynamic) {
|
|
4512
4491
|
blockExpr = `toggler(${expr}, ${blockExpr})`;
|
|
4513
4492
|
}
|
|
@@ -4592,10 +4571,15 @@ class CodeGenerator {
|
|
|
4592
4571
|
if (this.target.loopLevel || !this.hasSafeContext) {
|
|
4593
4572
|
ctxStr = generateId("ctx");
|
|
4594
4573
|
this.helpers.add("capture");
|
|
4595
|
-
this.define(ctxStr, `capture(ctx)
|
|
4574
|
+
this.define(ctxStr, `capture(ctx)`);
|
|
4596
4575
|
}
|
|
4576
|
+
let id = generateId("comp");
|
|
4577
|
+
this.staticDefs.push({
|
|
4578
|
+
id,
|
|
4579
|
+
expr: `app.createComponent(null, false, true, false, false)`,
|
|
4580
|
+
});
|
|
4597
4581
|
const target = compileExpr(ast.target);
|
|
4598
|
-
const blockString =
|
|
4582
|
+
const blockString = `${id}({target: ${target},slots: {'default': {__render: ${name}, __ctx: ${ctxStr}}}}, key + \`${key}\`, node, ctx, Portal)`;
|
|
4599
4583
|
if (block) {
|
|
4600
4584
|
this.insertAnchor(block);
|
|
4601
4585
|
}
|
|
@@ -4935,10 +4919,12 @@ function parseTCall(node, ctx) {
|
|
|
4935
4919
|
return null;
|
|
4936
4920
|
}
|
|
4937
4921
|
const subTemplate = node.getAttribute("t-call");
|
|
4922
|
+
const context = node.getAttribute("t-call-context");
|
|
4938
4923
|
node.removeAttribute("t-call");
|
|
4924
|
+
node.removeAttribute("t-call-context");
|
|
4939
4925
|
if (node.tagName !== "t") {
|
|
4940
4926
|
const ast = parseNode(node, ctx);
|
|
4941
|
-
const tcall = { type: 7 /* TCall */, name: subTemplate, body: null };
|
|
4927
|
+
const tcall = { type: 7 /* TCall */, name: subTemplate, body: null, context };
|
|
4942
4928
|
if (ast && ast.type === 2 /* DomNode */) {
|
|
4943
4929
|
ast.content = [tcall];
|
|
4944
4930
|
return ast;
|
|
@@ -4955,6 +4941,7 @@ function parseTCall(node, ctx) {
|
|
|
4955
4941
|
type: 7 /* TCall */,
|
|
4956
4942
|
name: subTemplate,
|
|
4957
4943
|
body: body.length ? body : null,
|
|
4944
|
+
context,
|
|
4958
4945
|
};
|
|
4959
4946
|
}
|
|
4960
4947
|
// -----------------------------------------------------------------------------
|
|
@@ -5538,6 +5525,55 @@ class App extends TemplateSet {
|
|
|
5538
5525
|
this.root.destroy();
|
|
5539
5526
|
}
|
|
5540
5527
|
}
|
|
5528
|
+
createComponent(name, isStatic, hasSlotsProp, hasDynamicPropList, hasNoProp) {
|
|
5529
|
+
const isDynamic = !isStatic;
|
|
5530
|
+
function _arePropsDifferent(props1, props2) {
|
|
5531
|
+
for (let k in props1) {
|
|
5532
|
+
if (props1[k] !== props2[k]) {
|
|
5533
|
+
return true;
|
|
5534
|
+
}
|
|
5535
|
+
}
|
|
5536
|
+
return hasDynamicPropList && Object.keys(props1).length !== Object.keys(props2).length;
|
|
5537
|
+
}
|
|
5538
|
+
const arePropsDifferent = hasSlotsProp
|
|
5539
|
+
? (_1, _2) => true
|
|
5540
|
+
: hasNoProp
|
|
5541
|
+
? (_1, _2) => false
|
|
5542
|
+
: _arePropsDifferent;
|
|
5543
|
+
const updateAndRender = ComponentNode.prototype.updateAndRender;
|
|
5544
|
+
const initiateRender = ComponentNode.prototype.initiateRender;
|
|
5545
|
+
return (props, key, ctx, parent, C) => {
|
|
5546
|
+
let children = ctx.children;
|
|
5547
|
+
let node = children[key];
|
|
5548
|
+
if (isDynamic && node && node.component.constructor !== C) {
|
|
5549
|
+
node = undefined;
|
|
5550
|
+
}
|
|
5551
|
+
const parentFiber = ctx.fiber;
|
|
5552
|
+
if (node) {
|
|
5553
|
+
if (arePropsDifferent(node.props, props) || parentFiber.deep || node.forceNextRender) {
|
|
5554
|
+
node.forceNextRender = false;
|
|
5555
|
+
updateAndRender.call(node, props, parentFiber);
|
|
5556
|
+
}
|
|
5557
|
+
}
|
|
5558
|
+
else {
|
|
5559
|
+
// new component
|
|
5560
|
+
if (isStatic) {
|
|
5561
|
+
C = parent.constructor.components[name];
|
|
5562
|
+
if (!C) {
|
|
5563
|
+
throw new Error(`Cannot find the definition of component "${name}"`);
|
|
5564
|
+
}
|
|
5565
|
+
else if (!(C.prototype instanceof Component)) {
|
|
5566
|
+
throw new Error(`"${name}" is not a Component. It must inherit from the Component class`);
|
|
5567
|
+
}
|
|
5568
|
+
}
|
|
5569
|
+
node = new ComponentNode(C, props, this, ctx, key);
|
|
5570
|
+
children[key] = node;
|
|
5571
|
+
initiateRender.call(node, new Fiber(node, parentFiber));
|
|
5572
|
+
}
|
|
5573
|
+
parentFiber.childrenMap[key] = node;
|
|
5574
|
+
return node;
|
|
5575
|
+
};
|
|
5576
|
+
}
|
|
5541
5577
|
}
|
|
5542
5578
|
App.validateTarget = validateTarget;
|
|
5543
5579
|
async function mount(C, target, config = {}) {
|
|
@@ -5718,7 +5754,7 @@ exports.whenReady = whenReady;
|
|
|
5718
5754
|
exports.xml = xml;
|
|
5719
5755
|
|
|
5720
5756
|
|
|
5721
|
-
__info__.version = '2.0.0-beta-
|
|
5722
|
-
__info__.date = '2022-06-
|
|
5723
|
-
__info__.hash = '
|
|
5757
|
+
__info__.version = '2.0.0-beta-12';
|
|
5758
|
+
__info__.date = '2022-06-29T09:13:12.680Z';
|
|
5759
|
+
__info__.hash = '6c72e0a';
|
|
5724
5760
|
__info__.url = 'https://github.com/odoo/owl';
|