@odoo/owl 2.0.0-alpha.3 → 2.0.0-beta-4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/owl.cjs.js +1810 -1400
- package/dist/owl.cjs.min.js +1 -1
- package/dist/owl.es.js +1811 -1400
- package/dist/owl.es.min.js +1 -1
- package/dist/owl.iife.js +1810 -1400
- package/dist/owl.iife.min.js +1 -1
- package/dist/types/app/template_helpers.d.ts +3 -2
- package/dist/types/app/template_set.d.ts +2 -9
- package/dist/types/blockdom/event_catcher.d.ts +7 -0
- package/dist/types/blockdom/events.d.ts +1 -0
- package/dist/types/blockdom/index.d.ts +1 -0
- package/dist/types/compiler/code_generator.d.ts +9 -6
- package/dist/types/compiler/parser.d.ts +25 -23
- package/dist/types/component/component_node.d.ts +5 -3
- package/dist/types/component/fibers.d.ts +5 -0
- package/dist/types/component/scheduler.d.ts +3 -4
- package/dist/types/index.d.ts +1 -3
- package/dist/types/reactivity.d.ts +4 -0
- package/dist/types/utils.d.ts +7 -0
- package/package.json +1 -1
- package/dist/types/memo.d.ts +0 -6
package/dist/owl.es.js
CHANGED
|
@@ -209,7 +209,8 @@ function updateClass(val, oldVal) {
|
|
|
209
209
|
}
|
|
210
210
|
function makePropSetter(name) {
|
|
211
211
|
return function setProp(value) {
|
|
212
|
-
|
|
212
|
+
// support 0, fallback to empty string for other falsy values
|
|
213
|
+
this[name] = value === 0 ? 0 : value || "";
|
|
213
214
|
};
|
|
214
215
|
}
|
|
215
216
|
function isProp(tag, key) {
|
|
@@ -263,10 +264,14 @@ function createElementHandler(evName, capture = false) {
|
|
|
263
264
|
this[eventKey] = data;
|
|
264
265
|
this.addEventListener(evName, listener, { capture });
|
|
265
266
|
}
|
|
267
|
+
function remove() {
|
|
268
|
+
delete this[eventKey];
|
|
269
|
+
this.removeEventListener(evName, listener, { capture });
|
|
270
|
+
}
|
|
266
271
|
function update(data) {
|
|
267
272
|
this[eventKey] = data;
|
|
268
273
|
}
|
|
269
|
-
return { setup, update };
|
|
274
|
+
return { setup, update, remove };
|
|
270
275
|
}
|
|
271
276
|
// Synthetic handler: a form of event delegation that allows placing only one
|
|
272
277
|
// listener per event type.
|
|
@@ -283,7 +288,10 @@ function createSyntheticHandler(evName, capture = false) {
|
|
|
283
288
|
_data[currentId] = data;
|
|
284
289
|
this[eventKey] = _data;
|
|
285
290
|
}
|
|
286
|
-
|
|
291
|
+
function remove() {
|
|
292
|
+
delete this[eventKey];
|
|
293
|
+
}
|
|
294
|
+
return { setup, update: setup, remove };
|
|
287
295
|
}
|
|
288
296
|
function nativeToSyntheticEvent(eventKey, event) {
|
|
289
297
|
let dom = event.target;
|
|
@@ -508,7 +516,7 @@ const characterDataProto = CharacterData.prototype;
|
|
|
508
516
|
const characterDataSetData = getDescriptor$1(characterDataProto, "data").set;
|
|
509
517
|
const nodeGetFirstChild = getDescriptor$1(nodeProto$2, "firstChild").get;
|
|
510
518
|
const nodeGetNextSibling = getDescriptor$1(nodeProto$2, "nextSibling").get;
|
|
511
|
-
const NO_OP
|
|
519
|
+
const NO_OP = () => { };
|
|
512
520
|
const cache$1 = {};
|
|
513
521
|
/**
|
|
514
522
|
* Compiling blocks is a multi-step process:
|
|
@@ -812,7 +820,7 @@ function updateCtx(ctx, tree) {
|
|
|
812
820
|
idx: info.idx,
|
|
813
821
|
refIdx: info.refIdx,
|
|
814
822
|
setData: makeRefSetter(index, ctx.refList),
|
|
815
|
-
updateData: NO_OP
|
|
823
|
+
updateData: NO_OP,
|
|
816
824
|
});
|
|
817
825
|
}
|
|
818
826
|
}
|
|
@@ -1278,6 +1286,75 @@ function html(str) {
|
|
|
1278
1286
|
return new VHtml(str);
|
|
1279
1287
|
}
|
|
1280
1288
|
|
|
1289
|
+
function createCatcher(eventsSpec) {
|
|
1290
|
+
let setupFns = [];
|
|
1291
|
+
let removeFns = [];
|
|
1292
|
+
for (let name in eventsSpec) {
|
|
1293
|
+
let index = eventsSpec[name];
|
|
1294
|
+
let { setup, remove } = createEventHandler(name);
|
|
1295
|
+
setupFns[index] = setup;
|
|
1296
|
+
removeFns[index] = remove;
|
|
1297
|
+
}
|
|
1298
|
+
let n = setupFns.length;
|
|
1299
|
+
class VCatcher {
|
|
1300
|
+
constructor(child, handlers) {
|
|
1301
|
+
this.afterNode = null;
|
|
1302
|
+
this.child = child;
|
|
1303
|
+
this.handlers = handlers;
|
|
1304
|
+
}
|
|
1305
|
+
mount(parent, afterNode) {
|
|
1306
|
+
this.parentEl = parent;
|
|
1307
|
+
this.afterNode = afterNode;
|
|
1308
|
+
this.child.mount(parent, afterNode);
|
|
1309
|
+
for (let i = 0; i < n; i++) {
|
|
1310
|
+
let origFn = this.handlers[i][0];
|
|
1311
|
+
const self = this;
|
|
1312
|
+
this.handlers[i][0] = function (ev) {
|
|
1313
|
+
const target = ev.target;
|
|
1314
|
+
let currentNode = self.child.firstNode();
|
|
1315
|
+
const afterNode = self.afterNode;
|
|
1316
|
+
while (currentNode !== afterNode) {
|
|
1317
|
+
if (currentNode.contains(target)) {
|
|
1318
|
+
return origFn.call(this, ev);
|
|
1319
|
+
}
|
|
1320
|
+
currentNode = currentNode.nextSibling;
|
|
1321
|
+
}
|
|
1322
|
+
};
|
|
1323
|
+
setupFns[i].call(parent, this.handlers[i]);
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
moveBefore(other, afterNode) {
|
|
1327
|
+
this.afterNode = null;
|
|
1328
|
+
this.child.moveBefore(other ? other.child : null, afterNode);
|
|
1329
|
+
}
|
|
1330
|
+
patch(other, withBeforeRemove) {
|
|
1331
|
+
if (this === other) {
|
|
1332
|
+
return;
|
|
1333
|
+
}
|
|
1334
|
+
this.handlers = other.handlers;
|
|
1335
|
+
this.child.patch(other.child, withBeforeRemove);
|
|
1336
|
+
}
|
|
1337
|
+
beforeRemove() {
|
|
1338
|
+
this.child.beforeRemove();
|
|
1339
|
+
}
|
|
1340
|
+
remove() {
|
|
1341
|
+
for (let i = 0; i < n; i++) {
|
|
1342
|
+
removeFns[i].call(this.parentEl);
|
|
1343
|
+
}
|
|
1344
|
+
this.child.remove();
|
|
1345
|
+
}
|
|
1346
|
+
firstNode() {
|
|
1347
|
+
return this.child.firstNode();
|
|
1348
|
+
}
|
|
1349
|
+
toString() {
|
|
1350
|
+
return this.child.toString();
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
return function (child, handlers) {
|
|
1354
|
+
return new VCatcher(child, handlers);
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1281
1358
|
function mount$1(vnode, fixture, afterNode = null) {
|
|
1282
1359
|
vnode.mount(fixture, afterNode);
|
|
1283
1360
|
}
|
|
@@ -1291,143 +1368,464 @@ function remove(vnode, withBeforeRemove = false) {
|
|
|
1291
1368
|
vnode.remove();
|
|
1292
1369
|
}
|
|
1293
1370
|
|
|
1371
|
+
const mainEventHandler = (data, ev, currentTarget) => {
|
|
1372
|
+
const { data: _data, modifiers } = filterOutModifiersFromData(data);
|
|
1373
|
+
data = _data;
|
|
1374
|
+
let stopped = false;
|
|
1375
|
+
if (modifiers.length) {
|
|
1376
|
+
let selfMode = false;
|
|
1377
|
+
const isSelf = ev.target === currentTarget;
|
|
1378
|
+
for (const mod of modifiers) {
|
|
1379
|
+
switch (mod) {
|
|
1380
|
+
case "self":
|
|
1381
|
+
selfMode = true;
|
|
1382
|
+
if (isSelf) {
|
|
1383
|
+
continue;
|
|
1384
|
+
}
|
|
1385
|
+
else {
|
|
1386
|
+
return stopped;
|
|
1387
|
+
}
|
|
1388
|
+
case "prevent":
|
|
1389
|
+
if ((selfMode && isSelf) || !selfMode)
|
|
1390
|
+
ev.preventDefault();
|
|
1391
|
+
continue;
|
|
1392
|
+
case "stop":
|
|
1393
|
+
if ((selfMode && isSelf) || !selfMode)
|
|
1394
|
+
ev.stopPropagation();
|
|
1395
|
+
stopped = true;
|
|
1396
|
+
continue;
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
// If handler is empty, the array slot 0 will also be empty, and data will not have the property 0
|
|
1401
|
+
// We check this rather than data[0] being truthy (or typeof function) so that it crashes
|
|
1402
|
+
// as expected when there is a handler expression that evaluates to a falsy value
|
|
1403
|
+
if (Object.hasOwnProperty.call(data, 0)) {
|
|
1404
|
+
const handler = data[0];
|
|
1405
|
+
if (typeof handler !== "function") {
|
|
1406
|
+
throw new Error(`Invalid handler (expected a function, received: '${handler}')`);
|
|
1407
|
+
}
|
|
1408
|
+
let node = data[1] ? data[1].__owl__ : null;
|
|
1409
|
+
if (node ? node.status === 1 /* MOUNTED */ : true) {
|
|
1410
|
+
handler.call(node ? node.component : null, ev);
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
return stopped;
|
|
1414
|
+
};
|
|
1415
|
+
|
|
1416
|
+
// Allows to get the target of a Reactive (used for making a new Reactive from the underlying object)
|
|
1417
|
+
const TARGET = Symbol("Target");
|
|
1418
|
+
// Escape hatch to prevent reactivity system to turn something into a reactive
|
|
1419
|
+
const SKIP = Symbol("Skip");
|
|
1420
|
+
// Special key to subscribe to, to be notified of key creation/deletion
|
|
1421
|
+
const KEYCHANGES = Symbol("Key changes");
|
|
1422
|
+
const objectToString = Object.prototype.toString;
|
|
1423
|
+
const objectHasOwnProperty = Object.prototype.hasOwnProperty;
|
|
1424
|
+
const SUPPORTED_RAW_TYPES = new Set(["Object", "Array", "Set", "Map", "WeakMap"]);
|
|
1425
|
+
const COLLECTION_RAWTYPES = new Set(["Set", "Map", "WeakMap"]);
|
|
1294
1426
|
/**
|
|
1295
|
-
*
|
|
1427
|
+
* extract "RawType" from strings like "[object RawType]" => this lets us ignore
|
|
1428
|
+
* many native objects such as Promise (whose toString is [object Promise])
|
|
1429
|
+
* or Date ([object Date]), while also supporting collections without using
|
|
1430
|
+
* instanceof in a loop
|
|
1296
1431
|
*
|
|
1297
|
-
*
|
|
1432
|
+
* @param obj the object to check
|
|
1433
|
+
* @returns the raw type of the object
|
|
1298
1434
|
*/
|
|
1299
|
-
function
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1435
|
+
function rawType(obj) {
|
|
1436
|
+
return objectToString.call(obj).slice(8, -1);
|
|
1437
|
+
}
|
|
1438
|
+
/**
|
|
1439
|
+
* Checks whether a given value can be made into a reactive object.
|
|
1440
|
+
*
|
|
1441
|
+
* @param value the value to check
|
|
1442
|
+
* @returns whether the value can be made reactive
|
|
1443
|
+
*/
|
|
1444
|
+
function canBeMadeReactive(value) {
|
|
1445
|
+
if (typeof value !== "object") {
|
|
1446
|
+
return false;
|
|
1307
1447
|
}
|
|
1448
|
+
return SUPPORTED_RAW_TYPES.has(rawType(value));
|
|
1308
1449
|
}
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1450
|
+
/**
|
|
1451
|
+
* Creates a reactive from the given object/callback if possible and returns it,
|
|
1452
|
+
* returns the original object otherwise.
|
|
1453
|
+
*
|
|
1454
|
+
* @param value the value make reactive
|
|
1455
|
+
* @returns a reactive for the given object when possible, the original otherwise
|
|
1456
|
+
*/
|
|
1457
|
+
function possiblyReactive(val, cb) {
|
|
1458
|
+
return canBeMadeReactive(val) ? reactive(val, cb) : val;
|
|
1459
|
+
}
|
|
1460
|
+
/**
|
|
1461
|
+
* Mark an object or array so that it is ignored by the reactivity system
|
|
1462
|
+
*
|
|
1463
|
+
* @param value the value to mark
|
|
1464
|
+
* @returns the object itself
|
|
1465
|
+
*/
|
|
1466
|
+
function markRaw(value) {
|
|
1467
|
+
value[SKIP] = true;
|
|
1468
|
+
return value;
|
|
1469
|
+
}
|
|
1470
|
+
/**
|
|
1471
|
+
* Given a reactive objet, return the raw (non reactive) underlying object
|
|
1472
|
+
*
|
|
1473
|
+
* @param value a reactive value
|
|
1474
|
+
* @returns the underlying value
|
|
1475
|
+
*/
|
|
1476
|
+
function toRaw(value) {
|
|
1477
|
+
return value[TARGET] || value;
|
|
1478
|
+
}
|
|
1479
|
+
const targetToKeysToCallbacks = new WeakMap();
|
|
1480
|
+
/**
|
|
1481
|
+
* Observes a given key on a target with an callback. The callback will be
|
|
1482
|
+
* called when the given key changes on the target.
|
|
1483
|
+
*
|
|
1484
|
+
* @param target the target whose key should be observed
|
|
1485
|
+
* @param key the key to observe (or Symbol(KEYCHANGES) for key creation
|
|
1486
|
+
* or deletion)
|
|
1487
|
+
* @param callback the function to call when the key changes
|
|
1488
|
+
*/
|
|
1489
|
+
function observeTargetKey(target, key, callback) {
|
|
1490
|
+
if (!targetToKeysToCallbacks.get(target)) {
|
|
1491
|
+
targetToKeysToCallbacks.set(target, new Map());
|
|
1315
1492
|
}
|
|
1316
|
-
|
|
1493
|
+
const keyToCallbacks = targetToKeysToCallbacks.get(target);
|
|
1494
|
+
if (!keyToCallbacks.get(key)) {
|
|
1495
|
+
keyToCallbacks.set(key, new Set());
|
|
1496
|
+
}
|
|
1497
|
+
keyToCallbacks.get(key).add(callback);
|
|
1498
|
+
if (!callbacksToTargets.has(callback)) {
|
|
1499
|
+
callbacksToTargets.set(callback, new Set());
|
|
1500
|
+
}
|
|
1501
|
+
callbacksToTargets.get(callback).add(target);
|
|
1317
1502
|
}
|
|
1318
1503
|
/**
|
|
1319
|
-
*
|
|
1320
|
-
*
|
|
1321
|
-
*
|
|
1322
|
-
*
|
|
1504
|
+
* Notify Reactives that are observing a given target that a key has changed on
|
|
1505
|
+
* the target.
|
|
1506
|
+
*
|
|
1507
|
+
* @param target target whose Reactives should be notified that the target was
|
|
1508
|
+
* changed.
|
|
1509
|
+
* @param key the key that changed (or Symbol `KEYCHANGES` if a key was created
|
|
1510
|
+
* or deleted)
|
|
1323
1511
|
*/
|
|
1324
|
-
function
|
|
1325
|
-
const
|
|
1326
|
-
|
|
1327
|
-
: parent.constructor.components[name];
|
|
1328
|
-
if (!ComponentClass) {
|
|
1329
|
-
// this is an error, wrong component. We silently return here instead so the
|
|
1330
|
-
// error is triggered by the usual path ('component' function)
|
|
1512
|
+
function notifyReactives(target, key) {
|
|
1513
|
+
const keyToCallbacks = targetToKeysToCallbacks.get(target);
|
|
1514
|
+
if (!keyToCallbacks) {
|
|
1331
1515
|
return;
|
|
1332
1516
|
}
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
const allowAdditionalProps = "*" in propsDef;
|
|
1337
|
-
for (let propName in propsDef) {
|
|
1338
|
-
if (propName === "*") {
|
|
1339
|
-
continue;
|
|
1340
|
-
}
|
|
1341
|
-
const propDef = propsDef[propName];
|
|
1342
|
-
let isMandatory = !!propDef;
|
|
1343
|
-
if (typeof propDef === "object" && "optional" in propDef) {
|
|
1344
|
-
isMandatory = !propDef.optional;
|
|
1345
|
-
}
|
|
1346
|
-
if (isMandatory && propName in defaultProps) {
|
|
1347
|
-
throw new Error(`A default value cannot be defined for a mandatory prop (name: '${propName}', component: ${ComponentClass.name})`);
|
|
1348
|
-
}
|
|
1349
|
-
if (props[propName] === undefined) {
|
|
1350
|
-
if (isMandatory) {
|
|
1351
|
-
throw new Error(`Missing props '${propName}' (component '${ComponentClass.name}')`);
|
|
1352
|
-
}
|
|
1353
|
-
else {
|
|
1354
|
-
continue;
|
|
1355
|
-
}
|
|
1356
|
-
}
|
|
1357
|
-
let isValid;
|
|
1358
|
-
try {
|
|
1359
|
-
isValid = isValidProp(props[propName], propDef);
|
|
1360
|
-
}
|
|
1361
|
-
catch (e) {
|
|
1362
|
-
e.message = `Invalid prop '${propName}' in component ${ComponentClass.name} (${e.message})`;
|
|
1363
|
-
throw e;
|
|
1364
|
-
}
|
|
1365
|
-
if (!isValid) {
|
|
1366
|
-
throw new Error(`Invalid Prop '${propName}' in component '${ComponentClass.name}'`);
|
|
1367
|
-
}
|
|
1517
|
+
const callbacks = keyToCallbacks.get(key);
|
|
1518
|
+
if (!callbacks) {
|
|
1519
|
+
return;
|
|
1368
1520
|
}
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
}
|
|
1374
|
-
}
|
|
1521
|
+
// Loop on copy because clearReactivesForCallback will modify the set in place
|
|
1522
|
+
for (const callback of [...callbacks]) {
|
|
1523
|
+
clearReactivesForCallback(callback);
|
|
1524
|
+
callback();
|
|
1375
1525
|
}
|
|
1376
1526
|
}
|
|
1527
|
+
const callbacksToTargets = new WeakMap();
|
|
1377
1528
|
/**
|
|
1378
|
-
*
|
|
1529
|
+
* Clears all subscriptions of the Reactives associated with a given callback.
|
|
1530
|
+
*
|
|
1531
|
+
* @param callback the callback for which the reactives need to be cleared
|
|
1379
1532
|
*/
|
|
1380
|
-
function
|
|
1381
|
-
|
|
1382
|
-
|
|
1533
|
+
function clearReactivesForCallback(callback) {
|
|
1534
|
+
const targetsToClear = callbacksToTargets.get(callback);
|
|
1535
|
+
if (!targetsToClear) {
|
|
1536
|
+
return;
|
|
1383
1537
|
}
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
// So, even though 1 is not an instance of Number, we want to consider that
|
|
1389
|
-
// it is valid.
|
|
1390
|
-
if (typeof prop === "object") {
|
|
1391
|
-
return prop instanceof propDef;
|
|
1538
|
+
for (const target of targetsToClear) {
|
|
1539
|
+
const observedKeys = targetToKeysToCallbacks.get(target);
|
|
1540
|
+
if (!observedKeys) {
|
|
1541
|
+
continue;
|
|
1392
1542
|
}
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
else if (propDef instanceof Array) {
|
|
1396
|
-
// If this code is executed, this means that we want to check if a prop
|
|
1397
|
-
// matches at least one of its descriptor.
|
|
1398
|
-
let result = false;
|
|
1399
|
-
for (let i = 0, iLen = propDef.length; i < iLen; i++) {
|
|
1400
|
-
result = result || isValidProp(prop, propDef[i]);
|
|
1543
|
+
for (const callbacks of observedKeys.values()) {
|
|
1544
|
+
callbacks.delete(callback);
|
|
1401
1545
|
}
|
|
1402
|
-
return result;
|
|
1403
1546
|
}
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1547
|
+
targetsToClear.clear();
|
|
1548
|
+
}
|
|
1549
|
+
function getSubscriptions(callback) {
|
|
1550
|
+
const targets = callbacksToTargets.get(callback) || [];
|
|
1551
|
+
return [...targets].map((target) => {
|
|
1552
|
+
const keysToCallbacks = targetToKeysToCallbacks.get(target);
|
|
1553
|
+
return {
|
|
1554
|
+
target,
|
|
1555
|
+
keys: keysToCallbacks ? [...keysToCallbacks.keys()] : [],
|
|
1556
|
+
};
|
|
1557
|
+
});
|
|
1558
|
+
}
|
|
1559
|
+
const reactiveCache = new WeakMap();
|
|
1560
|
+
/**
|
|
1561
|
+
* Creates a reactive proxy for an object. Reading data on the reactive object
|
|
1562
|
+
* subscribes to changes to the data. Writing data on the object will cause the
|
|
1563
|
+
* notify callback to be called if there are suscriptions to that data. Nested
|
|
1564
|
+
* objects and arrays are automatically made reactive as well.
|
|
1565
|
+
*
|
|
1566
|
+
* Whenever you are notified of a change, all subscriptions are cleared, and if
|
|
1567
|
+
* you would like to be notified of any further changes, you should go read
|
|
1568
|
+
* the underlying data again. We assume that if you don't go read it again after
|
|
1569
|
+
* being notified, it means that you are no longer interested in that data.
|
|
1570
|
+
*
|
|
1571
|
+
* Subscriptions:
|
|
1572
|
+
* + Reading a property on an object will subscribe you to changes in the value
|
|
1573
|
+
* of that property.
|
|
1574
|
+
* + Accessing an object keys (eg with Object.keys or with `for..in`) will
|
|
1575
|
+
* subscribe you to the creation/deletion of keys. Checking the presence of a
|
|
1576
|
+
* key on the object with 'in' has the same effect.
|
|
1577
|
+
* - getOwnPropertyDescriptor does not currently subscribe you to the property.
|
|
1578
|
+
* This is a choice that was made because changing a key's value will trigger
|
|
1579
|
+
* this trap and we do not want to subscribe by writes. This also means that
|
|
1580
|
+
* Object.hasOwnProperty doesn't subscribe as it goes through this trap.
|
|
1581
|
+
*
|
|
1582
|
+
* @param target the object for which to create a reactive proxy
|
|
1583
|
+
* @param callback the function to call when an observed property of the
|
|
1584
|
+
* reactive has changed
|
|
1585
|
+
* @returns a proxy that tracks changes to it
|
|
1586
|
+
*/
|
|
1587
|
+
function reactive(target, callback = () => { }) {
|
|
1588
|
+
if (!canBeMadeReactive(target)) {
|
|
1589
|
+
throw new Error(`Cannot make the given value reactive`);
|
|
1407
1590
|
}
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
result = result && propDef.validate(prop);
|
|
1591
|
+
if (SKIP in target) {
|
|
1592
|
+
return target;
|
|
1411
1593
|
}
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
}
|
|
1594
|
+
const originalTarget = target[TARGET];
|
|
1595
|
+
if (originalTarget) {
|
|
1596
|
+
return reactive(originalTarget, callback);
|
|
1416
1597
|
}
|
|
1417
|
-
if (
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1598
|
+
if (!reactiveCache.has(target)) {
|
|
1599
|
+
reactiveCache.set(target, new Map());
|
|
1600
|
+
}
|
|
1601
|
+
const reactivesForTarget = reactiveCache.get(target);
|
|
1602
|
+
if (!reactivesForTarget.has(callback)) {
|
|
1603
|
+
const targetRawType = rawType(target);
|
|
1604
|
+
const handler = COLLECTION_RAWTYPES.has(targetRawType)
|
|
1605
|
+
? collectionsProxyHandler(target, callback, targetRawType)
|
|
1606
|
+
: basicProxyHandler(callback);
|
|
1607
|
+
const proxy = new Proxy(target, handler);
|
|
1608
|
+
reactivesForTarget.set(callback, proxy);
|
|
1609
|
+
}
|
|
1610
|
+
return reactivesForTarget.get(callback);
|
|
1611
|
+
}
|
|
1612
|
+
/**
|
|
1613
|
+
* Creates a basic proxy handler for regular objects and arrays.
|
|
1614
|
+
*
|
|
1615
|
+
* @param callback @see reactive
|
|
1616
|
+
* @returns a proxy handler object
|
|
1617
|
+
*/
|
|
1618
|
+
function basicProxyHandler(callback) {
|
|
1619
|
+
return {
|
|
1620
|
+
get(target, key, proxy) {
|
|
1621
|
+
if (key === TARGET) {
|
|
1622
|
+
return target;
|
|
1623
|
+
}
|
|
1624
|
+
// non-writable non-configurable properties cannot be made reactive
|
|
1625
|
+
const desc = Object.getOwnPropertyDescriptor(target, key);
|
|
1626
|
+
if (desc && !desc.writable && !desc.configurable) {
|
|
1627
|
+
return Reflect.get(target, key, proxy);
|
|
1628
|
+
}
|
|
1629
|
+
observeTargetKey(target, key, callback);
|
|
1630
|
+
return possiblyReactive(Reflect.get(target, key, proxy), callback);
|
|
1631
|
+
},
|
|
1632
|
+
set(target, key, value, proxy) {
|
|
1633
|
+
const isNewKey = !objectHasOwnProperty.call(target, key);
|
|
1634
|
+
const originalValue = Reflect.get(target, key, proxy);
|
|
1635
|
+
const ret = Reflect.set(target, key, value, proxy);
|
|
1636
|
+
if (isNewKey) {
|
|
1637
|
+
notifyReactives(target, KEYCHANGES);
|
|
1638
|
+
}
|
|
1639
|
+
// While Array length may trigger the set trap, it's not actually set by this
|
|
1640
|
+
// method but is updated behind the scenes, and the trap is not called with the
|
|
1641
|
+
// new value. We disable the "same-value-optimization" for it because of that.
|
|
1642
|
+
if (originalValue !== value || (Array.isArray(target) && key === "length")) {
|
|
1643
|
+
notifyReactives(target, key);
|
|
1427
1644
|
}
|
|
1645
|
+
return ret;
|
|
1646
|
+
},
|
|
1647
|
+
deleteProperty(target, key) {
|
|
1648
|
+
const ret = Reflect.deleteProperty(target, key);
|
|
1649
|
+
// TODO: only notify when something was actually deleted
|
|
1650
|
+
notifyReactives(target, KEYCHANGES);
|
|
1651
|
+
notifyReactives(target, key);
|
|
1652
|
+
return ret;
|
|
1653
|
+
},
|
|
1654
|
+
ownKeys(target) {
|
|
1655
|
+
observeTargetKey(target, KEYCHANGES, callback);
|
|
1656
|
+
return Reflect.ownKeys(target);
|
|
1657
|
+
},
|
|
1658
|
+
has(target, key) {
|
|
1659
|
+
// TODO: this observes all key changes instead of only the presence of the argument key
|
|
1660
|
+
// observing the key itself would observe value changes instead of presence changes
|
|
1661
|
+
// so we may need a finer grained system to distinguish observing value vs presence.
|
|
1662
|
+
observeTargetKey(target, KEYCHANGES, callback);
|
|
1663
|
+
return Reflect.has(target, key);
|
|
1664
|
+
},
|
|
1665
|
+
};
|
|
1666
|
+
}
|
|
1667
|
+
/**
|
|
1668
|
+
* Creates a function that will observe the key that is passed to it when called
|
|
1669
|
+
* and delegates to the underlying method.
|
|
1670
|
+
*
|
|
1671
|
+
* @param methodName name of the method to delegate to
|
|
1672
|
+
* @param target @see reactive
|
|
1673
|
+
* @param callback @see reactive
|
|
1674
|
+
*/
|
|
1675
|
+
function makeKeyObserver(methodName, target, callback) {
|
|
1676
|
+
return (key) => {
|
|
1677
|
+
key = toRaw(key);
|
|
1678
|
+
observeTargetKey(target, key, callback);
|
|
1679
|
+
return possiblyReactive(target[methodName](key), callback);
|
|
1680
|
+
};
|
|
1681
|
+
}
|
|
1682
|
+
/**
|
|
1683
|
+
* Creates an iterable that will delegate to the underlying iteration method and
|
|
1684
|
+
* observe keys as necessary.
|
|
1685
|
+
*
|
|
1686
|
+
* @param methodName name of the method to delegate to
|
|
1687
|
+
* @param target @see reactive
|
|
1688
|
+
* @param callback @see reactive
|
|
1689
|
+
*/
|
|
1690
|
+
function makeIteratorObserver(methodName, target, callback) {
|
|
1691
|
+
return function* () {
|
|
1692
|
+
observeTargetKey(target, KEYCHANGES, callback);
|
|
1693
|
+
const keys = target.keys();
|
|
1694
|
+
for (const item of target[methodName]()) {
|
|
1695
|
+
const key = keys.next().value;
|
|
1696
|
+
observeTargetKey(target, key, callback);
|
|
1697
|
+
yield possiblyReactive(item, callback);
|
|
1428
1698
|
}
|
|
1429
|
-
}
|
|
1430
|
-
|
|
1699
|
+
};
|
|
1700
|
+
}
|
|
1701
|
+
/**
|
|
1702
|
+
* Creates a forEach function that will delegate to forEach on the underlying
|
|
1703
|
+
* collection while observing key changes, and keys as they're iterated over,
|
|
1704
|
+
* and making the passed keys/values reactive.
|
|
1705
|
+
*
|
|
1706
|
+
* @param target @see reactive
|
|
1707
|
+
* @param callback @see reactive
|
|
1708
|
+
*/
|
|
1709
|
+
function makeForEachObserver(target, callback) {
|
|
1710
|
+
return function forEach(forEachCb, thisArg) {
|
|
1711
|
+
observeTargetKey(target, KEYCHANGES, callback);
|
|
1712
|
+
target.forEach(function (val, key, targetObj) {
|
|
1713
|
+
observeTargetKey(target, key, callback);
|
|
1714
|
+
forEachCb.call(thisArg, possiblyReactive(val, callback), possiblyReactive(key, callback), possiblyReactive(targetObj, callback));
|
|
1715
|
+
}, thisArg);
|
|
1716
|
+
};
|
|
1717
|
+
}
|
|
1718
|
+
/**
|
|
1719
|
+
* Creates a function that will delegate to an underlying method, and check if
|
|
1720
|
+
* that method has modified the presence or value of a key, and notify the
|
|
1721
|
+
* reactives appropriately.
|
|
1722
|
+
*
|
|
1723
|
+
* @param setterName name of the method to delegate to
|
|
1724
|
+
* @param getterName name of the method which should be used to retrieve the
|
|
1725
|
+
* value before calling the delegate method for comparison purposes
|
|
1726
|
+
* @param target @see reactive
|
|
1727
|
+
*/
|
|
1728
|
+
function delegateAndNotify(setterName, getterName, target) {
|
|
1729
|
+
return (key, value) => {
|
|
1730
|
+
key = toRaw(key);
|
|
1731
|
+
const hadKey = target.has(key);
|
|
1732
|
+
const originalValue = target[getterName](key);
|
|
1733
|
+
const ret = target[setterName](key, value);
|
|
1734
|
+
const hasKey = target.has(key);
|
|
1735
|
+
if (hadKey !== hasKey) {
|
|
1736
|
+
notifyReactives(target, KEYCHANGES);
|
|
1737
|
+
}
|
|
1738
|
+
if (originalValue !== value) {
|
|
1739
|
+
notifyReactives(target, key);
|
|
1740
|
+
}
|
|
1741
|
+
return ret;
|
|
1742
|
+
};
|
|
1743
|
+
}
|
|
1744
|
+
/**
|
|
1745
|
+
* Creates a function that will clear the underlying collection and notify that
|
|
1746
|
+
* the keys of the collection have changed.
|
|
1747
|
+
*
|
|
1748
|
+
* @param target @see reactive
|
|
1749
|
+
*/
|
|
1750
|
+
function makeClearNotifier(target) {
|
|
1751
|
+
return () => {
|
|
1752
|
+
const allKeys = [...target.keys()];
|
|
1753
|
+
target.clear();
|
|
1754
|
+
notifyReactives(target, KEYCHANGES);
|
|
1755
|
+
for (const key of allKeys) {
|
|
1756
|
+
notifyReactives(target, key);
|
|
1757
|
+
}
|
|
1758
|
+
};
|
|
1759
|
+
}
|
|
1760
|
+
/**
|
|
1761
|
+
* Maps raw type of an object to an object containing functions that can be used
|
|
1762
|
+
* to build an appropritate proxy handler for that raw type. Eg: when making a
|
|
1763
|
+
* reactive set, calling the has method should mark the key that is being
|
|
1764
|
+
* retrieved as observed, and calling the add or delete method should notify the
|
|
1765
|
+
* reactives that the key which is being added or deleted has been modified.
|
|
1766
|
+
*/
|
|
1767
|
+
const rawTypeToFuncHandlers = {
|
|
1768
|
+
Set: (target, callback) => ({
|
|
1769
|
+
has: makeKeyObserver("has", target, callback),
|
|
1770
|
+
add: delegateAndNotify("add", "has", target),
|
|
1771
|
+
delete: delegateAndNotify("delete", "has", target),
|
|
1772
|
+
keys: makeIteratorObserver("keys", target, callback),
|
|
1773
|
+
values: makeIteratorObserver("values", target, callback),
|
|
1774
|
+
entries: makeIteratorObserver("entries", target, callback),
|
|
1775
|
+
[Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
|
|
1776
|
+
forEach: makeForEachObserver(target, callback),
|
|
1777
|
+
clear: makeClearNotifier(target),
|
|
1778
|
+
get size() {
|
|
1779
|
+
observeTargetKey(target, KEYCHANGES, callback);
|
|
1780
|
+
return target.size;
|
|
1781
|
+
},
|
|
1782
|
+
}),
|
|
1783
|
+
Map: (target, callback) => ({
|
|
1784
|
+
has: makeKeyObserver("has", target, callback),
|
|
1785
|
+
get: makeKeyObserver("get", target, callback),
|
|
1786
|
+
set: delegateAndNotify("set", "get", target),
|
|
1787
|
+
delete: delegateAndNotify("delete", "has", target),
|
|
1788
|
+
keys: makeIteratorObserver("keys", target, callback),
|
|
1789
|
+
values: makeIteratorObserver("values", target, callback),
|
|
1790
|
+
entries: makeIteratorObserver("entries", target, callback),
|
|
1791
|
+
[Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
|
|
1792
|
+
forEach: makeForEachObserver(target, callback),
|
|
1793
|
+
clear: makeClearNotifier(target),
|
|
1794
|
+
get size() {
|
|
1795
|
+
observeTargetKey(target, KEYCHANGES, callback);
|
|
1796
|
+
return target.size;
|
|
1797
|
+
},
|
|
1798
|
+
}),
|
|
1799
|
+
WeakMap: (target, callback) => ({
|
|
1800
|
+
has: makeKeyObserver("has", target, callback),
|
|
1801
|
+
get: makeKeyObserver("get", target, callback),
|
|
1802
|
+
set: delegateAndNotify("set", "get", target),
|
|
1803
|
+
delete: delegateAndNotify("delete", "has", target),
|
|
1804
|
+
}),
|
|
1805
|
+
};
|
|
1806
|
+
/**
|
|
1807
|
+
* Creates a proxy handler for collections (Set/Map/WeakMap)
|
|
1808
|
+
*
|
|
1809
|
+
* @param callback @see reactive
|
|
1810
|
+
* @param target @see reactive
|
|
1811
|
+
* @returns a proxy handler object
|
|
1812
|
+
*/
|
|
1813
|
+
function collectionsProxyHandler(target, callback, targetRawType) {
|
|
1814
|
+
// TODO: if performance is an issue we can create the special handlers lazily when each
|
|
1815
|
+
// property is read.
|
|
1816
|
+
const specialHandlers = rawTypeToFuncHandlers[targetRawType](target, callback);
|
|
1817
|
+
return Object.assign(basicProxyHandler(callback), {
|
|
1818
|
+
get(target, key) {
|
|
1819
|
+
if (key === TARGET) {
|
|
1820
|
+
return target;
|
|
1821
|
+
}
|
|
1822
|
+
if (objectHasOwnProperty.call(specialHandlers, key)) {
|
|
1823
|
+
return specialHandlers[key];
|
|
1824
|
+
}
|
|
1825
|
+
observeTargetKey(target, key, callback);
|
|
1826
|
+
return possiblyReactive(target[key], callback);
|
|
1827
|
+
},
|
|
1828
|
+
});
|
|
1431
1829
|
}
|
|
1432
1830
|
|
|
1433
1831
|
/**
|
|
@@ -1445,11 +1843,13 @@ function batched(callback) {
|
|
|
1445
1843
|
await Promise.resolve();
|
|
1446
1844
|
if (!called) {
|
|
1447
1845
|
called = true;
|
|
1448
|
-
callback();
|
|
1449
1846
|
// wait for all calls in this microtick to fall through before resetting "called"
|
|
1450
|
-
// so that only the first call to the batched function calls the original callback
|
|
1451
|
-
|
|
1452
|
-
called
|
|
1847
|
+
// so that only the first call to the batched function calls the original callback.
|
|
1848
|
+
// Schedule this before calling the callback so that calls to the batched function
|
|
1849
|
+
// within the callback will proceed only after resetting called to false, and have
|
|
1850
|
+
// a chance to execute the callback again
|
|
1851
|
+
Promise.resolve().then(() => (called = false));
|
|
1852
|
+
callback();
|
|
1453
1853
|
}
|
|
1454
1854
|
};
|
|
1455
1855
|
}
|
|
@@ -1496,769 +1896,580 @@ class Markup extends String {
|
|
|
1496
1896
|
*/
|
|
1497
1897
|
function markup(value) {
|
|
1498
1898
|
return new Markup(value);
|
|
1499
|
-
}
|
|
1899
|
+
}
|
|
1900
|
+
// -----------------------------------------------------------------------------
|
|
1901
|
+
// xml tag helper
|
|
1902
|
+
// -----------------------------------------------------------------------------
|
|
1903
|
+
const globalTemplates = {};
|
|
1904
|
+
function xml(...args) {
|
|
1905
|
+
const name = `__template__${xml.nextId++}`;
|
|
1906
|
+
const value = String.raw(...args);
|
|
1907
|
+
globalTemplates[name] = value;
|
|
1908
|
+
return name;
|
|
1909
|
+
}
|
|
1910
|
+
xml.nextId = 1;
|
|
1500
1911
|
|
|
1501
|
-
//
|
|
1502
|
-
const
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
const KEYCHANGES = Symbol("Key changes");
|
|
1507
|
-
const objectToString = Object.prototype.toString;
|
|
1508
|
-
/**
|
|
1509
|
-
* Checks whether a given value can be made into a reactive object.
|
|
1510
|
-
*
|
|
1511
|
-
* @param value the value to check
|
|
1512
|
-
* @returns whether the value can be made reactive
|
|
1513
|
-
*/
|
|
1514
|
-
function canBeMadeReactive(value) {
|
|
1515
|
-
if (typeof value !== "object") {
|
|
1912
|
+
// Maps fibers to thrown errors
|
|
1913
|
+
const fibersInError = new WeakMap();
|
|
1914
|
+
const nodeErrorHandlers = new WeakMap();
|
|
1915
|
+
function _handleError(node, error, isFirstRound = false) {
|
|
1916
|
+
if (!node) {
|
|
1516
1917
|
return false;
|
|
1517
1918
|
}
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
const rawType = objectToString.call(value).slice(8, -1);
|
|
1522
|
-
return rawType === "Object" || rawType === "Array";
|
|
1523
|
-
}
|
|
1524
|
-
/**
|
|
1525
|
-
* Mark an object or array so that it is ignored by the reactivity system
|
|
1526
|
-
*
|
|
1527
|
-
* @param value the value to mark
|
|
1528
|
-
* @returns the object itself
|
|
1529
|
-
*/
|
|
1530
|
-
function markRaw(value) {
|
|
1531
|
-
value[SKIP] = true;
|
|
1532
|
-
return value;
|
|
1533
|
-
}
|
|
1534
|
-
/**
|
|
1535
|
-
* Given a reactive objet, return the raw (non reactive) underlying object
|
|
1536
|
-
*
|
|
1537
|
-
* @param value a reactive value
|
|
1538
|
-
* @returns the underlying value
|
|
1539
|
-
*/
|
|
1540
|
-
function toRaw(value) {
|
|
1541
|
-
return value[TARGET] || value;
|
|
1542
|
-
}
|
|
1543
|
-
const targetToKeysToCallbacks = new WeakMap();
|
|
1544
|
-
/**
|
|
1545
|
-
* Observes a given key on a target with an callback. The callback will be
|
|
1546
|
-
* called when the given key changes on the target.
|
|
1547
|
-
*
|
|
1548
|
-
* @param target the target whose key should be observed
|
|
1549
|
-
* @param key the key to observe (or Symbol(KEYCHANGES) for key creation
|
|
1550
|
-
* or deletion)
|
|
1551
|
-
* @param callback the function to call when the key changes
|
|
1552
|
-
*/
|
|
1553
|
-
function observeTargetKey(target, key, callback) {
|
|
1554
|
-
if (!targetToKeysToCallbacks.get(target)) {
|
|
1555
|
-
targetToKeysToCallbacks.set(target, new Map());
|
|
1919
|
+
const fiber = node.fiber;
|
|
1920
|
+
if (fiber) {
|
|
1921
|
+
fibersInError.set(fiber, error);
|
|
1556
1922
|
}
|
|
1557
|
-
const
|
|
1558
|
-
if (
|
|
1559
|
-
|
|
1923
|
+
const errorHandlers = nodeErrorHandlers.get(node);
|
|
1924
|
+
if (errorHandlers) {
|
|
1925
|
+
let stopped = false;
|
|
1926
|
+
// execute in the opposite order
|
|
1927
|
+
for (let i = errorHandlers.length - 1; i >= 0; i--) {
|
|
1928
|
+
try {
|
|
1929
|
+
errorHandlers[i](error);
|
|
1930
|
+
stopped = true;
|
|
1931
|
+
break;
|
|
1932
|
+
}
|
|
1933
|
+
catch (e) {
|
|
1934
|
+
error = e;
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
if (stopped) {
|
|
1938
|
+
if (isFirstRound && fiber && fiber.node.fiber) {
|
|
1939
|
+
const root = fiber.root;
|
|
1940
|
+
root.setCounter(root.counter - 1);
|
|
1941
|
+
}
|
|
1942
|
+
return true;
|
|
1943
|
+
}
|
|
1560
1944
|
}
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1945
|
+
return _handleError(node.parent, error);
|
|
1946
|
+
}
|
|
1947
|
+
function handleError(params) {
|
|
1948
|
+
const error = params.error;
|
|
1949
|
+
const node = "node" in params ? params.node : params.fiber.node;
|
|
1950
|
+
const fiber = "fiber" in params ? params.fiber : node.fiber;
|
|
1951
|
+
// resets the fibers on components if possible. This is important so that
|
|
1952
|
+
// new renderings can be properly included in the initial one, if any.
|
|
1953
|
+
let current = fiber;
|
|
1954
|
+
do {
|
|
1955
|
+
current.node.fiber = current;
|
|
1956
|
+
current = current.parent;
|
|
1957
|
+
} while (current);
|
|
1958
|
+
fibersInError.set(fiber.root, error);
|
|
1959
|
+
const handled = _handleError(node, error, true);
|
|
1960
|
+
if (!handled) {
|
|
1961
|
+
console.warn(`[Owl] Unhandled error. Destroying the root component`);
|
|
1962
|
+
try {
|
|
1963
|
+
node.app.destroy();
|
|
1964
|
+
}
|
|
1965
|
+
catch (e) {
|
|
1966
|
+
console.error(e);
|
|
1967
|
+
}
|
|
1564
1968
|
}
|
|
1565
|
-
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
function makeChildFiber(node, parent) {
|
|
1972
|
+
let current = node.fiber;
|
|
1973
|
+
if (current) {
|
|
1974
|
+
cancelFibers(current.children);
|
|
1975
|
+
current.root = null;
|
|
1976
|
+
if (current instanceof RootFiber && current.delayedRenders.length) {
|
|
1977
|
+
let root = parent.root;
|
|
1978
|
+
root.delayedRenders = root.delayedRenders.concat(current.delayedRenders);
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
return new Fiber(node, parent);
|
|
1566
1982
|
}
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1983
|
+
function makeRootFiber(node) {
|
|
1984
|
+
let current = node.fiber;
|
|
1985
|
+
if (current) {
|
|
1986
|
+
let root = current.root;
|
|
1987
|
+
root.setCounter(root.counter + 1 - cancelFibers(current.children));
|
|
1988
|
+
current.children = [];
|
|
1989
|
+
current.bdom = null;
|
|
1990
|
+
if (current === root) {
|
|
1991
|
+
root.reachedChildren = new WeakSet();
|
|
1992
|
+
}
|
|
1993
|
+
if (fibersInError.has(current)) {
|
|
1994
|
+
fibersInError.delete(current);
|
|
1995
|
+
fibersInError.delete(root);
|
|
1996
|
+
current.appliedToDom = false;
|
|
1997
|
+
}
|
|
1998
|
+
return current;
|
|
1580
1999
|
}
|
|
1581
|
-
const
|
|
1582
|
-
if (
|
|
1583
|
-
|
|
2000
|
+
const fiber = new RootFiber(node, null);
|
|
2001
|
+
if (node.willPatch.length) {
|
|
2002
|
+
fiber.willPatch.push(fiber);
|
|
1584
2003
|
}
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
clearReactivesForCallback(callback);
|
|
1588
|
-
callback();
|
|
2004
|
+
if (node.patched.length) {
|
|
2005
|
+
fiber.patched.push(fiber);
|
|
1589
2006
|
}
|
|
2007
|
+
return fiber;
|
|
1590
2008
|
}
|
|
1591
|
-
const callbacksToTargets = new WeakMap();
|
|
1592
2009
|
/**
|
|
1593
|
-
*
|
|
1594
|
-
*
|
|
1595
|
-
* @param callback the callback for which the reactives need to be cleared
|
|
2010
|
+
* @returns number of not-yet rendered fibers cancelled
|
|
1596
2011
|
*/
|
|
1597
|
-
function
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
2012
|
+
function cancelFibers(fibers) {
|
|
2013
|
+
let result = 0;
|
|
2014
|
+
for (let fiber of fibers) {
|
|
2015
|
+
fiber.node.fiber = null;
|
|
2016
|
+
if (fiber.bdom) {
|
|
2017
|
+
// if fiber has been rendered, this means that the component props have
|
|
2018
|
+
// been updated. however, this fiber will not be patched to the dom, so
|
|
2019
|
+
// it could happen that the next render compare the current props with
|
|
2020
|
+
// the same props, and skip the render completely. With the next line,
|
|
2021
|
+
// we kindly request the component code to force a render, so it works as
|
|
2022
|
+
// expected.
|
|
2023
|
+
fiber.node.forceNextRender = true;
|
|
1606
2024
|
}
|
|
1607
|
-
|
|
1608
|
-
|
|
2025
|
+
else {
|
|
2026
|
+
result++;
|
|
1609
2027
|
}
|
|
2028
|
+
result += cancelFibers(fiber.children);
|
|
1610
2029
|
}
|
|
1611
|
-
|
|
2030
|
+
return result;
|
|
1612
2031
|
}
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
* - getOwnPropertyDescriptor does not currently subscribe you to the property.
|
|
1632
|
-
* This is a choice that was made because changing a key's value will trigger
|
|
1633
|
-
* this trap and we do not want to subscribe by writes. This also means that
|
|
1634
|
-
* Object.hasOwnProperty doesn't subscribe as it goes through this trap.
|
|
1635
|
-
*
|
|
1636
|
-
* @param target the object for which to create a reactive proxy
|
|
1637
|
-
* @param callback the function to call when an observed property of the
|
|
1638
|
-
* reactive has changed
|
|
1639
|
-
* @returns a proxy that tracks changes to it
|
|
1640
|
-
*/
|
|
1641
|
-
function reactive(target, callback = () => { }) {
|
|
1642
|
-
if (!canBeMadeReactive(target)) {
|
|
1643
|
-
throw new Error(`Cannot make the given value reactive`);
|
|
2032
|
+
class Fiber {
|
|
2033
|
+
constructor(node, parent) {
|
|
2034
|
+
this.bdom = null;
|
|
2035
|
+
this.children = [];
|
|
2036
|
+
this.appliedToDom = false;
|
|
2037
|
+
this.deep = false;
|
|
2038
|
+
this.node = node;
|
|
2039
|
+
this.parent = parent;
|
|
2040
|
+
if (parent) {
|
|
2041
|
+
this.deep = parent.deep;
|
|
2042
|
+
const root = parent.root;
|
|
2043
|
+
root.setCounter(root.counter + 1);
|
|
2044
|
+
this.root = root;
|
|
2045
|
+
parent.children.push(this);
|
|
2046
|
+
}
|
|
2047
|
+
else {
|
|
2048
|
+
this.root = this;
|
|
2049
|
+
}
|
|
1644
2050
|
}
|
|
1645
|
-
|
|
1646
|
-
|
|
2051
|
+
render() {
|
|
2052
|
+
// if some parent has a fiber => register in followup
|
|
2053
|
+
let prev = this.root.node;
|
|
2054
|
+
let current = prev.parent;
|
|
2055
|
+
while (current) {
|
|
2056
|
+
if (current.fiber) {
|
|
2057
|
+
let root = current.fiber.root;
|
|
2058
|
+
if (root.counter) {
|
|
2059
|
+
root.delayedRenders.push(this);
|
|
2060
|
+
return;
|
|
2061
|
+
}
|
|
2062
|
+
else {
|
|
2063
|
+
if (!root.reachedChildren.has(prev)) {
|
|
2064
|
+
// is dead
|
|
2065
|
+
this.node.app.scheduler.shouldClear = true;
|
|
2066
|
+
return;
|
|
2067
|
+
}
|
|
2068
|
+
current = root.node;
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
prev = current;
|
|
2072
|
+
current = current.parent;
|
|
2073
|
+
}
|
|
2074
|
+
// there are no current rendering from above => we can render
|
|
2075
|
+
this._render();
|
|
1647
2076
|
}
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
2077
|
+
_render() {
|
|
2078
|
+
const node = this.node;
|
|
2079
|
+
const root = this.root;
|
|
2080
|
+
if (root) {
|
|
2081
|
+
try {
|
|
2082
|
+
this.bdom = node.renderFn();
|
|
2083
|
+
root.setCounter(root.counter - 1);
|
|
2084
|
+
}
|
|
2085
|
+
catch (e) {
|
|
2086
|
+
handleError({ node, error: e });
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
1651
2089
|
}
|
|
1652
|
-
|
|
1653
|
-
|
|
2090
|
+
}
|
|
2091
|
+
class RootFiber extends Fiber {
|
|
2092
|
+
constructor() {
|
|
2093
|
+
super(...arguments);
|
|
2094
|
+
this.counter = 1;
|
|
2095
|
+
// only add stuff in this if they have registered some hooks
|
|
2096
|
+
this.willPatch = [];
|
|
2097
|
+
this.patched = [];
|
|
2098
|
+
this.mounted = [];
|
|
2099
|
+
// A fiber is typically locked when it is completing and the patch has not, or is being applied.
|
|
2100
|
+
// i.e.: render triggered in onWillUnmount or in willPatch will be delayed
|
|
2101
|
+
this.locked = false;
|
|
2102
|
+
this.delayedRenders = [];
|
|
2103
|
+
this.reachedChildren = new WeakSet();
|
|
1654
2104
|
}
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
2105
|
+
complete() {
|
|
2106
|
+
const node = this.node;
|
|
2107
|
+
this.locked = true;
|
|
2108
|
+
let current = undefined;
|
|
2109
|
+
try {
|
|
2110
|
+
// Step 1: calling all willPatch lifecycle hooks
|
|
2111
|
+
for (current of this.willPatch) {
|
|
2112
|
+
// because of the asynchronous nature of the rendering, some parts of the
|
|
2113
|
+
// UI may have been rendered, then deleted in a followup rendering, and we
|
|
2114
|
+
// do not want to call onWillPatch in that case.
|
|
2115
|
+
let node = current.node;
|
|
2116
|
+
if (node.fiber === current) {
|
|
2117
|
+
const component = node.component;
|
|
2118
|
+
for (let cb of node.willPatch) {
|
|
2119
|
+
cb.call(component);
|
|
2120
|
+
}
|
|
1666
2121
|
}
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
2122
|
+
}
|
|
2123
|
+
current = undefined;
|
|
2124
|
+
// Step 2: patching the dom
|
|
2125
|
+
node._patch();
|
|
2126
|
+
this.locked = false;
|
|
2127
|
+
// Step 4: calling all mounted lifecycle hooks
|
|
2128
|
+
let mountedFibers = this.mounted;
|
|
2129
|
+
while ((current = mountedFibers.pop())) {
|
|
2130
|
+
current = current;
|
|
2131
|
+
if (current.appliedToDom) {
|
|
2132
|
+
for (let cb of current.node.mounted) {
|
|
2133
|
+
cb();
|
|
2134
|
+
}
|
|
1675
2135
|
}
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
2136
|
+
}
|
|
2137
|
+
// Step 5: calling all patched hooks
|
|
2138
|
+
let patchedFibers = this.patched;
|
|
2139
|
+
while ((current = patchedFibers.pop())) {
|
|
2140
|
+
current = current;
|
|
2141
|
+
if (current.appliedToDom) {
|
|
2142
|
+
for (let cb of current.node.patched) {
|
|
2143
|
+
cb();
|
|
2144
|
+
}
|
|
1681
2145
|
}
|
|
1682
|
-
|
|
1683
|
-
},
|
|
1684
|
-
deleteProperty(target, key) {
|
|
1685
|
-
const ret = Reflect.deleteProperty(target, key);
|
|
1686
|
-
notifyReactives(target, KEYCHANGES);
|
|
1687
|
-
notifyReactives(target, key);
|
|
1688
|
-
return ret;
|
|
1689
|
-
},
|
|
1690
|
-
ownKeys(target) {
|
|
1691
|
-
observeTargetKey(target, KEYCHANGES, callback);
|
|
1692
|
-
return Reflect.ownKeys(target);
|
|
1693
|
-
},
|
|
1694
|
-
has(target, key) {
|
|
1695
|
-
// TODO: this observes all key changes instead of only the presence of the argument key
|
|
1696
|
-
observeTargetKey(target, KEYCHANGES, callback);
|
|
1697
|
-
return Reflect.has(target, key);
|
|
1698
|
-
},
|
|
1699
|
-
});
|
|
1700
|
-
reactivesForTarget.set(callback, proxy);
|
|
1701
|
-
}
|
|
1702
|
-
return reactivesForTarget.get(callback);
|
|
1703
|
-
}
|
|
1704
|
-
|
|
1705
|
-
/**
|
|
1706
|
-
* This file contains utility functions that will be injected in each template,
|
|
1707
|
-
* to perform various useful tasks in the compiled code.
|
|
1708
|
-
*/
|
|
1709
|
-
function withDefault(value, defaultValue) {
|
|
1710
|
-
return value === undefined || value === null || value === false ? defaultValue : value;
|
|
1711
|
-
}
|
|
1712
|
-
function callSlot(ctx, parent, key, name, dynamic, extra, defaultContent) {
|
|
1713
|
-
key = key + "__slot_" + name;
|
|
1714
|
-
const slots = ctx.props[TARGET].slots || {};
|
|
1715
|
-
const { __render, __ctx, __scope } = slots[name] || {};
|
|
1716
|
-
const slotScope = Object.create(__ctx || {});
|
|
1717
|
-
if (__scope) {
|
|
1718
|
-
slotScope[__scope] = extra || {};
|
|
1719
|
-
}
|
|
1720
|
-
const slotBDom = __render ? __render.call(__ctx.__owl__.component, slotScope, parent, key) : null;
|
|
1721
|
-
if (defaultContent) {
|
|
1722
|
-
let child1 = undefined;
|
|
1723
|
-
let child2 = undefined;
|
|
1724
|
-
if (slotBDom) {
|
|
1725
|
-
child1 = dynamic ? toggler(name, slotBDom) : slotBDom;
|
|
2146
|
+
}
|
|
1726
2147
|
}
|
|
1727
|
-
|
|
1728
|
-
|
|
2148
|
+
catch (e) {
|
|
2149
|
+
this.locked = false;
|
|
2150
|
+
handleError({ fiber: current || this, error: e });
|
|
1729
2151
|
}
|
|
1730
|
-
return multi([child1, child2]);
|
|
1731
2152
|
}
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
2153
|
+
setCounter(newValue) {
|
|
2154
|
+
this.counter = newValue;
|
|
2155
|
+
if (newValue === 0) {
|
|
2156
|
+
if (this.delayedRenders.length) {
|
|
2157
|
+
for (let f of this.delayedRenders) {
|
|
2158
|
+
if (f.root) {
|
|
2159
|
+
f.render();
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
this.delayedRenders = [];
|
|
2163
|
+
}
|
|
2164
|
+
this.node.app.scheduler.flush();
|
|
2165
|
+
}
|
|
1739
2166
|
}
|
|
1740
|
-
return result;
|
|
1741
|
-
}
|
|
1742
|
-
function withKey(elem, k) {
|
|
1743
|
-
elem.key = k;
|
|
1744
|
-
return elem;
|
|
1745
2167
|
}
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
values = collection;
|
|
1752
|
-
}
|
|
1753
|
-
else if (collection) {
|
|
1754
|
-
values = Object.keys(collection);
|
|
1755
|
-
keys = Object.values(collection);
|
|
2168
|
+
class MountFiber extends RootFiber {
|
|
2169
|
+
constructor(node, target, options = {}) {
|
|
2170
|
+
super(node, null);
|
|
2171
|
+
this.target = target;
|
|
2172
|
+
this.position = options.position || "last-child";
|
|
1756
2173
|
}
|
|
1757
|
-
|
|
1758
|
-
|
|
2174
|
+
complete() {
|
|
2175
|
+
let current = this;
|
|
2176
|
+
try {
|
|
2177
|
+
const node = this.node;
|
|
2178
|
+
node.app.constructor.validateTarget(this.target);
|
|
2179
|
+
if (node.bdom) {
|
|
2180
|
+
// this is a complicated situation: if we mount a fiber with an existing
|
|
2181
|
+
// bdom, this means that this same fiber was already completed, mounted,
|
|
2182
|
+
// but a crash occurred in some mounted hook. Then, it was handled and
|
|
2183
|
+
// the new rendering is being applied.
|
|
2184
|
+
node.updateDom();
|
|
2185
|
+
}
|
|
2186
|
+
else {
|
|
2187
|
+
node.bdom = this.bdom;
|
|
2188
|
+
if (this.position === "last-child" || this.target.childNodes.length === 0) {
|
|
2189
|
+
mount$1(node.bdom, this.target);
|
|
2190
|
+
}
|
|
2191
|
+
else {
|
|
2192
|
+
const firstChild = this.target.childNodes[0];
|
|
2193
|
+
mount$1(node.bdom, this.target, firstChild);
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
// unregistering the fiber before mounted since it can do another render
|
|
2197
|
+
// and that the current rendering is obviously completed
|
|
2198
|
+
node.fiber = null;
|
|
2199
|
+
node.status = 1 /* MOUNTED */;
|
|
2200
|
+
this.appliedToDom = true;
|
|
2201
|
+
let mountedFibers = this.mounted;
|
|
2202
|
+
while ((current = mountedFibers.pop())) {
|
|
2203
|
+
if (current.appliedToDom) {
|
|
2204
|
+
for (let cb of current.node.mounted) {
|
|
2205
|
+
cb();
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
catch (e) {
|
|
2211
|
+
handleError({ fiber: current, error: e });
|
|
2212
|
+
}
|
|
1759
2213
|
}
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
2214
|
+
}
|
|
2215
|
+
|
|
2216
|
+
/**
|
|
2217
|
+
* Apply default props (only top level).
|
|
2218
|
+
*
|
|
2219
|
+
* Note that this method does modify in place the props
|
|
2220
|
+
*/
|
|
2221
|
+
function applyDefaultProps(props, ComponentClass) {
|
|
2222
|
+
const defaultProps = ComponentClass.defaultProps;
|
|
2223
|
+
if (defaultProps) {
|
|
2224
|
+
for (let propName in defaultProps) {
|
|
2225
|
+
if (props[propName] === undefined) {
|
|
2226
|
+
props[propName] = defaultProps[propName];
|
|
2227
|
+
}
|
|
1771
2228
|
}
|
|
1772
|
-
ctx = newCtx;
|
|
1773
2229
|
}
|
|
1774
|
-
ctx[key] = value;
|
|
1775
|
-
}
|
|
1776
|
-
function toNumber(val) {
|
|
1777
|
-
const n = parseFloat(val);
|
|
1778
|
-
return isNaN(n) ? val : n;
|
|
1779
2230
|
}
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
2231
|
+
//------------------------------------------------------------------------------
|
|
2232
|
+
// Prop validation helper
|
|
2233
|
+
//------------------------------------------------------------------------------
|
|
2234
|
+
function getPropDescription(staticProps) {
|
|
2235
|
+
if (staticProps instanceof Array) {
|
|
2236
|
+
return Object.fromEntries(staticProps.map((p) => (p.endsWith("?") ? [p.slice(0, -1), false] : [p, true])));
|
|
1785
2237
|
}
|
|
1786
|
-
return true;
|
|
2238
|
+
return staticProps || { "*": true };
|
|
1787
2239
|
}
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
2240
|
+
/**
|
|
2241
|
+
* Validate the component props (or next props) against the (static) props
|
|
2242
|
+
* description. This is potentially an expensive operation: it may needs to
|
|
2243
|
+
* visit recursively the props and all the children to check if they are valid.
|
|
2244
|
+
* This is why it is only done in 'dev' mode.
|
|
2245
|
+
*/
|
|
2246
|
+
function validateProps(name, props, parent) {
|
|
2247
|
+
const ComponentClass = typeof name !== "string"
|
|
2248
|
+
? name
|
|
2249
|
+
: parent.constructor.components[name];
|
|
2250
|
+
if (!ComponentClass) {
|
|
2251
|
+
// this is an error, wrong component. We silently return here instead so the
|
|
2252
|
+
// error is triggered by the usual path ('component' function)
|
|
2253
|
+
return;
|
|
1793
2254
|
}
|
|
1794
|
-
|
|
1795
|
-
|
|
2255
|
+
applyDefaultProps(props, ComponentClass);
|
|
2256
|
+
const defaultProps = ComponentClass.defaultProps || {};
|
|
2257
|
+
let propsDef = getPropDescription(ComponentClass.props);
|
|
2258
|
+
const allowAdditionalProps = "*" in propsDef;
|
|
2259
|
+
for (let propName in propsDef) {
|
|
2260
|
+
if (propName === "*") {
|
|
2261
|
+
continue;
|
|
2262
|
+
}
|
|
2263
|
+
const propDef = propsDef[propName];
|
|
2264
|
+
let isMandatory = !!propDef;
|
|
2265
|
+
if (typeof propDef === "object" && "optional" in propDef) {
|
|
2266
|
+
isMandatory = !propDef.optional;
|
|
2267
|
+
}
|
|
2268
|
+
if (isMandatory && propName in defaultProps) {
|
|
2269
|
+
throw new Error(`A default value cannot be defined for a mandatory prop (name: '${propName}', component: ${ComponentClass.name})`);
|
|
2270
|
+
}
|
|
2271
|
+
if (props[propName] === undefined) {
|
|
2272
|
+
if (isMandatory) {
|
|
2273
|
+
throw new Error(`Missing props '${propName}' (component '${ComponentClass.name}')`);
|
|
2274
|
+
}
|
|
2275
|
+
else {
|
|
2276
|
+
continue;
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
let isValid;
|
|
2280
|
+
try {
|
|
2281
|
+
isValid = isValidProp(props[propName], propDef);
|
|
2282
|
+
}
|
|
2283
|
+
catch (e) {
|
|
2284
|
+
e.message = `Invalid prop '${propName}' in component ${ComponentClass.name} (${e.message})`;
|
|
2285
|
+
throw e;
|
|
2286
|
+
}
|
|
2287
|
+
if (!isValid) {
|
|
2288
|
+
throw new Error(`Invalid Prop '${propName}' in component '${ComponentClass.name}'`);
|
|
2289
|
+
}
|
|
1796
2290
|
}
|
|
1797
|
-
|
|
1798
|
-
|
|
2291
|
+
if (!allowAdditionalProps) {
|
|
2292
|
+
for (let propName in props) {
|
|
2293
|
+
if (!(propName in propsDef)) {
|
|
2294
|
+
throw new Error(`Unknown prop '${propName}' given to component '${ComponentClass.name}'`);
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
1799
2297
|
}
|
|
1800
2298
|
}
|
|
1801
|
-
|
|
1802
|
-
*
|
|
2299
|
+
/**
|
|
2300
|
+
* Check if an invidual prop value matches its (static) prop definition
|
|
1803
2301
|
*/
|
|
1804
|
-
function
|
|
1805
|
-
if (
|
|
1806
|
-
return
|
|
1807
|
-
}
|
|
1808
|
-
let safeKey;
|
|
1809
|
-
let block;
|
|
1810
|
-
if (value instanceof Markup) {
|
|
1811
|
-
safeKey = `string_safe`;
|
|
1812
|
-
block = html(value);
|
|
2302
|
+
function isValidProp(prop, propDef) {
|
|
2303
|
+
if (propDef === true) {
|
|
2304
|
+
return true;
|
|
1813
2305
|
}
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
2306
|
+
if (typeof propDef === "function") {
|
|
2307
|
+
// Check if a value is constructed by some Constructor. Note that there is a
|
|
2308
|
+
// slight abuse of language: we want to consider primitive values as well.
|
|
2309
|
+
//
|
|
2310
|
+
// So, even though 1 is not an instance of Number, we want to consider that
|
|
2311
|
+
// it is valid.
|
|
2312
|
+
if (typeof prop === "object") {
|
|
2313
|
+
return prop instanceof propDef;
|
|
2314
|
+
}
|
|
2315
|
+
return typeof prop === propDef.name.toLowerCase();
|
|
1817
2316
|
}
|
|
1818
|
-
else if (
|
|
1819
|
-
|
|
1820
|
-
|
|
2317
|
+
else if (propDef instanceof Array) {
|
|
2318
|
+
// If this code is executed, this means that we want to check if a prop
|
|
2319
|
+
// matches at least one of its descriptor.
|
|
2320
|
+
let result = false;
|
|
2321
|
+
for (let i = 0, iLen = propDef.length; i < iLen; i++) {
|
|
2322
|
+
result = result || isValidProp(prop, propDef[i]);
|
|
2323
|
+
}
|
|
2324
|
+
return result;
|
|
1821
2325
|
}
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
block = value;
|
|
2326
|
+
// propsDef is an object
|
|
2327
|
+
if (propDef.optional && prop === undefined) {
|
|
2328
|
+
return true;
|
|
1826
2329
|
}
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
function bind(ctx, fn) {
|
|
1831
|
-
let component = ctx.__owl__.component;
|
|
1832
|
-
let boundFnMap = boundFunctions.get(component);
|
|
1833
|
-
if (!boundFnMap) {
|
|
1834
|
-
boundFnMap = new WeakMap();
|
|
1835
|
-
boundFunctions.set(component, boundFnMap);
|
|
2330
|
+
let result = propDef.type ? isValidProp(prop, propDef.type) : true;
|
|
2331
|
+
if (propDef.validate) {
|
|
2332
|
+
result = result && propDef.validate(prop);
|
|
1836
2333
|
}
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
2334
|
+
if (propDef.type === Array && propDef.element) {
|
|
2335
|
+
for (let i = 0, iLen = prop.length; i < iLen; i++) {
|
|
2336
|
+
result = result && isValidProp(prop[i], propDef.element);
|
|
2337
|
+
}
|
|
1841
2338
|
}
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
return (el) => {
|
|
1847
|
-
if (el) {
|
|
1848
|
-
count++;
|
|
1849
|
-
if (count > 1) {
|
|
1850
|
-
throw new Error("Cannot have 2 elements with same ref name at the same time");
|
|
1851
|
-
}
|
|
2339
|
+
if (propDef.type === Object && propDef.shape) {
|
|
2340
|
+
const shape = propDef.shape;
|
|
2341
|
+
for (let key in shape) {
|
|
2342
|
+
result = result && isValidProp(prop[key], shape[key]);
|
|
1852
2343
|
}
|
|
1853
|
-
if (
|
|
1854
|
-
|
|
2344
|
+
if (result) {
|
|
2345
|
+
for (let propName in prop) {
|
|
2346
|
+
if (!(propName in shape)) {
|
|
2347
|
+
throw new Error(`unknown prop '${propName}'`);
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
1855
2350
|
}
|
|
1856
|
-
}
|
|
2351
|
+
}
|
|
2352
|
+
return result;
|
|
2353
|
+
}
|
|
2354
|
+
|
|
2355
|
+
let currentNode = null;
|
|
2356
|
+
function getCurrent() {
|
|
2357
|
+
if (!currentNode) {
|
|
2358
|
+
throw new Error("No active component (a hook function should only be called in 'setup')");
|
|
2359
|
+
}
|
|
2360
|
+
return currentNode;
|
|
1857
2361
|
}
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
const
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
for (const mod of modifiers) {
|
|
1884
|
-
switch (mod) {
|
|
1885
|
-
case "self":
|
|
1886
|
-
selfMode = true;
|
|
1887
|
-
if (isSelf) {
|
|
1888
|
-
continue;
|
|
1889
|
-
}
|
|
1890
|
-
else {
|
|
1891
|
-
return stopped;
|
|
1892
|
-
}
|
|
1893
|
-
case "prevent":
|
|
1894
|
-
if ((selfMode && isSelf) || !selfMode)
|
|
1895
|
-
ev.preventDefault();
|
|
1896
|
-
continue;
|
|
1897
|
-
case "stop":
|
|
1898
|
-
if ((selfMode && isSelf) || !selfMode)
|
|
1899
|
-
ev.stopPropagation();
|
|
1900
|
-
stopped = true;
|
|
1901
|
-
continue;
|
|
1902
|
-
}
|
|
1903
|
-
}
|
|
1904
|
-
}
|
|
1905
|
-
// If handler is empty, the array slot 0 will also be empty, and data will not have the property 0
|
|
1906
|
-
// We check this rather than data[0] being truthy (or typeof function) so that it crashes
|
|
1907
|
-
// as expected when there is a handler expression that evaluates to a falsy value
|
|
1908
|
-
if (Object.hasOwnProperty.call(data, 0)) {
|
|
1909
|
-
const handler = data[0];
|
|
1910
|
-
if (typeof handler !== "function") {
|
|
1911
|
-
throw new Error(`Invalid handler (expected a function, received: '${handler}')`);
|
|
1912
|
-
}
|
|
1913
|
-
let node = data[1] ? data[1].__owl__ : null;
|
|
1914
|
-
if (node ? node.status === 1 /* MOUNTED */ : true) {
|
|
1915
|
-
handler.call(node ? node.component : null, ev);
|
|
1916
|
-
}
|
|
1917
|
-
}
|
|
1918
|
-
return stopped;
|
|
1919
|
-
};
|
|
1920
|
-
|
|
1921
|
-
// Maps fibers to thrown errors
|
|
1922
|
-
const fibersInError = new WeakMap();
|
|
1923
|
-
const nodeErrorHandlers = new WeakMap();
|
|
1924
|
-
function _handleError(node, error, isFirstRound = false) {
|
|
1925
|
-
if (!node) {
|
|
1926
|
-
return false;
|
|
1927
|
-
}
|
|
1928
|
-
const fiber = node.fiber;
|
|
1929
|
-
if (fiber) {
|
|
1930
|
-
fibersInError.set(fiber, error);
|
|
2362
|
+
function useComponent() {
|
|
2363
|
+
return currentNode.component;
|
|
2364
|
+
}
|
|
2365
|
+
// -----------------------------------------------------------------------------
|
|
2366
|
+
// Integration with reactivity system (useState)
|
|
2367
|
+
// -----------------------------------------------------------------------------
|
|
2368
|
+
const batchedRenderFunctions = new WeakMap();
|
|
2369
|
+
/**
|
|
2370
|
+
* Creates a reactive object that will be observed by the current component.
|
|
2371
|
+
* Reading data from the returned object (eg during rendering) will cause the
|
|
2372
|
+
* component to subscribe to that data and be rerendered when it changes.
|
|
2373
|
+
*
|
|
2374
|
+
* @param state the state to observe
|
|
2375
|
+
* @returns a reactive object that will cause the component to re-render on
|
|
2376
|
+
* relevant changes
|
|
2377
|
+
* @see reactive
|
|
2378
|
+
*/
|
|
2379
|
+
function useState(state) {
|
|
2380
|
+
const node = getCurrent();
|
|
2381
|
+
let render = batchedRenderFunctions.get(node);
|
|
2382
|
+
if (!render) {
|
|
2383
|
+
render = batched(node.render.bind(node));
|
|
2384
|
+
batchedRenderFunctions.set(node, render);
|
|
2385
|
+
// manual implementation of onWillDestroy to break cyclic dependency
|
|
2386
|
+
node.willDestroy.push(clearReactivesForCallback.bind(null, render));
|
|
1931
2387
|
}
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
try {
|
|
1938
|
-
errorHandlers[i](error);
|
|
1939
|
-
stopped = true;
|
|
1940
|
-
break;
|
|
1941
|
-
}
|
|
1942
|
-
catch (e) {
|
|
1943
|
-
error = e;
|
|
1944
|
-
}
|
|
1945
|
-
}
|
|
1946
|
-
if (stopped) {
|
|
1947
|
-
if (isFirstRound && fiber && fiber.node.fiber) {
|
|
1948
|
-
fiber.root.counter--;
|
|
1949
|
-
}
|
|
2388
|
+
return reactive(state, render);
|
|
2389
|
+
}
|
|
2390
|
+
function arePropsDifferent(props1, props2) {
|
|
2391
|
+
for (let k in props1) {
|
|
2392
|
+
if (props1[k] !== props2[k]) {
|
|
1950
2393
|
return true;
|
|
1951
2394
|
}
|
|
1952
2395
|
}
|
|
1953
|
-
return
|
|
2396
|
+
return Object.keys(props1).length !== Object.keys(props2).length;
|
|
1954
2397
|
}
|
|
1955
|
-
function
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
do {
|
|
1963
|
-
current.node.fiber = current;
|
|
1964
|
-
current = current.parent;
|
|
1965
|
-
} while (current);
|
|
1966
|
-
fibersInError.set(fiber.root, error);
|
|
1967
|
-
const handled = _handleError(node, error, true);
|
|
1968
|
-
if (!handled) {
|
|
1969
|
-
console.warn(`[Owl] Unhandled error. Destroying the root component`);
|
|
1970
|
-
try {
|
|
1971
|
-
node.app.destroy();
|
|
2398
|
+
function component(name, props, key, ctx, parent) {
|
|
2399
|
+
let node = ctx.children[key];
|
|
2400
|
+
let isDynamic = typeof name !== "string";
|
|
2401
|
+
if (node) {
|
|
2402
|
+
if (node.status < 1 /* MOUNTED */) {
|
|
2403
|
+
node.destroy();
|
|
2404
|
+
node = undefined;
|
|
1972
2405
|
}
|
|
1973
|
-
|
|
1974
|
-
|
|
2406
|
+
else if (node.status === 2 /* DESTROYED */) {
|
|
2407
|
+
node = undefined;
|
|
1975
2408
|
}
|
|
1976
2409
|
}
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
function makeChildFiber(node, parent) {
|
|
1980
|
-
let current = node.fiber;
|
|
1981
|
-
if (current) {
|
|
1982
|
-
cancelFibers(current.children);
|
|
1983
|
-
current.root = null;
|
|
2410
|
+
if (isDynamic && node && node.component.constructor !== name) {
|
|
2411
|
+
node = undefined;
|
|
1984
2412
|
}
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
let root = current.root;
|
|
1991
|
-
root.counter = root.counter + 1 - cancelFibers(current.children);
|
|
1992
|
-
current.children = [];
|
|
1993
|
-
current.bdom = null;
|
|
1994
|
-
if (fibersInError.has(current)) {
|
|
1995
|
-
fibersInError.delete(current);
|
|
1996
|
-
fibersInError.delete(root);
|
|
1997
|
-
current.appliedToDom = false;
|
|
2413
|
+
const parentFiber = ctx.fiber;
|
|
2414
|
+
if (node) {
|
|
2415
|
+
let shouldRender = node.forceNextRender;
|
|
2416
|
+
if (shouldRender) {
|
|
2417
|
+
node.forceNextRender = false;
|
|
1998
2418
|
}
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
if (node.patched.length) {
|
|
2006
|
-
fiber.patched.push(fiber);
|
|
2007
|
-
}
|
|
2008
|
-
return fiber;
|
|
2009
|
-
}
|
|
2010
|
-
/**
|
|
2011
|
-
* @returns number of not-yet rendered fibers cancelled
|
|
2012
|
-
*/
|
|
2013
|
-
function cancelFibers(fibers) {
|
|
2014
|
-
let result = 0;
|
|
2015
|
-
for (let fiber of fibers) {
|
|
2016
|
-
fiber.node.fiber = null;
|
|
2017
|
-
if (!fiber.bdom) {
|
|
2018
|
-
result++;
|
|
2419
|
+
else {
|
|
2420
|
+
const currentProps = node.component.props[TARGET];
|
|
2421
|
+
shouldRender = parentFiber.deep || arePropsDifferent(currentProps, props);
|
|
2422
|
+
}
|
|
2423
|
+
if (shouldRender) {
|
|
2424
|
+
node.updateAndRender(props, parentFiber);
|
|
2019
2425
|
}
|
|
2020
|
-
result += cancelFibers(fiber.children);
|
|
2021
2426
|
}
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
this.children = [];
|
|
2028
|
-
this.appliedToDom = false;
|
|
2029
|
-
this.deep = false;
|
|
2030
|
-
this.node = node;
|
|
2031
|
-
this.parent = parent;
|
|
2032
|
-
if (parent) {
|
|
2033
|
-
this.deep = parent.deep;
|
|
2034
|
-
const root = parent.root;
|
|
2035
|
-
root.counter++;
|
|
2036
|
-
this.root = root;
|
|
2037
|
-
parent.children.push(this);
|
|
2427
|
+
else {
|
|
2428
|
+
// new component
|
|
2429
|
+
let C;
|
|
2430
|
+
if (isDynamic) {
|
|
2431
|
+
C = name;
|
|
2038
2432
|
}
|
|
2039
2433
|
else {
|
|
2040
|
-
|
|
2434
|
+
C = parent.constructor.components[name];
|
|
2435
|
+
if (!C) {
|
|
2436
|
+
throw new Error(`Cannot find the definition of component "${name}"`);
|
|
2437
|
+
}
|
|
2041
2438
|
}
|
|
2439
|
+
node = new ComponentNode(C, props, ctx.app, ctx);
|
|
2440
|
+
ctx.children[key] = node;
|
|
2441
|
+
node.initiateRender(new Fiber(node, parentFiber));
|
|
2042
2442
|
}
|
|
2443
|
+
parentFiber.root.reachedChildren.add(node);
|
|
2444
|
+
return node;
|
|
2043
2445
|
}
|
|
2044
|
-
class
|
|
2045
|
-
constructor() {
|
|
2046
|
-
|
|
2047
|
-
this.
|
|
2048
|
-
|
|
2446
|
+
class ComponentNode {
|
|
2447
|
+
constructor(C, props, app, parent) {
|
|
2448
|
+
this.fiber = null;
|
|
2449
|
+
this.bdom = null;
|
|
2450
|
+
this.status = 0 /* NEW */;
|
|
2451
|
+
this.forceNextRender = false;
|
|
2452
|
+
this.children = Object.create(null);
|
|
2453
|
+
this.refs = {};
|
|
2454
|
+
this.willStart = [];
|
|
2455
|
+
this.willUpdateProps = [];
|
|
2456
|
+
this.willUnmount = [];
|
|
2457
|
+
this.mounted = [];
|
|
2049
2458
|
this.willPatch = [];
|
|
2050
2459
|
this.patched = [];
|
|
2051
|
-
this.
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
this.
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
const
|
|
2058
|
-
this.
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
// UI may have been rendered, then deleted in a followup rendering, and we
|
|
2065
|
-
// do not want to call onWillPatch in that case.
|
|
2066
|
-
let node = current.node;
|
|
2067
|
-
if (node.fiber === current) {
|
|
2068
|
-
const component = node.component;
|
|
2069
|
-
for (let cb of node.willPatch) {
|
|
2070
|
-
cb.call(component);
|
|
2071
|
-
}
|
|
2072
|
-
}
|
|
2073
|
-
}
|
|
2074
|
-
current = undefined;
|
|
2075
|
-
// Step 2: patching the dom
|
|
2076
|
-
node._patch();
|
|
2077
|
-
this.locked = false;
|
|
2078
|
-
// Step 4: calling all mounted lifecycle hooks
|
|
2079
|
-
let mountedFibers = this.mounted;
|
|
2080
|
-
while ((current = mountedFibers.pop())) {
|
|
2081
|
-
current = current;
|
|
2082
|
-
if (current.appliedToDom) {
|
|
2083
|
-
for (let cb of current.node.mounted) {
|
|
2084
|
-
cb();
|
|
2085
|
-
}
|
|
2086
|
-
}
|
|
2087
|
-
}
|
|
2088
|
-
// Step 5: calling all patched hooks
|
|
2089
|
-
let patchedFibers = this.patched;
|
|
2090
|
-
while ((current = patchedFibers.pop())) {
|
|
2091
|
-
current = current;
|
|
2092
|
-
if (current.appliedToDom) {
|
|
2093
|
-
for (let cb of current.node.patched) {
|
|
2094
|
-
cb();
|
|
2095
|
-
}
|
|
2096
|
-
}
|
|
2097
|
-
}
|
|
2098
|
-
}
|
|
2099
|
-
catch (e) {
|
|
2100
|
-
this.locked = false;
|
|
2101
|
-
handleError({ fiber: current || this, error: e });
|
|
2102
|
-
}
|
|
2103
|
-
}
|
|
2104
|
-
}
|
|
2105
|
-
class MountFiber extends RootFiber {
|
|
2106
|
-
constructor(node, target, options = {}) {
|
|
2107
|
-
super(node, null);
|
|
2108
|
-
this.target = target;
|
|
2109
|
-
this.position = options.position || "last-child";
|
|
2110
|
-
}
|
|
2111
|
-
complete() {
|
|
2112
|
-
let current = this;
|
|
2113
|
-
try {
|
|
2114
|
-
const node = this.node;
|
|
2115
|
-
node.app.constructor.validateTarget(this.target);
|
|
2116
|
-
if (node.bdom) {
|
|
2117
|
-
// this is a complicated situation: if we mount a fiber with an existing
|
|
2118
|
-
// bdom, this means that this same fiber was already completed, mounted,
|
|
2119
|
-
// but a crash occurred in some mounted hook. Then, it was handled and
|
|
2120
|
-
// the new rendering is being applied.
|
|
2121
|
-
node.updateDom();
|
|
2122
|
-
}
|
|
2123
|
-
else {
|
|
2124
|
-
node.bdom = this.bdom;
|
|
2125
|
-
if (this.position === "last-child" || this.target.childNodes.length === 0) {
|
|
2126
|
-
mount$1(node.bdom, this.target);
|
|
2127
|
-
}
|
|
2128
|
-
else {
|
|
2129
|
-
const firstChild = this.target.childNodes[0];
|
|
2130
|
-
mount$1(node.bdom, this.target, firstChild);
|
|
2131
|
-
}
|
|
2132
|
-
}
|
|
2133
|
-
// unregistering the fiber before mounted since it can do another render
|
|
2134
|
-
// and that the current rendering is obviously completed
|
|
2135
|
-
node.fiber = null;
|
|
2136
|
-
node.status = 1 /* MOUNTED */;
|
|
2137
|
-
this.appliedToDom = true;
|
|
2138
|
-
let mountedFibers = this.mounted;
|
|
2139
|
-
while ((current = mountedFibers.pop())) {
|
|
2140
|
-
if (current.appliedToDom) {
|
|
2141
|
-
for (let cb of current.node.mounted) {
|
|
2142
|
-
cb();
|
|
2143
|
-
}
|
|
2144
|
-
}
|
|
2145
|
-
}
|
|
2146
|
-
}
|
|
2147
|
-
catch (e) {
|
|
2148
|
-
handleError({ fiber: current, error: e });
|
|
2149
|
-
}
|
|
2150
|
-
}
|
|
2151
|
-
}
|
|
2152
|
-
|
|
2153
|
-
let currentNode = null;
|
|
2154
|
-
function getCurrent() {
|
|
2155
|
-
if (!currentNode) {
|
|
2156
|
-
throw new Error("No active component (a hook function should only be called in 'setup')");
|
|
2157
|
-
}
|
|
2158
|
-
return currentNode;
|
|
2159
|
-
}
|
|
2160
|
-
function useComponent() {
|
|
2161
|
-
return currentNode.component;
|
|
2162
|
-
}
|
|
2163
|
-
// -----------------------------------------------------------------------------
|
|
2164
|
-
// Integration with reactivity system (useState)
|
|
2165
|
-
// -----------------------------------------------------------------------------
|
|
2166
|
-
const batchedRenderFunctions = new WeakMap();
|
|
2167
|
-
/**
|
|
2168
|
-
* Creates a reactive object that will be observed by the current component.
|
|
2169
|
-
* Reading data from the returned object (eg during rendering) will cause the
|
|
2170
|
-
* component to subscribe to that data and be rerendered when it changes.
|
|
2171
|
-
*
|
|
2172
|
-
* @param state the state to observe
|
|
2173
|
-
* @returns a reactive object that will cause the component to re-render on
|
|
2174
|
-
* relevant changes
|
|
2175
|
-
* @see reactive
|
|
2176
|
-
*/
|
|
2177
|
-
function useState(state) {
|
|
2178
|
-
const node = getCurrent();
|
|
2179
|
-
let render = batchedRenderFunctions.get(node);
|
|
2180
|
-
if (!render) {
|
|
2181
|
-
render = batched(node.render.bind(node));
|
|
2182
|
-
batchedRenderFunctions.set(node, render);
|
|
2183
|
-
// manual implementation of onWillDestroy to break cyclic dependency
|
|
2184
|
-
node.willDestroy.push(clearReactivesForCallback.bind(null, render));
|
|
2185
|
-
}
|
|
2186
|
-
return reactive(state, render);
|
|
2187
|
-
}
|
|
2188
|
-
function arePropsDifferent(props1, props2) {
|
|
2189
|
-
for (let k in props1) {
|
|
2190
|
-
if (props1[k] !== props2[k]) {
|
|
2191
|
-
return true;
|
|
2192
|
-
}
|
|
2193
|
-
}
|
|
2194
|
-
return Object.keys(props1).length !== Object.keys(props2).length;
|
|
2195
|
-
}
|
|
2196
|
-
function component(name, props, key, ctx, parent) {
|
|
2197
|
-
let node = ctx.children[key];
|
|
2198
|
-
let isDynamic = typeof name !== "string";
|
|
2199
|
-
if (node) {
|
|
2200
|
-
if (node.status < 1 /* MOUNTED */) {
|
|
2201
|
-
node.destroy();
|
|
2202
|
-
node = undefined;
|
|
2203
|
-
}
|
|
2204
|
-
else if (node.status === 2 /* DESTROYED */) {
|
|
2205
|
-
node = undefined;
|
|
2206
|
-
}
|
|
2207
|
-
}
|
|
2208
|
-
if (isDynamic && node && node.component.constructor !== name) {
|
|
2209
|
-
node = undefined;
|
|
2210
|
-
}
|
|
2211
|
-
const parentFiber = ctx.fiber;
|
|
2212
|
-
if (node) {
|
|
2213
|
-
const currentProps = node.component.props[TARGET];
|
|
2214
|
-
if (parentFiber.deep || arePropsDifferent(currentProps, props)) {
|
|
2215
|
-
node.updateAndRender(props, parentFiber);
|
|
2216
|
-
}
|
|
2217
|
-
}
|
|
2218
|
-
else {
|
|
2219
|
-
// new component
|
|
2220
|
-
let C;
|
|
2221
|
-
if (isDynamic) {
|
|
2222
|
-
C = name;
|
|
2223
|
-
}
|
|
2224
|
-
else {
|
|
2225
|
-
C = parent.constructor.components[name];
|
|
2226
|
-
if (!C) {
|
|
2227
|
-
throw new Error(`Cannot find the definition of component "${name}"`);
|
|
2228
|
-
}
|
|
2229
|
-
}
|
|
2230
|
-
node = new ComponentNode(C, props, ctx.app, ctx);
|
|
2231
|
-
ctx.children[key] = node;
|
|
2232
|
-
node.initiateRender(new Fiber(node, parentFiber));
|
|
2233
|
-
}
|
|
2234
|
-
return node;
|
|
2235
|
-
}
|
|
2236
|
-
class ComponentNode {
|
|
2237
|
-
constructor(C, props, app, parent) {
|
|
2238
|
-
this.fiber = null;
|
|
2239
|
-
this.bdom = null;
|
|
2240
|
-
this.status = 0 /* NEW */;
|
|
2241
|
-
this.children = Object.create(null);
|
|
2242
|
-
this.refs = {};
|
|
2243
|
-
this.willStart = [];
|
|
2244
|
-
this.willUpdateProps = [];
|
|
2245
|
-
this.willUnmount = [];
|
|
2246
|
-
this.mounted = [];
|
|
2247
|
-
this.willPatch = [];
|
|
2248
|
-
this.patched = [];
|
|
2249
|
-
this.willDestroy = [];
|
|
2250
|
-
currentNode = this;
|
|
2251
|
-
this.app = app;
|
|
2252
|
-
this.parent = parent || null;
|
|
2253
|
-
this.level = parent ? parent.level + 1 : 0;
|
|
2254
|
-
applyDefaultProps(props, C);
|
|
2255
|
-
const env = (parent && parent.childEnv) || app.env;
|
|
2256
|
-
this.childEnv = env;
|
|
2257
|
-
props = useState(props);
|
|
2258
|
-
this.component = new C(props, env, this);
|
|
2259
|
-
this.renderFn = app.getTemplate(C.template).bind(this.component, this.component, this);
|
|
2260
|
-
this.component.setup();
|
|
2261
|
-
currentNode = null;
|
|
2460
|
+
this.willDestroy = [];
|
|
2461
|
+
currentNode = this;
|
|
2462
|
+
this.app = app;
|
|
2463
|
+
this.parent = parent || null;
|
|
2464
|
+
this.level = parent ? parent.level + 1 : 0;
|
|
2465
|
+
applyDefaultProps(props, C);
|
|
2466
|
+
const env = (parent && parent.childEnv) || app.env;
|
|
2467
|
+
this.childEnv = env;
|
|
2468
|
+
props = useState(props);
|
|
2469
|
+
this.component = new C(props, env, this);
|
|
2470
|
+
this.renderFn = app.getTemplate(C.template).bind(this.component, this.component, this);
|
|
2471
|
+
this.component.setup();
|
|
2472
|
+
currentNode = null;
|
|
2262
2473
|
}
|
|
2263
2474
|
mountComponent(target, options) {
|
|
2264
2475
|
const fiber = new MountFiber(this, target, options);
|
|
@@ -2279,7 +2490,7 @@ class ComponentNode {
|
|
|
2279
2490
|
return;
|
|
2280
2491
|
}
|
|
2281
2492
|
if (this.status === 0 /* NEW */ && this.fiber === fiber) {
|
|
2282
|
-
|
|
2493
|
+
fiber.render();
|
|
2283
2494
|
}
|
|
2284
2495
|
}
|
|
2285
2496
|
async render(deep = false) {
|
|
@@ -2323,16 +2534,7 @@ class ComponentNode {
|
|
|
2323
2534
|
// embedded in a rendering coming from above, so the fiber will be rendered
|
|
2324
2535
|
// in the next microtick anyway, so we should not render it again.
|
|
2325
2536
|
if (this.fiber === fiber && (current || !fiber.parent)) {
|
|
2326
|
-
|
|
2327
|
-
}
|
|
2328
|
-
}
|
|
2329
|
-
_render(fiber) {
|
|
2330
|
-
try {
|
|
2331
|
-
fiber.bdom = this.renderFn();
|
|
2332
|
-
fiber.root.counter--;
|
|
2333
|
-
}
|
|
2334
|
-
catch (e) {
|
|
2335
|
-
handleError({ node: this, error: e });
|
|
2537
|
+
fiber.render();
|
|
2336
2538
|
}
|
|
2337
2539
|
}
|
|
2338
2540
|
destroy() {
|
|
@@ -2372,7 +2574,7 @@ class ComponentNode {
|
|
|
2372
2574
|
return;
|
|
2373
2575
|
}
|
|
2374
2576
|
component.props = props;
|
|
2375
|
-
|
|
2577
|
+
fiber.render();
|
|
2376
2578
|
const parentRoot = parentFiber.root;
|
|
2377
2579
|
if (this.willPatch.length) {
|
|
2378
2580
|
parentRoot.willPatch.push(fiber);
|
|
@@ -2460,98 +2662,76 @@ class ComponentNode {
|
|
|
2460
2662
|
}
|
|
2461
2663
|
}
|
|
2462
2664
|
}
|
|
2665
|
+
// ---------------------------------------------------------------------------
|
|
2666
|
+
// Some debug helpers
|
|
2667
|
+
// ---------------------------------------------------------------------------
|
|
2668
|
+
get name() {
|
|
2669
|
+
return this.component.constructor.name;
|
|
2670
|
+
}
|
|
2671
|
+
get subscriptions() {
|
|
2672
|
+
const render = batchedRenderFunctions.get(this);
|
|
2673
|
+
return render ? getSubscriptions(render) : [];
|
|
2674
|
+
}
|
|
2463
2675
|
}
|
|
2464
2676
|
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2677
|
+
// -----------------------------------------------------------------------------
|
|
2678
|
+
// Scheduler
|
|
2679
|
+
// -----------------------------------------------------------------------------
|
|
2680
|
+
class Scheduler {
|
|
2681
|
+
constructor() {
|
|
2682
|
+
this.tasks = new Set();
|
|
2683
|
+
this.frame = 0;
|
|
2684
|
+
this.shouldClear = false;
|
|
2685
|
+
this.requestAnimationFrame = Scheduler.requestAnimationFrame;
|
|
2686
|
+
}
|
|
2687
|
+
addFiber(fiber) {
|
|
2688
|
+
this.tasks.add(fiber.root);
|
|
2689
|
+
}
|
|
2690
|
+
/**
|
|
2691
|
+
* Process all current tasks. This only applies to the fibers that are ready.
|
|
2692
|
+
* Other tasks are left unchanged.
|
|
2693
|
+
*/
|
|
2694
|
+
flush() {
|
|
2695
|
+
if (this.frame === 0) {
|
|
2696
|
+
this.frame = this.requestAnimationFrame(() => {
|
|
2697
|
+
this.frame = 0;
|
|
2698
|
+
this.tasks.forEach((fiber) => this.processFiber(fiber));
|
|
2699
|
+
if (this.shouldClear) {
|
|
2700
|
+
this.shouldClear = false;
|
|
2701
|
+
for (let task of this.tasks) {
|
|
2702
|
+
if (task.node.status === 2 /* DESTROYED */) {
|
|
2703
|
+
this.tasks.delete(task);
|
|
2704
|
+
}
|
|
2475
2705
|
}
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
}
|
|
2479
|
-
return result;
|
|
2706
|
+
}
|
|
2707
|
+
});
|
|
2480
2708
|
}
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2709
|
+
}
|
|
2710
|
+
processFiber(fiber) {
|
|
2711
|
+
if (fiber.root !== fiber) {
|
|
2712
|
+
this.tasks.delete(fiber);
|
|
2713
|
+
return;
|
|
2714
|
+
}
|
|
2715
|
+
const hasError = fibersInError.has(fiber);
|
|
2716
|
+
if (hasError && fiber.counter !== 0) {
|
|
2717
|
+
this.tasks.delete(fiber);
|
|
2718
|
+
return;
|
|
2719
|
+
}
|
|
2720
|
+
if (fiber.node.status === 2 /* DESTROYED */) {
|
|
2721
|
+
this.tasks.delete(fiber);
|
|
2722
|
+
return;
|
|
2723
|
+
}
|
|
2724
|
+
if (fiber.counter === 0) {
|
|
2725
|
+
if (!hasError) {
|
|
2726
|
+
fiber.complete();
|
|
2484
2727
|
}
|
|
2485
|
-
|
|
2728
|
+
this.tasks.delete(fiber);
|
|
2486
2729
|
}
|
|
2487
|
-
}
|
|
2730
|
+
}
|
|
2488
2731
|
}
|
|
2489
|
-
//
|
|
2490
|
-
//
|
|
2491
|
-
|
|
2492
|
-
function onWillStart(fn) {
|
|
2493
|
-
const node = getCurrent();
|
|
2494
|
-
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
2495
|
-
node.willStart.push(decorate(fn.bind(node.component), "onWillStart"));
|
|
2496
|
-
}
|
|
2497
|
-
function onWillUpdateProps(fn) {
|
|
2498
|
-
const node = getCurrent();
|
|
2499
|
-
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
2500
|
-
node.willUpdateProps.push(decorate(fn.bind(node.component), "onWillUpdateProps"));
|
|
2501
|
-
}
|
|
2502
|
-
function onMounted(fn) {
|
|
2503
|
-
const node = getCurrent();
|
|
2504
|
-
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
2505
|
-
node.mounted.push(decorate(fn.bind(node.component), "onMounted"));
|
|
2506
|
-
}
|
|
2507
|
-
function onWillPatch(fn) {
|
|
2508
|
-
const node = getCurrent();
|
|
2509
|
-
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
2510
|
-
node.willPatch.unshift(decorate(fn.bind(node.component), "onWillPatch"));
|
|
2511
|
-
}
|
|
2512
|
-
function onPatched(fn) {
|
|
2513
|
-
const node = getCurrent();
|
|
2514
|
-
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
2515
|
-
node.patched.push(decorate(fn.bind(node.component), "onPatched"));
|
|
2516
|
-
}
|
|
2517
|
-
function onWillUnmount(fn) {
|
|
2518
|
-
const node = getCurrent();
|
|
2519
|
-
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
2520
|
-
node.willUnmount.unshift(decorate(fn.bind(node.component), "onWillUnmount"));
|
|
2521
|
-
}
|
|
2522
|
-
function onWillDestroy(fn) {
|
|
2523
|
-
const node = getCurrent();
|
|
2524
|
-
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
2525
|
-
node.willDestroy.push(decorate(fn.bind(node.component), "onWillDestroy"));
|
|
2526
|
-
}
|
|
2527
|
-
function onWillRender(fn) {
|
|
2528
|
-
const node = getCurrent();
|
|
2529
|
-
const renderFn = node.renderFn;
|
|
2530
|
-
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
2531
|
-
node.renderFn = decorate(() => {
|
|
2532
|
-
fn.call(node.component);
|
|
2533
|
-
return renderFn();
|
|
2534
|
-
}, "onWillRender");
|
|
2535
|
-
}
|
|
2536
|
-
function onRendered(fn) {
|
|
2537
|
-
const node = getCurrent();
|
|
2538
|
-
const renderFn = node.renderFn;
|
|
2539
|
-
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
2540
|
-
node.renderFn = decorate(() => {
|
|
2541
|
-
const result = renderFn();
|
|
2542
|
-
fn.call(node.component);
|
|
2543
|
-
return result;
|
|
2544
|
-
}, "onRendered");
|
|
2545
|
-
}
|
|
2546
|
-
function onError(callback) {
|
|
2547
|
-
const node = getCurrent();
|
|
2548
|
-
let handlers = nodeErrorHandlers.get(node);
|
|
2549
|
-
if (!handlers) {
|
|
2550
|
-
handlers = [];
|
|
2551
|
-
nodeErrorHandlers.set(node, handlers);
|
|
2552
|
-
}
|
|
2553
|
-
handlers.push(callback.bind(node.component));
|
|
2554
|
-
}
|
|
2732
|
+
// capture the value of requestAnimationFrame as soon as possible, to avoid
|
|
2733
|
+
// interactions with other code, such as test frameworks that override them
|
|
2734
|
+
Scheduler.requestAnimationFrame = window.requestAnimationFrame.bind(window);
|
|
2555
2735
|
|
|
2556
2736
|
/**
|
|
2557
2737
|
* Owl QWeb Expression Parser
|
|
@@ -2705,24 +2885,31 @@ const TOKENIZERS = [
|
|
|
2705
2885
|
function tokenize(expr) {
|
|
2706
2886
|
const result = [];
|
|
2707
2887
|
let token = true;
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2888
|
+
let error;
|
|
2889
|
+
let current = expr;
|
|
2890
|
+
try {
|
|
2891
|
+
while (token) {
|
|
2892
|
+
current = current.trim();
|
|
2893
|
+
if (current) {
|
|
2894
|
+
for (let tokenizer of TOKENIZERS) {
|
|
2895
|
+
token = tokenizer(current);
|
|
2896
|
+
if (token) {
|
|
2897
|
+
result.push(token);
|
|
2898
|
+
current = current.slice(token.size || token.value.length);
|
|
2899
|
+
break;
|
|
2900
|
+
}
|
|
2717
2901
|
}
|
|
2718
2902
|
}
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2903
|
+
else {
|
|
2904
|
+
token = false;
|
|
2905
|
+
}
|
|
2722
2906
|
}
|
|
2723
2907
|
}
|
|
2724
|
-
|
|
2725
|
-
|
|
2908
|
+
catch (e) {
|
|
2909
|
+
error = e; // Silence all errors and throw a generic error below
|
|
2910
|
+
}
|
|
2911
|
+
if (current.length || error) {
|
|
2912
|
+
throw new Error(`Tokenizer error: could not tokenize \`${expr}\``);
|
|
2726
2913
|
}
|
|
2727
2914
|
return result;
|
|
2728
2915
|
}
|
|
@@ -2927,7 +3114,7 @@ function createContext(parentCtx, params) {
|
|
|
2927
3114
|
}, params);
|
|
2928
3115
|
}
|
|
2929
3116
|
class CodeTarget {
|
|
2930
|
-
constructor(name) {
|
|
3117
|
+
constructor(name, on) {
|
|
2931
3118
|
this.indentLevel = 0;
|
|
2932
3119
|
this.loopLevel = 0;
|
|
2933
3120
|
this.code = [];
|
|
@@ -2938,6 +3125,7 @@ class CodeTarget {
|
|
|
2938
3125
|
this.refInfo = {};
|
|
2939
3126
|
this.shouldProtectScope = false;
|
|
2940
3127
|
this.name = name;
|
|
3128
|
+
this.on = on || null;
|
|
2941
3129
|
}
|
|
2942
3130
|
addLine(line, idx) {
|
|
2943
3131
|
const prefix = new Array(this.indentLevel + 2).join(" ");
|
|
@@ -2987,7 +3175,7 @@ class CodeGenerator {
|
|
|
2987
3175
|
this.targets = [];
|
|
2988
3176
|
this.target = new CodeTarget("template");
|
|
2989
3177
|
this.translatableAttributes = TRANSLATABLE_ATTRS;
|
|
2990
|
-
this.
|
|
3178
|
+
this.staticDefs = [];
|
|
2991
3179
|
this.helpers = new Set();
|
|
2992
3180
|
this.translateFn = options.translateFn || ((s) => s);
|
|
2993
3181
|
if (options.translatableAttributes) {
|
|
@@ -3030,8 +3218,8 @@ class CodeGenerator {
|
|
|
3030
3218
|
if (this.templateName) {
|
|
3031
3219
|
mainCode.push(`// Template name: "${this.templateName}"`);
|
|
3032
3220
|
}
|
|
3033
|
-
for (let { id,
|
|
3034
|
-
mainCode.push(`const ${id} =
|
|
3221
|
+
for (let { id, expr } of this.staticDefs) {
|
|
3222
|
+
mainCode.push(`const ${id} = ${expr};`);
|
|
3035
3223
|
}
|
|
3036
3224
|
// define all blocks
|
|
3037
3225
|
if (this.blocks.length) {
|
|
@@ -3067,19 +3255,21 @@ class CodeGenerator {
|
|
|
3067
3255
|
}
|
|
3068
3256
|
return code;
|
|
3069
3257
|
}
|
|
3070
|
-
compileInNewTarget(prefix, ast, ctx) {
|
|
3258
|
+
compileInNewTarget(prefix, ast, ctx, on) {
|
|
3071
3259
|
const name = this.generateId(prefix);
|
|
3072
3260
|
const initialTarget = this.target;
|
|
3073
|
-
const target = new CodeTarget(name);
|
|
3261
|
+
const target = new CodeTarget(name, on);
|
|
3074
3262
|
this.targets.push(target);
|
|
3075
3263
|
this.target = target;
|
|
3076
|
-
|
|
3077
|
-
this.compileAST(ast, subCtx);
|
|
3264
|
+
this.compileAST(ast, createContext(ctx));
|
|
3078
3265
|
this.target = initialTarget;
|
|
3079
3266
|
return name;
|
|
3080
3267
|
}
|
|
3081
|
-
addLine(line) {
|
|
3082
|
-
this.target.addLine(line);
|
|
3268
|
+
addLine(line, idx) {
|
|
3269
|
+
this.target.addLine(line, idx);
|
|
3270
|
+
}
|
|
3271
|
+
define(varName, expr) {
|
|
3272
|
+
this.addLine(`const ${varName} = ${expr};`);
|
|
3083
3273
|
}
|
|
3084
3274
|
generateId(prefix = "") {
|
|
3085
3275
|
this.ids[prefix] = (this.ids[prefix] || 0) + 1;
|
|
@@ -3121,10 +3311,13 @@ class CodeGenerator {
|
|
|
3121
3311
|
blockExpr = `toggler(${tKeyExpr}, ${blockExpr})`;
|
|
3122
3312
|
}
|
|
3123
3313
|
if (block.isRoot && !ctx.preventRoot) {
|
|
3314
|
+
if (this.target.on) {
|
|
3315
|
+
blockExpr = this.wrapWithEventCatcher(blockExpr, this.target.on);
|
|
3316
|
+
}
|
|
3124
3317
|
this.addLine(`return ${blockExpr};`);
|
|
3125
3318
|
}
|
|
3126
3319
|
else {
|
|
3127
|
-
this.
|
|
3320
|
+
this.define(block.varName, blockExpr);
|
|
3128
3321
|
}
|
|
3129
3322
|
}
|
|
3130
3323
|
/**
|
|
@@ -3152,7 +3345,7 @@ class CodeGenerator {
|
|
|
3152
3345
|
if (!mapping.has(tok.varName)) {
|
|
3153
3346
|
const varId = this.generateId("v");
|
|
3154
3347
|
mapping.set(tok.varName, varId);
|
|
3155
|
-
this.
|
|
3348
|
+
this.define(varId, tok.value);
|
|
3156
3349
|
}
|
|
3157
3350
|
tok.value = mapping.get(tok.varName);
|
|
3158
3351
|
}
|
|
@@ -3291,7 +3484,7 @@ class CodeGenerator {
|
|
|
3291
3484
|
this.blocks.push(block);
|
|
3292
3485
|
if (ast.dynamicTag) {
|
|
3293
3486
|
const tagExpr = this.generateId("tag");
|
|
3294
|
-
this.
|
|
3487
|
+
this.define(tagExpr, compileExpr(ast.dynamicTag));
|
|
3295
3488
|
block.dynamicTagName = tagExpr;
|
|
3296
3489
|
}
|
|
3297
3490
|
}
|
|
@@ -3373,10 +3566,10 @@ class CodeGenerator {
|
|
|
3373
3566
|
const { hasDynamicChildren, baseExpr, expr, eventType, shouldNumberize, shouldTrim, targetAttr, specialInitTargetAttr, } = ast.model;
|
|
3374
3567
|
const baseExpression = compileExpr(baseExpr);
|
|
3375
3568
|
const bExprId = this.generateId("bExpr");
|
|
3376
|
-
this.
|
|
3569
|
+
this.define(bExprId, baseExpression);
|
|
3377
3570
|
const expression = compileExpr(expr);
|
|
3378
3571
|
const exprId = this.generateId("expr");
|
|
3379
|
-
this.
|
|
3572
|
+
this.define(exprId, expression);
|
|
3380
3573
|
const fullExpression = `${bExprId}[${exprId}]`;
|
|
3381
3574
|
let idx;
|
|
3382
3575
|
if (specialInitTargetAttr) {
|
|
@@ -3386,7 +3579,7 @@ class CodeGenerator {
|
|
|
3386
3579
|
else if (hasDynamicChildren) {
|
|
3387
3580
|
const bValueId = this.generateId("bValue");
|
|
3388
3581
|
tModelSelectedExpr = `${bValueId}`;
|
|
3389
|
-
this.
|
|
3582
|
+
this.define(tModelSelectedExpr, fullExpression);
|
|
3390
3583
|
}
|
|
3391
3584
|
else {
|
|
3392
3585
|
idx = block.insertData(`${fullExpression}`, "attr");
|
|
@@ -3434,14 +3627,14 @@ class CodeGenerator {
|
|
|
3434
3627
|
const children = block.children.slice();
|
|
3435
3628
|
let current = children.shift();
|
|
3436
3629
|
for (let i = codeIdx; i < code.length; i++) {
|
|
3437
|
-
if (code[i].trimStart().startsWith(`
|
|
3438
|
-
code[i] = code[i].replace(`
|
|
3630
|
+
if (code[i].trimStart().startsWith(`const ${current.varName} `)) {
|
|
3631
|
+
code[i] = code[i].replace(`const ${current.varName}`, current.varName);
|
|
3439
3632
|
current = children.shift();
|
|
3440
3633
|
if (!current)
|
|
3441
3634
|
break;
|
|
3442
3635
|
}
|
|
3443
3636
|
}
|
|
3444
|
-
this.
|
|
3637
|
+
this.addLine(`let ${block.children.map((c) => c.varName)};`, codeIdx);
|
|
3445
3638
|
}
|
|
3446
3639
|
}
|
|
3447
3640
|
}
|
|
@@ -3529,14 +3722,14 @@ class CodeGenerator {
|
|
|
3529
3722
|
const children = block.children.slice();
|
|
3530
3723
|
let current = children.shift();
|
|
3531
3724
|
for (let i = codeIdx; i < code.length; i++) {
|
|
3532
|
-
if (code[i].trimStart().startsWith(`
|
|
3533
|
-
code[i] = code[i].replace(`
|
|
3725
|
+
if (code[i].trimStart().startsWith(`const ${current.varName} `)) {
|
|
3726
|
+
code[i] = code[i].replace(`const ${current.varName}`, current.varName);
|
|
3534
3727
|
current = children.shift();
|
|
3535
3728
|
if (!current)
|
|
3536
3729
|
break;
|
|
3537
3730
|
}
|
|
3538
3731
|
}
|
|
3539
|
-
this.
|
|
3732
|
+
this.addLine(`let ${block.children.map((c) => c.varName)};`, codeIdx);
|
|
3540
3733
|
}
|
|
3541
3734
|
// note: this part is duplicated from end of compilemulti:
|
|
3542
3735
|
const args = block.children.map((c) => c.varName).join(", ");
|
|
@@ -3557,10 +3750,10 @@ class CodeGenerator {
|
|
|
3557
3750
|
const l = `l_block${block.id}`;
|
|
3558
3751
|
const c = `c_block${block.id}`;
|
|
3559
3752
|
this.helpers.add("prepareList");
|
|
3560
|
-
this.
|
|
3753
|
+
this.define(`[${keys}, ${vals}, ${l}, ${c}]`, `prepareList(${compileExpr(ast.collection)});`);
|
|
3561
3754
|
// Throw errors on duplicate keys in dev mode
|
|
3562
3755
|
if (this.dev) {
|
|
3563
|
-
this.
|
|
3756
|
+
this.define(`keys${block.id}`, `new Set()`);
|
|
3564
3757
|
}
|
|
3565
3758
|
this.addLine(`for (let ${loopVar} = 0; ${loopVar} < ${l}; ${loopVar}++) {`);
|
|
3566
3759
|
this.target.indentLevel++;
|
|
@@ -3577,7 +3770,7 @@ class CodeGenerator {
|
|
|
3577
3770
|
if (!ast.hasNoValue) {
|
|
3578
3771
|
this.addLine(`ctx[\`${ast.elem}_value\`] = ${keys}[${loopVar}];`);
|
|
3579
3772
|
}
|
|
3580
|
-
this.
|
|
3773
|
+
this.define(`key${this.target.loopLevel}`, ast.key ? compileExpr(ast.key) : loopVar);
|
|
3581
3774
|
if (this.dev) {
|
|
3582
3775
|
// Throw error on duplicate keys in dev mode
|
|
3583
3776
|
this.addLine(`if (keys${block.id}.has(key${this.target.loopLevel})) { throw new Error(\`Got duplicate key in t-foreach: \${key${this.target.loopLevel}}\`)}`);
|
|
@@ -3587,8 +3780,8 @@ class CodeGenerator {
|
|
|
3587
3780
|
if (ast.memo) {
|
|
3588
3781
|
this.target.hasCache = true;
|
|
3589
3782
|
id = this.generateId();
|
|
3590
|
-
this.
|
|
3591
|
-
this.
|
|
3783
|
+
this.define(`memo${id}`, compileExpr(ast.memo));
|
|
3784
|
+
this.define(`vnode${id}`, `cache[key${this.target.loopLevel}];`);
|
|
3592
3785
|
this.addLine(`if (vnode${id}) {`);
|
|
3593
3786
|
this.target.indentLevel++;
|
|
3594
3787
|
this.addLine(`if (shallowEqual(vnode${id}.memo, memo${id})) {`);
|
|
@@ -3616,7 +3809,7 @@ class CodeGenerator {
|
|
|
3616
3809
|
}
|
|
3617
3810
|
compileTKey(ast, ctx) {
|
|
3618
3811
|
const tKeyExpr = this.generateId("tKey_");
|
|
3619
|
-
this.
|
|
3812
|
+
this.define(tKeyExpr, compileExpr(ast.expr));
|
|
3620
3813
|
ctx = createContext(ctx, {
|
|
3621
3814
|
tKeyExpr,
|
|
3622
3815
|
block: ctx.block,
|
|
@@ -3661,14 +3854,14 @@ class CodeGenerator {
|
|
|
3661
3854
|
const children = block.children.slice();
|
|
3662
3855
|
let current = children.shift();
|
|
3663
3856
|
for (let i = codeIdx; i < code.length; i++) {
|
|
3664
|
-
if (code[i].trimStart().startsWith(`
|
|
3665
|
-
code[i] = code[i].replace(`
|
|
3857
|
+
if (code[i].trimStart().startsWith(`const ${current.varName} `)) {
|
|
3858
|
+
code[i] = code[i].replace(`const ${current.varName}`, current.varName);
|
|
3666
3859
|
current = children.shift();
|
|
3667
3860
|
if (!current)
|
|
3668
3861
|
break;
|
|
3669
3862
|
}
|
|
3670
3863
|
}
|
|
3671
|
-
this.
|
|
3864
|
+
this.addLine(`let ${block.children.map((c) => c.varName)};`, codeIdx);
|
|
3672
3865
|
}
|
|
3673
3866
|
}
|
|
3674
3867
|
const args = block.children.map((c) => c.varName).join(", ");
|
|
@@ -3699,7 +3892,7 @@ class CodeGenerator {
|
|
|
3699
3892
|
const key = `key + \`${this.generateComponentKey()}\``;
|
|
3700
3893
|
if (isDynamic) {
|
|
3701
3894
|
const templateVar = this.generateId("template");
|
|
3702
|
-
this.
|
|
3895
|
+
this.define(templateVar, subTemplate);
|
|
3703
3896
|
block = this.createBlock(block, "multi", ctx);
|
|
3704
3897
|
this.helpers.add("call");
|
|
3705
3898
|
this.insertBlock(`call(this, ${templateVar}, ctx, node, ${key})`, block, {
|
|
@@ -3710,7 +3903,7 @@ class CodeGenerator {
|
|
|
3710
3903
|
else {
|
|
3711
3904
|
const id = this.generateId(`callTemplate_`);
|
|
3712
3905
|
this.helpers.add("getTemplate");
|
|
3713
|
-
this.
|
|
3906
|
+
this.staticDefs.push({ id, expr: `getTemplate(${subTemplate})` });
|
|
3714
3907
|
block = this.createBlock(block, "multi", ctx);
|
|
3715
3908
|
this.insertBlock(`${id}.call(this, ctx, node, ${key})`, block, {
|
|
3716
3909
|
...ctx,
|
|
@@ -3804,26 +3997,25 @@ class CodeGenerator {
|
|
|
3804
3997
|
compileComponent(ast, ctx) {
|
|
3805
3998
|
let { block } = ctx;
|
|
3806
3999
|
// props
|
|
3807
|
-
const hasSlotsProp = "slots" in ast.props;
|
|
4000
|
+
const hasSlotsProp = "slots" in (ast.props || {});
|
|
3808
4001
|
const props = [];
|
|
3809
|
-
const propExpr = this.formatPropObject(ast.props);
|
|
4002
|
+
const propExpr = this.formatPropObject(ast.props || {});
|
|
3810
4003
|
if (propExpr) {
|
|
3811
4004
|
props.push(propExpr);
|
|
3812
4005
|
}
|
|
3813
4006
|
// slots
|
|
3814
|
-
const hasSlot = !!Object.keys(ast.slots).length;
|
|
3815
4007
|
let slotDef = "";
|
|
3816
|
-
if (
|
|
4008
|
+
if (ast.slots) {
|
|
3817
4009
|
let ctxStr = "ctx";
|
|
3818
4010
|
if (this.target.loopLevel || !this.hasSafeContext) {
|
|
3819
4011
|
ctxStr = this.generateId("ctx");
|
|
3820
4012
|
this.helpers.add("capture");
|
|
3821
|
-
this.
|
|
4013
|
+
this.define(ctxStr, `capture(ctx)`);
|
|
3822
4014
|
}
|
|
3823
4015
|
let slotStr = [];
|
|
3824
4016
|
for (let slotName in ast.slots) {
|
|
3825
|
-
const slotAst = ast.slots[slotName]
|
|
3826
|
-
const name = this.compileInNewTarget("slot", slotAst, ctx);
|
|
4017
|
+
const slotAst = ast.slots[slotName];
|
|
4018
|
+
const name = this.compileInNewTarget("slot", slotAst.content, ctx, slotAst.on);
|
|
3827
4019
|
const params = [`__render: ${name}, __ctx: ${ctxStr}`];
|
|
3828
4020
|
const scope = ast.slots[slotName].scope;
|
|
3829
4021
|
if (scope) {
|
|
@@ -3849,7 +4041,7 @@ class CodeGenerator {
|
|
|
3849
4041
|
let propVar;
|
|
3850
4042
|
if ((slotDef && (ast.dynamicProps || hasSlotsProp)) || this.dev) {
|
|
3851
4043
|
propVar = this.generateId("props");
|
|
3852
|
-
this.
|
|
4044
|
+
this.define(propVar, propString);
|
|
3853
4045
|
propString = propVar;
|
|
3854
4046
|
}
|
|
3855
4047
|
if (slotDef && (ast.dynamicProps || hasSlotsProp)) {
|
|
@@ -3861,7 +4053,7 @@ class CodeGenerator {
|
|
|
3861
4053
|
let expr;
|
|
3862
4054
|
if (ast.isDynamic) {
|
|
3863
4055
|
expr = this.generateId("Comp");
|
|
3864
|
-
this.
|
|
4056
|
+
this.define(expr, compileExpr(ast.name));
|
|
3865
4057
|
}
|
|
3866
4058
|
else {
|
|
3867
4059
|
expr = `\`${ast.name}\``;
|
|
@@ -3882,9 +4074,28 @@ class CodeGenerator {
|
|
|
3882
4074
|
if (ast.isDynamic) {
|
|
3883
4075
|
blockExpr = `toggler(${expr}, ${blockExpr})`;
|
|
3884
4076
|
}
|
|
4077
|
+
// event handling
|
|
4078
|
+
if (ast.on) {
|
|
4079
|
+
blockExpr = this.wrapWithEventCatcher(blockExpr, ast.on);
|
|
4080
|
+
}
|
|
3885
4081
|
block = this.createBlock(block, "multi", ctx);
|
|
3886
4082
|
this.insertBlock(blockExpr, block, ctx);
|
|
3887
4083
|
}
|
|
4084
|
+
wrapWithEventCatcher(expr, on) {
|
|
4085
|
+
this.helpers.add("createCatcher");
|
|
4086
|
+
let name = this.generateId("catcher");
|
|
4087
|
+
let spec = {};
|
|
4088
|
+
let handlers = [];
|
|
4089
|
+
for (let ev in on) {
|
|
4090
|
+
let handlerId = this.generateId("hdlr");
|
|
4091
|
+
let idx = handlers.push(handlerId) - 1;
|
|
4092
|
+
spec[ev] = idx;
|
|
4093
|
+
const handler = this.generateHandlerCode(ev, on[ev]);
|
|
4094
|
+
this.define(handlerId, handler);
|
|
4095
|
+
}
|
|
4096
|
+
this.staticDefs.push({ id: name, expr: `createCatcher(${JSON.stringify(spec)})` });
|
|
4097
|
+
return `${name}(${expr}, [${handlers.join(",")}])`;
|
|
4098
|
+
}
|
|
3888
4099
|
compileTSlot(ast, ctx) {
|
|
3889
4100
|
this.helpers.add("callSlot");
|
|
3890
4101
|
let { block } = ctx;
|
|
@@ -3906,13 +4117,17 @@ class CodeGenerator {
|
|
|
3906
4117
|
else {
|
|
3907
4118
|
if (dynamic) {
|
|
3908
4119
|
let name = this.generateId("slot");
|
|
3909
|
-
this.
|
|
4120
|
+
this.define(name, slotName);
|
|
3910
4121
|
blockString = `toggler(${name}, callSlot(ctx, node, key, ${name}), ${dynamic}, ${scope})`;
|
|
3911
4122
|
}
|
|
3912
4123
|
else {
|
|
3913
4124
|
blockString = `callSlot(ctx, node, key, ${slotName}, ${dynamic}, ${scope})`;
|
|
3914
4125
|
}
|
|
3915
4126
|
}
|
|
4127
|
+
// event handling
|
|
4128
|
+
if (ast.on) {
|
|
4129
|
+
blockString = this.wrapWithEventCatcher(blockString, ast.on);
|
|
4130
|
+
}
|
|
3916
4131
|
if (block) {
|
|
3917
4132
|
this.insertAnchor(block);
|
|
3918
4133
|
}
|
|
@@ -3933,7 +4148,7 @@ class CodeGenerator {
|
|
|
3933
4148
|
if (this.target.loopLevel || !this.hasSafeContext) {
|
|
3934
4149
|
ctxStr = this.generateId("ctx");
|
|
3935
4150
|
this.helpers.add("capture");
|
|
3936
|
-
this.
|
|
4151
|
+
this.define(ctxStr, `capture(ctx);`);
|
|
3937
4152
|
}
|
|
3938
4153
|
const blockString = `component(Portal, {target: ${ast.target},slots: {'default': {__render: ${name}, __ctx: ${ctxStr}}}}, key + \`${key}\`, node, ctx)`;
|
|
3939
4154
|
if (block) {
|
|
@@ -4067,8 +4282,8 @@ function parseDOMNode(node, ctx) {
|
|
|
4067
4282
|
const ref = node.getAttribute("t-ref");
|
|
4068
4283
|
node.removeAttribute("t-ref");
|
|
4069
4284
|
const nodeAttrsNames = node.getAttributeNames();
|
|
4070
|
-
|
|
4071
|
-
|
|
4285
|
+
let attrs = null;
|
|
4286
|
+
let on = null;
|
|
4072
4287
|
let model = null;
|
|
4073
4288
|
for (let attr of nodeAttrsNames) {
|
|
4074
4289
|
const value = node.getAttribute(attr);
|
|
@@ -4076,6 +4291,7 @@ function parseDOMNode(node, ctx) {
|
|
|
4076
4291
|
if (attr === "t-on") {
|
|
4077
4292
|
throw new Error("Missing event name with t-on directive");
|
|
4078
4293
|
}
|
|
4294
|
+
on = on || {};
|
|
4079
4295
|
on[attr.slice(5)] = value;
|
|
4080
4296
|
}
|
|
4081
4297
|
else if (attr.startsWith("t-model")) {
|
|
@@ -4113,6 +4329,7 @@ function parseDOMNode(node, ctx) {
|
|
|
4113
4329
|
targetAttr: isCheckboxInput ? "checked" : "value",
|
|
4114
4330
|
specialInitTargetAttr: isRadioInput ? "checked" : null,
|
|
4115
4331
|
eventType,
|
|
4332
|
+
hasDynamicChildren: false,
|
|
4116
4333
|
shouldTrim: hasTrimMod && (isOtherInput || isTextarea),
|
|
4117
4334
|
shouldNumberize: hasNumberMod && (isOtherInput || isTextarea),
|
|
4118
4335
|
};
|
|
@@ -4133,6 +4350,7 @@ function parseDOMNode(node, ctx) {
|
|
|
4133
4350
|
if (tModel && ["t-att-value", "t-attf-value"].includes(attr)) {
|
|
4134
4351
|
tModel.hasDynamicChildren = true;
|
|
4135
4352
|
}
|
|
4353
|
+
attrs = attrs || {};
|
|
4136
4354
|
attrs[attr] = value;
|
|
4137
4355
|
}
|
|
4138
4356
|
}
|
|
@@ -4283,7 +4501,7 @@ function parseTCall(node, ctx) {
|
|
|
4283
4501
|
if (ast && ast.type === 11 /* TComponent */) {
|
|
4284
4502
|
return {
|
|
4285
4503
|
...ast,
|
|
4286
|
-
slots: { default: { content: tcall } },
|
|
4504
|
+
slots: { default: { content: tcall, scope: null, on: null, attrs: null } },
|
|
4287
4505
|
};
|
|
4288
4506
|
}
|
|
4289
4507
|
}
|
|
@@ -4367,7 +4585,6 @@ function parseTSetNode(node, ctx) {
|
|
|
4367
4585
|
// -----------------------------------------------------------------------------
|
|
4368
4586
|
// Error messages when trying to use an unsupported directive on a component
|
|
4369
4587
|
const directiveErrorMap = new Map([
|
|
4370
|
-
["t-on", "t-on is no longer supported on components. Consider passing a callback in props."],
|
|
4371
4588
|
[
|
|
4372
4589
|
"t-ref",
|
|
4373
4590
|
"t-ref is no longer supported on components. Consider exposing only the public part of the component's API through a callback prop.",
|
|
@@ -4396,18 +4613,26 @@ function parseComponent(node, ctx) {
|
|
|
4396
4613
|
node.removeAttribute("t-props");
|
|
4397
4614
|
const defaultSlotScope = node.getAttribute("t-slot-scope");
|
|
4398
4615
|
node.removeAttribute("t-slot-scope");
|
|
4399
|
-
|
|
4616
|
+
let on = null;
|
|
4617
|
+
let props = null;
|
|
4400
4618
|
for (let name of node.getAttributeNames()) {
|
|
4401
4619
|
const value = node.getAttribute(name);
|
|
4402
4620
|
if (name.startsWith("t-")) {
|
|
4403
|
-
|
|
4404
|
-
|
|
4621
|
+
if (name.startsWith("t-on-")) {
|
|
4622
|
+
on = on || {};
|
|
4623
|
+
on[name.slice(5)] = value;
|
|
4624
|
+
}
|
|
4625
|
+
else {
|
|
4626
|
+
const message = directiveErrorMap.get(name.split("-").slice(0, 2).join("-"));
|
|
4627
|
+
throw new Error(message || `unsupported directive on Component: ${name}`);
|
|
4628
|
+
}
|
|
4405
4629
|
}
|
|
4406
4630
|
else {
|
|
4631
|
+
props = props || {};
|
|
4407
4632
|
props[name] = value;
|
|
4408
4633
|
}
|
|
4409
4634
|
}
|
|
4410
|
-
|
|
4635
|
+
let slots = null;
|
|
4411
4636
|
if (node.hasChildNodes()) {
|
|
4412
4637
|
const clone = node.cloneNode(true);
|
|
4413
4638
|
// named slots
|
|
@@ -4435,246 +4660,614 @@ function parseComponent(node, ctx) {
|
|
|
4435
4660
|
slotNode.remove();
|
|
4436
4661
|
const slotAst = parseNode(slotNode, ctx);
|
|
4437
4662
|
if (slotAst) {
|
|
4438
|
-
|
|
4439
|
-
|
|
4663
|
+
let on = null;
|
|
4664
|
+
let attrs = null;
|
|
4665
|
+
let scope = null;
|
|
4440
4666
|
for (let attributeName of slotNode.getAttributeNames()) {
|
|
4441
4667
|
const value = slotNode.getAttribute(attributeName);
|
|
4442
4668
|
if (attributeName === "t-slot-scope") {
|
|
4443
|
-
|
|
4669
|
+
scope = value;
|
|
4444
4670
|
continue;
|
|
4445
4671
|
}
|
|
4446
|
-
|
|
4672
|
+
else if (attributeName.startsWith("t-on-")) {
|
|
4673
|
+
on = on || {};
|
|
4674
|
+
on[attributeName.slice(5)] = value;
|
|
4675
|
+
}
|
|
4676
|
+
else {
|
|
4677
|
+
attrs = attrs || {};
|
|
4678
|
+
attrs[attributeName] = value;
|
|
4679
|
+
}
|
|
4680
|
+
}
|
|
4681
|
+
slots = slots || {};
|
|
4682
|
+
slots[name] = { content: slotAst, on, attrs, scope };
|
|
4683
|
+
}
|
|
4684
|
+
}
|
|
4685
|
+
// default slot
|
|
4686
|
+
const defaultContent = parseChildNodes(clone, ctx);
|
|
4687
|
+
if (defaultContent) {
|
|
4688
|
+
slots = slots || {};
|
|
4689
|
+
slots.default = { content: defaultContent, on, attrs: null, scope: defaultSlotScope };
|
|
4690
|
+
}
|
|
4691
|
+
}
|
|
4692
|
+
return { type: 11 /* TComponent */, name, isDynamic, dynamicProps, props, slots, on };
|
|
4693
|
+
}
|
|
4694
|
+
// -----------------------------------------------------------------------------
|
|
4695
|
+
// Slots
|
|
4696
|
+
// -----------------------------------------------------------------------------
|
|
4697
|
+
function parseTSlot(node, ctx) {
|
|
4698
|
+
if (!node.hasAttribute("t-slot")) {
|
|
4699
|
+
return null;
|
|
4700
|
+
}
|
|
4701
|
+
const name = node.getAttribute("t-slot");
|
|
4702
|
+
node.removeAttribute("t-slot");
|
|
4703
|
+
let attrs = null;
|
|
4704
|
+
let on = null;
|
|
4705
|
+
for (let attributeName of node.getAttributeNames()) {
|
|
4706
|
+
const value = node.getAttribute(attributeName);
|
|
4707
|
+
if (attributeName.startsWith("t-on-")) {
|
|
4708
|
+
on = on || {};
|
|
4709
|
+
on[attributeName.slice(5)] = value;
|
|
4710
|
+
}
|
|
4711
|
+
else {
|
|
4712
|
+
attrs = attrs || {};
|
|
4713
|
+
attrs[attributeName] = value;
|
|
4714
|
+
}
|
|
4715
|
+
}
|
|
4716
|
+
return {
|
|
4717
|
+
type: 14 /* TSlot */,
|
|
4718
|
+
name,
|
|
4719
|
+
attrs,
|
|
4720
|
+
on,
|
|
4721
|
+
defaultContent: parseChildNodes(node, ctx),
|
|
4722
|
+
};
|
|
4723
|
+
}
|
|
4724
|
+
function parseTTranslation(node, ctx) {
|
|
4725
|
+
if (node.getAttribute("t-translation") !== "off") {
|
|
4726
|
+
return null;
|
|
4727
|
+
}
|
|
4728
|
+
node.removeAttribute("t-translation");
|
|
4729
|
+
return {
|
|
4730
|
+
type: 16 /* TTranslation */,
|
|
4731
|
+
content: parseNode(node, ctx),
|
|
4732
|
+
};
|
|
4733
|
+
}
|
|
4734
|
+
// -----------------------------------------------------------------------------
|
|
4735
|
+
// Portal
|
|
4736
|
+
// -----------------------------------------------------------------------------
|
|
4737
|
+
function parseTPortal(node, ctx) {
|
|
4738
|
+
if (!node.hasAttribute("t-portal")) {
|
|
4739
|
+
return null;
|
|
4740
|
+
}
|
|
4741
|
+
const target = node.getAttribute("t-portal");
|
|
4742
|
+
node.removeAttribute("t-portal");
|
|
4743
|
+
const content = parseNode(node, ctx);
|
|
4744
|
+
if (!content) {
|
|
4745
|
+
return {
|
|
4746
|
+
type: 0 /* Text */,
|
|
4747
|
+
value: "",
|
|
4748
|
+
};
|
|
4749
|
+
}
|
|
4750
|
+
return {
|
|
4751
|
+
type: 17 /* TPortal */,
|
|
4752
|
+
target,
|
|
4753
|
+
content,
|
|
4754
|
+
};
|
|
4755
|
+
}
|
|
4756
|
+
// -----------------------------------------------------------------------------
|
|
4757
|
+
// helpers
|
|
4758
|
+
// -----------------------------------------------------------------------------
|
|
4759
|
+
/**
|
|
4760
|
+
* Parse all the child nodes of a given node and return a list of ast elements
|
|
4761
|
+
*/
|
|
4762
|
+
function parseChildren(node, ctx) {
|
|
4763
|
+
const children = [];
|
|
4764
|
+
for (let child of node.childNodes) {
|
|
4765
|
+
const childAst = parseNode(child, ctx);
|
|
4766
|
+
if (childAst) {
|
|
4767
|
+
if (childAst.type === 3 /* Multi */) {
|
|
4768
|
+
children.push(...childAst.content);
|
|
4769
|
+
}
|
|
4770
|
+
else {
|
|
4771
|
+
children.push(childAst);
|
|
4772
|
+
}
|
|
4773
|
+
}
|
|
4774
|
+
}
|
|
4775
|
+
return children;
|
|
4776
|
+
}
|
|
4777
|
+
/**
|
|
4778
|
+
* Parse all the child nodes of a given node and return an ast if possible.
|
|
4779
|
+
* In the case there are multiple children, they are wrapped in a astmulti.
|
|
4780
|
+
*/
|
|
4781
|
+
function parseChildNodes(node, ctx) {
|
|
4782
|
+
const children = parseChildren(node, ctx);
|
|
4783
|
+
switch (children.length) {
|
|
4784
|
+
case 0:
|
|
4785
|
+
return null;
|
|
4786
|
+
case 1:
|
|
4787
|
+
return children[0];
|
|
4788
|
+
default:
|
|
4789
|
+
return { type: 3 /* Multi */, content: children };
|
|
4790
|
+
}
|
|
4791
|
+
}
|
|
4792
|
+
/**
|
|
4793
|
+
* Normalizes the content of an Element so that t-if/t-elif/t-else directives
|
|
4794
|
+
* immediately follow one another (by removing empty text nodes or comments).
|
|
4795
|
+
* Throws an error when a conditional branching statement is malformed. This
|
|
4796
|
+
* function modifies the Element in place.
|
|
4797
|
+
*
|
|
4798
|
+
* @param el the element containing the tree that should be normalized
|
|
4799
|
+
*/
|
|
4800
|
+
function normalizeTIf(el) {
|
|
4801
|
+
let tbranch = el.querySelectorAll("[t-elif], [t-else]");
|
|
4802
|
+
for (let i = 0, ilen = tbranch.length; i < ilen; i++) {
|
|
4803
|
+
let node = tbranch[i];
|
|
4804
|
+
let prevElem = node.previousElementSibling;
|
|
4805
|
+
let pattr = (name) => prevElem.getAttribute(name);
|
|
4806
|
+
let nattr = (name) => +!!node.getAttribute(name);
|
|
4807
|
+
if (prevElem && (pattr("t-if") || pattr("t-elif"))) {
|
|
4808
|
+
if (pattr("t-foreach")) {
|
|
4809
|
+
throw new Error("t-if cannot stay at the same level as t-foreach when using t-elif or t-else");
|
|
4810
|
+
}
|
|
4811
|
+
if (["t-if", "t-elif", "t-else"].map(nattr).reduce(function (a, b) {
|
|
4812
|
+
return a + b;
|
|
4813
|
+
}) > 1) {
|
|
4814
|
+
throw new Error("Only one conditional branching directive is allowed per node");
|
|
4815
|
+
}
|
|
4816
|
+
// All text (with only spaces) and comment nodes (nodeType 8) between
|
|
4817
|
+
// branch nodes are removed
|
|
4818
|
+
let textNode;
|
|
4819
|
+
while ((textNode = node.previousSibling) !== prevElem) {
|
|
4820
|
+
if (textNode.nodeValue.trim().length && textNode.nodeType !== 8) {
|
|
4821
|
+
throw new Error("text is not allowed between branching directives");
|
|
4822
|
+
}
|
|
4823
|
+
textNode.remove();
|
|
4824
|
+
}
|
|
4825
|
+
}
|
|
4826
|
+
else {
|
|
4827
|
+
throw new Error("t-elif and t-else directives must be preceded by a t-if or t-elif directive");
|
|
4828
|
+
}
|
|
4829
|
+
}
|
|
4830
|
+
}
|
|
4831
|
+
/**
|
|
4832
|
+
* Normalizes the content of an Element so that t-esc directives on components
|
|
4833
|
+
* are removed and instead places a <t t-esc=""> as the default slot of the
|
|
4834
|
+
* component. Also throws if the component already has content. This function
|
|
4835
|
+
* modifies the Element in place.
|
|
4836
|
+
*
|
|
4837
|
+
* @param el the element containing the tree that should be normalized
|
|
4838
|
+
*/
|
|
4839
|
+
function normalizeTEsc(el) {
|
|
4840
|
+
const elements = [...el.querySelectorAll("[t-esc]")].filter((el) => el.tagName[0] === el.tagName[0].toUpperCase() || el.hasAttribute("t-component"));
|
|
4841
|
+
for (const el of elements) {
|
|
4842
|
+
if (el.childNodes.length) {
|
|
4843
|
+
throw new Error("Cannot have t-esc on a component that already has content");
|
|
4844
|
+
}
|
|
4845
|
+
const value = el.getAttribute("t-esc");
|
|
4846
|
+
el.removeAttribute("t-esc");
|
|
4847
|
+
const t = el.ownerDocument.createElement("t");
|
|
4848
|
+
if (value != null) {
|
|
4849
|
+
t.setAttribute("t-esc", value);
|
|
4850
|
+
}
|
|
4851
|
+
el.appendChild(t);
|
|
4852
|
+
}
|
|
4853
|
+
}
|
|
4854
|
+
/**
|
|
4855
|
+
* Normalizes the tree inside a given element and do some preliminary validation
|
|
4856
|
+
* on it. This function modifies the Element in place.
|
|
4857
|
+
*
|
|
4858
|
+
* @param el the element containing the tree that should be normalized
|
|
4859
|
+
*/
|
|
4860
|
+
function normalizeXML(el) {
|
|
4861
|
+
normalizeTIf(el);
|
|
4862
|
+
normalizeTEsc(el);
|
|
4863
|
+
}
|
|
4864
|
+
/**
|
|
4865
|
+
* Parses an XML string into an XML document, throwing errors on parser errors
|
|
4866
|
+
* instead of returning an XML document containing the parseerror.
|
|
4867
|
+
*
|
|
4868
|
+
* @param xml the string to parse
|
|
4869
|
+
* @returns an XML document corresponding to the content of the string
|
|
4870
|
+
*/
|
|
4871
|
+
function parseXML$1(xml) {
|
|
4872
|
+
const parser = new DOMParser();
|
|
4873
|
+
const doc = parser.parseFromString(xml, "text/xml");
|
|
4874
|
+
if (doc.getElementsByTagName("parsererror").length) {
|
|
4875
|
+
let msg = "Invalid XML in template.";
|
|
4876
|
+
const parsererrorText = doc.getElementsByTagName("parsererror")[0].textContent;
|
|
4877
|
+
if (parsererrorText) {
|
|
4878
|
+
msg += "\nThe parser has produced the following error message:\n" + parsererrorText;
|
|
4879
|
+
const re = /\d+/g;
|
|
4880
|
+
const firstMatch = re.exec(parsererrorText);
|
|
4881
|
+
if (firstMatch) {
|
|
4882
|
+
const lineNumber = Number(firstMatch[0]);
|
|
4883
|
+
const line = xml.split("\n")[lineNumber - 1];
|
|
4884
|
+
const secondMatch = re.exec(parsererrorText);
|
|
4885
|
+
if (line && secondMatch) {
|
|
4886
|
+
const columnIndex = Number(secondMatch[0]) - 1;
|
|
4887
|
+
if (line[columnIndex]) {
|
|
4888
|
+
msg +=
|
|
4889
|
+
`\nThe error might be located at xml line ${lineNumber} column ${columnIndex}\n` +
|
|
4890
|
+
`${line}\n${"-".repeat(columnIndex - 1)}^`;
|
|
4891
|
+
}
|
|
4447
4892
|
}
|
|
4448
|
-
|
|
4449
|
-
|
|
4893
|
+
}
|
|
4894
|
+
}
|
|
4895
|
+
throw new Error(msg);
|
|
4896
|
+
}
|
|
4897
|
+
return doc;
|
|
4898
|
+
}
|
|
4899
|
+
|
|
4900
|
+
function compile(template, options = {}) {
|
|
4901
|
+
// parsing
|
|
4902
|
+
const ast = parse(template);
|
|
4903
|
+
// some work
|
|
4904
|
+
const hasSafeContext = template instanceof Node
|
|
4905
|
+
? !(template instanceof Element) || template.querySelector("[t-set], [t-call]") === null
|
|
4906
|
+
: !template.includes("t-set") && !template.includes("t-call");
|
|
4907
|
+
// code generation
|
|
4908
|
+
const codeGenerator = new CodeGenerator(ast, { ...options, hasSafeContext });
|
|
4909
|
+
const code = codeGenerator.generateCode();
|
|
4910
|
+
// template function
|
|
4911
|
+
return new Function("bdom, helpers", code);
|
|
4912
|
+
}
|
|
4913
|
+
|
|
4914
|
+
const TIMEOUT = Symbol("timeout");
|
|
4915
|
+
function wrapError(fn, hookName) {
|
|
4916
|
+
const error = new Error(`The following error occurred in ${hookName}: `);
|
|
4917
|
+
const timeoutError = new Error(`${hookName}'s promise hasn't resolved after 3 seconds`);
|
|
4918
|
+
const node = getCurrent();
|
|
4919
|
+
return (...args) => {
|
|
4920
|
+
try {
|
|
4921
|
+
const result = fn(...args);
|
|
4922
|
+
if (result instanceof Promise) {
|
|
4923
|
+
if (hookName === "onWillStart" || hookName === "onWillUpdateProps") {
|
|
4924
|
+
const fiber = node.fiber;
|
|
4925
|
+
Promise.race([
|
|
4926
|
+
result,
|
|
4927
|
+
new Promise((resolve) => setTimeout(() => resolve(TIMEOUT), 3000)),
|
|
4928
|
+
]).then((res) => {
|
|
4929
|
+
if (res === TIMEOUT && node.fiber === fiber) {
|
|
4930
|
+
console.warn(timeoutError);
|
|
4931
|
+
}
|
|
4932
|
+
});
|
|
4450
4933
|
}
|
|
4451
|
-
|
|
4934
|
+
return result.catch((cause) => {
|
|
4935
|
+
error.cause = cause;
|
|
4936
|
+
if (cause instanceof Error) {
|
|
4937
|
+
error.message += `"${cause.message}"`;
|
|
4938
|
+
}
|
|
4939
|
+
throw error;
|
|
4940
|
+
});
|
|
4452
4941
|
}
|
|
4942
|
+
return result;
|
|
4943
|
+
}
|
|
4944
|
+
catch (cause) {
|
|
4945
|
+
if (cause instanceof Error) {
|
|
4946
|
+
error.message += `"${cause.message}"`;
|
|
4947
|
+
}
|
|
4948
|
+
throw error;
|
|
4949
|
+
}
|
|
4950
|
+
};
|
|
4951
|
+
}
|
|
4952
|
+
// -----------------------------------------------------------------------------
|
|
4953
|
+
// hooks
|
|
4954
|
+
// -----------------------------------------------------------------------------
|
|
4955
|
+
function onWillStart(fn) {
|
|
4956
|
+
const node = getCurrent();
|
|
4957
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4958
|
+
node.willStart.push(decorate(fn.bind(node.component), "onWillStart"));
|
|
4959
|
+
}
|
|
4960
|
+
function onWillUpdateProps(fn) {
|
|
4961
|
+
const node = getCurrent();
|
|
4962
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4963
|
+
node.willUpdateProps.push(decorate(fn.bind(node.component), "onWillUpdateProps"));
|
|
4964
|
+
}
|
|
4965
|
+
function onMounted(fn) {
|
|
4966
|
+
const node = getCurrent();
|
|
4967
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4968
|
+
node.mounted.push(decorate(fn.bind(node.component), "onMounted"));
|
|
4969
|
+
}
|
|
4970
|
+
function onWillPatch(fn) {
|
|
4971
|
+
const node = getCurrent();
|
|
4972
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4973
|
+
node.willPatch.unshift(decorate(fn.bind(node.component), "onWillPatch"));
|
|
4974
|
+
}
|
|
4975
|
+
function onPatched(fn) {
|
|
4976
|
+
const node = getCurrent();
|
|
4977
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4978
|
+
node.patched.push(decorate(fn.bind(node.component), "onPatched"));
|
|
4979
|
+
}
|
|
4980
|
+
function onWillUnmount(fn) {
|
|
4981
|
+
const node = getCurrent();
|
|
4982
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4983
|
+
node.willUnmount.unshift(decorate(fn.bind(node.component), "onWillUnmount"));
|
|
4984
|
+
}
|
|
4985
|
+
function onWillDestroy(fn) {
|
|
4986
|
+
const node = getCurrent();
|
|
4987
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4988
|
+
node.willDestroy.push(decorate(fn.bind(node.component), "onWillDestroy"));
|
|
4989
|
+
}
|
|
4990
|
+
function onWillRender(fn) {
|
|
4991
|
+
const node = getCurrent();
|
|
4992
|
+
const renderFn = node.renderFn;
|
|
4993
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4994
|
+
fn = decorate(fn.bind(node.component), "onWillRender");
|
|
4995
|
+
node.renderFn = () => {
|
|
4996
|
+
fn();
|
|
4997
|
+
return renderFn();
|
|
4998
|
+
};
|
|
4999
|
+
}
|
|
5000
|
+
function onRendered(fn) {
|
|
5001
|
+
const node = getCurrent();
|
|
5002
|
+
const renderFn = node.renderFn;
|
|
5003
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
5004
|
+
fn = decorate(fn.bind(node.component), "onRendered");
|
|
5005
|
+
node.renderFn = () => {
|
|
5006
|
+
const result = renderFn();
|
|
5007
|
+
fn();
|
|
5008
|
+
return result;
|
|
5009
|
+
};
|
|
5010
|
+
}
|
|
5011
|
+
function onError(callback) {
|
|
5012
|
+
const node = getCurrent();
|
|
5013
|
+
let handlers = nodeErrorHandlers.get(node);
|
|
5014
|
+
if (!handlers) {
|
|
5015
|
+
handlers = [];
|
|
5016
|
+
nodeErrorHandlers.set(node, handlers);
|
|
5017
|
+
}
|
|
5018
|
+
handlers.push(callback.bind(node.component));
|
|
5019
|
+
}
|
|
5020
|
+
|
|
5021
|
+
class Component {
|
|
5022
|
+
constructor(props, env, node) {
|
|
5023
|
+
this.props = props;
|
|
5024
|
+
this.env = env;
|
|
5025
|
+
this.__owl__ = node;
|
|
5026
|
+
}
|
|
5027
|
+
setup() { }
|
|
5028
|
+
render(deep = false) {
|
|
5029
|
+
this.__owl__.render(deep);
|
|
5030
|
+
}
|
|
5031
|
+
}
|
|
5032
|
+
Component.template = "";
|
|
5033
|
+
|
|
5034
|
+
const VText = text("").constructor;
|
|
5035
|
+
class VPortal extends VText {
|
|
5036
|
+
constructor(selector, realBDom) {
|
|
5037
|
+
super("");
|
|
5038
|
+
this.target = null;
|
|
5039
|
+
this.selector = selector;
|
|
5040
|
+
this.realBDom = realBDom;
|
|
5041
|
+
}
|
|
5042
|
+
mount(parent, anchor) {
|
|
5043
|
+
super.mount(parent, anchor);
|
|
5044
|
+
this.target = document.querySelector(this.selector);
|
|
5045
|
+
if (!this.target) {
|
|
5046
|
+
let el = this.el;
|
|
5047
|
+
while (el && el.parentElement instanceof HTMLElement) {
|
|
5048
|
+
el = el.parentElement;
|
|
5049
|
+
}
|
|
5050
|
+
this.target = el && el.querySelector(this.selector);
|
|
5051
|
+
if (!this.target) {
|
|
5052
|
+
throw new Error("invalid portal target");
|
|
5053
|
+
}
|
|
5054
|
+
}
|
|
5055
|
+
this.realBDom.mount(this.target, null);
|
|
5056
|
+
}
|
|
5057
|
+
beforeRemove() {
|
|
5058
|
+
this.realBDom.beforeRemove();
|
|
5059
|
+
}
|
|
5060
|
+
remove() {
|
|
5061
|
+
if (this.realBDom) {
|
|
5062
|
+
super.remove();
|
|
5063
|
+
this.realBDom.remove();
|
|
5064
|
+
this.realBDom = null;
|
|
5065
|
+
}
|
|
5066
|
+
}
|
|
5067
|
+
patch(other) {
|
|
5068
|
+
super.patch(other);
|
|
5069
|
+
if (this.realBDom) {
|
|
5070
|
+
this.realBDom.patch(other.realBDom, true);
|
|
4453
5071
|
}
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
slots.default = { content: defaultContent };
|
|
4458
|
-
if (defaultSlotScope) {
|
|
4459
|
-
slots.default.scope = defaultSlotScope;
|
|
4460
|
-
}
|
|
5072
|
+
else {
|
|
5073
|
+
this.realBDom = other.realBDom;
|
|
5074
|
+
this.realBDom.mount(this.target, null);
|
|
4461
5075
|
}
|
|
4462
5076
|
}
|
|
4463
|
-
return { type: 11 /* TComponent */, name, isDynamic, dynamicProps, props, slots };
|
|
4464
5077
|
}
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
5078
|
+
class Portal extends Component {
|
|
5079
|
+
setup() {
|
|
5080
|
+
const node = this.__owl__;
|
|
5081
|
+
const renderFn = node.renderFn;
|
|
5082
|
+
node.renderFn = () => new VPortal(this.props.target, renderFn());
|
|
5083
|
+
onWillUnmount(() => {
|
|
5084
|
+
if (node.bdom) {
|
|
5085
|
+
node.bdom.remove();
|
|
5086
|
+
}
|
|
5087
|
+
});
|
|
4471
5088
|
}
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
5089
|
+
}
|
|
5090
|
+
Portal.template = xml `<t t-slot="default"/>`;
|
|
5091
|
+
Portal.props = {
|
|
5092
|
+
target: {
|
|
5093
|
+
type: String,
|
|
5094
|
+
},
|
|
5095
|
+
slots: true,
|
|
5096
|
+
};
|
|
5097
|
+
|
|
5098
|
+
/**
|
|
5099
|
+
* This file contains utility functions that will be injected in each template,
|
|
5100
|
+
* to perform various useful tasks in the compiled code.
|
|
5101
|
+
*/
|
|
5102
|
+
function withDefault(value, defaultValue) {
|
|
5103
|
+
return value === undefined || value === null || value === false ? defaultValue : value;
|
|
5104
|
+
}
|
|
5105
|
+
function callSlot(ctx, parent, key, name, dynamic, extra, defaultContent) {
|
|
5106
|
+
key = key + "__slot_" + name;
|
|
5107
|
+
const slots = ctx.props[TARGET].slots || {};
|
|
5108
|
+
const { __render, __ctx, __scope } = slots[name] || {};
|
|
5109
|
+
const slotScope = Object.create(__ctx || {});
|
|
5110
|
+
if (__scope) {
|
|
5111
|
+
slotScope[__scope] = extra;
|
|
4478
5112
|
}
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
5113
|
+
const slotBDom = __render ? __render.call(__ctx.__owl__.component, slotScope, parent, key) : null;
|
|
5114
|
+
if (defaultContent) {
|
|
5115
|
+
let child1 = undefined;
|
|
5116
|
+
let child2 = undefined;
|
|
5117
|
+
if (slotBDom) {
|
|
5118
|
+
child1 = dynamic ? toggler(name, slotBDom) : slotBDom;
|
|
5119
|
+
}
|
|
5120
|
+
else {
|
|
5121
|
+
child2 = defaultContent.call(ctx.__owl__.component, ctx, parent, key);
|
|
5122
|
+
}
|
|
5123
|
+
return multi([child1, child2]);
|
|
5124
|
+
}
|
|
5125
|
+
return slotBDom || text("");
|
|
4485
5126
|
}
|
|
4486
|
-
function
|
|
4487
|
-
|
|
4488
|
-
|
|
5127
|
+
function capture(ctx) {
|
|
5128
|
+
const component = ctx.__owl__.component;
|
|
5129
|
+
const result = Object.create(component);
|
|
5130
|
+
for (let k in ctx) {
|
|
5131
|
+
result[k] = ctx[k];
|
|
4489
5132
|
}
|
|
4490
|
-
|
|
4491
|
-
return {
|
|
4492
|
-
type: 16 /* TTranslation */,
|
|
4493
|
-
content: parseNode(node, ctx),
|
|
4494
|
-
};
|
|
5133
|
+
return result;
|
|
4495
5134
|
}
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
5135
|
+
function withKey(elem, k) {
|
|
5136
|
+
elem.key = k;
|
|
5137
|
+
return elem;
|
|
5138
|
+
}
|
|
5139
|
+
function prepareList(collection) {
|
|
5140
|
+
let keys;
|
|
5141
|
+
let values;
|
|
5142
|
+
if (Array.isArray(collection)) {
|
|
5143
|
+
keys = collection;
|
|
5144
|
+
values = collection;
|
|
4502
5145
|
}
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
if (!content) {
|
|
4507
|
-
return {
|
|
4508
|
-
type: 0 /* Text */,
|
|
4509
|
-
value: "",
|
|
4510
|
-
};
|
|
5146
|
+
else if (collection) {
|
|
5147
|
+
values = Object.keys(collection);
|
|
5148
|
+
keys = Object.values(collection);
|
|
4511
5149
|
}
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
5150
|
+
else {
|
|
5151
|
+
throw new Error("Invalid loop expression");
|
|
5152
|
+
}
|
|
5153
|
+
const n = values.length;
|
|
5154
|
+
return [keys, values, n, new Array(n)];
|
|
4517
5155
|
}
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
for (let child of node.childNodes) {
|
|
4527
|
-
const childAst = parseNode(child, ctx);
|
|
4528
|
-
if (childAst) {
|
|
4529
|
-
if (childAst.type === 3 /* Multi */) {
|
|
4530
|
-
children.push(...childAst.content);
|
|
4531
|
-
}
|
|
4532
|
-
else {
|
|
4533
|
-
children.push(childAst);
|
|
4534
|
-
}
|
|
5156
|
+
const isBoundary = Symbol("isBoundary");
|
|
5157
|
+
function setContextValue(ctx, key, value) {
|
|
5158
|
+
const ctx0 = ctx;
|
|
5159
|
+
while (!ctx.hasOwnProperty(key) && !ctx.hasOwnProperty(isBoundary)) {
|
|
5160
|
+
const newCtx = ctx.__proto__;
|
|
5161
|
+
if (!newCtx) {
|
|
5162
|
+
ctx = ctx0;
|
|
5163
|
+
break;
|
|
4535
5164
|
}
|
|
5165
|
+
ctx = newCtx;
|
|
4536
5166
|
}
|
|
4537
|
-
|
|
5167
|
+
ctx[key] = value;
|
|
4538
5168
|
}
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
*/
|
|
4543
|
-
function parseChildNodes(node, ctx) {
|
|
4544
|
-
const children = parseChildren(node, ctx);
|
|
4545
|
-
switch (children.length) {
|
|
4546
|
-
case 0:
|
|
4547
|
-
return null;
|
|
4548
|
-
case 1:
|
|
4549
|
-
return children[0];
|
|
4550
|
-
default:
|
|
4551
|
-
return { type: 3 /* Multi */, content: children };
|
|
4552
|
-
}
|
|
5169
|
+
function toNumber(val) {
|
|
5170
|
+
const n = parseFloat(val);
|
|
5171
|
+
return isNaN(n) ? val : n;
|
|
4553
5172
|
}
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
* function modifies the Element in place.
|
|
4559
|
-
*
|
|
4560
|
-
* @param el the element containing the tree that should be normalized
|
|
4561
|
-
*/
|
|
4562
|
-
function normalizeTIf(el) {
|
|
4563
|
-
let tbranch = el.querySelectorAll("[t-elif], [t-else]");
|
|
4564
|
-
for (let i = 0, ilen = tbranch.length; i < ilen; i++) {
|
|
4565
|
-
let node = tbranch[i];
|
|
4566
|
-
let prevElem = node.previousElementSibling;
|
|
4567
|
-
let pattr = (name) => prevElem.getAttribute(name);
|
|
4568
|
-
let nattr = (name) => +!!node.getAttribute(name);
|
|
4569
|
-
if (prevElem && (pattr("t-if") || pattr("t-elif"))) {
|
|
4570
|
-
if (pattr("t-foreach")) {
|
|
4571
|
-
throw new Error("t-if cannot stay at the same level as t-foreach when using t-elif or t-else");
|
|
4572
|
-
}
|
|
4573
|
-
if (["t-if", "t-elif", "t-else"].map(nattr).reduce(function (a, b) {
|
|
4574
|
-
return a + b;
|
|
4575
|
-
}) > 1) {
|
|
4576
|
-
throw new Error("Only one conditional branching directive is allowed per node");
|
|
4577
|
-
}
|
|
4578
|
-
// All text (with only spaces) and comment nodes (nodeType 8) between
|
|
4579
|
-
// branch nodes are removed
|
|
4580
|
-
let textNode;
|
|
4581
|
-
while ((textNode = node.previousSibling) !== prevElem) {
|
|
4582
|
-
if (textNode.nodeValue.trim().length && textNode.nodeType !== 8) {
|
|
4583
|
-
throw new Error("text is not allowed between branching directives");
|
|
4584
|
-
}
|
|
4585
|
-
textNode.remove();
|
|
4586
|
-
}
|
|
4587
|
-
}
|
|
4588
|
-
else {
|
|
4589
|
-
throw new Error("t-elif and t-else directives must be preceded by a t-if or t-elif directive");
|
|
5173
|
+
function shallowEqual(l1, l2) {
|
|
5174
|
+
for (let i = 0, l = l1.length; i < l; i++) {
|
|
5175
|
+
if (l1[i] !== l2[i]) {
|
|
5176
|
+
return false;
|
|
4590
5177
|
}
|
|
4591
5178
|
}
|
|
5179
|
+
return true;
|
|
4592
5180
|
}
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
5181
|
+
class LazyValue {
|
|
5182
|
+
constructor(fn, ctx, node) {
|
|
5183
|
+
this.fn = fn;
|
|
5184
|
+
this.ctx = capture(ctx);
|
|
5185
|
+
this.node = node;
|
|
5186
|
+
}
|
|
5187
|
+
evaluate() {
|
|
5188
|
+
return this.fn(this.ctx, this.node);
|
|
5189
|
+
}
|
|
5190
|
+
toString() {
|
|
5191
|
+
return this.evaluate().toString();
|
|
5192
|
+
}
|
|
5193
|
+
}
|
|
5194
|
+
/*
|
|
5195
|
+
* Safely outputs `value` as a block depending on the nature of `value`
|
|
4600
5196
|
*/
|
|
4601
|
-
function
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
5197
|
+
function safeOutput(value) {
|
|
5198
|
+
if (!value) {
|
|
5199
|
+
return value;
|
|
5200
|
+
}
|
|
5201
|
+
let safeKey;
|
|
5202
|
+
let block;
|
|
5203
|
+
if (value instanceof Markup) {
|
|
5204
|
+
safeKey = `string_safe`;
|
|
5205
|
+
block = html(value);
|
|
5206
|
+
}
|
|
5207
|
+
else if (value instanceof LazyValue) {
|
|
5208
|
+
safeKey = `lazy_value`;
|
|
5209
|
+
block = value.evaluate();
|
|
5210
|
+
}
|
|
5211
|
+
else if (value instanceof String || typeof value === "string") {
|
|
5212
|
+
safeKey = "string_unsafe";
|
|
5213
|
+
block = text(value);
|
|
4614
5214
|
}
|
|
5215
|
+
else {
|
|
5216
|
+
// Assuming it is a block
|
|
5217
|
+
safeKey = "block_safe";
|
|
5218
|
+
block = value;
|
|
5219
|
+
}
|
|
5220
|
+
return toggler(safeKey, block);
|
|
4615
5221
|
}
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
5222
|
+
let boundFunctions = new WeakMap();
|
|
5223
|
+
function bind(ctx, fn) {
|
|
5224
|
+
let component = ctx.__owl__.component;
|
|
5225
|
+
let boundFnMap = boundFunctions.get(component);
|
|
5226
|
+
if (!boundFnMap) {
|
|
5227
|
+
boundFnMap = new WeakMap();
|
|
5228
|
+
boundFunctions.set(component, boundFnMap);
|
|
5229
|
+
}
|
|
5230
|
+
let boundFn = boundFnMap.get(fn);
|
|
5231
|
+
if (!boundFn) {
|
|
5232
|
+
boundFn = fn.bind(component);
|
|
5233
|
+
boundFnMap.set(fn, boundFn);
|
|
5234
|
+
}
|
|
5235
|
+
return boundFn;
|
|
4625
5236
|
}
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
function parseXML$1(xml) {
|
|
4634
|
-
const parser = new DOMParser();
|
|
4635
|
-
const doc = parser.parseFromString(xml, "text/xml");
|
|
4636
|
-
if (doc.getElementsByTagName("parsererror").length) {
|
|
4637
|
-
let msg = "Invalid XML in template.";
|
|
4638
|
-
const parsererrorText = doc.getElementsByTagName("parsererror")[0].textContent;
|
|
4639
|
-
if (parsererrorText) {
|
|
4640
|
-
msg += "\nThe parser has produced the following error message:\n" + parsererrorText;
|
|
4641
|
-
const re = /\d+/g;
|
|
4642
|
-
const firstMatch = re.exec(parsererrorText);
|
|
4643
|
-
if (firstMatch) {
|
|
4644
|
-
const lineNumber = Number(firstMatch[0]);
|
|
4645
|
-
const line = xml.split("\n")[lineNumber - 1];
|
|
4646
|
-
const secondMatch = re.exec(parsererrorText);
|
|
4647
|
-
if (line && secondMatch) {
|
|
4648
|
-
const columnIndex = Number(secondMatch[0]) - 1;
|
|
4649
|
-
if (line[columnIndex]) {
|
|
4650
|
-
msg +=
|
|
4651
|
-
`\nThe error might be located at xml line ${lineNumber} column ${columnIndex}\n` +
|
|
4652
|
-
`${line}\n${"-".repeat(columnIndex - 1)}^`;
|
|
4653
|
-
}
|
|
4654
|
-
}
|
|
5237
|
+
function multiRefSetter(refs, name) {
|
|
5238
|
+
let count = 0;
|
|
5239
|
+
return (el) => {
|
|
5240
|
+
if (el) {
|
|
5241
|
+
count++;
|
|
5242
|
+
if (count > 1) {
|
|
5243
|
+
throw new Error("Cannot have 2 elements with same ref name at the same time");
|
|
4655
5244
|
}
|
|
4656
5245
|
}
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
}
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
5246
|
+
if (count === 0 || el) {
|
|
5247
|
+
refs[name] = el;
|
|
5248
|
+
}
|
|
5249
|
+
};
|
|
5250
|
+
}
|
|
5251
|
+
const helpers = {
|
|
5252
|
+
withDefault,
|
|
5253
|
+
zero: Symbol("zero"),
|
|
5254
|
+
isBoundary,
|
|
5255
|
+
callSlot,
|
|
5256
|
+
capture,
|
|
5257
|
+
withKey,
|
|
5258
|
+
prepareList,
|
|
5259
|
+
setContextValue,
|
|
5260
|
+
multiRefSetter,
|
|
5261
|
+
shallowEqual,
|
|
5262
|
+
toNumber,
|
|
5263
|
+
validateProps,
|
|
5264
|
+
LazyValue,
|
|
5265
|
+
safeOutput,
|
|
5266
|
+
bind,
|
|
5267
|
+
createCatcher,
|
|
5268
|
+
};
|
|
4675
5269
|
|
|
4676
5270
|
const bdom = { text, createBlock, list, multi, html, toggler, component, comment };
|
|
4677
|
-
const globalTemplates = {};
|
|
4678
5271
|
function parseXML(xml) {
|
|
4679
5272
|
const parser = new DOMParser();
|
|
4680
5273
|
const doc = parser.parseFromString(xml, "text/xml");
|
|
@@ -4703,23 +5296,32 @@ function parseXML(xml) {
|
|
|
4703
5296
|
}
|
|
4704
5297
|
return doc;
|
|
4705
5298
|
}
|
|
5299
|
+
/**
|
|
5300
|
+
* Returns the helpers object that will be injected in each template closure
|
|
5301
|
+
* function
|
|
5302
|
+
*/
|
|
5303
|
+
function makeHelpers(getTemplate) {
|
|
5304
|
+
return Object.assign({}, helpers, {
|
|
5305
|
+
Portal,
|
|
5306
|
+
markRaw,
|
|
5307
|
+
getTemplate,
|
|
5308
|
+
call: (owner, subTemplate, ctx, parent, key) => {
|
|
5309
|
+
const template = getTemplate(subTemplate);
|
|
5310
|
+
return toggler(subTemplate, template.call(owner, ctx, parent, key));
|
|
5311
|
+
},
|
|
5312
|
+
});
|
|
5313
|
+
}
|
|
4706
5314
|
class TemplateSet {
|
|
4707
5315
|
constructor(config = {}) {
|
|
4708
5316
|
this.rawTemplates = Object.create(globalTemplates);
|
|
4709
5317
|
this.templates = {};
|
|
4710
|
-
this.utils = Object.assign({}, UTILS, {
|
|
4711
|
-
call: (owner, subTemplate, ctx, parent, key) => {
|
|
4712
|
-
const template = this.getTemplate(subTemplate);
|
|
4713
|
-
return toggler(subTemplate, template.call(owner, ctx, parent, key));
|
|
4714
|
-
},
|
|
4715
|
-
getTemplate: (name) => this.getTemplate(name),
|
|
4716
|
-
});
|
|
4717
5318
|
this.dev = config.dev || false;
|
|
4718
5319
|
this.translateFn = config.translateFn;
|
|
4719
5320
|
this.translatableAttributes = config.translatableAttributes;
|
|
4720
5321
|
if (config.templates) {
|
|
4721
5322
|
this.addTemplates(config.templates);
|
|
4722
5323
|
}
|
|
5324
|
+
this.helpers = makeHelpers(this.getTemplate.bind(this));
|
|
4723
5325
|
}
|
|
4724
5326
|
addTemplate(name, template, options = {}) {
|
|
4725
5327
|
if (name in this.rawTemplates && !options.allowDuplicate) {
|
|
@@ -4757,7 +5359,7 @@ class TemplateSet {
|
|
|
4757
5359
|
this.templates[name] = function (context, parent) {
|
|
4758
5360
|
return templates[name].call(this, context, parent);
|
|
4759
5361
|
};
|
|
4760
|
-
const template = templateFn(bdom, this.
|
|
5362
|
+
const template = templateFn(bdom, this.helpers);
|
|
4761
5363
|
this.templates[name] = template;
|
|
4762
5364
|
}
|
|
4763
5365
|
return this.templates[name];
|
|
@@ -4770,160 +5372,9 @@ class TemplateSet {
|
|
|
4770
5372
|
translatableAttributes: this.translatableAttributes,
|
|
4771
5373
|
});
|
|
4772
5374
|
}
|
|
4773
|
-
}
|
|
4774
|
-
// -----------------------------------------------------------------------------
|
|
4775
|
-
// xml tag helper
|
|
4776
|
-
// -----------------------------------------------------------------------------
|
|
4777
|
-
function xml(...args) {
|
|
4778
|
-
const name = `__template__${xml.nextId++}`;
|
|
4779
|
-
const value = String.raw(...args);
|
|
4780
|
-
globalTemplates[name] = value;
|
|
4781
|
-
return name;
|
|
4782
|
-
}
|
|
4783
|
-
xml.nextId = 1;
|
|
4784
|
-
|
|
4785
|
-
class Component {
|
|
4786
|
-
constructor(props, env, node) {
|
|
4787
|
-
this.props = props;
|
|
4788
|
-
this.env = env;
|
|
4789
|
-
this.__owl__ = node;
|
|
4790
|
-
}
|
|
4791
|
-
setup() { }
|
|
4792
|
-
render(deep = false) {
|
|
4793
|
-
this.__owl__.render(deep);
|
|
4794
|
-
}
|
|
4795
|
-
}
|
|
4796
|
-
Component.template = "";
|
|
4797
|
-
|
|
4798
|
-
const VText = text("").constructor;
|
|
4799
|
-
class VPortal extends VText {
|
|
4800
|
-
constructor(selector, realBDom) {
|
|
4801
|
-
super("");
|
|
4802
|
-
this.target = null;
|
|
4803
|
-
this.selector = selector;
|
|
4804
|
-
this.realBDom = realBDom;
|
|
4805
|
-
}
|
|
4806
|
-
mount(parent, anchor) {
|
|
4807
|
-
super.mount(parent, anchor);
|
|
4808
|
-
this.target = document.querySelector(this.selector);
|
|
4809
|
-
if (!this.target) {
|
|
4810
|
-
let el = this.el;
|
|
4811
|
-
while (el && el.parentElement instanceof HTMLElement) {
|
|
4812
|
-
el = el.parentElement;
|
|
4813
|
-
}
|
|
4814
|
-
this.target = el && el.querySelector(this.selector);
|
|
4815
|
-
if (!this.target) {
|
|
4816
|
-
throw new Error("invalid portal target");
|
|
4817
|
-
}
|
|
4818
|
-
}
|
|
4819
|
-
this.realBDom.mount(this.target, null);
|
|
4820
|
-
}
|
|
4821
|
-
beforeRemove() {
|
|
4822
|
-
this.realBDom.beforeRemove();
|
|
4823
|
-
}
|
|
4824
|
-
remove() {
|
|
4825
|
-
if (this.realBDom) {
|
|
4826
|
-
super.remove();
|
|
4827
|
-
this.realBDom.remove();
|
|
4828
|
-
this.realBDom = null;
|
|
4829
|
-
}
|
|
4830
|
-
}
|
|
4831
|
-
patch(other) {
|
|
4832
|
-
super.patch(other);
|
|
4833
|
-
if (this.realBDom) {
|
|
4834
|
-
this.realBDom.patch(other.realBDom, true);
|
|
4835
|
-
}
|
|
4836
|
-
else {
|
|
4837
|
-
this.realBDom = other.realBDom;
|
|
4838
|
-
this.realBDom.mount(this.target, null);
|
|
4839
|
-
}
|
|
4840
|
-
}
|
|
4841
|
-
}
|
|
4842
|
-
class Portal extends Component {
|
|
4843
|
-
setup() {
|
|
4844
|
-
const node = this.__owl__;
|
|
4845
|
-
const renderFn = node.renderFn;
|
|
4846
|
-
node.renderFn = () => new VPortal(this.props.target, renderFn());
|
|
4847
|
-
onWillUnmount(() => {
|
|
4848
|
-
if (node.bdom) {
|
|
4849
|
-
node.bdom.remove();
|
|
4850
|
-
}
|
|
4851
|
-
});
|
|
4852
|
-
}
|
|
4853
|
-
}
|
|
4854
|
-
Portal.template = xml `<t t-slot="default"/>`;
|
|
4855
|
-
Portal.props = {
|
|
4856
|
-
target: {
|
|
4857
|
-
type: String,
|
|
4858
|
-
},
|
|
4859
|
-
slots: true,
|
|
4860
|
-
};
|
|
4861
|
-
|
|
4862
|
-
// -----------------------------------------------------------------------------
|
|
4863
|
-
// Scheduler
|
|
4864
|
-
// -----------------------------------------------------------------------------
|
|
4865
|
-
class Scheduler {
|
|
4866
|
-
constructor() {
|
|
4867
|
-
this.tasks = new Set();
|
|
4868
|
-
this.isRunning = false;
|
|
4869
|
-
this.requestAnimationFrame = Scheduler.requestAnimationFrame;
|
|
4870
|
-
}
|
|
4871
|
-
start() {
|
|
4872
|
-
this.isRunning = true;
|
|
4873
|
-
this.scheduleTasks();
|
|
4874
|
-
}
|
|
4875
|
-
stop() {
|
|
4876
|
-
this.isRunning = false;
|
|
4877
|
-
}
|
|
4878
|
-
addFiber(fiber) {
|
|
4879
|
-
this.tasks.add(fiber.root);
|
|
4880
|
-
if (!this.isRunning) {
|
|
4881
|
-
this.start();
|
|
4882
|
-
}
|
|
4883
|
-
}
|
|
4884
|
-
/**
|
|
4885
|
-
* Process all current tasks. This only applies to the fibers that are ready.
|
|
4886
|
-
* Other tasks are left unchanged.
|
|
4887
|
-
*/
|
|
4888
|
-
flush() {
|
|
4889
|
-
this.tasks.forEach((fiber) => {
|
|
4890
|
-
if (fiber.root !== fiber) {
|
|
4891
|
-
this.tasks.delete(fiber);
|
|
4892
|
-
return;
|
|
4893
|
-
}
|
|
4894
|
-
const hasError = fibersInError.has(fiber);
|
|
4895
|
-
if (hasError && fiber.counter !== 0) {
|
|
4896
|
-
this.tasks.delete(fiber);
|
|
4897
|
-
return;
|
|
4898
|
-
}
|
|
4899
|
-
if (fiber.node.status === 2 /* DESTROYED */) {
|
|
4900
|
-
this.tasks.delete(fiber);
|
|
4901
|
-
return;
|
|
4902
|
-
}
|
|
4903
|
-
if (fiber.counter === 0) {
|
|
4904
|
-
if (!hasError) {
|
|
4905
|
-
fiber.complete();
|
|
4906
|
-
}
|
|
4907
|
-
this.tasks.delete(fiber);
|
|
4908
|
-
}
|
|
4909
|
-
});
|
|
4910
|
-
if (this.tasks.size === 0) {
|
|
4911
|
-
this.stop();
|
|
4912
|
-
}
|
|
4913
|
-
}
|
|
4914
|
-
scheduleTasks() {
|
|
4915
|
-
this.requestAnimationFrame(() => {
|
|
4916
|
-
this.flush();
|
|
4917
|
-
if (this.isRunning) {
|
|
4918
|
-
this.scheduleTasks();
|
|
4919
|
-
}
|
|
4920
|
-
});
|
|
4921
|
-
}
|
|
4922
|
-
}
|
|
4923
|
-
// capture the value of requestAnimationFrame as soon as possible, to avoid
|
|
4924
|
-
// interactions with other code, such as test frameworks that override them
|
|
4925
|
-
Scheduler.requestAnimationFrame = window.requestAnimationFrame.bind(window);
|
|
5375
|
+
}
|
|
4926
5376
|
|
|
5377
|
+
let hasBeenLogged = false;
|
|
4927
5378
|
const DEV_MSG = () => {
|
|
4928
5379
|
const hash = window.owl ? window.owl.__info__.hash : "master";
|
|
4929
5380
|
return `Owl is running in 'dev' mode.
|
|
@@ -4940,11 +5391,13 @@ class App extends TemplateSet {
|
|
|
4940
5391
|
if (config.test) {
|
|
4941
5392
|
this.dev = true;
|
|
4942
5393
|
}
|
|
4943
|
-
if (this.dev && !config.test) {
|
|
5394
|
+
if (this.dev && !config.test && !hasBeenLogged) {
|
|
4944
5395
|
console.info(DEV_MSG());
|
|
5396
|
+
hasBeenLogged = true;
|
|
4945
5397
|
}
|
|
4946
|
-
const
|
|
4947
|
-
|
|
5398
|
+
const env = config.env || {};
|
|
5399
|
+
const descrs = Object.getOwnPropertyDescriptors(env);
|
|
5400
|
+
this.env = Object.freeze(Object.create(Object.getPrototypeOf(env), descrs));
|
|
4948
5401
|
this.props = config.props || {};
|
|
4949
5402
|
}
|
|
4950
5403
|
mount(target, options) {
|
|
@@ -4987,6 +5440,7 @@ class App extends TemplateSet {
|
|
|
4987
5440
|
}
|
|
4988
5441
|
destroy() {
|
|
4989
5442
|
if (this.root) {
|
|
5443
|
+
this.scheduler.flush();
|
|
4990
5444
|
this.root.destroy();
|
|
4991
5445
|
}
|
|
4992
5446
|
}
|
|
@@ -5007,45 +5461,6 @@ function status(component) {
|
|
|
5007
5461
|
}
|
|
5008
5462
|
}
|
|
5009
5463
|
|
|
5010
|
-
class Memo extends Component {
|
|
5011
|
-
constructor(props, env, node) {
|
|
5012
|
-
super(props, env, node);
|
|
5013
|
-
// prevent patching process conditionally
|
|
5014
|
-
let applyPatch = false;
|
|
5015
|
-
const patchFn = node.patch;
|
|
5016
|
-
node.patch = () => {
|
|
5017
|
-
if (applyPatch) {
|
|
5018
|
-
patchFn.call(node);
|
|
5019
|
-
applyPatch = false;
|
|
5020
|
-
}
|
|
5021
|
-
};
|
|
5022
|
-
// check props change, and render/apply patch if it changed
|
|
5023
|
-
let prevProps = props;
|
|
5024
|
-
const updateAndRender = node.updateAndRender;
|
|
5025
|
-
node.updateAndRender = function (props, parentFiber) {
|
|
5026
|
-
const shouldUpdate = !shallowEqual(prevProps, props);
|
|
5027
|
-
if (shouldUpdate) {
|
|
5028
|
-
prevProps = props;
|
|
5029
|
-
updateAndRender.call(node, props, parentFiber);
|
|
5030
|
-
applyPatch = true;
|
|
5031
|
-
}
|
|
5032
|
-
return Promise.resolve();
|
|
5033
|
-
};
|
|
5034
|
-
}
|
|
5035
|
-
}
|
|
5036
|
-
Memo.template = xml `<t t-slot="default"/>`;
|
|
5037
|
-
/**
|
|
5038
|
-
* we assume that each object have the same set of keys
|
|
5039
|
-
*/
|
|
5040
|
-
function shallowEqual(p1, p2) {
|
|
5041
|
-
for (let k in p1) {
|
|
5042
|
-
if (k !== "slots" && p1[k] !== p2[k]) {
|
|
5043
|
-
return false;
|
|
5044
|
-
}
|
|
5045
|
-
}
|
|
5046
|
-
return true;
|
|
5047
|
-
}
|
|
5048
|
-
|
|
5049
5464
|
// -----------------------------------------------------------------------------
|
|
5050
5465
|
// useRef
|
|
5051
5466
|
// -----------------------------------------------------------------------------
|
|
@@ -5091,10 +5506,6 @@ function useChildSubEnv(envExtension) {
|
|
|
5091
5506
|
const node = getCurrent();
|
|
5092
5507
|
node.childEnv = extendEnv(node.childEnv, envExtension);
|
|
5093
5508
|
}
|
|
5094
|
-
// -----------------------------------------------------------------------------
|
|
5095
|
-
// useEffect
|
|
5096
|
-
// -----------------------------------------------------------------------------
|
|
5097
|
-
const NO_OP = () => { };
|
|
5098
5509
|
/**
|
|
5099
5510
|
* This hook will run a callback when a component is mounted and patched, and
|
|
5100
5511
|
* will run a cleanup function before patching and before unmounting the
|
|
@@ -5112,18 +5523,20 @@ function useEffect(effect, computeDependencies = () => [NaN]) {
|
|
|
5112
5523
|
let dependencies;
|
|
5113
5524
|
onMounted(() => {
|
|
5114
5525
|
dependencies = computeDependencies();
|
|
5115
|
-
cleanup = effect(...dependencies)
|
|
5526
|
+
cleanup = effect(...dependencies);
|
|
5116
5527
|
});
|
|
5117
5528
|
onPatched(() => {
|
|
5118
5529
|
const newDeps = computeDependencies();
|
|
5119
5530
|
const shouldReapply = newDeps.some((val, i) => val !== dependencies[i]);
|
|
5120
5531
|
if (shouldReapply) {
|
|
5121
5532
|
dependencies = newDeps;
|
|
5122
|
-
cleanup
|
|
5123
|
-
|
|
5533
|
+
if (cleanup) {
|
|
5534
|
+
cleanup();
|
|
5535
|
+
}
|
|
5536
|
+
cleanup = effect(...dependencies);
|
|
5124
5537
|
}
|
|
5125
5538
|
});
|
|
5126
|
-
onWillUnmount(() => cleanup());
|
|
5539
|
+
onWillUnmount(() => cleanup && cleanup());
|
|
5127
5540
|
}
|
|
5128
5541
|
// -----------------------------------------------------------------------------
|
|
5129
5542
|
// useExternalListener
|
|
@@ -5150,8 +5563,6 @@ function useExternalListener(target, eventName, handler, eventParams) {
|
|
|
5150
5563
|
|
|
5151
5564
|
config.shouldNormalizeDom = false;
|
|
5152
5565
|
config.mainEventHandler = mainEventHandler;
|
|
5153
|
-
UTILS.Portal = Portal;
|
|
5154
|
-
UTILS.markRaw = markRaw;
|
|
5155
5566
|
const blockDom = {
|
|
5156
5567
|
config,
|
|
5157
5568
|
// bdom entry points
|
|
@@ -5169,10 +5580,10 @@ const blockDom = {
|
|
|
5169
5580
|
};
|
|
5170
5581
|
const __info__ = {};
|
|
5171
5582
|
|
|
5172
|
-
export { App, Component, EventBus,
|
|
5583
|
+
export { App, Component, EventBus, __info__, blockDom, loadFile, markRaw, markup, mount, onError, onMounted, onPatched, onRendered, onWillDestroy, onWillPatch, onWillRender, onWillStart, onWillUnmount, onWillUpdateProps, reactive, status, toRaw, useChildSubEnv, useComponent, useEffect, useEnv, useExternalListener, useRef, useState, useSubEnv, whenReady, xml };
|
|
5173
5584
|
|
|
5174
5585
|
|
|
5175
|
-
__info__.version = '2.0.0-
|
|
5176
|
-
__info__.date = '2022-
|
|
5177
|
-
__info__.hash = '
|
|
5586
|
+
__info__.version = '2.0.0-beta-4';
|
|
5587
|
+
__info__.date = '2022-03-29T13:50:04.545Z';
|
|
5588
|
+
__info__.hash = '55dbc01';
|
|
5178
5589
|
__info__.url = 'https://github.com/odoo/owl';
|