@odoo/owl 2.0.0-beta-9 → 2.0.0-beta-10
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 +805 -787
- package/dist/owl.es.js +805 -787
- package/dist/owl.iife.js +805 -787
- package/dist/owl.iife.min.js +1 -1
- 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/validation.d.ts +4 -2
- package/package.json +1 -1
package/dist/owl.iife.js
CHANGED
|
@@ -1381,798 +1381,785 @@
|
|
|
1381
1381
|
vnode.remove();
|
|
1382
1382
|
}
|
|
1383
1383
|
|
|
1384
|
-
//
|
|
1385
|
-
const
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
const KEYCHANGES = Symbol("Key changes");
|
|
1390
|
-
const objectToString = Object.prototype.toString;
|
|
1391
|
-
const objectHasOwnProperty = Object.prototype.hasOwnProperty;
|
|
1392
|
-
const SUPPORTED_RAW_TYPES = new Set(["Object", "Array", "Set", "Map", "WeakMap"]);
|
|
1393
|
-
const COLLECTION_RAWTYPES = new Set(["Set", "Map", "WeakMap"]);
|
|
1394
|
-
/**
|
|
1395
|
-
* extract "RawType" from strings like "[object RawType]" => this lets us ignore
|
|
1396
|
-
* many native objects such as Promise (whose toString is [object Promise])
|
|
1397
|
-
* or Date ([object Date]), while also supporting collections without using
|
|
1398
|
-
* instanceof in a loop
|
|
1399
|
-
*
|
|
1400
|
-
* @param obj the object to check
|
|
1401
|
-
* @returns the raw type of the object
|
|
1402
|
-
*/
|
|
1403
|
-
function rawType(obj) {
|
|
1404
|
-
return objectToString.call(obj).slice(8, -1);
|
|
1405
|
-
}
|
|
1406
|
-
/**
|
|
1407
|
-
* Checks whether a given value can be made into a reactive object.
|
|
1408
|
-
*
|
|
1409
|
-
* @param value the value to check
|
|
1410
|
-
* @returns whether the value can be made reactive
|
|
1411
|
-
*/
|
|
1412
|
-
function canBeMadeReactive(value) {
|
|
1413
|
-
if (typeof value !== "object") {
|
|
1384
|
+
// Maps fibers to thrown errors
|
|
1385
|
+
const fibersInError = new WeakMap();
|
|
1386
|
+
const nodeErrorHandlers = new WeakMap();
|
|
1387
|
+
function _handleError(node, error) {
|
|
1388
|
+
if (!node) {
|
|
1414
1389
|
return false;
|
|
1415
1390
|
}
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
* Creates a reactive from the given object/callback if possible and returns it,
|
|
1420
|
-
* returns the original object otherwise.
|
|
1421
|
-
*
|
|
1422
|
-
* @param value the value make reactive
|
|
1423
|
-
* @returns a reactive for the given object when possible, the original otherwise
|
|
1424
|
-
*/
|
|
1425
|
-
function possiblyReactive(val, cb) {
|
|
1426
|
-
return canBeMadeReactive(val) ? reactive(val, cb) : val;
|
|
1427
|
-
}
|
|
1428
|
-
/**
|
|
1429
|
-
* Mark an object or array so that it is ignored by the reactivity system
|
|
1430
|
-
*
|
|
1431
|
-
* @param value the value to mark
|
|
1432
|
-
* @returns the object itself
|
|
1433
|
-
*/
|
|
1434
|
-
function markRaw(value) {
|
|
1435
|
-
value[SKIP] = true;
|
|
1436
|
-
return value;
|
|
1437
|
-
}
|
|
1438
|
-
/**
|
|
1439
|
-
* Given a reactive objet, return the raw (non reactive) underlying object
|
|
1440
|
-
*
|
|
1441
|
-
* @param value a reactive value
|
|
1442
|
-
* @returns the underlying value
|
|
1443
|
-
*/
|
|
1444
|
-
function toRaw(value) {
|
|
1445
|
-
return value[TARGET] || value;
|
|
1446
|
-
}
|
|
1447
|
-
const targetToKeysToCallbacks = new WeakMap();
|
|
1448
|
-
/**
|
|
1449
|
-
* Observes a given key on a target with an callback. The callback will be
|
|
1450
|
-
* called when the given key changes on the target.
|
|
1451
|
-
*
|
|
1452
|
-
* @param target the target whose key should be observed
|
|
1453
|
-
* @param key the key to observe (or Symbol(KEYCHANGES) for key creation
|
|
1454
|
-
* or deletion)
|
|
1455
|
-
* @param callback the function to call when the key changes
|
|
1456
|
-
*/
|
|
1457
|
-
function observeTargetKey(target, key, callback) {
|
|
1458
|
-
if (!targetToKeysToCallbacks.get(target)) {
|
|
1459
|
-
targetToKeysToCallbacks.set(target, new Map());
|
|
1391
|
+
const fiber = node.fiber;
|
|
1392
|
+
if (fiber) {
|
|
1393
|
+
fibersInError.set(fiber, error);
|
|
1460
1394
|
}
|
|
1461
|
-
const
|
|
1462
|
-
if (
|
|
1463
|
-
|
|
1395
|
+
const errorHandlers = nodeErrorHandlers.get(node);
|
|
1396
|
+
if (errorHandlers) {
|
|
1397
|
+
let handled = false;
|
|
1398
|
+
// execute in the opposite order
|
|
1399
|
+
for (let i = errorHandlers.length - 1; i >= 0; i--) {
|
|
1400
|
+
try {
|
|
1401
|
+
errorHandlers[i](error);
|
|
1402
|
+
handled = true;
|
|
1403
|
+
break;
|
|
1404
|
+
}
|
|
1405
|
+
catch (e) {
|
|
1406
|
+
error = e;
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
if (handled) {
|
|
1410
|
+
return true;
|
|
1411
|
+
}
|
|
1464
1412
|
}
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1413
|
+
return _handleError(node.parent, error);
|
|
1414
|
+
}
|
|
1415
|
+
function handleError(params) {
|
|
1416
|
+
const error = params.error;
|
|
1417
|
+
const node = "node" in params ? params.node : params.fiber.node;
|
|
1418
|
+
const fiber = "fiber" in params ? params.fiber : node.fiber;
|
|
1419
|
+
// resets the fibers on components if possible. This is important so that
|
|
1420
|
+
// new renderings can be properly included in the initial one, if any.
|
|
1421
|
+
let current = fiber;
|
|
1422
|
+
do {
|
|
1423
|
+
current.node.fiber = current;
|
|
1424
|
+
current = current.parent;
|
|
1425
|
+
} while (current);
|
|
1426
|
+
fibersInError.set(fiber.root, error);
|
|
1427
|
+
const handled = _handleError(node, error);
|
|
1428
|
+
if (!handled) {
|
|
1429
|
+
console.warn(`[Owl] Unhandled error. Destroying the root component`);
|
|
1430
|
+
try {
|
|
1431
|
+
node.app.destroy();
|
|
1432
|
+
}
|
|
1433
|
+
catch (e) {
|
|
1434
|
+
console.error(e);
|
|
1435
|
+
}
|
|
1468
1436
|
}
|
|
1469
|
-
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
function makeChildFiber(node, parent) {
|
|
1440
|
+
let current = node.fiber;
|
|
1441
|
+
if (current) {
|
|
1442
|
+
cancelFibers(current.children);
|
|
1443
|
+
current.root = null;
|
|
1444
|
+
}
|
|
1445
|
+
return new Fiber(node, parent);
|
|
1470
1446
|
}
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1447
|
+
function makeRootFiber(node) {
|
|
1448
|
+
let current = node.fiber;
|
|
1449
|
+
if (current) {
|
|
1450
|
+
let root = current.root;
|
|
1451
|
+
// lock root fiber because canceling children fibers may destroy components,
|
|
1452
|
+
// which means any arbitrary code can be run in onWillDestroy, which may
|
|
1453
|
+
// trigger new renderings
|
|
1454
|
+
root.locked = true;
|
|
1455
|
+
root.setCounter(root.counter + 1 - cancelFibers(current.children));
|
|
1456
|
+
root.locked = false;
|
|
1457
|
+
current.children = [];
|
|
1458
|
+
current.childrenMap = {};
|
|
1459
|
+
current.bdom = null;
|
|
1460
|
+
if (fibersInError.has(current)) {
|
|
1461
|
+
fibersInError.delete(current);
|
|
1462
|
+
fibersInError.delete(root);
|
|
1463
|
+
current.appliedToDom = false;
|
|
1464
|
+
}
|
|
1465
|
+
return current;
|
|
1484
1466
|
}
|
|
1485
|
-
const
|
|
1486
|
-
if (
|
|
1487
|
-
|
|
1467
|
+
const fiber = new RootFiber(node, null);
|
|
1468
|
+
if (node.willPatch.length) {
|
|
1469
|
+
fiber.willPatch.push(fiber);
|
|
1488
1470
|
}
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
clearReactivesForCallback(callback);
|
|
1492
|
-
callback();
|
|
1471
|
+
if (node.patched.length) {
|
|
1472
|
+
fiber.patched.push(fiber);
|
|
1493
1473
|
}
|
|
1474
|
+
return fiber;
|
|
1475
|
+
}
|
|
1476
|
+
function throwOnRender() {
|
|
1477
|
+
throw new Error("Attempted to render cancelled fiber");
|
|
1494
1478
|
}
|
|
1495
|
-
const callbacksToTargets = new WeakMap();
|
|
1496
1479
|
/**
|
|
1497
|
-
*
|
|
1498
|
-
*
|
|
1499
|
-
* @param callback the callback for which the reactives need to be cleared
|
|
1480
|
+
* @returns number of not-yet rendered fibers cancelled
|
|
1500
1481
|
*/
|
|
1501
|
-
function
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
if (!observedKeys) {
|
|
1509
|
-
continue;
|
|
1482
|
+
function cancelFibers(fibers) {
|
|
1483
|
+
let result = 0;
|
|
1484
|
+
for (let fiber of fibers) {
|
|
1485
|
+
let node = fiber.node;
|
|
1486
|
+
fiber.render = throwOnRender;
|
|
1487
|
+
if (node.status === 0 /* NEW */) {
|
|
1488
|
+
node.destroy();
|
|
1510
1489
|
}
|
|
1511
|
-
|
|
1512
|
-
|
|
1490
|
+
node.fiber = null;
|
|
1491
|
+
if (fiber.bdom) {
|
|
1492
|
+
// if fiber has been rendered, this means that the component props have
|
|
1493
|
+
// been updated. however, this fiber will not be patched to the dom, so
|
|
1494
|
+
// it could happen that the next render compare the current props with
|
|
1495
|
+
// the same props, and skip the render completely. With the next line,
|
|
1496
|
+
// we kindly request the component code to force a render, so it works as
|
|
1497
|
+
// expected.
|
|
1498
|
+
node.forceNextRender = true;
|
|
1499
|
+
}
|
|
1500
|
+
else {
|
|
1501
|
+
result++;
|
|
1513
1502
|
}
|
|
1503
|
+
result += cancelFibers(fiber.children);
|
|
1514
1504
|
}
|
|
1515
|
-
|
|
1516
|
-
}
|
|
1517
|
-
function getSubscriptions(callback) {
|
|
1518
|
-
const targets = callbacksToTargets.get(callback) || [];
|
|
1519
|
-
return [...targets].map((target) => {
|
|
1520
|
-
const keysToCallbacks = targetToKeysToCallbacks.get(target);
|
|
1521
|
-
return {
|
|
1522
|
-
target,
|
|
1523
|
-
keys: keysToCallbacks ? [...keysToCallbacks.keys()] : [],
|
|
1524
|
-
};
|
|
1525
|
-
});
|
|
1505
|
+
return result;
|
|
1526
1506
|
}
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
* This is a choice that was made because changing a key's value will trigger
|
|
1547
|
-
* this trap and we do not want to subscribe by writes. This also means that
|
|
1548
|
-
* Object.hasOwnProperty doesn't subscribe as it goes through this trap.
|
|
1549
|
-
*
|
|
1550
|
-
* @param target the object for which to create a reactive proxy
|
|
1551
|
-
* @param callback the function to call when an observed property of the
|
|
1552
|
-
* reactive has changed
|
|
1553
|
-
* @returns a proxy that tracks changes to it
|
|
1554
|
-
*/
|
|
1555
|
-
function reactive(target, callback = () => { }) {
|
|
1556
|
-
if (!canBeMadeReactive(target)) {
|
|
1557
|
-
throw new Error(`Cannot make the given value reactive`);
|
|
1507
|
+
class Fiber {
|
|
1508
|
+
constructor(node, parent) {
|
|
1509
|
+
this.bdom = null;
|
|
1510
|
+
this.children = [];
|
|
1511
|
+
this.appliedToDom = false;
|
|
1512
|
+
this.deep = false;
|
|
1513
|
+
this.childrenMap = {};
|
|
1514
|
+
this.node = node;
|
|
1515
|
+
this.parent = parent;
|
|
1516
|
+
if (parent) {
|
|
1517
|
+
this.deep = parent.deep;
|
|
1518
|
+
const root = parent.root;
|
|
1519
|
+
root.setCounter(root.counter + 1);
|
|
1520
|
+
this.root = root;
|
|
1521
|
+
parent.children.push(this);
|
|
1522
|
+
}
|
|
1523
|
+
else {
|
|
1524
|
+
this.root = this;
|
|
1525
|
+
}
|
|
1558
1526
|
}
|
|
1559
|
-
|
|
1560
|
-
|
|
1527
|
+
render() {
|
|
1528
|
+
// if some parent has a fiber => register in followup
|
|
1529
|
+
let prev = this.root.node;
|
|
1530
|
+
let scheduler = prev.app.scheduler;
|
|
1531
|
+
let current = prev.parent;
|
|
1532
|
+
while (current) {
|
|
1533
|
+
if (current.fiber) {
|
|
1534
|
+
let root = current.fiber.root;
|
|
1535
|
+
if (root.counter === 0 && prev.parentKey in current.fiber.childrenMap) {
|
|
1536
|
+
current = root.node;
|
|
1537
|
+
}
|
|
1538
|
+
else {
|
|
1539
|
+
scheduler.delayedRenders.push(this);
|
|
1540
|
+
return;
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
prev = current;
|
|
1544
|
+
current = current.parent;
|
|
1545
|
+
}
|
|
1546
|
+
// there are no current rendering from above => we can render
|
|
1547
|
+
this._render();
|
|
1561
1548
|
}
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1549
|
+
_render() {
|
|
1550
|
+
const node = this.node;
|
|
1551
|
+
const root = this.root;
|
|
1552
|
+
if (root) {
|
|
1553
|
+
try {
|
|
1554
|
+
this.bdom = true;
|
|
1555
|
+
this.bdom = node.renderFn();
|
|
1556
|
+
}
|
|
1557
|
+
catch (e) {
|
|
1558
|
+
handleError({ node, error: e });
|
|
1559
|
+
}
|
|
1560
|
+
root.setCounter(root.counter - 1);
|
|
1561
|
+
}
|
|
1565
1562
|
}
|
|
1566
|
-
|
|
1567
|
-
|
|
1563
|
+
}
|
|
1564
|
+
class RootFiber extends Fiber {
|
|
1565
|
+
constructor() {
|
|
1566
|
+
super(...arguments);
|
|
1567
|
+
this.counter = 1;
|
|
1568
|
+
// only add stuff in this if they have registered some hooks
|
|
1569
|
+
this.willPatch = [];
|
|
1570
|
+
this.patched = [];
|
|
1571
|
+
this.mounted = [];
|
|
1572
|
+
// A fiber is typically locked when it is completing and the patch has not, or is being applied.
|
|
1573
|
+
// i.e.: render triggered in onWillUnmount or in willPatch will be delayed
|
|
1574
|
+
this.locked = false;
|
|
1568
1575
|
}
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
:
|
|
1575
|
-
|
|
1576
|
-
|
|
1576
|
+
complete() {
|
|
1577
|
+
const node = this.node;
|
|
1578
|
+
this.locked = true;
|
|
1579
|
+
let current = undefined;
|
|
1580
|
+
try {
|
|
1581
|
+
// Step 1: calling all willPatch lifecycle hooks
|
|
1582
|
+
for (current of this.willPatch) {
|
|
1583
|
+
// because of the asynchronous nature of the rendering, some parts of the
|
|
1584
|
+
// UI may have been rendered, then deleted in a followup rendering, and we
|
|
1585
|
+
// do not want to call onWillPatch in that case.
|
|
1586
|
+
let node = current.node;
|
|
1587
|
+
if (node.fiber === current) {
|
|
1588
|
+
const component = node.component;
|
|
1589
|
+
for (let cb of node.willPatch) {
|
|
1590
|
+
cb.call(component);
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
current = undefined;
|
|
1595
|
+
// Step 2: patching the dom
|
|
1596
|
+
node._patch();
|
|
1597
|
+
this.locked = false;
|
|
1598
|
+
// Step 4: calling all mounted lifecycle hooks
|
|
1599
|
+
let mountedFibers = this.mounted;
|
|
1600
|
+
while ((current = mountedFibers.pop())) {
|
|
1601
|
+
current = current;
|
|
1602
|
+
if (current.appliedToDom) {
|
|
1603
|
+
for (let cb of current.node.mounted) {
|
|
1604
|
+
cb();
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
// Step 5: calling all patched hooks
|
|
1609
|
+
let patchedFibers = this.patched;
|
|
1610
|
+
while ((current = patchedFibers.pop())) {
|
|
1611
|
+
current = current;
|
|
1612
|
+
if (current.appliedToDom) {
|
|
1613
|
+
for (let cb of current.node.patched) {
|
|
1614
|
+
cb();
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
catch (e) {
|
|
1620
|
+
this.locked = false;
|
|
1621
|
+
handleError({ fiber: current || this, error: e });
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
setCounter(newValue) {
|
|
1625
|
+
this.counter = newValue;
|
|
1626
|
+
if (newValue === 0) {
|
|
1627
|
+
this.node.app.scheduler.flush();
|
|
1628
|
+
}
|
|
1577
1629
|
}
|
|
1578
|
-
return reactivesForTarget.get(callback);
|
|
1579
1630
|
}
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1631
|
+
class MountFiber extends RootFiber {
|
|
1632
|
+
constructor(node, target, options = {}) {
|
|
1633
|
+
super(node, null);
|
|
1634
|
+
this.target = target;
|
|
1635
|
+
this.position = options.position || "last-child";
|
|
1636
|
+
}
|
|
1637
|
+
complete() {
|
|
1638
|
+
let current = this;
|
|
1639
|
+
try {
|
|
1640
|
+
const node = this.node;
|
|
1641
|
+
node.children = this.childrenMap;
|
|
1642
|
+
node.app.constructor.validateTarget(this.target);
|
|
1643
|
+
if (node.bdom) {
|
|
1644
|
+
// this is a complicated situation: if we mount a fiber with an existing
|
|
1645
|
+
// bdom, this means that this same fiber was already completed, mounted,
|
|
1646
|
+
// but a crash occurred in some mounted hook. Then, it was handled and
|
|
1647
|
+
// the new rendering is being applied.
|
|
1648
|
+
node.updateDom();
|
|
1596
1649
|
}
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1650
|
+
else {
|
|
1651
|
+
node.bdom = this.bdom;
|
|
1652
|
+
if (this.position === "last-child" || this.target.childNodes.length === 0) {
|
|
1653
|
+
mount$1(node.bdom, this.target);
|
|
1654
|
+
}
|
|
1655
|
+
else {
|
|
1656
|
+
const firstChild = this.target.childNodes[0];
|
|
1657
|
+
mount$1(node.bdom, this.target, firstChild);
|
|
1658
|
+
}
|
|
1606
1659
|
}
|
|
1607
|
-
//
|
|
1608
|
-
//
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1660
|
+
// unregistering the fiber before mounted since it can do another render
|
|
1661
|
+
// and that the current rendering is obviously completed
|
|
1662
|
+
node.fiber = null;
|
|
1663
|
+
node.status = 1 /* MOUNTED */;
|
|
1664
|
+
this.appliedToDom = true;
|
|
1665
|
+
let mountedFibers = this.mounted;
|
|
1666
|
+
while ((current = mountedFibers.pop())) {
|
|
1667
|
+
if (current.appliedToDom) {
|
|
1668
|
+
for (let cb of current.node.mounted) {
|
|
1669
|
+
cb();
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1612
1672
|
}
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
observeTargetKey(target, KEYCHANGES, callback);
|
|
1631
|
-
return Reflect.has(target, key);
|
|
1632
|
-
},
|
|
1633
|
-
};
|
|
1634
|
-
}
|
|
1635
|
-
/**
|
|
1636
|
-
* Creates a function that will observe the key that is passed to it when called
|
|
1637
|
-
* and delegates to the underlying method.
|
|
1638
|
-
*
|
|
1639
|
-
* @param methodName name of the method to delegate to
|
|
1640
|
-
* @param target @see reactive
|
|
1641
|
-
* @param callback @see reactive
|
|
1642
|
-
*/
|
|
1643
|
-
function makeKeyObserver(methodName, target, callback) {
|
|
1644
|
-
return (key) => {
|
|
1645
|
-
key = toRaw(key);
|
|
1646
|
-
observeTargetKey(target, key, callback);
|
|
1647
|
-
return possiblyReactive(target[methodName](key), callback);
|
|
1648
|
-
};
|
|
1649
|
-
}
|
|
1673
|
+
}
|
|
1674
|
+
catch (e) {
|
|
1675
|
+
handleError({ fiber: current, error: e });
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
// Allows to get the target of a Reactive (used for making a new Reactive from the underlying object)
|
|
1681
|
+
const TARGET = Symbol("Target");
|
|
1682
|
+
// Escape hatch to prevent reactivity system to turn something into a reactive
|
|
1683
|
+
const SKIP = Symbol("Skip");
|
|
1684
|
+
// Special key to subscribe to, to be notified of key creation/deletion
|
|
1685
|
+
const KEYCHANGES = Symbol("Key changes");
|
|
1686
|
+
const objectToString = Object.prototype.toString;
|
|
1687
|
+
const objectHasOwnProperty = Object.prototype.hasOwnProperty;
|
|
1688
|
+
const SUPPORTED_RAW_TYPES = new Set(["Object", "Array", "Set", "Map", "WeakMap"]);
|
|
1689
|
+
const COLLECTION_RAWTYPES = new Set(["Set", "Map", "WeakMap"]);
|
|
1650
1690
|
/**
|
|
1651
|
-
*
|
|
1652
|
-
*
|
|
1691
|
+
* extract "RawType" from strings like "[object RawType]" => this lets us ignore
|
|
1692
|
+
* many native objects such as Promise (whose toString is [object Promise])
|
|
1693
|
+
* or Date ([object Date]), while also supporting collections without using
|
|
1694
|
+
* instanceof in a loop
|
|
1653
1695
|
*
|
|
1654
|
-
* @param
|
|
1655
|
-
* @
|
|
1656
|
-
* @param callback @see reactive
|
|
1696
|
+
* @param obj the object to check
|
|
1697
|
+
* @returns the raw type of the object
|
|
1657
1698
|
*/
|
|
1658
|
-
function
|
|
1659
|
-
return
|
|
1660
|
-
observeTargetKey(target, KEYCHANGES, callback);
|
|
1661
|
-
const keys = target.keys();
|
|
1662
|
-
for (const item of target[methodName]()) {
|
|
1663
|
-
const key = keys.next().value;
|
|
1664
|
-
observeTargetKey(target, key, callback);
|
|
1665
|
-
yield possiblyReactive(item, callback);
|
|
1666
|
-
}
|
|
1667
|
-
};
|
|
1699
|
+
function rawType(obj) {
|
|
1700
|
+
return objectToString.call(obj).slice(8, -1);
|
|
1668
1701
|
}
|
|
1669
1702
|
/**
|
|
1670
|
-
*
|
|
1671
|
-
* collection while observing key changes, and keys as they're iterated over,
|
|
1672
|
-
* and making the passed keys/values reactive.
|
|
1703
|
+
* Checks whether a given value can be made into a reactive object.
|
|
1673
1704
|
*
|
|
1674
|
-
* @param
|
|
1675
|
-
* @
|
|
1705
|
+
* @param value the value to check
|
|
1706
|
+
* @returns whether the value can be made reactive
|
|
1676
1707
|
*/
|
|
1677
|
-
function
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
forEachCb.call(thisArg, possiblyReactive(val, callback), possiblyReactive(key, callback), possiblyReactive(targetObj, callback));
|
|
1683
|
-
}, thisArg);
|
|
1684
|
-
};
|
|
1708
|
+
function canBeMadeReactive(value) {
|
|
1709
|
+
if (typeof value !== "object") {
|
|
1710
|
+
return false;
|
|
1711
|
+
}
|
|
1712
|
+
return SUPPORTED_RAW_TYPES.has(rawType(value));
|
|
1685
1713
|
}
|
|
1686
1714
|
/**
|
|
1687
|
-
* Creates a
|
|
1688
|
-
*
|
|
1689
|
-
* reactives appropriately.
|
|
1715
|
+
* Creates a reactive from the given object/callback if possible and returns it,
|
|
1716
|
+
* returns the original object otherwise.
|
|
1690
1717
|
*
|
|
1691
|
-
* @param
|
|
1692
|
-
* @
|
|
1693
|
-
* value before calling the delegate method for comparison purposes
|
|
1694
|
-
* @param target @see reactive
|
|
1718
|
+
* @param value the value make reactive
|
|
1719
|
+
* @returns a reactive for the given object when possible, the original otherwise
|
|
1695
1720
|
*/
|
|
1696
|
-
function
|
|
1697
|
-
return (
|
|
1698
|
-
key = toRaw(key);
|
|
1699
|
-
const hadKey = target.has(key);
|
|
1700
|
-
const originalValue = target[getterName](key);
|
|
1701
|
-
const ret = target[setterName](key, value);
|
|
1702
|
-
const hasKey = target.has(key);
|
|
1703
|
-
if (hadKey !== hasKey) {
|
|
1704
|
-
notifyReactives(target, KEYCHANGES);
|
|
1705
|
-
}
|
|
1706
|
-
if (originalValue !== value) {
|
|
1707
|
-
notifyReactives(target, key);
|
|
1708
|
-
}
|
|
1709
|
-
return ret;
|
|
1710
|
-
};
|
|
1721
|
+
function possiblyReactive(val, cb) {
|
|
1722
|
+
return canBeMadeReactive(val) ? reactive(val, cb) : val;
|
|
1711
1723
|
}
|
|
1712
1724
|
/**
|
|
1713
|
-
*
|
|
1714
|
-
* the keys of the collection have changed.
|
|
1725
|
+
* Mark an object or array so that it is ignored by the reactivity system
|
|
1715
1726
|
*
|
|
1716
|
-
* @param
|
|
1727
|
+
* @param value the value to mark
|
|
1728
|
+
* @returns the object itself
|
|
1717
1729
|
*/
|
|
1718
|
-
function
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
target.clear();
|
|
1722
|
-
notifyReactives(target, KEYCHANGES);
|
|
1723
|
-
for (const key of allKeys) {
|
|
1724
|
-
notifyReactives(target, key);
|
|
1725
|
-
}
|
|
1726
|
-
};
|
|
1730
|
+
function markRaw(value) {
|
|
1731
|
+
value[SKIP] = true;
|
|
1732
|
+
return value;
|
|
1727
1733
|
}
|
|
1728
1734
|
/**
|
|
1729
|
-
*
|
|
1730
|
-
* to build an appropritate proxy handler for that raw type. Eg: when making a
|
|
1731
|
-
* reactive set, calling the has method should mark the key that is being
|
|
1732
|
-
* retrieved as observed, and calling the add or delete method should notify the
|
|
1733
|
-
* reactives that the key which is being added or deleted has been modified.
|
|
1734
|
-
*/
|
|
1735
|
-
const rawTypeToFuncHandlers = {
|
|
1736
|
-
Set: (target, callback) => ({
|
|
1737
|
-
has: makeKeyObserver("has", target, callback),
|
|
1738
|
-
add: delegateAndNotify("add", "has", target),
|
|
1739
|
-
delete: delegateAndNotify("delete", "has", target),
|
|
1740
|
-
keys: makeIteratorObserver("keys", target, callback),
|
|
1741
|
-
values: makeIteratorObserver("values", target, callback),
|
|
1742
|
-
entries: makeIteratorObserver("entries", target, callback),
|
|
1743
|
-
[Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
|
|
1744
|
-
forEach: makeForEachObserver(target, callback),
|
|
1745
|
-
clear: makeClearNotifier(target),
|
|
1746
|
-
get size() {
|
|
1747
|
-
observeTargetKey(target, KEYCHANGES, callback);
|
|
1748
|
-
return target.size;
|
|
1749
|
-
},
|
|
1750
|
-
}),
|
|
1751
|
-
Map: (target, callback) => ({
|
|
1752
|
-
has: makeKeyObserver("has", target, callback),
|
|
1753
|
-
get: makeKeyObserver("get", target, callback),
|
|
1754
|
-
set: delegateAndNotify("set", "get", target),
|
|
1755
|
-
delete: delegateAndNotify("delete", "has", target),
|
|
1756
|
-
keys: makeIteratorObserver("keys", target, callback),
|
|
1757
|
-
values: makeIteratorObserver("values", target, callback),
|
|
1758
|
-
entries: makeIteratorObserver("entries", target, callback),
|
|
1759
|
-
[Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
|
|
1760
|
-
forEach: makeForEachObserver(target, callback),
|
|
1761
|
-
clear: makeClearNotifier(target),
|
|
1762
|
-
get size() {
|
|
1763
|
-
observeTargetKey(target, KEYCHANGES, callback);
|
|
1764
|
-
return target.size;
|
|
1765
|
-
},
|
|
1766
|
-
}),
|
|
1767
|
-
WeakMap: (target, callback) => ({
|
|
1768
|
-
has: makeKeyObserver("has", target, callback),
|
|
1769
|
-
get: makeKeyObserver("get", target, callback),
|
|
1770
|
-
set: delegateAndNotify("set", "get", target),
|
|
1771
|
-
delete: delegateAndNotify("delete", "has", target),
|
|
1772
|
-
}),
|
|
1773
|
-
};
|
|
1774
|
-
/**
|
|
1775
|
-
* Creates a proxy handler for collections (Set/Map/WeakMap)
|
|
1735
|
+
* Given a reactive objet, return the raw (non reactive) underlying object
|
|
1776
1736
|
*
|
|
1777
|
-
* @param
|
|
1778
|
-
* @
|
|
1779
|
-
* @returns a proxy handler object
|
|
1737
|
+
* @param value a reactive value
|
|
1738
|
+
* @returns the underlying value
|
|
1780
1739
|
*/
|
|
1781
|
-
function
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
return Object.assign(basicProxyHandler(callback), {
|
|
1786
|
-
get(target, key) {
|
|
1787
|
-
if (key === TARGET) {
|
|
1788
|
-
return target;
|
|
1789
|
-
}
|
|
1790
|
-
if (objectHasOwnProperty.call(specialHandlers, key)) {
|
|
1791
|
-
return specialHandlers[key];
|
|
1792
|
-
}
|
|
1793
|
-
observeTargetKey(target, key, callback);
|
|
1794
|
-
return possiblyReactive(target[key], callback);
|
|
1795
|
-
},
|
|
1796
|
-
});
|
|
1797
|
-
}
|
|
1798
|
-
|
|
1740
|
+
function toRaw(value) {
|
|
1741
|
+
return value[TARGET] || value;
|
|
1742
|
+
}
|
|
1743
|
+
const targetToKeysToCallbacks = new WeakMap();
|
|
1799
1744
|
/**
|
|
1800
|
-
*
|
|
1801
|
-
*
|
|
1745
|
+
* Observes a given key on a target with an callback. The callback will be
|
|
1746
|
+
* called when the given key changes on the target.
|
|
1802
1747
|
*
|
|
1803
|
-
* @param
|
|
1804
|
-
* @
|
|
1748
|
+
* @param target the target whose key should be observed
|
|
1749
|
+
* @param key the key to observe (or Symbol(KEYCHANGES) for key creation
|
|
1750
|
+
* or deletion)
|
|
1751
|
+
* @param callback the function to call when the key changes
|
|
1805
1752
|
*/
|
|
1806
|
-
function
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
// This await blocks all calls to the callback here, then releases them sequentially
|
|
1810
|
-
// in the next microtick. This line decides the granularity of the batch.
|
|
1811
|
-
await Promise.resolve();
|
|
1812
|
-
if (!called) {
|
|
1813
|
-
called = true;
|
|
1814
|
-
// wait for all calls in this microtick to fall through before resetting "called"
|
|
1815
|
-
// so that only the first call to the batched function calls the original callback.
|
|
1816
|
-
// Schedule this before calling the callback so that calls to the batched function
|
|
1817
|
-
// within the callback will proceed only after resetting called to false, and have
|
|
1818
|
-
// a chance to execute the callback again
|
|
1819
|
-
Promise.resolve().then(() => (called = false));
|
|
1820
|
-
callback();
|
|
1821
|
-
}
|
|
1822
|
-
};
|
|
1823
|
-
}
|
|
1824
|
-
function validateTarget(target) {
|
|
1825
|
-
if (!(target instanceof HTMLElement)) {
|
|
1826
|
-
throw new Error("Cannot mount component: the target is not a valid DOM element");
|
|
1753
|
+
function observeTargetKey(target, key, callback) {
|
|
1754
|
+
if (!targetToKeysToCallbacks.get(target)) {
|
|
1755
|
+
targetToKeysToCallbacks.set(target, new Map());
|
|
1827
1756
|
}
|
|
1828
|
-
|
|
1829
|
-
|
|
1757
|
+
const keyToCallbacks = targetToKeysToCallbacks.get(target);
|
|
1758
|
+
if (!keyToCallbacks.get(key)) {
|
|
1759
|
+
keyToCallbacks.set(key, new Set());
|
|
1760
|
+
}
|
|
1761
|
+
keyToCallbacks.get(key).add(callback);
|
|
1762
|
+
if (!callbacksToTargets.has(callback)) {
|
|
1763
|
+
callbacksToTargets.set(callback, new Set());
|
|
1830
1764
|
}
|
|
1765
|
+
callbacksToTargets.get(callback).add(target);
|
|
1831
1766
|
}
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1767
|
+
/**
|
|
1768
|
+
* Notify Reactives that are observing a given target that a key has changed on
|
|
1769
|
+
* the target.
|
|
1770
|
+
*
|
|
1771
|
+
* @param target target whose Reactives should be notified that the target was
|
|
1772
|
+
* changed.
|
|
1773
|
+
* @param key the key that changed (or Symbol `KEYCHANGES` if a key was created
|
|
1774
|
+
* or deleted)
|
|
1775
|
+
*/
|
|
1776
|
+
function notifyReactives(target, key) {
|
|
1777
|
+
const keyToCallbacks = targetToKeysToCallbacks.get(target);
|
|
1778
|
+
if (!keyToCallbacks) {
|
|
1779
|
+
return;
|
|
1780
|
+
}
|
|
1781
|
+
const callbacks = keyToCallbacks.get(key);
|
|
1782
|
+
if (!callbacks) {
|
|
1783
|
+
return;
|
|
1784
|
+
}
|
|
1785
|
+
// Loop on copy because clearReactivesForCallback will modify the set in place
|
|
1786
|
+
for (const callback of [...callbacks]) {
|
|
1787
|
+
clearReactivesForCallback(callback);
|
|
1788
|
+
callback();
|
|
1835
1789
|
}
|
|
1836
1790
|
}
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1791
|
+
const callbacksToTargets = new WeakMap();
|
|
1792
|
+
/**
|
|
1793
|
+
* Clears all subscriptions of the Reactives associated with a given callback.
|
|
1794
|
+
*
|
|
1795
|
+
* @param callback the callback for which the reactives need to be cleared
|
|
1796
|
+
*/
|
|
1797
|
+
function clearReactivesForCallback(callback) {
|
|
1798
|
+
const targetsToClear = callbacksToTargets.get(callback);
|
|
1799
|
+
if (!targetsToClear) {
|
|
1800
|
+
return;
|
|
1801
|
+
}
|
|
1802
|
+
for (const target of targetsToClear) {
|
|
1803
|
+
const observedKeys = targetToKeysToCallbacks.get(target);
|
|
1804
|
+
if (!observedKeys) {
|
|
1805
|
+
continue;
|
|
1841
1806
|
}
|
|
1842
|
-
|
|
1843
|
-
|
|
1807
|
+
for (const callbacks of observedKeys.values()) {
|
|
1808
|
+
callbacks.delete(callback);
|
|
1844
1809
|
}
|
|
1845
|
-
}).then(fn || function () { });
|
|
1846
|
-
}
|
|
1847
|
-
async function loadFile(url) {
|
|
1848
|
-
const result = await fetch(url);
|
|
1849
|
-
if (!result.ok) {
|
|
1850
|
-
throw new Error("Error while fetching xml templates");
|
|
1851
1810
|
}
|
|
1852
|
-
|
|
1811
|
+
targetsToClear.clear();
|
|
1853
1812
|
}
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1813
|
+
function getSubscriptions(callback) {
|
|
1814
|
+
const targets = callbacksToTargets.get(callback) || [];
|
|
1815
|
+
return [...targets].map((target) => {
|
|
1816
|
+
const keysToCallbacks = targetToKeysToCallbacks.get(target);
|
|
1817
|
+
return {
|
|
1818
|
+
target,
|
|
1819
|
+
keys: keysToCallbacks ? [...keysToCallbacks.keys()] : [],
|
|
1820
|
+
};
|
|
1821
|
+
});
|
|
1860
1822
|
}
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
*
|
|
1823
|
+
const reactiveCache = new WeakMap();
|
|
1824
|
+
/**
|
|
1825
|
+
* Creates a reactive proxy for an object. Reading data on the reactive object
|
|
1826
|
+
* subscribes to changes to the data. Writing data on the object will cause the
|
|
1827
|
+
* notify callback to be called if there are suscriptions to that data. Nested
|
|
1828
|
+
* objects and arrays are automatically made reactive as well.
|
|
1829
|
+
*
|
|
1830
|
+
* Whenever you are notified of a change, all subscriptions are cleared, and if
|
|
1831
|
+
* you would like to be notified of any further changes, you should go read
|
|
1832
|
+
* the underlying data again. We assume that if you don't go read it again after
|
|
1833
|
+
* being notified, it means that you are no longer interested in that data.
|
|
1834
|
+
*
|
|
1835
|
+
* Subscriptions:
|
|
1836
|
+
* + Reading a property on an object will subscribe you to changes in the value
|
|
1837
|
+
* of that property.
|
|
1838
|
+
* + Accessing an object keys (eg with Object.keys or with `for..in`) will
|
|
1839
|
+
* subscribe you to the creation/deletion of keys. Checking the presence of a
|
|
1840
|
+
* key on the object with 'in' has the same effect.
|
|
1841
|
+
* - getOwnPropertyDescriptor does not currently subscribe you to the property.
|
|
1842
|
+
* This is a choice that was made because changing a key's value will trigger
|
|
1843
|
+
* this trap and we do not want to subscribe by writes. This also means that
|
|
1844
|
+
* Object.hasOwnProperty doesn't subscribe as it goes through this trap.
|
|
1845
|
+
*
|
|
1846
|
+
* @param target the object for which to create a reactive proxy
|
|
1847
|
+
* @param callback the function to call when an observed property of the
|
|
1848
|
+
* reactive has changed
|
|
1849
|
+
* @returns a proxy that tracks changes to it
|
|
1864
1850
|
*/
|
|
1865
|
-
function
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
class Component {
|
|
1870
|
-
constructor(props, env, node) {
|
|
1871
|
-
this.props = props;
|
|
1872
|
-
this.env = env;
|
|
1873
|
-
this.__owl__ = node;
|
|
1851
|
+
function reactive(target, callback = () => { }) {
|
|
1852
|
+
if (!canBeMadeReactive(target)) {
|
|
1853
|
+
throw new Error(`Cannot make the given value reactive`);
|
|
1874
1854
|
}
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
this.__owl__.render(deep === true);
|
|
1855
|
+
if (SKIP in target) {
|
|
1856
|
+
return target;
|
|
1878
1857
|
}
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
// Maps fibers to thrown errors
|
|
1883
|
-
const fibersInError = new WeakMap();
|
|
1884
|
-
const nodeErrorHandlers = new WeakMap();
|
|
1885
|
-
function _handleError(node, error) {
|
|
1886
|
-
if (!node) {
|
|
1887
|
-
return false;
|
|
1858
|
+
const originalTarget = target[TARGET];
|
|
1859
|
+
if (originalTarget) {
|
|
1860
|
+
return reactive(originalTarget, callback);
|
|
1888
1861
|
}
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
fibersInError.set(fiber, error);
|
|
1862
|
+
if (!reactiveCache.has(target)) {
|
|
1863
|
+
reactiveCache.set(target, new WeakMap());
|
|
1892
1864
|
}
|
|
1893
|
-
const
|
|
1894
|
-
if (
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1865
|
+
const reactivesForTarget = reactiveCache.get(target);
|
|
1866
|
+
if (!reactivesForTarget.has(callback)) {
|
|
1867
|
+
const targetRawType = rawType(target);
|
|
1868
|
+
const handler = COLLECTION_RAWTYPES.has(targetRawType)
|
|
1869
|
+
? collectionsProxyHandler(target, callback, targetRawType)
|
|
1870
|
+
: basicProxyHandler(callback);
|
|
1871
|
+
const proxy = new Proxy(target, handler);
|
|
1872
|
+
reactivesForTarget.set(callback, proxy);
|
|
1873
|
+
}
|
|
1874
|
+
return reactivesForTarget.get(callback);
|
|
1875
|
+
}
|
|
1876
|
+
/**
|
|
1877
|
+
* Creates a basic proxy handler for regular objects and arrays.
|
|
1878
|
+
*
|
|
1879
|
+
* @param callback @see reactive
|
|
1880
|
+
* @returns a proxy handler object
|
|
1881
|
+
*/
|
|
1882
|
+
function basicProxyHandler(callback) {
|
|
1883
|
+
return {
|
|
1884
|
+
get(target, key, proxy) {
|
|
1885
|
+
if (key === TARGET) {
|
|
1886
|
+
return target;
|
|
1902
1887
|
}
|
|
1903
|
-
|
|
1904
|
-
|
|
1888
|
+
// non-writable non-configurable properties cannot be made reactive
|
|
1889
|
+
const desc = Object.getOwnPropertyDescriptor(target, key);
|
|
1890
|
+
if (desc && !desc.writable && !desc.configurable) {
|
|
1891
|
+
return Reflect.get(target, key, proxy);
|
|
1905
1892
|
}
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1893
|
+
observeTargetKey(target, key, callback);
|
|
1894
|
+
return possiblyReactive(Reflect.get(target, key, proxy), callback);
|
|
1895
|
+
},
|
|
1896
|
+
set(target, key, value, proxy) {
|
|
1897
|
+
const isNewKey = !objectHasOwnProperty.call(target, key);
|
|
1898
|
+
const originalValue = Reflect.get(target, key, proxy);
|
|
1899
|
+
const ret = Reflect.set(target, key, value, proxy);
|
|
1900
|
+
if (isNewKey) {
|
|
1901
|
+
notifyReactives(target, KEYCHANGES);
|
|
1902
|
+
}
|
|
1903
|
+
// While Array length may trigger the set trap, it's not actually set by this
|
|
1904
|
+
// method but is updated behind the scenes, and the trap is not called with the
|
|
1905
|
+
// new value. We disable the "same-value-optimization" for it because of that.
|
|
1906
|
+
if (originalValue !== value || (Array.isArray(target) && key === "length")) {
|
|
1907
|
+
notifyReactives(target, key);
|
|
1908
|
+
}
|
|
1909
|
+
return ret;
|
|
1910
|
+
},
|
|
1911
|
+
deleteProperty(target, key) {
|
|
1912
|
+
const ret = Reflect.deleteProperty(target, key);
|
|
1913
|
+
// TODO: only notify when something was actually deleted
|
|
1914
|
+
notifyReactives(target, KEYCHANGES);
|
|
1915
|
+
notifyReactives(target, key);
|
|
1916
|
+
return ret;
|
|
1917
|
+
},
|
|
1918
|
+
ownKeys(target) {
|
|
1919
|
+
observeTargetKey(target, KEYCHANGES, callback);
|
|
1920
|
+
return Reflect.ownKeys(target);
|
|
1921
|
+
},
|
|
1922
|
+
has(target, key) {
|
|
1923
|
+
// TODO: this observes all key changes instead of only the presence of the argument key
|
|
1924
|
+
// observing the key itself would observe value changes instead of presence changes
|
|
1925
|
+
// so we may need a finer grained system to distinguish observing value vs presence.
|
|
1926
|
+
observeTargetKey(target, KEYCHANGES, callback);
|
|
1927
|
+
return Reflect.has(target, key);
|
|
1928
|
+
},
|
|
1929
|
+
};
|
|
1912
1930
|
}
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
console.warn(`[Owl] Unhandled error. Destroying the root component`);
|
|
1928
|
-
try {
|
|
1929
|
-
node.app.destroy();
|
|
1930
|
-
}
|
|
1931
|
-
catch (e) {
|
|
1932
|
-
console.error(e);
|
|
1933
|
-
}
|
|
1934
|
-
}
|
|
1935
|
-
}
|
|
1936
|
-
|
|
1937
|
-
function makeChildFiber(node, parent) {
|
|
1938
|
-
let current = node.fiber;
|
|
1939
|
-
if (current) {
|
|
1940
|
-
cancelFibers(current.children);
|
|
1941
|
-
current.root = null;
|
|
1942
|
-
}
|
|
1943
|
-
return new Fiber(node, parent);
|
|
1931
|
+
/**
|
|
1932
|
+
* Creates a function that will observe the key that is passed to it when called
|
|
1933
|
+
* and delegates to the underlying method.
|
|
1934
|
+
*
|
|
1935
|
+
* @param methodName name of the method to delegate to
|
|
1936
|
+
* @param target @see reactive
|
|
1937
|
+
* @param callback @see reactive
|
|
1938
|
+
*/
|
|
1939
|
+
function makeKeyObserver(methodName, target, callback) {
|
|
1940
|
+
return (key) => {
|
|
1941
|
+
key = toRaw(key);
|
|
1942
|
+
observeTargetKey(target, key, callback);
|
|
1943
|
+
return possiblyReactive(target[methodName](key), callback);
|
|
1944
|
+
};
|
|
1944
1945
|
}
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
current.appliedToDom = false;
|
|
1946
|
+
/**
|
|
1947
|
+
* Creates an iterable that will delegate to the underlying iteration method and
|
|
1948
|
+
* observe keys as necessary.
|
|
1949
|
+
*
|
|
1950
|
+
* @param methodName name of the method to delegate to
|
|
1951
|
+
* @param target @see reactive
|
|
1952
|
+
* @param callback @see reactive
|
|
1953
|
+
*/
|
|
1954
|
+
function makeIteratorObserver(methodName, target, callback) {
|
|
1955
|
+
return function* () {
|
|
1956
|
+
observeTargetKey(target, KEYCHANGES, callback);
|
|
1957
|
+
const keys = target.keys();
|
|
1958
|
+
for (const item of target[methodName]()) {
|
|
1959
|
+
const key = keys.next().value;
|
|
1960
|
+
observeTargetKey(target, key, callback);
|
|
1961
|
+
yield possiblyReactive(item, callback);
|
|
1962
1962
|
}
|
|
1963
|
-
|
|
1964
|
-
}
|
|
1965
|
-
const fiber = new RootFiber(node, null);
|
|
1966
|
-
if (node.willPatch.length) {
|
|
1967
|
-
fiber.willPatch.push(fiber);
|
|
1968
|
-
}
|
|
1969
|
-
if (node.patched.length) {
|
|
1970
|
-
fiber.patched.push(fiber);
|
|
1971
|
-
}
|
|
1972
|
-
return fiber;
|
|
1963
|
+
};
|
|
1973
1964
|
}
|
|
1974
|
-
|
|
1975
|
-
|
|
1965
|
+
/**
|
|
1966
|
+
* Creates a forEach function that will delegate to forEach on the underlying
|
|
1967
|
+
* collection while observing key changes, and keys as they're iterated over,
|
|
1968
|
+
* and making the passed keys/values reactive.
|
|
1969
|
+
*
|
|
1970
|
+
* @param target @see reactive
|
|
1971
|
+
* @param callback @see reactive
|
|
1972
|
+
*/
|
|
1973
|
+
function makeForEachObserver(target, callback) {
|
|
1974
|
+
return function forEach(forEachCb, thisArg) {
|
|
1975
|
+
observeTargetKey(target, KEYCHANGES, callback);
|
|
1976
|
+
target.forEach(function (val, key, targetObj) {
|
|
1977
|
+
observeTargetKey(target, key, callback);
|
|
1978
|
+
forEachCb.call(thisArg, possiblyReactive(val, callback), possiblyReactive(key, callback), possiblyReactive(targetObj, callback));
|
|
1979
|
+
}, thisArg);
|
|
1980
|
+
};
|
|
1976
1981
|
}
|
|
1977
1982
|
/**
|
|
1978
|
-
*
|
|
1983
|
+
* Creates a function that will delegate to an underlying method, and check if
|
|
1984
|
+
* that method has modified the presence or value of a key, and notify the
|
|
1985
|
+
* reactives appropriately.
|
|
1986
|
+
*
|
|
1987
|
+
* @param setterName name of the method to delegate to
|
|
1988
|
+
* @param getterName name of the method which should be used to retrieve the
|
|
1989
|
+
* value before calling the delegate method for comparison purposes
|
|
1990
|
+
* @param target @see reactive
|
|
1979
1991
|
*/
|
|
1980
|
-
function
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
if (fiber.bdom) {
|
|
1990
|
-
// if fiber has been rendered, this means that the component props have
|
|
1991
|
-
// been updated. however, this fiber will not be patched to the dom, so
|
|
1992
|
-
// it could happen that the next render compare the current props with
|
|
1993
|
-
// the same props, and skip the render completely. With the next line,
|
|
1994
|
-
// we kindly request the component code to force a render, so it works as
|
|
1995
|
-
// expected.
|
|
1996
|
-
node.forceNextRender = true;
|
|
1992
|
+
function delegateAndNotify(setterName, getterName, target) {
|
|
1993
|
+
return (key, value) => {
|
|
1994
|
+
key = toRaw(key);
|
|
1995
|
+
const hadKey = target.has(key);
|
|
1996
|
+
const originalValue = target[getterName](key);
|
|
1997
|
+
const ret = target[setterName](key, value);
|
|
1998
|
+
const hasKey = target.has(key);
|
|
1999
|
+
if (hadKey !== hasKey) {
|
|
2000
|
+
notifyReactives(target, KEYCHANGES);
|
|
1997
2001
|
}
|
|
1998
|
-
|
|
1999
|
-
|
|
2002
|
+
if (originalValue !== value) {
|
|
2003
|
+
notifyReactives(target, key);
|
|
2000
2004
|
}
|
|
2001
|
-
|
|
2002
|
-
}
|
|
2003
|
-
return result;
|
|
2005
|
+
return ret;
|
|
2006
|
+
};
|
|
2004
2007
|
}
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
this.root = root;
|
|
2019
|
-
parent.children.push(this);
|
|
2020
|
-
}
|
|
2021
|
-
else {
|
|
2022
|
-
this.root = this;
|
|
2023
|
-
}
|
|
2024
|
-
}
|
|
2025
|
-
render() {
|
|
2026
|
-
// if some parent has a fiber => register in followup
|
|
2027
|
-
let prev = this.root.node;
|
|
2028
|
-
let scheduler = prev.app.scheduler;
|
|
2029
|
-
let current = prev.parent;
|
|
2030
|
-
while (current) {
|
|
2031
|
-
if (current.fiber) {
|
|
2032
|
-
let root = current.fiber.root;
|
|
2033
|
-
if (root.counter === 0 && prev.parentKey in current.fiber.childrenMap) {
|
|
2034
|
-
current = root.node;
|
|
2035
|
-
}
|
|
2036
|
-
else {
|
|
2037
|
-
scheduler.delayedRenders.push(this);
|
|
2038
|
-
return;
|
|
2039
|
-
}
|
|
2040
|
-
}
|
|
2041
|
-
prev = current;
|
|
2042
|
-
current = current.parent;
|
|
2008
|
+
/**
|
|
2009
|
+
* Creates a function that will clear the underlying collection and notify that
|
|
2010
|
+
* the keys of the collection have changed.
|
|
2011
|
+
*
|
|
2012
|
+
* @param target @see reactive
|
|
2013
|
+
*/
|
|
2014
|
+
function makeClearNotifier(target) {
|
|
2015
|
+
return () => {
|
|
2016
|
+
const allKeys = [...target.keys()];
|
|
2017
|
+
target.clear();
|
|
2018
|
+
notifyReactives(target, KEYCHANGES);
|
|
2019
|
+
for (const key of allKeys) {
|
|
2020
|
+
notifyReactives(target, key);
|
|
2043
2021
|
}
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2022
|
+
};
|
|
2023
|
+
}
|
|
2024
|
+
/**
|
|
2025
|
+
* Maps raw type of an object to an object containing functions that can be used
|
|
2026
|
+
* to build an appropritate proxy handler for that raw type. Eg: when making a
|
|
2027
|
+
* reactive set, calling the has method should mark the key that is being
|
|
2028
|
+
* retrieved as observed, and calling the add or delete method should notify the
|
|
2029
|
+
* reactives that the key which is being added or deleted has been modified.
|
|
2030
|
+
*/
|
|
2031
|
+
const rawTypeToFuncHandlers = {
|
|
2032
|
+
Set: (target, callback) => ({
|
|
2033
|
+
has: makeKeyObserver("has", target, callback),
|
|
2034
|
+
add: delegateAndNotify("add", "has", target),
|
|
2035
|
+
delete: delegateAndNotify("delete", "has", target),
|
|
2036
|
+
keys: makeIteratorObserver("keys", target, callback),
|
|
2037
|
+
values: makeIteratorObserver("values", target, callback),
|
|
2038
|
+
entries: makeIteratorObserver("entries", target, callback),
|
|
2039
|
+
[Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
|
|
2040
|
+
forEach: makeForEachObserver(target, callback),
|
|
2041
|
+
clear: makeClearNotifier(target),
|
|
2042
|
+
get size() {
|
|
2043
|
+
observeTargetKey(target, KEYCHANGES, callback);
|
|
2044
|
+
return target.size;
|
|
2045
|
+
},
|
|
2046
|
+
}),
|
|
2047
|
+
Map: (target, callback) => ({
|
|
2048
|
+
has: makeKeyObserver("has", target, callback),
|
|
2049
|
+
get: makeKeyObserver("get", target, callback),
|
|
2050
|
+
set: delegateAndNotify("set", "get", target),
|
|
2051
|
+
delete: delegateAndNotify("delete", "has", target),
|
|
2052
|
+
keys: makeIteratorObserver("keys", target, callback),
|
|
2053
|
+
values: makeIteratorObserver("values", target, callback),
|
|
2054
|
+
entries: makeIteratorObserver("entries", target, callback),
|
|
2055
|
+
[Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
|
|
2056
|
+
forEach: makeForEachObserver(target, callback),
|
|
2057
|
+
clear: makeClearNotifier(target),
|
|
2058
|
+
get size() {
|
|
2059
|
+
observeTargetKey(target, KEYCHANGES, callback);
|
|
2060
|
+
return target.size;
|
|
2061
|
+
},
|
|
2062
|
+
}),
|
|
2063
|
+
WeakMap: (target, callback) => ({
|
|
2064
|
+
has: makeKeyObserver("has", target, callback),
|
|
2065
|
+
get: makeKeyObserver("get", target, callback),
|
|
2066
|
+
set: delegateAndNotify("set", "get", target),
|
|
2067
|
+
delete: delegateAndNotify("delete", "has", target),
|
|
2068
|
+
}),
|
|
2069
|
+
};
|
|
2070
|
+
/**
|
|
2071
|
+
* Creates a proxy handler for collections (Set/Map/WeakMap)
|
|
2072
|
+
*
|
|
2073
|
+
* @param callback @see reactive
|
|
2074
|
+
* @param target @see reactive
|
|
2075
|
+
* @returns a proxy handler object
|
|
2076
|
+
*/
|
|
2077
|
+
function collectionsProxyHandler(target, callback, targetRawType) {
|
|
2078
|
+
// TODO: if performance is an issue we can create the special handlers lazily when each
|
|
2079
|
+
// property is read.
|
|
2080
|
+
const specialHandlers = rawTypeToFuncHandlers[targetRawType](target, callback);
|
|
2081
|
+
return Object.assign(basicProxyHandler(callback), {
|
|
2082
|
+
get(target, key) {
|
|
2083
|
+
if (key === TARGET) {
|
|
2084
|
+
return target;
|
|
2054
2085
|
}
|
|
2055
|
-
|
|
2056
|
-
|
|
2086
|
+
if (objectHasOwnProperty.call(specialHandlers, key)) {
|
|
2087
|
+
return specialHandlers[key];
|
|
2057
2088
|
}
|
|
2058
|
-
|
|
2089
|
+
observeTargetKey(target, key, callback);
|
|
2090
|
+
return possiblyReactive(target[key], callback);
|
|
2091
|
+
},
|
|
2092
|
+
});
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
/**
|
|
2096
|
+
* Creates a batched version of a callback so that all calls to it in the same
|
|
2097
|
+
* microtick will only call the original callback once.
|
|
2098
|
+
*
|
|
2099
|
+
* @param callback the callback to batch
|
|
2100
|
+
* @returns a batched version of the original callback
|
|
2101
|
+
*/
|
|
2102
|
+
function batched(callback) {
|
|
2103
|
+
let called = false;
|
|
2104
|
+
return async () => {
|
|
2105
|
+
// This await blocks all calls to the callback here, then releases them sequentially
|
|
2106
|
+
// in the next microtick. This line decides the granularity of the batch.
|
|
2107
|
+
await Promise.resolve();
|
|
2108
|
+
if (!called) {
|
|
2109
|
+
called = true;
|
|
2110
|
+
// wait for all calls in this microtick to fall through before resetting "called"
|
|
2111
|
+
// so that only the first call to the batched function calls the original callback.
|
|
2112
|
+
// Schedule this before calling the callback so that calls to the batched function
|
|
2113
|
+
// within the callback will proceed only after resetting called to false, and have
|
|
2114
|
+
// a chance to execute the callback again
|
|
2115
|
+
Promise.resolve().then(() => (called = false));
|
|
2116
|
+
callback();
|
|
2059
2117
|
}
|
|
2060
|
-
}
|
|
2118
|
+
};
|
|
2061
2119
|
}
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
this.counter = 1;
|
|
2066
|
-
// only add stuff in this if they have registered some hooks
|
|
2067
|
-
this.willPatch = [];
|
|
2068
|
-
this.patched = [];
|
|
2069
|
-
this.mounted = [];
|
|
2070
|
-
// A fiber is typically locked when it is completing and the patch has not, or is being applied.
|
|
2071
|
-
// i.e.: render triggered in onWillUnmount or in willPatch will be delayed
|
|
2072
|
-
this.locked = false;
|
|
2073
|
-
}
|
|
2074
|
-
complete() {
|
|
2075
|
-
const node = this.node;
|
|
2076
|
-
this.locked = true;
|
|
2077
|
-
let current = undefined;
|
|
2078
|
-
try {
|
|
2079
|
-
// Step 1: calling all willPatch lifecycle hooks
|
|
2080
|
-
for (current of this.willPatch) {
|
|
2081
|
-
// because of the asynchronous nature of the rendering, some parts of the
|
|
2082
|
-
// UI may have been rendered, then deleted in a followup rendering, and we
|
|
2083
|
-
// do not want to call onWillPatch in that case.
|
|
2084
|
-
let node = current.node;
|
|
2085
|
-
if (node.fiber === current) {
|
|
2086
|
-
const component = node.component;
|
|
2087
|
-
for (let cb of node.willPatch) {
|
|
2088
|
-
cb.call(component);
|
|
2089
|
-
}
|
|
2090
|
-
}
|
|
2091
|
-
}
|
|
2092
|
-
current = undefined;
|
|
2093
|
-
// Step 2: patching the dom
|
|
2094
|
-
node._patch();
|
|
2095
|
-
this.locked = false;
|
|
2096
|
-
// Step 4: calling all mounted lifecycle hooks
|
|
2097
|
-
let mountedFibers = this.mounted;
|
|
2098
|
-
while ((current = mountedFibers.pop())) {
|
|
2099
|
-
current = current;
|
|
2100
|
-
if (current.appliedToDom) {
|
|
2101
|
-
for (let cb of current.node.mounted) {
|
|
2102
|
-
cb();
|
|
2103
|
-
}
|
|
2104
|
-
}
|
|
2105
|
-
}
|
|
2106
|
-
// Step 5: calling all patched hooks
|
|
2107
|
-
let patchedFibers = this.patched;
|
|
2108
|
-
while ((current = patchedFibers.pop())) {
|
|
2109
|
-
current = current;
|
|
2110
|
-
if (current.appliedToDom) {
|
|
2111
|
-
for (let cb of current.node.patched) {
|
|
2112
|
-
cb();
|
|
2113
|
-
}
|
|
2114
|
-
}
|
|
2115
|
-
}
|
|
2116
|
-
}
|
|
2117
|
-
catch (e) {
|
|
2118
|
-
this.locked = false;
|
|
2119
|
-
handleError({ fiber: current || this, error: e });
|
|
2120
|
-
}
|
|
2120
|
+
function validateTarget(target) {
|
|
2121
|
+
if (!(target instanceof HTMLElement)) {
|
|
2122
|
+
throw new Error("Cannot mount component: the target is not a valid DOM element");
|
|
2121
2123
|
}
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
if (newValue === 0) {
|
|
2125
|
-
this.node.app.scheduler.flush();
|
|
2126
|
-
}
|
|
2124
|
+
if (!document.body.contains(target)) {
|
|
2125
|
+
throw new Error("Cannot mount a component on a detached dom node");
|
|
2127
2126
|
}
|
|
2128
2127
|
}
|
|
2129
|
-
class
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
this.target = target;
|
|
2133
|
-
this.position = options.position || "last-child";
|
|
2128
|
+
class EventBus extends EventTarget {
|
|
2129
|
+
trigger(name, payload) {
|
|
2130
|
+
this.dispatchEvent(new CustomEvent(name, { detail: payload }));
|
|
2134
2131
|
}
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
node.app.constructor.validateTarget(this.target);
|
|
2141
|
-
if (node.bdom) {
|
|
2142
|
-
// this is a complicated situation: if we mount a fiber with an existing
|
|
2143
|
-
// bdom, this means that this same fiber was already completed, mounted,
|
|
2144
|
-
// but a crash occurred in some mounted hook. Then, it was handled and
|
|
2145
|
-
// the new rendering is being applied.
|
|
2146
|
-
node.updateDom();
|
|
2147
|
-
}
|
|
2148
|
-
else {
|
|
2149
|
-
node.bdom = this.bdom;
|
|
2150
|
-
if (this.position === "last-child" || this.target.childNodes.length === 0) {
|
|
2151
|
-
mount$1(node.bdom, this.target);
|
|
2152
|
-
}
|
|
2153
|
-
else {
|
|
2154
|
-
const firstChild = this.target.childNodes[0];
|
|
2155
|
-
mount$1(node.bdom, this.target, firstChild);
|
|
2156
|
-
}
|
|
2157
|
-
}
|
|
2158
|
-
// unregistering the fiber before mounted since it can do another render
|
|
2159
|
-
// and that the current rendering is obviously completed
|
|
2160
|
-
node.fiber = null;
|
|
2161
|
-
node.status = 1 /* MOUNTED */;
|
|
2162
|
-
this.appliedToDom = true;
|
|
2163
|
-
let mountedFibers = this.mounted;
|
|
2164
|
-
while ((current = mountedFibers.pop())) {
|
|
2165
|
-
if (current.appliedToDom) {
|
|
2166
|
-
for (let cb of current.node.mounted) {
|
|
2167
|
-
cb();
|
|
2168
|
-
}
|
|
2169
|
-
}
|
|
2170
|
-
}
|
|
2132
|
+
}
|
|
2133
|
+
function whenReady(fn) {
|
|
2134
|
+
return new Promise(function (resolve) {
|
|
2135
|
+
if (document.readyState !== "loading") {
|
|
2136
|
+
resolve(true);
|
|
2171
2137
|
}
|
|
2172
|
-
|
|
2173
|
-
|
|
2138
|
+
else {
|
|
2139
|
+
document.addEventListener("DOMContentLoaded", resolve, false);
|
|
2174
2140
|
}
|
|
2141
|
+
}).then(fn || function () { });
|
|
2142
|
+
}
|
|
2143
|
+
async function loadFile(url) {
|
|
2144
|
+
const result = await fetch(url);
|
|
2145
|
+
if (!result.ok) {
|
|
2146
|
+
throw new Error("Error while fetching xml templates");
|
|
2175
2147
|
}
|
|
2148
|
+
return await result.text();
|
|
2149
|
+
}
|
|
2150
|
+
/*
|
|
2151
|
+
* This class just transports the fact that a string is safe
|
|
2152
|
+
* to be injected as HTML. Overriding a JS primitive is quite painful though
|
|
2153
|
+
* so we need to redfine toString and valueOf.
|
|
2154
|
+
*/
|
|
2155
|
+
class Markup extends String {
|
|
2156
|
+
}
|
|
2157
|
+
/*
|
|
2158
|
+
* Marks a value as safe, that is, a value that can be injected as HTML directly.
|
|
2159
|
+
* It should be used to wrap the value passed to a t-out directive to allow a raw rendering.
|
|
2160
|
+
*/
|
|
2161
|
+
function markup(value) {
|
|
2162
|
+
return new Markup(value);
|
|
2176
2163
|
}
|
|
2177
2164
|
|
|
2178
2165
|
let currentNode = null;
|
|
@@ -2189,13 +2176,11 @@
|
|
|
2189
2176
|
* Apply default props (only top level).
|
|
2190
2177
|
*/
|
|
2191
2178
|
function applyDefaultProps(props, defaultProps) {
|
|
2192
|
-
const result = Object.assign({}, props);
|
|
2193
2179
|
for (let propName in defaultProps) {
|
|
2194
2180
|
if (props[propName] === undefined) {
|
|
2195
|
-
|
|
2181
|
+
props[propName] = defaultProps[propName];
|
|
2196
2182
|
}
|
|
2197
2183
|
}
|
|
2198
|
-
return result;
|
|
2199
2184
|
}
|
|
2200
2185
|
// -----------------------------------------------------------------------------
|
|
2201
2186
|
// Integration with reactivity system (useState)
|
|
@@ -2222,61 +2207,6 @@
|
|
|
2222
2207
|
}
|
|
2223
2208
|
return reactive(state, render);
|
|
2224
2209
|
}
|
|
2225
|
-
function arePropsDifferent(props1, props2) {
|
|
2226
|
-
for (let k in props1) {
|
|
2227
|
-
const prop1 = props1[k] && typeof props1[k] === "object" ? toRaw(props1[k]) : props1[k];
|
|
2228
|
-
const prop2 = props2[k] && typeof props2[k] === "object" ? toRaw(props2[k]) : props2[k];
|
|
2229
|
-
if (prop1 !== prop2) {
|
|
2230
|
-
return true;
|
|
2231
|
-
}
|
|
2232
|
-
}
|
|
2233
|
-
return Object.keys(props1).length !== Object.keys(props2).length;
|
|
2234
|
-
}
|
|
2235
|
-
function component(name, props, key, ctx, parent) {
|
|
2236
|
-
let node = ctx.children[key];
|
|
2237
|
-
let isDynamic = typeof name !== "string";
|
|
2238
|
-
if (node && node.status === 2 /* DESTROYED */) {
|
|
2239
|
-
node = undefined;
|
|
2240
|
-
}
|
|
2241
|
-
if (isDynamic && node && node.component.constructor !== name) {
|
|
2242
|
-
node = undefined;
|
|
2243
|
-
}
|
|
2244
|
-
const parentFiber = ctx.fiber;
|
|
2245
|
-
if (node) {
|
|
2246
|
-
let shouldRender = node.forceNextRender;
|
|
2247
|
-
if (shouldRender) {
|
|
2248
|
-
node.forceNextRender = false;
|
|
2249
|
-
}
|
|
2250
|
-
else {
|
|
2251
|
-
const currentProps = node.props;
|
|
2252
|
-
shouldRender = parentFiber.deep || arePropsDifferent(currentProps, props);
|
|
2253
|
-
}
|
|
2254
|
-
if (shouldRender) {
|
|
2255
|
-
node.updateAndRender(props, parentFiber);
|
|
2256
|
-
}
|
|
2257
|
-
}
|
|
2258
|
-
else {
|
|
2259
|
-
// new component
|
|
2260
|
-
let C;
|
|
2261
|
-
if (isDynamic) {
|
|
2262
|
-
C = name;
|
|
2263
|
-
}
|
|
2264
|
-
else {
|
|
2265
|
-
C = parent.constructor.components[name];
|
|
2266
|
-
if (!C) {
|
|
2267
|
-
throw new Error(`Cannot find the definition of component "${name}"`);
|
|
2268
|
-
}
|
|
2269
|
-
else if (!(C.prototype instanceof Component)) {
|
|
2270
|
-
throw new Error(`"${name}" is not a Component. It must inherit from the Component class`);
|
|
2271
|
-
}
|
|
2272
|
-
}
|
|
2273
|
-
node = new ComponentNode(C, props, ctx.app, ctx, key);
|
|
2274
|
-
ctx.children[key] = node;
|
|
2275
|
-
node.initiateRender(new Fiber(node, parentFiber));
|
|
2276
|
-
}
|
|
2277
|
-
parentFiber.childrenMap[key] = node;
|
|
2278
|
-
return node;
|
|
2279
|
-
}
|
|
2280
2210
|
class ComponentNode {
|
|
2281
2211
|
constructor(C, props, app, parent, parentKey) {
|
|
2282
2212
|
this.fiber = null;
|
|
@@ -2299,8 +2229,9 @@
|
|
|
2299
2229
|
this.parentKey = parentKey;
|
|
2300
2230
|
this.level = parent ? parent.level + 1 : 0;
|
|
2301
2231
|
const defaultProps = C.defaultProps;
|
|
2232
|
+
props = Object.assign({}, props);
|
|
2302
2233
|
if (defaultProps) {
|
|
2303
|
-
|
|
2234
|
+
applyDefaultProps(props, defaultProps);
|
|
2304
2235
|
}
|
|
2305
2236
|
const env = (parent && parent.childEnv) || app.env;
|
|
2306
2237
|
this.childEnv = env;
|
|
@@ -2412,13 +2343,14 @@
|
|
|
2412
2343
|
}
|
|
2413
2344
|
async updateAndRender(props, parentFiber) {
|
|
2414
2345
|
const rawProps = props;
|
|
2346
|
+
props = Object.assign({}, props);
|
|
2415
2347
|
// update
|
|
2416
2348
|
const fiber = makeChildFiber(this, parentFiber);
|
|
2417
2349
|
this.fiber = fiber;
|
|
2418
2350
|
const component = this.component;
|
|
2419
2351
|
const defaultProps = component.constructor.defaultProps;
|
|
2420
2352
|
if (defaultProps) {
|
|
2421
|
-
|
|
2353
|
+
applyDefaultProps(props, defaultProps);
|
|
2422
2354
|
}
|
|
2423
2355
|
currentNode = this;
|
|
2424
2356
|
for (const key in props) {
|
|
@@ -2497,10 +2429,15 @@
|
|
|
2497
2429
|
}
|
|
2498
2430
|
}
|
|
2499
2431
|
_patch() {
|
|
2500
|
-
|
|
2501
|
-
this.children
|
|
2502
|
-
|
|
2503
|
-
|
|
2432
|
+
let hasChildren = false;
|
|
2433
|
+
for (let _k in this.children) {
|
|
2434
|
+
hasChildren = true;
|
|
2435
|
+
break;
|
|
2436
|
+
}
|
|
2437
|
+
const fiber = this.fiber;
|
|
2438
|
+
this.children = fiber.childrenMap;
|
|
2439
|
+
this.bdom.patch(fiber.bdom, hasChildren);
|
|
2440
|
+
fiber.appliedToDom = true;
|
|
2504
2441
|
this.fiber = null;
|
|
2505
2442
|
}
|
|
2506
2443
|
beforeRemove() {
|
|
@@ -2628,6 +2565,19 @@
|
|
|
2628
2565
|
handlers.push(callback.bind(node.component));
|
|
2629
2566
|
}
|
|
2630
2567
|
|
|
2568
|
+
class Component {
|
|
2569
|
+
constructor(props, env, node) {
|
|
2570
|
+
this.props = props;
|
|
2571
|
+
this.env = env;
|
|
2572
|
+
this.__owl__ = node;
|
|
2573
|
+
}
|
|
2574
|
+
setup() { }
|
|
2575
|
+
render(deep = false) {
|
|
2576
|
+
this.__owl__.render(deep === true);
|
|
2577
|
+
}
|
|
2578
|
+
}
|
|
2579
|
+
Component.template = "";
|
|
2580
|
+
|
|
2631
2581
|
const VText = text("").constructor;
|
|
2632
2582
|
class VPortal extends VText {
|
|
2633
2583
|
constructor(selector, realBDom) {
|
|
@@ -2706,6 +2656,7 @@
|
|
|
2706
2656
|
// -----------------------------------------------------------------------------
|
|
2707
2657
|
const isUnionType = (t) => Array.isArray(t);
|
|
2708
2658
|
const isBaseType = (t) => typeof t !== "object";
|
|
2659
|
+
const isValueType = (t) => typeof t === "object" && t && "value" in t;
|
|
2709
2660
|
function isOptional(t) {
|
|
2710
2661
|
return typeof t === "object" && "optional" in t ? t.optional || false : false;
|
|
2711
2662
|
}
|
|
@@ -2719,6 +2670,9 @@
|
|
|
2719
2670
|
else if (isUnionType(info)) {
|
|
2720
2671
|
return info.map(describe).join(" or ");
|
|
2721
2672
|
}
|
|
2673
|
+
else if (isValueType(info)) {
|
|
2674
|
+
return String(info.value);
|
|
2675
|
+
}
|
|
2722
2676
|
if ("element" in info) {
|
|
2723
2677
|
return `list of ${describe({ type: info.element, optional: false })}s`;
|
|
2724
2678
|
}
|
|
@@ -2804,6 +2758,9 @@
|
|
|
2804
2758
|
else if (isBaseType(descr)) {
|
|
2805
2759
|
return validateBaseType(key, value, descr);
|
|
2806
2760
|
}
|
|
2761
|
+
else if (isValueType(descr)) {
|
|
2762
|
+
return value === descr.value ? null : `'${key}' is not equal to '${descr.value}'`;
|
|
2763
|
+
}
|
|
2807
2764
|
else if (isUnionType(descr)) {
|
|
2808
2765
|
let validDescr = descr.find((p) => !validateType(key, value, p));
|
|
2809
2766
|
return validDescr ? null : `'${key}' is not a ${describe(descr)}`;
|
|
@@ -2832,6 +2789,7 @@
|
|
|
2832
2789
|
return result;
|
|
2833
2790
|
}
|
|
2834
2791
|
|
|
2792
|
+
const ObjectCreate = Object.create;
|
|
2835
2793
|
/**
|
|
2836
2794
|
* This file contains utility functions that will be injected in each template,
|
|
2837
2795
|
* to perform various useful tasks in the compiled code.
|
|
@@ -2843,7 +2801,7 @@
|
|
|
2843
2801
|
key = key + "__slot_" + name;
|
|
2844
2802
|
const slots = ctx.props.slots || {};
|
|
2845
2803
|
const { __render, __ctx, __scope } = slots[name] || {};
|
|
2846
|
-
const slotScope =
|
|
2804
|
+
const slotScope = ObjectCreate(__ctx || {});
|
|
2847
2805
|
if (__scope) {
|
|
2848
2806
|
slotScope[__scope] = extra;
|
|
2849
2807
|
}
|
|
@@ -2863,7 +2821,7 @@
|
|
|
2863
2821
|
}
|
|
2864
2822
|
function capture(ctx) {
|
|
2865
2823
|
const component = ctx.__owl__.component;
|
|
2866
|
-
const result =
|
|
2824
|
+
const result = ObjectCreate(component);
|
|
2867
2825
|
for (let k in ctx) {
|
|
2868
2826
|
result[k] = ctx[k];
|
|
2869
2827
|
}
|
|
@@ -2968,17 +2926,19 @@
|
|
|
2968
2926
|
return toggler(safeKey, block);
|
|
2969
2927
|
}
|
|
2970
2928
|
let boundFunctions = new WeakMap();
|
|
2929
|
+
const WeakMapGet = WeakMap.prototype.get;
|
|
2930
|
+
const WeakMapSet = WeakMap.prototype.set;
|
|
2971
2931
|
function bind(ctx, fn) {
|
|
2972
2932
|
let component = ctx.__owl__.component;
|
|
2973
|
-
let boundFnMap =
|
|
2933
|
+
let boundFnMap = WeakMapGet.call(boundFunctions, component);
|
|
2974
2934
|
if (!boundFnMap) {
|
|
2975
2935
|
boundFnMap = new WeakMap();
|
|
2976
|
-
|
|
2936
|
+
WeakMapSet.call(boundFunctions, component, boundFnMap);
|
|
2977
2937
|
}
|
|
2978
|
-
let boundFn =
|
|
2938
|
+
let boundFn = WeakMapGet.call(boundFnMap, fn);
|
|
2979
2939
|
if (!boundFn) {
|
|
2980
2940
|
boundFn = fn.bind(component);
|
|
2981
|
-
|
|
2941
|
+
WeakMapSet.call(boundFnMap, fn, boundFn);
|
|
2982
2942
|
}
|
|
2983
2943
|
return boundFn;
|
|
2984
2944
|
}
|
|
@@ -3054,7 +3014,7 @@
|
|
|
3054
3014
|
markRaw,
|
|
3055
3015
|
};
|
|
3056
3016
|
|
|
3057
|
-
const bdom = { text, createBlock, list, multi, html, toggler,
|
|
3017
|
+
const bdom = { text, createBlock, list, multi, html, toggler, comment };
|
|
3058
3018
|
function parseXML$1(xml) {
|
|
3059
3019
|
const parser = new DOMParser();
|
|
3060
3020
|
const doc = parser.parseFromString(xml, "text/xml");
|
|
@@ -3650,9 +3610,7 @@
|
|
|
3650
3610
|
tKeyExpr: null,
|
|
3651
3611
|
});
|
|
3652
3612
|
// define blocks and utility functions
|
|
3653
|
-
let mainCode = [
|
|
3654
|
-
` let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;`,
|
|
3655
|
-
];
|
|
3613
|
+
let mainCode = [` let { text, createBlock, list, multi, html, toggler, comment } = bdom;`];
|
|
3656
3614
|
if (this.helpers.size) {
|
|
3657
3615
|
mainCode.push(`let { ${[...this.helpers].join(", ")} } = helpers;`);
|
|
3658
3616
|
}
|
|
@@ -3668,6 +3626,7 @@
|
|
|
3668
3626
|
for (let block of this.blocks) {
|
|
3669
3627
|
if (block.dom) {
|
|
3670
3628
|
let xmlString = block.asXmlString();
|
|
3629
|
+
xmlString = xmlString.replace(/`/g, "\\`");
|
|
3671
3630
|
if (block.dynamicTagName) {
|
|
3672
3631
|
xmlString = xmlString.replace(/^<\w+/, `<\${tag || '${block.dom.nodeName}'}`);
|
|
3673
3632
|
xmlString = xmlString.replace(/\w+>$/, `\${tag || '${block.dom.nodeName}'}>`);
|
|
@@ -4505,8 +4464,12 @@
|
|
|
4505
4464
|
if (ctx.tKeyExpr) {
|
|
4506
4465
|
keyArg = `${ctx.tKeyExpr} + ${keyArg}`;
|
|
4507
4466
|
}
|
|
4508
|
-
|
|
4509
|
-
|
|
4467
|
+
let id = generateId("comp");
|
|
4468
|
+
this.staticDefs.push({
|
|
4469
|
+
id,
|
|
4470
|
+
expr: `app.createComponent(${ast.isDynamic ? null : expr}, ${!ast.isDynamic}, ${!!ast.slots}, ${!!ast.dynamicProps}, ${!ast.props && !ast.dynamicProps})`,
|
|
4471
|
+
});
|
|
4472
|
+
let blockExpr = `${id}(${propString}, ${keyArg}, node, ctx, ${ast.isDynamic ? expr : null})`;
|
|
4510
4473
|
if (ast.isDynamic) {
|
|
4511
4474
|
blockExpr = `toggler(${expr}, ${blockExpr})`;
|
|
4512
4475
|
}
|
|
@@ -4591,10 +4554,15 @@
|
|
|
4591
4554
|
if (this.target.loopLevel || !this.hasSafeContext) {
|
|
4592
4555
|
ctxStr = generateId("ctx");
|
|
4593
4556
|
this.helpers.add("capture");
|
|
4594
|
-
this.define(ctxStr, `capture(ctx)
|
|
4557
|
+
this.define(ctxStr, `capture(ctx)`);
|
|
4595
4558
|
}
|
|
4559
|
+
let id = generateId("comp");
|
|
4560
|
+
this.staticDefs.push({
|
|
4561
|
+
id,
|
|
4562
|
+
expr: `app.createComponent(null, false, true, false, false)`,
|
|
4563
|
+
});
|
|
4596
4564
|
const target = compileExpr(ast.target);
|
|
4597
|
-
const blockString =
|
|
4565
|
+
const blockString = `${id}({target: ${target},slots: {'default': {__render: ${name}, __ctx: ${ctxStr}}}}, key + \`${key}\`, node, ctx, Portal)`;
|
|
4598
4566
|
if (block) {
|
|
4599
4567
|
this.insertAnchor(block);
|
|
4600
4568
|
}
|
|
@@ -5537,6 +5505,56 @@ See https://github.com/odoo/owl/blob/${hash}/doc/reference/app.md#configuration
|
|
|
5537
5505
|
this.root.destroy();
|
|
5538
5506
|
}
|
|
5539
5507
|
}
|
|
5508
|
+
createComponent(name, isStatic, hasSlotsProp, hasDynamicPropList, hasNoProp) {
|
|
5509
|
+
const isDynamic = !isStatic;
|
|
5510
|
+
function _arePropsDifferent(props1, props2) {
|
|
5511
|
+
for (let k in props1) {
|
|
5512
|
+
if (props1[k] !== props2[k]) {
|
|
5513
|
+
return true;
|
|
5514
|
+
}
|
|
5515
|
+
}
|
|
5516
|
+
return hasDynamicPropList && Object.keys(props1).length !== Object.keys(props2).length;
|
|
5517
|
+
}
|
|
5518
|
+
const arePropsDifferent = hasSlotsProp
|
|
5519
|
+
? (_1, _2) => true
|
|
5520
|
+
: hasNoProp
|
|
5521
|
+
? (_1, _2) => false
|
|
5522
|
+
: _arePropsDifferent;
|
|
5523
|
+
const updateAndRender = ComponentNode.prototype.updateAndRender;
|
|
5524
|
+
const initiateRender = ComponentNode.prototype.initiateRender;
|
|
5525
|
+
return (props, key, ctx, parent, C) => {
|
|
5526
|
+
let children = ctx.children;
|
|
5527
|
+
let node = children[key];
|
|
5528
|
+
if (node &&
|
|
5529
|
+
(node.status === 2 /* DESTROYED */ || (isDynamic && node.component.constructor !== C))) {
|
|
5530
|
+
node = undefined;
|
|
5531
|
+
}
|
|
5532
|
+
const parentFiber = ctx.fiber;
|
|
5533
|
+
if (node) {
|
|
5534
|
+
if (arePropsDifferent(node.props, props) || parentFiber.deep || node.forceNextRender) {
|
|
5535
|
+
node.forceNextRender = false;
|
|
5536
|
+
updateAndRender.call(node, props, parentFiber);
|
|
5537
|
+
}
|
|
5538
|
+
}
|
|
5539
|
+
else {
|
|
5540
|
+
// new component
|
|
5541
|
+
if (isStatic) {
|
|
5542
|
+
C = parent.constructor.components[name];
|
|
5543
|
+
if (!C) {
|
|
5544
|
+
throw new Error(`Cannot find the definition of component "${name}"`);
|
|
5545
|
+
}
|
|
5546
|
+
else if (!(C.prototype instanceof Component)) {
|
|
5547
|
+
throw new Error(`"${name}" is not a Component. It must inherit from the Component class`);
|
|
5548
|
+
}
|
|
5549
|
+
}
|
|
5550
|
+
node = new ComponentNode(C, props, this, ctx, key);
|
|
5551
|
+
children[key] = node;
|
|
5552
|
+
initiateRender.call(node, new Fiber(node, parentFiber));
|
|
5553
|
+
}
|
|
5554
|
+
parentFiber.childrenMap[key] = node;
|
|
5555
|
+
return node;
|
|
5556
|
+
};
|
|
5557
|
+
}
|
|
5540
5558
|
}
|
|
5541
5559
|
App.validateTarget = validateTarget;
|
|
5542
5560
|
async function mount(C, target, config = {}) {
|
|
@@ -5719,9 +5737,9 @@ See https://github.com/odoo/owl/blob/${hash}/doc/reference/app.md#configuration
|
|
|
5719
5737
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
5720
5738
|
|
|
5721
5739
|
|
|
5722
|
-
__info__.version = '2.0.0-beta-
|
|
5723
|
-
__info__.date = '2022-06-
|
|
5724
|
-
__info__.hash = '
|
|
5740
|
+
__info__.version = '2.0.0-beta-10';
|
|
5741
|
+
__info__.date = '2022-06-22T08:33:26.205Z';
|
|
5742
|
+
__info__.hash = '2e03332';
|
|
5725
5743
|
__info__.url = 'https://github.com/odoo/owl';
|
|
5726
5744
|
|
|
5727
5745
|
|