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