@odoo/owl 2.0.0-beta.3 → 2.0.0
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 +3 -1
- package/dist/owl.cjs.js +2077 -1794
- package/dist/owl.es.js +2076 -1795
- package/dist/owl.iife.js +2077 -1794
- package/dist/owl.iife.min.js +1 -1
- package/dist/types/compiler/code_generator.d.ts +30 -30
- package/dist/types/compiler/index.d.ts +3 -2
- package/dist/types/compiler/inline_expressions.d.ts +1 -22
- package/dist/types/compiler/parser.d.ts +2 -1
- package/dist/types/index.d.ts +1 -27
- package/dist/types/owl.d.ts +532 -0
- package/dist/types/{app → runtime}/app.d.ts +10 -5
- package/dist/types/{blockdom → runtime/blockdom}/attributes.d.ts +0 -0
- package/dist/types/{blockdom → runtime/blockdom}/block_compiler.d.ts +0 -0
- package/dist/types/{blockdom → runtime/blockdom}/config.d.ts +0 -0
- package/dist/types/{blockdom → runtime/blockdom}/event_catcher.d.ts +0 -0
- package/dist/types/{blockdom → runtime/blockdom}/events.d.ts +0 -0
- package/dist/types/{blockdom → runtime/blockdom}/html.d.ts +0 -0
- package/dist/types/{blockdom → runtime/blockdom}/index.d.ts +0 -0
- package/dist/types/{blockdom → runtime/blockdom}/list.d.ts +0 -0
- package/dist/types/{blockdom → runtime/blockdom}/multi.d.ts +0 -0
- package/dist/types/{blockdom → runtime/blockdom}/text.d.ts +0 -0
- package/dist/types/{blockdom → runtime/blockdom}/toggler.d.ts +0 -0
- package/dist/types/{component → runtime}/component.d.ts +6 -2
- package/dist/types/{component → runtime}/component_node.d.ts +13 -13
- package/dist/types/{component → runtime}/error_handling.d.ts +3 -0
- package/dist/types/{component/handler.d.ts → runtime/event_handling.d.ts} +0 -0
- package/dist/types/{component → runtime}/fibers.d.ts +5 -1
- package/dist/types/{hooks.d.ts → runtime/hooks.d.ts} +1 -1
- package/dist/types/runtime/index.d.ts +31 -0
- package/dist/types/{component → runtime}/lifecycle_hooks.d.ts +0 -0
- package/dist/types/runtime/portal.d.ts +15 -0
- package/dist/types/{reactivity.d.ts → runtime/reactivity.d.ts} +0 -0
- package/dist/types/{component → runtime}/scheduler.d.ts +3 -4
- package/dist/types/{component → runtime}/status.d.ts +0 -0
- package/dist/types/{app → runtime}/template_helpers.d.ts +17 -4
- package/dist/types/runtime/template_set.d.ts +32 -0
- package/dist/types/{utils.d.ts → runtime/utils.d.ts} +0 -7
- package/dist/types/runtime/validation.d.ts +32 -0
- package/package.json +7 -4
- package/CHANGELOG.md +0 -766
- package/dist/owl.cjs.min.js +0 -1
- package/dist/owl.es.min.js +0 -1
- package/dist/types/app/template_set.d.ts +0 -27
- package/dist/types/component/props_validation.d.ts +0 -14
- package/dist/types/portal.d.ts +0 -11
package/dist/owl.es.js
CHANGED
|
@@ -77,6 +77,69 @@ function toggler(key, child) {
|
|
|
77
77
|
return new VToggler(key, child);
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
// Custom error class that wraps error that happen in the owl lifecycle
|
|
81
|
+
class OwlError extends Error {
|
|
82
|
+
}
|
|
83
|
+
// Maps fibers to thrown errors
|
|
84
|
+
const fibersInError = new WeakMap();
|
|
85
|
+
const nodeErrorHandlers = new WeakMap();
|
|
86
|
+
function _handleError(node, error) {
|
|
87
|
+
if (!node) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
const fiber = node.fiber;
|
|
91
|
+
if (fiber) {
|
|
92
|
+
fibersInError.set(fiber, error);
|
|
93
|
+
}
|
|
94
|
+
const errorHandlers = nodeErrorHandlers.get(node);
|
|
95
|
+
if (errorHandlers) {
|
|
96
|
+
let handled = false;
|
|
97
|
+
// execute in the opposite order
|
|
98
|
+
for (let i = errorHandlers.length - 1; i >= 0; i--) {
|
|
99
|
+
try {
|
|
100
|
+
errorHandlers[i](error);
|
|
101
|
+
handled = true;
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
catch (e) {
|
|
105
|
+
error = e;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (handled) {
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return _handleError(node.parent, error);
|
|
113
|
+
}
|
|
114
|
+
function handleError(params) {
|
|
115
|
+
let { error } = params;
|
|
116
|
+
// Wrap error if it wasn't wrapped by wrapError (ie when not in dev mode)
|
|
117
|
+
if (!(error instanceof OwlError)) {
|
|
118
|
+
error = Object.assign(new OwlError(`An error occured in the owl lifecycle (see this Error's "cause" property)`), { cause: error });
|
|
119
|
+
}
|
|
120
|
+
const node = "node" in params ? params.node : params.fiber.node;
|
|
121
|
+
const fiber = "fiber" in params ? params.fiber : node.fiber;
|
|
122
|
+
// resets the fibers on components if possible. This is important so that
|
|
123
|
+
// new renderings can be properly included in the initial one, if any.
|
|
124
|
+
let current = fiber;
|
|
125
|
+
do {
|
|
126
|
+
current.node.fiber = current;
|
|
127
|
+
current = current.parent;
|
|
128
|
+
} while (current);
|
|
129
|
+
fibersInError.set(fiber.root, error);
|
|
130
|
+
const handled = _handleError(node, error);
|
|
131
|
+
if (!handled) {
|
|
132
|
+
console.warn(`[Owl] Unhandled error. Destroying the root component`);
|
|
133
|
+
try {
|
|
134
|
+
node.app.destroy();
|
|
135
|
+
}
|
|
136
|
+
catch (e) {
|
|
137
|
+
console.error(e);
|
|
138
|
+
}
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
80
143
|
const { setAttribute: elemSetAttribute, removeAttribute } = Element.prototype;
|
|
81
144
|
const tokenList = DOMTokenList.prototype;
|
|
82
145
|
const tokenListAdd = tokenList.add;
|
|
@@ -167,6 +230,10 @@ function toClassObj(expr) {
|
|
|
167
230
|
for (let key in expr) {
|
|
168
231
|
const value = expr[key];
|
|
169
232
|
if (value) {
|
|
233
|
+
key = trim.call(key);
|
|
234
|
+
if (!key) {
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
170
237
|
const words = split.call(key, wordRegexp);
|
|
171
238
|
for (let word of words) {
|
|
172
239
|
result[word] = value;
|
|
@@ -209,7 +276,8 @@ function updateClass(val, oldVal) {
|
|
|
209
276
|
}
|
|
210
277
|
function makePropSetter(name) {
|
|
211
278
|
return function setProp(value) {
|
|
212
|
-
|
|
279
|
+
// support 0, fallback to empty string for other falsy values
|
|
280
|
+
this[name] = value === 0 ? 0 : value ? value.valueOf() : "";
|
|
213
281
|
};
|
|
214
282
|
}
|
|
215
283
|
function isProp(tag, key) {
|
|
@@ -252,7 +320,7 @@ function createElementHandler(evName, capture = false) {
|
|
|
252
320
|
}
|
|
253
321
|
function listener(ev) {
|
|
254
322
|
const currentTarget = ev.currentTarget;
|
|
255
|
-
if (!currentTarget || !
|
|
323
|
+
if (!currentTarget || !currentTarget.ownerDocument.contains(currentTarget))
|
|
256
324
|
return;
|
|
257
325
|
const data = currentTarget[eventKey];
|
|
258
326
|
if (!data)
|
|
@@ -515,7 +583,7 @@ const characterDataProto = CharacterData.prototype;
|
|
|
515
583
|
const characterDataSetData = getDescriptor$1(characterDataProto, "data").set;
|
|
516
584
|
const nodeGetFirstChild = getDescriptor$1(nodeProto$2, "firstChild").get;
|
|
517
585
|
const nodeGetNextSibling = getDescriptor$1(nodeProto$2, "nextSibling").get;
|
|
518
|
-
const NO_OP
|
|
586
|
+
const NO_OP = () => { };
|
|
519
587
|
const cache$1 = {};
|
|
520
588
|
/**
|
|
521
589
|
* Compiling blocks is a multi-step process:
|
|
@@ -604,6 +672,15 @@ function buildTree(node, parent = null, domParentTree = null) {
|
|
|
604
672
|
: document.createElement(tagName);
|
|
605
673
|
}
|
|
606
674
|
if (el instanceof Element) {
|
|
675
|
+
if (!domParentTree) {
|
|
676
|
+
// some html elements may have side effects when setting their attributes.
|
|
677
|
+
// For example, setting the src attribute of an <img/> will trigger a
|
|
678
|
+
// request to get the corresponding image. This is something that we
|
|
679
|
+
// don't want at compile time. We avoid that by putting the content of
|
|
680
|
+
// the block in a <template/> element
|
|
681
|
+
const fragment = document.createElement("template").content;
|
|
682
|
+
fragment.appendChild(el);
|
|
683
|
+
}
|
|
607
684
|
for (let i = 0; i < attrs.length; i++) {
|
|
608
685
|
const attrName = attrs[i].name;
|
|
609
686
|
const attrValue = attrs[i].value;
|
|
@@ -693,7 +770,7 @@ function buildTree(node, parent = null, domParentTree = null) {
|
|
|
693
770
|
};
|
|
694
771
|
}
|
|
695
772
|
}
|
|
696
|
-
throw new
|
|
773
|
+
throw new OwlError("boom");
|
|
697
774
|
}
|
|
698
775
|
function addRef(tree) {
|
|
699
776
|
tree.isRef = true;
|
|
@@ -819,7 +896,7 @@ function updateCtx(ctx, tree) {
|
|
|
819
896
|
idx: info.idx,
|
|
820
897
|
refIdx: info.refIdx,
|
|
821
898
|
setData: makeRefSetter(index, ctx.refList),
|
|
822
|
-
updateData: NO_OP
|
|
899
|
+
updateData: NO_OP,
|
|
823
900
|
});
|
|
824
901
|
}
|
|
825
902
|
}
|
|
@@ -1286,29 +1363,35 @@ function html(str) {
|
|
|
1286
1363
|
}
|
|
1287
1364
|
|
|
1288
1365
|
function createCatcher(eventsSpec) {
|
|
1289
|
-
|
|
1290
|
-
let removeFns = [];
|
|
1291
|
-
for (let name in eventsSpec) {
|
|
1292
|
-
let index = eventsSpec[name];
|
|
1293
|
-
let { setup, remove } = createEventHandler(name);
|
|
1294
|
-
setupFns[index] = setup;
|
|
1295
|
-
removeFns[index] = remove;
|
|
1296
|
-
}
|
|
1297
|
-
let n = setupFns.length;
|
|
1366
|
+
const n = Object.keys(eventsSpec).length;
|
|
1298
1367
|
class VCatcher {
|
|
1299
1368
|
constructor(child, handlers) {
|
|
1369
|
+
this.handlerFns = [];
|
|
1300
1370
|
this.afterNode = null;
|
|
1301
1371
|
this.child = child;
|
|
1302
|
-
this.
|
|
1372
|
+
this.handlerData = handlers;
|
|
1303
1373
|
}
|
|
1304
1374
|
mount(parent, afterNode) {
|
|
1305
1375
|
this.parentEl = parent;
|
|
1306
|
-
this.afterNode = afterNode;
|
|
1307
1376
|
this.child.mount(parent, afterNode);
|
|
1377
|
+
this.afterNode = document.createTextNode("");
|
|
1378
|
+
parent.insertBefore(this.afterNode, afterNode);
|
|
1379
|
+
this.wrapHandlerData();
|
|
1380
|
+
for (let name in eventsSpec) {
|
|
1381
|
+
const index = eventsSpec[name];
|
|
1382
|
+
const handler = createEventHandler(name);
|
|
1383
|
+
this.handlerFns[index] = handler;
|
|
1384
|
+
handler.setup.call(parent, this.handlerData[index]);
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
wrapHandlerData() {
|
|
1308
1388
|
for (let i = 0; i < n; i++) {
|
|
1309
|
-
let
|
|
1389
|
+
let handler = this.handlerData[i];
|
|
1390
|
+
// handler = [...mods, fn, comp], so we need to replace second to last elem
|
|
1391
|
+
let idx = handler.length - 2;
|
|
1392
|
+
let origFn = handler[idx];
|
|
1310
1393
|
const self = this;
|
|
1311
|
-
|
|
1394
|
+
handler[idx] = function (ev) {
|
|
1312
1395
|
const target = ev.target;
|
|
1313
1396
|
let currentNode = self.child.firstNode();
|
|
1314
1397
|
const afterNode = self.afterNode;
|
|
@@ -1319,18 +1402,21 @@ function createCatcher(eventsSpec) {
|
|
|
1319
1402
|
currentNode = currentNode.nextSibling;
|
|
1320
1403
|
}
|
|
1321
1404
|
};
|
|
1322
|
-
setupFns[i].call(parent, this.handlers[i]);
|
|
1323
1405
|
}
|
|
1324
1406
|
}
|
|
1325
1407
|
moveBefore(other, afterNode) {
|
|
1326
|
-
this.afterNode = null;
|
|
1327
1408
|
this.child.moveBefore(other ? other.child : null, afterNode);
|
|
1409
|
+
this.parentEl.insertBefore(this.afterNode, afterNode);
|
|
1328
1410
|
}
|
|
1329
1411
|
patch(other, withBeforeRemove) {
|
|
1330
1412
|
if (this === other) {
|
|
1331
1413
|
return;
|
|
1332
1414
|
}
|
|
1333
|
-
this.
|
|
1415
|
+
this.handlerData = other.handlerData;
|
|
1416
|
+
this.wrapHandlerData();
|
|
1417
|
+
for (let i = 0; i < n; i++) {
|
|
1418
|
+
this.handlerFns[i].update.call(this.parentEl, this.handlerData[i]);
|
|
1419
|
+
}
|
|
1334
1420
|
this.child.patch(other.child, withBeforeRemove);
|
|
1335
1421
|
}
|
|
1336
1422
|
beforeRemove() {
|
|
@@ -1338,9 +1424,10 @@ function createCatcher(eventsSpec) {
|
|
|
1338
1424
|
}
|
|
1339
1425
|
remove() {
|
|
1340
1426
|
for (let i = 0; i < n; i++) {
|
|
1341
|
-
|
|
1427
|
+
this.handlerFns[i].remove.call(this.parentEl);
|
|
1342
1428
|
}
|
|
1343
1429
|
this.child.remove();
|
|
1430
|
+
this.afterNode.remove();
|
|
1344
1431
|
}
|
|
1345
1432
|
firstNode() {
|
|
1346
1433
|
return this.child.firstNode();
|
|
@@ -1367,142 +1454,339 @@ function remove(vnode, withBeforeRemove = false) {
|
|
|
1367
1454
|
vnode.remove();
|
|
1368
1455
|
}
|
|
1369
1456
|
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
let selfMode = false;
|
|
1376
|
-
const isSelf = ev.target === currentTarget;
|
|
1377
|
-
for (const mod of modifiers) {
|
|
1378
|
-
switch (mod) {
|
|
1379
|
-
case "self":
|
|
1380
|
-
selfMode = true;
|
|
1381
|
-
if (isSelf) {
|
|
1382
|
-
continue;
|
|
1383
|
-
}
|
|
1384
|
-
else {
|
|
1385
|
-
return stopped;
|
|
1386
|
-
}
|
|
1387
|
-
case "prevent":
|
|
1388
|
-
if ((selfMode && isSelf) || !selfMode)
|
|
1389
|
-
ev.preventDefault();
|
|
1390
|
-
continue;
|
|
1391
|
-
case "stop":
|
|
1392
|
-
if ((selfMode && isSelf) || !selfMode)
|
|
1393
|
-
ev.stopPropagation();
|
|
1394
|
-
stopped = true;
|
|
1395
|
-
continue;
|
|
1396
|
-
}
|
|
1397
|
-
}
|
|
1457
|
+
function makeChildFiber(node, parent) {
|
|
1458
|
+
let current = node.fiber;
|
|
1459
|
+
if (current) {
|
|
1460
|
+
cancelFibers(current.children);
|
|
1461
|
+
current.root = null;
|
|
1398
1462
|
}
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1463
|
+
return new Fiber(node, parent);
|
|
1464
|
+
}
|
|
1465
|
+
function makeRootFiber(node) {
|
|
1466
|
+
let current = node.fiber;
|
|
1467
|
+
if (current) {
|
|
1468
|
+
let root = current.root;
|
|
1469
|
+
// lock root fiber because canceling children fibers may destroy components,
|
|
1470
|
+
// which means any arbitrary code can be run in onWillDestroy, which may
|
|
1471
|
+
// trigger new renderings
|
|
1472
|
+
root.locked = true;
|
|
1473
|
+
root.setCounter(root.counter + 1 - cancelFibers(current.children));
|
|
1474
|
+
root.locked = false;
|
|
1475
|
+
current.children = [];
|
|
1476
|
+
current.childrenMap = {};
|
|
1477
|
+
current.bdom = null;
|
|
1478
|
+
if (fibersInError.has(current)) {
|
|
1479
|
+
fibersInError.delete(current);
|
|
1480
|
+
fibersInError.delete(root);
|
|
1481
|
+
current.appliedToDom = false;
|
|
1410
1482
|
}
|
|
1483
|
+
return current;
|
|
1411
1484
|
}
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
// Allows to get the target of a Reactive (used for making a new Reactive from the underlying object)
|
|
1416
|
-
const TARGET = Symbol("Target");
|
|
1417
|
-
// Escape hatch to prevent reactivity system to turn something into a reactive
|
|
1418
|
-
const SKIP = Symbol("Skip");
|
|
1419
|
-
// Special key to subscribe to, to be notified of key creation/deletion
|
|
1420
|
-
const KEYCHANGES = Symbol("Key changes");
|
|
1421
|
-
const objectToString = Object.prototype.toString;
|
|
1422
|
-
const objectHasOwnProperty = Object.prototype.hasOwnProperty;
|
|
1423
|
-
const SUPPORTED_RAW_TYPES = new Set(["Object", "Array", "Set", "Map", "WeakMap"]);
|
|
1424
|
-
const COLLECTION_RAWTYPES = new Set(["Set", "Map", "WeakMap"]);
|
|
1425
|
-
/**
|
|
1426
|
-
* extract "RawType" from strings like "[object RawType]" => this lets us ignore
|
|
1427
|
-
* many native objects such as Promise (whose toString is [object Promise])
|
|
1428
|
-
* or Date ([object Date]), while also supporting collections without using
|
|
1429
|
-
* instanceof in a loop
|
|
1430
|
-
*
|
|
1431
|
-
* @param obj the object to check
|
|
1432
|
-
* @returns the raw type of the object
|
|
1433
|
-
*/
|
|
1434
|
-
function rawType(obj) {
|
|
1435
|
-
return objectToString.call(obj).slice(8, -1);
|
|
1436
|
-
}
|
|
1437
|
-
/**
|
|
1438
|
-
* Checks whether a given value can be made into a reactive object.
|
|
1439
|
-
*
|
|
1440
|
-
* @param value the value to check
|
|
1441
|
-
* @returns whether the value can be made reactive
|
|
1442
|
-
*/
|
|
1443
|
-
function canBeMadeReactive(value) {
|
|
1444
|
-
if (typeof value !== "object") {
|
|
1445
|
-
return false;
|
|
1485
|
+
const fiber = new RootFiber(node, null);
|
|
1486
|
+
if (node.willPatch.length) {
|
|
1487
|
+
fiber.willPatch.push(fiber);
|
|
1446
1488
|
}
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
* returns the original object otherwise.
|
|
1452
|
-
*
|
|
1453
|
-
* @param value the value make reactive
|
|
1454
|
-
* @returns a reactive for the given object when possible, the original otherwise
|
|
1455
|
-
*/
|
|
1456
|
-
function possiblyReactive(val, cb) {
|
|
1457
|
-
return canBeMadeReactive(val) ? reactive(val, cb) : val;
|
|
1489
|
+
if (node.patched.length) {
|
|
1490
|
+
fiber.patched.push(fiber);
|
|
1491
|
+
}
|
|
1492
|
+
return fiber;
|
|
1458
1493
|
}
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
*
|
|
1462
|
-
* @param value the value to mark
|
|
1463
|
-
* @returns the object itself
|
|
1464
|
-
*/
|
|
1465
|
-
function markRaw(value) {
|
|
1466
|
-
value[SKIP] = true;
|
|
1467
|
-
return value;
|
|
1494
|
+
function throwOnRender() {
|
|
1495
|
+
throw new OwlError("Attempted to render cancelled fiber");
|
|
1468
1496
|
}
|
|
1469
1497
|
/**
|
|
1470
|
-
*
|
|
1471
|
-
*
|
|
1472
|
-
* @param value a reactive value
|
|
1473
|
-
* @returns the underlying value
|
|
1498
|
+
* @returns number of not-yet rendered fibers cancelled
|
|
1474
1499
|
*/
|
|
1475
|
-
function
|
|
1476
|
-
|
|
1500
|
+
function cancelFibers(fibers) {
|
|
1501
|
+
let result = 0;
|
|
1502
|
+
for (let fiber of fibers) {
|
|
1503
|
+
let node = fiber.node;
|
|
1504
|
+
fiber.render = throwOnRender;
|
|
1505
|
+
if (node.status === 0 /* NEW */) {
|
|
1506
|
+
node.destroy();
|
|
1507
|
+
delete node.parent.children[node.parentKey];
|
|
1508
|
+
}
|
|
1509
|
+
node.fiber = null;
|
|
1510
|
+
if (fiber.bdom) {
|
|
1511
|
+
// if fiber has been rendered, this means that the component props have
|
|
1512
|
+
// been updated. however, this fiber will not be patched to the dom, so
|
|
1513
|
+
// it could happen that the next render compare the current props with
|
|
1514
|
+
// the same props, and skip the render completely. With the next line,
|
|
1515
|
+
// we kindly request the component code to force a render, so it works as
|
|
1516
|
+
// expected.
|
|
1517
|
+
node.forceNextRender = true;
|
|
1518
|
+
}
|
|
1519
|
+
else {
|
|
1520
|
+
result++;
|
|
1521
|
+
}
|
|
1522
|
+
result += cancelFibers(fiber.children);
|
|
1523
|
+
}
|
|
1524
|
+
return result;
|
|
1477
1525
|
}
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1526
|
+
class Fiber {
|
|
1527
|
+
constructor(node, parent) {
|
|
1528
|
+
this.bdom = null;
|
|
1529
|
+
this.children = [];
|
|
1530
|
+
this.appliedToDom = false;
|
|
1531
|
+
this.deep = false;
|
|
1532
|
+
this.childrenMap = {};
|
|
1533
|
+
this.node = node;
|
|
1534
|
+
this.parent = parent;
|
|
1535
|
+
if (parent) {
|
|
1536
|
+
this.deep = parent.deep;
|
|
1537
|
+
const root = parent.root;
|
|
1538
|
+
root.setCounter(root.counter + 1);
|
|
1539
|
+
this.root = root;
|
|
1540
|
+
parent.children.push(this);
|
|
1541
|
+
}
|
|
1542
|
+
else {
|
|
1543
|
+
this.root = this;
|
|
1544
|
+
}
|
|
1491
1545
|
}
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1546
|
+
render() {
|
|
1547
|
+
// if some parent has a fiber => register in followup
|
|
1548
|
+
let prev = this.root.node;
|
|
1549
|
+
let scheduler = prev.app.scheduler;
|
|
1550
|
+
let current = prev.parent;
|
|
1551
|
+
while (current) {
|
|
1552
|
+
if (current.fiber) {
|
|
1553
|
+
let root = current.fiber.root;
|
|
1554
|
+
if (root.counter === 0 && prev.parentKey in current.fiber.childrenMap) {
|
|
1555
|
+
current = root.node;
|
|
1556
|
+
}
|
|
1557
|
+
else {
|
|
1558
|
+
scheduler.delayedRenders.push(this);
|
|
1559
|
+
return;
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
prev = current;
|
|
1563
|
+
current = current.parent;
|
|
1564
|
+
}
|
|
1565
|
+
// there are no current rendering from above => we can render
|
|
1566
|
+
this._render();
|
|
1495
1567
|
}
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1568
|
+
_render() {
|
|
1569
|
+
const node = this.node;
|
|
1570
|
+
const root = this.root;
|
|
1571
|
+
if (root) {
|
|
1572
|
+
try {
|
|
1573
|
+
this.bdom = true;
|
|
1574
|
+
this.bdom = node.renderFn();
|
|
1575
|
+
}
|
|
1576
|
+
catch (e) {
|
|
1577
|
+
node.app.handleError({ node, error: e });
|
|
1578
|
+
}
|
|
1579
|
+
root.setCounter(root.counter - 1);
|
|
1580
|
+
}
|
|
1499
1581
|
}
|
|
1500
|
-
callbacksToTargets.get(callback).add(target);
|
|
1501
1582
|
}
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1583
|
+
class RootFiber extends Fiber {
|
|
1584
|
+
constructor() {
|
|
1585
|
+
super(...arguments);
|
|
1586
|
+
this.counter = 1;
|
|
1587
|
+
// only add stuff in this if they have registered some hooks
|
|
1588
|
+
this.willPatch = [];
|
|
1589
|
+
this.patched = [];
|
|
1590
|
+
this.mounted = [];
|
|
1591
|
+
// A fiber is typically locked when it is completing and the patch has not, or is being applied.
|
|
1592
|
+
// i.e.: render triggered in onWillUnmount or in willPatch will be delayed
|
|
1593
|
+
this.locked = false;
|
|
1594
|
+
}
|
|
1595
|
+
complete() {
|
|
1596
|
+
const node = this.node;
|
|
1597
|
+
this.locked = true;
|
|
1598
|
+
let current = undefined;
|
|
1599
|
+
try {
|
|
1600
|
+
// Step 1: calling all willPatch lifecycle hooks
|
|
1601
|
+
for (current of this.willPatch) {
|
|
1602
|
+
// because of the asynchronous nature of the rendering, some parts of the
|
|
1603
|
+
// UI may have been rendered, then deleted in a followup rendering, and we
|
|
1604
|
+
// do not want to call onWillPatch in that case.
|
|
1605
|
+
let node = current.node;
|
|
1606
|
+
if (node.fiber === current) {
|
|
1607
|
+
const component = node.component;
|
|
1608
|
+
for (let cb of node.willPatch) {
|
|
1609
|
+
cb.call(component);
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
current = undefined;
|
|
1614
|
+
// Step 2: patching the dom
|
|
1615
|
+
node._patch();
|
|
1616
|
+
this.locked = false;
|
|
1617
|
+
// Step 4: calling all mounted lifecycle hooks
|
|
1618
|
+
let mountedFibers = this.mounted;
|
|
1619
|
+
while ((current = mountedFibers.pop())) {
|
|
1620
|
+
current = current;
|
|
1621
|
+
if (current.appliedToDom) {
|
|
1622
|
+
for (let cb of current.node.mounted) {
|
|
1623
|
+
cb();
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
// Step 5: calling all patched hooks
|
|
1628
|
+
let patchedFibers = this.patched;
|
|
1629
|
+
while ((current = patchedFibers.pop())) {
|
|
1630
|
+
current = current;
|
|
1631
|
+
if (current.appliedToDom) {
|
|
1632
|
+
for (let cb of current.node.patched) {
|
|
1633
|
+
cb();
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
catch (e) {
|
|
1639
|
+
this.locked = false;
|
|
1640
|
+
node.app.handleError({ fiber: current || this, error: e });
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
setCounter(newValue) {
|
|
1644
|
+
this.counter = newValue;
|
|
1645
|
+
if (newValue === 0) {
|
|
1646
|
+
this.node.app.scheduler.flush();
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
class MountFiber extends RootFiber {
|
|
1651
|
+
constructor(node, target, options = {}) {
|
|
1652
|
+
super(node, null);
|
|
1653
|
+
this.target = target;
|
|
1654
|
+
this.position = options.position || "last-child";
|
|
1655
|
+
}
|
|
1656
|
+
complete() {
|
|
1657
|
+
let current = this;
|
|
1658
|
+
try {
|
|
1659
|
+
const node = this.node;
|
|
1660
|
+
node.children = this.childrenMap;
|
|
1661
|
+
node.app.constructor.validateTarget(this.target);
|
|
1662
|
+
if (node.bdom) {
|
|
1663
|
+
// this is a complicated situation: if we mount a fiber with an existing
|
|
1664
|
+
// bdom, this means that this same fiber was already completed, mounted,
|
|
1665
|
+
// but a crash occurred in some mounted hook. Then, it was handled and
|
|
1666
|
+
// the new rendering is being applied.
|
|
1667
|
+
node.updateDom();
|
|
1668
|
+
}
|
|
1669
|
+
else {
|
|
1670
|
+
node.bdom = this.bdom;
|
|
1671
|
+
if (this.position === "last-child" || this.target.childNodes.length === 0) {
|
|
1672
|
+
mount$1(node.bdom, this.target);
|
|
1673
|
+
}
|
|
1674
|
+
else {
|
|
1675
|
+
const firstChild = this.target.childNodes[0];
|
|
1676
|
+
mount$1(node.bdom, this.target, firstChild);
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
// unregistering the fiber before mounted since it can do another render
|
|
1680
|
+
// and that the current rendering is obviously completed
|
|
1681
|
+
node.fiber = null;
|
|
1682
|
+
node.status = 1 /* MOUNTED */;
|
|
1683
|
+
this.appliedToDom = true;
|
|
1684
|
+
let mountedFibers = this.mounted;
|
|
1685
|
+
while ((current = mountedFibers.pop())) {
|
|
1686
|
+
if (current.appliedToDom) {
|
|
1687
|
+
for (let cb of current.node.mounted) {
|
|
1688
|
+
cb();
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
catch (e) {
|
|
1694
|
+
this.node.app.handleError({ fiber: current, error: e });
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
// Allows to get the target of a Reactive (used for making a new Reactive from the underlying object)
|
|
1700
|
+
const TARGET = Symbol("Target");
|
|
1701
|
+
// Escape hatch to prevent reactivity system to turn something into a reactive
|
|
1702
|
+
const SKIP = Symbol("Skip");
|
|
1703
|
+
// Special key to subscribe to, to be notified of key creation/deletion
|
|
1704
|
+
const KEYCHANGES = Symbol("Key changes");
|
|
1705
|
+
const objectToString = Object.prototype.toString;
|
|
1706
|
+
const objectHasOwnProperty = Object.prototype.hasOwnProperty;
|
|
1707
|
+
const SUPPORTED_RAW_TYPES = new Set(["Object", "Array", "Set", "Map", "WeakMap"]);
|
|
1708
|
+
const COLLECTION_RAWTYPES = new Set(["Set", "Map", "WeakMap"]);
|
|
1709
|
+
/**
|
|
1710
|
+
* extract "RawType" from strings like "[object RawType]" => this lets us ignore
|
|
1711
|
+
* many native objects such as Promise (whose toString is [object Promise])
|
|
1712
|
+
* or Date ([object Date]), while also supporting collections without using
|
|
1713
|
+
* instanceof in a loop
|
|
1714
|
+
*
|
|
1715
|
+
* @param obj the object to check
|
|
1716
|
+
* @returns the raw type of the object
|
|
1717
|
+
*/
|
|
1718
|
+
function rawType(obj) {
|
|
1719
|
+
return objectToString.call(obj).slice(8, -1);
|
|
1720
|
+
}
|
|
1721
|
+
/**
|
|
1722
|
+
* Checks whether a given value can be made into a reactive object.
|
|
1723
|
+
*
|
|
1724
|
+
* @param value the value to check
|
|
1725
|
+
* @returns whether the value can be made reactive
|
|
1726
|
+
*/
|
|
1727
|
+
function canBeMadeReactive(value) {
|
|
1728
|
+
if (typeof value !== "object") {
|
|
1729
|
+
return false;
|
|
1730
|
+
}
|
|
1731
|
+
return SUPPORTED_RAW_TYPES.has(rawType(value));
|
|
1732
|
+
}
|
|
1733
|
+
/**
|
|
1734
|
+
* Creates a reactive from the given object/callback if possible and returns it,
|
|
1735
|
+
* returns the original object otherwise.
|
|
1736
|
+
*
|
|
1737
|
+
* @param value the value make reactive
|
|
1738
|
+
* @returns a reactive for the given object when possible, the original otherwise
|
|
1739
|
+
*/
|
|
1740
|
+
function possiblyReactive(val, cb) {
|
|
1741
|
+
return canBeMadeReactive(val) ? reactive(val, cb) : val;
|
|
1742
|
+
}
|
|
1743
|
+
/**
|
|
1744
|
+
* Mark an object or array so that it is ignored by the reactivity system
|
|
1745
|
+
*
|
|
1746
|
+
* @param value the value to mark
|
|
1747
|
+
* @returns the object itself
|
|
1748
|
+
*/
|
|
1749
|
+
function markRaw(value) {
|
|
1750
|
+
value[SKIP] = true;
|
|
1751
|
+
return value;
|
|
1752
|
+
}
|
|
1753
|
+
/**
|
|
1754
|
+
* Given a reactive objet, return the raw (non reactive) underlying object
|
|
1755
|
+
*
|
|
1756
|
+
* @param value a reactive value
|
|
1757
|
+
* @returns the underlying value
|
|
1758
|
+
*/
|
|
1759
|
+
function toRaw(value) {
|
|
1760
|
+
return value[TARGET] || value;
|
|
1761
|
+
}
|
|
1762
|
+
const targetToKeysToCallbacks = new WeakMap();
|
|
1763
|
+
/**
|
|
1764
|
+
* Observes a given key on a target with an callback. The callback will be
|
|
1765
|
+
* called when the given key changes on the target.
|
|
1766
|
+
*
|
|
1767
|
+
* @param target the target whose key should be observed
|
|
1768
|
+
* @param key the key to observe (or Symbol(KEYCHANGES) for key creation
|
|
1769
|
+
* or deletion)
|
|
1770
|
+
* @param callback the function to call when the key changes
|
|
1771
|
+
*/
|
|
1772
|
+
function observeTargetKey(target, key, callback) {
|
|
1773
|
+
if (!targetToKeysToCallbacks.get(target)) {
|
|
1774
|
+
targetToKeysToCallbacks.set(target, new Map());
|
|
1775
|
+
}
|
|
1776
|
+
const keyToCallbacks = targetToKeysToCallbacks.get(target);
|
|
1777
|
+
if (!keyToCallbacks.get(key)) {
|
|
1778
|
+
keyToCallbacks.set(key, new Set());
|
|
1779
|
+
}
|
|
1780
|
+
keyToCallbacks.get(key).add(callback);
|
|
1781
|
+
if (!callbacksToTargets.has(callback)) {
|
|
1782
|
+
callbacksToTargets.set(callback, new Set());
|
|
1783
|
+
}
|
|
1784
|
+
callbacksToTargets.get(callback).add(target);
|
|
1785
|
+
}
|
|
1786
|
+
/**
|
|
1787
|
+
* Notify Reactives that are observing a given target that a key has changed on
|
|
1788
|
+
* the target.
|
|
1789
|
+
*
|
|
1506
1790
|
* @param target target whose Reactives should be notified that the target was
|
|
1507
1791
|
* changed.
|
|
1508
1792
|
* @param key the key that changed (or Symbol `KEYCHANGES` if a key was created
|
|
@@ -1585,7 +1869,7 @@ const reactiveCache = new WeakMap();
|
|
|
1585
1869
|
*/
|
|
1586
1870
|
function reactive(target, callback = () => { }) {
|
|
1587
1871
|
if (!canBeMadeReactive(target)) {
|
|
1588
|
-
throw new
|
|
1872
|
+
throw new OwlError(`Cannot make the given value reactive`);
|
|
1589
1873
|
}
|
|
1590
1874
|
if (SKIP in target) {
|
|
1591
1875
|
return target;
|
|
@@ -1595,7 +1879,7 @@ function reactive(target, callback = () => { }) {
|
|
|
1595
1879
|
return reactive(originalTarget, callback);
|
|
1596
1880
|
}
|
|
1597
1881
|
if (!reactiveCache.has(target)) {
|
|
1598
|
-
reactiveCache.set(target, new
|
|
1882
|
+
reactiveCache.set(target, new WeakMap());
|
|
1599
1883
|
}
|
|
1600
1884
|
const reactivesForTarget = reactiveCache.get(target);
|
|
1601
1885
|
if (!reactivesForTarget.has(callback)) {
|
|
@@ -1620,6 +1904,11 @@ function basicProxyHandler(callback) {
|
|
|
1620
1904
|
if (key === TARGET) {
|
|
1621
1905
|
return target;
|
|
1622
1906
|
}
|
|
1907
|
+
// non-writable non-configurable properties cannot be made reactive
|
|
1908
|
+
const desc = Object.getOwnPropertyDescriptor(target, key);
|
|
1909
|
+
if (desc && !desc.writable && !desc.configurable) {
|
|
1910
|
+
return Reflect.get(target, key, proxy);
|
|
1911
|
+
}
|
|
1623
1912
|
observeTargetKey(target, key, callback);
|
|
1624
1913
|
return possiblyReactive(Reflect.get(target, key, proxy), callback);
|
|
1625
1914
|
},
|
|
@@ -1692,6 +1981,23 @@ function makeIteratorObserver(methodName, target, callback) {
|
|
|
1692
1981
|
}
|
|
1693
1982
|
};
|
|
1694
1983
|
}
|
|
1984
|
+
/**
|
|
1985
|
+
* Creates a forEach function that will delegate to forEach on the underlying
|
|
1986
|
+
* collection while observing key changes, and keys as they're iterated over,
|
|
1987
|
+
* and making the passed keys/values reactive.
|
|
1988
|
+
*
|
|
1989
|
+
* @param target @see reactive
|
|
1990
|
+
* @param callback @see reactive
|
|
1991
|
+
*/
|
|
1992
|
+
function makeForEachObserver(target, callback) {
|
|
1993
|
+
return function forEach(forEachCb, thisArg) {
|
|
1994
|
+
observeTargetKey(target, KEYCHANGES, callback);
|
|
1995
|
+
target.forEach(function (val, key, targetObj) {
|
|
1996
|
+
observeTargetKey(target, key, callback);
|
|
1997
|
+
forEachCb.call(thisArg, possiblyReactive(val, callback), possiblyReactive(key, callback), possiblyReactive(targetObj, callback));
|
|
1998
|
+
}, thisArg);
|
|
1999
|
+
};
|
|
2000
|
+
}
|
|
1695
2001
|
/**
|
|
1696
2002
|
* Creates a function that will delegate to an underlying method, and check if
|
|
1697
2003
|
* that method has modified the presence or value of a key, and notify the
|
|
@@ -1750,6 +2056,7 @@ const rawTypeToFuncHandlers = {
|
|
|
1750
2056
|
values: makeIteratorObserver("values", target, callback),
|
|
1751
2057
|
entries: makeIteratorObserver("entries", target, callback),
|
|
1752
2058
|
[Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
|
|
2059
|
+
forEach: makeForEachObserver(target, callback),
|
|
1753
2060
|
clear: makeClearNotifier(target),
|
|
1754
2061
|
get size() {
|
|
1755
2062
|
observeTargetKey(target, KEYCHANGES, callback);
|
|
@@ -1765,6 +2072,7 @@ const rawTypeToFuncHandlers = {
|
|
|
1765
2072
|
values: makeIteratorObserver("values", target, callback),
|
|
1766
2073
|
entries: makeIteratorObserver("entries", target, callback),
|
|
1767
2074
|
[Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
|
|
2075
|
+
forEach: makeForEachObserver(target, callback),
|
|
1768
2076
|
clear: makeClearNotifier(target),
|
|
1769
2077
|
get size() {
|
|
1770
2078
|
observeTargetKey(target, KEYCHANGES, callback);
|
|
@@ -1818,21 +2126,29 @@ function batched(callback) {
|
|
|
1818
2126
|
await Promise.resolve();
|
|
1819
2127
|
if (!called) {
|
|
1820
2128
|
called = true;
|
|
1821
|
-
callback();
|
|
1822
2129
|
// wait for all calls in this microtick to fall through before resetting "called"
|
|
1823
|
-
// so that only the first call to the batched function calls the original callback
|
|
1824
|
-
|
|
1825
|
-
called
|
|
2130
|
+
// so that only the first call to the batched function calls the original callback.
|
|
2131
|
+
// Schedule this before calling the callback so that calls to the batched function
|
|
2132
|
+
// within the callback will proceed only after resetting called to false, and have
|
|
2133
|
+
// a chance to execute the callback again
|
|
2134
|
+
Promise.resolve().then(() => (called = false));
|
|
2135
|
+
callback();
|
|
1826
2136
|
}
|
|
1827
2137
|
};
|
|
1828
2138
|
}
|
|
1829
2139
|
function validateTarget(target) {
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
2140
|
+
// Get the document and HTMLElement corresponding to the target to allow mounting in iframes
|
|
2141
|
+
const document = target && target.ownerDocument;
|
|
2142
|
+
if (document) {
|
|
2143
|
+
const HTMLElement = document.defaultView.HTMLElement;
|
|
2144
|
+
if (target instanceof HTMLElement) {
|
|
2145
|
+
if (!document.body.contains(target)) {
|
|
2146
|
+
throw new OwlError("Cannot mount a component on a detached dom node");
|
|
2147
|
+
}
|
|
2148
|
+
return;
|
|
2149
|
+
}
|
|
1835
2150
|
}
|
|
2151
|
+
throw new OwlError("Cannot mount component: the target is not a valid DOM element");
|
|
1836
2152
|
}
|
|
1837
2153
|
class EventBus extends EventTarget {
|
|
1838
2154
|
trigger(name, payload) {
|
|
@@ -1852,7 +2168,7 @@ function whenReady(fn) {
|
|
|
1852
2168
|
async function loadFile(url) {
|
|
1853
2169
|
const result = await fetch(url);
|
|
1854
2170
|
if (!result.ok) {
|
|
1855
|
-
throw new
|
|
2171
|
+
throw new OwlError("Error while fetching xml templates");
|
|
1856
2172
|
}
|
|
1857
2173
|
return await result.text();
|
|
1858
2174
|
}
|
|
@@ -1869,861 +2185,1054 @@ class Markup extends String {
|
|
|
1869
2185
|
*/
|
|
1870
2186
|
function markup(value) {
|
|
1871
2187
|
return new Markup(value);
|
|
1872
|
-
}
|
|
1873
|
-
// -----------------------------------------------------------------------------
|
|
1874
|
-
// xml tag helper
|
|
1875
|
-
// -----------------------------------------------------------------------------
|
|
1876
|
-
const globalTemplates = {};
|
|
1877
|
-
function xml(...args) {
|
|
1878
|
-
const name = `__template__${xml.nextId++}`;
|
|
1879
|
-
const value = String.raw(...args);
|
|
1880
|
-
globalTemplates[name] = value;
|
|
1881
|
-
return name;
|
|
1882
|
-
}
|
|
1883
|
-
xml.nextId = 1;
|
|
2188
|
+
}
|
|
1884
2189
|
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
if (!node) {
|
|
1890
|
-
return false;
|
|
2190
|
+
let currentNode = null;
|
|
2191
|
+
function getCurrent() {
|
|
2192
|
+
if (!currentNode) {
|
|
2193
|
+
throw new OwlError("No active component (a hook function should only be called in 'setup')");
|
|
1891
2194
|
}
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
2195
|
+
return currentNode;
|
|
2196
|
+
}
|
|
2197
|
+
function useComponent() {
|
|
2198
|
+
return currentNode.component;
|
|
2199
|
+
}
|
|
2200
|
+
/**
|
|
2201
|
+
* Apply default props (only top level).
|
|
2202
|
+
*/
|
|
2203
|
+
function applyDefaultProps(props, defaultProps) {
|
|
2204
|
+
for (let propName in defaultProps) {
|
|
2205
|
+
if (props[propName] === undefined) {
|
|
2206
|
+
props[propName] = defaultProps[propName];
|
|
2207
|
+
}
|
|
1895
2208
|
}
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
2209
|
+
}
|
|
2210
|
+
// -----------------------------------------------------------------------------
|
|
2211
|
+
// Integration with reactivity system (useState)
|
|
2212
|
+
// -----------------------------------------------------------------------------
|
|
2213
|
+
const batchedRenderFunctions = new WeakMap();
|
|
2214
|
+
/**
|
|
2215
|
+
* Creates a reactive object that will be observed by the current component.
|
|
2216
|
+
* Reading data from the returned object (eg during rendering) will cause the
|
|
2217
|
+
* component to subscribe to that data and be rerendered when it changes.
|
|
2218
|
+
*
|
|
2219
|
+
* @param state the state to observe
|
|
2220
|
+
* @returns a reactive object that will cause the component to re-render on
|
|
2221
|
+
* relevant changes
|
|
2222
|
+
* @see reactive
|
|
2223
|
+
*/
|
|
2224
|
+
function useState(state) {
|
|
2225
|
+
const node = getCurrent();
|
|
2226
|
+
let render = batchedRenderFunctions.get(node);
|
|
2227
|
+
if (!render) {
|
|
2228
|
+
render = batched(node.render.bind(node, false));
|
|
2229
|
+
batchedRenderFunctions.set(node, render);
|
|
2230
|
+
// manual implementation of onWillDestroy to break cyclic dependency
|
|
2231
|
+
node.willDestroy.push(clearReactivesForCallback.bind(null, render));
|
|
2232
|
+
}
|
|
2233
|
+
return reactive(state, render);
|
|
2234
|
+
}
|
|
2235
|
+
class ComponentNode {
|
|
2236
|
+
constructor(C, props, app, parent, parentKey) {
|
|
2237
|
+
this.fiber = null;
|
|
2238
|
+
this.bdom = null;
|
|
2239
|
+
this.status = 0 /* NEW */;
|
|
2240
|
+
this.forceNextRender = false;
|
|
2241
|
+
this.children = Object.create(null);
|
|
2242
|
+
this.refs = {};
|
|
2243
|
+
this.willStart = [];
|
|
2244
|
+
this.willUpdateProps = [];
|
|
2245
|
+
this.willUnmount = [];
|
|
2246
|
+
this.mounted = [];
|
|
2247
|
+
this.willPatch = [];
|
|
2248
|
+
this.patched = [];
|
|
2249
|
+
this.willDestroy = [];
|
|
2250
|
+
currentNode = this;
|
|
2251
|
+
this.app = app;
|
|
2252
|
+
this.parent = parent;
|
|
2253
|
+
this.props = props;
|
|
2254
|
+
this.parentKey = parentKey;
|
|
2255
|
+
const defaultProps = C.defaultProps;
|
|
2256
|
+
props = Object.assign({}, props);
|
|
2257
|
+
if (defaultProps) {
|
|
2258
|
+
applyDefaultProps(props, defaultProps);
|
|
1909
2259
|
}
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
2260
|
+
const env = (parent && parent.childEnv) || app.env;
|
|
2261
|
+
this.childEnv = env;
|
|
2262
|
+
for (const key in props) {
|
|
2263
|
+
const prop = props[key];
|
|
2264
|
+
if (prop && typeof prop === "object" && prop[TARGET]) {
|
|
2265
|
+
props[key] = useState(prop);
|
|
1913
2266
|
}
|
|
1914
|
-
return true;
|
|
1915
2267
|
}
|
|
2268
|
+
this.component = new C(props, env, this);
|
|
2269
|
+
this.renderFn = app.getTemplate(C.template).bind(this.component, this.component, this);
|
|
2270
|
+
this.component.setup();
|
|
2271
|
+
currentNode = null;
|
|
1916
2272
|
}
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
current = current.parent;
|
|
1929
|
-
} while (current);
|
|
1930
|
-
fibersInError.set(fiber.root, error);
|
|
1931
|
-
const handled = _handleError(node, error, true);
|
|
1932
|
-
if (!handled) {
|
|
1933
|
-
console.warn(`[Owl] Unhandled error. Destroying the root component`);
|
|
2273
|
+
mountComponent(target, options) {
|
|
2274
|
+
const fiber = new MountFiber(this, target, options);
|
|
2275
|
+
this.app.scheduler.addFiber(fiber);
|
|
2276
|
+
this.initiateRender(fiber);
|
|
2277
|
+
}
|
|
2278
|
+
async initiateRender(fiber) {
|
|
2279
|
+
this.fiber = fiber;
|
|
2280
|
+
if (this.mounted.length) {
|
|
2281
|
+
fiber.root.mounted.push(fiber);
|
|
2282
|
+
}
|
|
2283
|
+
const component = this.component;
|
|
1934
2284
|
try {
|
|
1935
|
-
|
|
2285
|
+
await Promise.all(this.willStart.map((f) => f.call(component)));
|
|
1936
2286
|
}
|
|
1937
2287
|
catch (e) {
|
|
1938
|
-
|
|
2288
|
+
this.app.handleError({ node: this, error: e });
|
|
2289
|
+
return;
|
|
1939
2290
|
}
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
function makeChildFiber(node, parent) {
|
|
1944
|
-
let current = node.fiber;
|
|
1945
|
-
if (current) {
|
|
1946
|
-
cancelFibers(current.children);
|
|
1947
|
-
current.root = null;
|
|
1948
|
-
}
|
|
1949
|
-
return new Fiber(node, parent);
|
|
1950
|
-
}
|
|
1951
|
-
function makeRootFiber(node) {
|
|
1952
|
-
let current = node.fiber;
|
|
1953
|
-
if (current) {
|
|
1954
|
-
let root = current.root;
|
|
1955
|
-
root.counter = root.counter + 1 - cancelFibers(current.children);
|
|
1956
|
-
current.children = [];
|
|
1957
|
-
current.bdom = null;
|
|
1958
|
-
if (fibersInError.has(current)) {
|
|
1959
|
-
fibersInError.delete(current);
|
|
1960
|
-
fibersInError.delete(root);
|
|
1961
|
-
current.appliedToDom = false;
|
|
2291
|
+
if (this.status === 0 /* NEW */ && this.fiber === fiber) {
|
|
2292
|
+
fiber.render();
|
|
1962
2293
|
}
|
|
1963
|
-
return current;
|
|
1964
|
-
}
|
|
1965
|
-
const fiber = new RootFiber(node, null);
|
|
1966
|
-
if (node.willPatch.length) {
|
|
1967
|
-
fiber.willPatch.push(fiber);
|
|
1968
2294
|
}
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
* @returns number of not-yet rendered fibers cancelled
|
|
1976
|
-
*/
|
|
1977
|
-
function cancelFibers(fibers) {
|
|
1978
|
-
let result = 0;
|
|
1979
|
-
for (let fiber of fibers) {
|
|
1980
|
-
fiber.node.fiber = null;
|
|
1981
|
-
if (fiber.bdom) {
|
|
1982
|
-
// if fiber has been rendered, this means that the component props have
|
|
1983
|
-
// been updated. however, this fiber will not be patched to the dom, so
|
|
1984
|
-
// it could happen that the next render compare the current props with
|
|
1985
|
-
// the same props, and skip the render completely. With the next line,
|
|
1986
|
-
// we kindly request the component code to force a render, so it works as
|
|
1987
|
-
// expected.
|
|
1988
|
-
fiber.node.forceNextRender = true;
|
|
2295
|
+
async render(deep) {
|
|
2296
|
+
let current = this.fiber;
|
|
2297
|
+
if (current && (current.root.locked || current.bdom === true)) {
|
|
2298
|
+
await Promise.resolve();
|
|
2299
|
+
// situation may have changed after the microtask tick
|
|
2300
|
+
current = this.fiber;
|
|
1989
2301
|
}
|
|
1990
|
-
|
|
1991
|
-
|
|
2302
|
+
if (current) {
|
|
2303
|
+
if (!current.bdom && !fibersInError.has(current)) {
|
|
2304
|
+
if (deep) {
|
|
2305
|
+
// we want the render from this point on to be with deep=true
|
|
2306
|
+
current.deep = deep;
|
|
2307
|
+
}
|
|
2308
|
+
return;
|
|
2309
|
+
}
|
|
2310
|
+
// if current rendering was with deep=true, we want this one to be the same
|
|
2311
|
+
deep = deep || current.deep;
|
|
1992
2312
|
}
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
return result;
|
|
1996
|
-
}
|
|
1997
|
-
class Fiber {
|
|
1998
|
-
constructor(node, parent) {
|
|
1999
|
-
this.bdom = null;
|
|
2000
|
-
this.children = [];
|
|
2001
|
-
this.appliedToDom = false;
|
|
2002
|
-
this.deep = false;
|
|
2003
|
-
this.node = node;
|
|
2004
|
-
this.parent = parent;
|
|
2005
|
-
if (parent) {
|
|
2006
|
-
this.deep = parent.deep;
|
|
2007
|
-
const root = parent.root;
|
|
2008
|
-
root.counter++;
|
|
2009
|
-
this.root = root;
|
|
2010
|
-
parent.children.push(this);
|
|
2313
|
+
else if (!this.bdom) {
|
|
2314
|
+
return;
|
|
2011
2315
|
}
|
|
2012
|
-
|
|
2013
|
-
|
|
2316
|
+
const fiber = makeRootFiber(this);
|
|
2317
|
+
fiber.deep = deep;
|
|
2318
|
+
this.fiber = fiber;
|
|
2319
|
+
this.app.scheduler.addFiber(fiber);
|
|
2320
|
+
await Promise.resolve();
|
|
2321
|
+
if (this.status === 2 /* DESTROYED */) {
|
|
2322
|
+
return;
|
|
2323
|
+
}
|
|
2324
|
+
// We only want to actually render the component if the following two
|
|
2325
|
+
// conditions are true:
|
|
2326
|
+
// * this.fiber: it could be null, in which case the render has been cancelled
|
|
2327
|
+
// * (current || !fiber.parent): if current is not null, this means that the
|
|
2328
|
+
// render function was called when a render was already occurring. In this
|
|
2329
|
+
// case, the pending rendering was cancelled, and the fiber needs to be
|
|
2330
|
+
// rendered to complete the work. If current is null, we check that the
|
|
2331
|
+
// fiber has no parent. If that is the case, the fiber was downgraded from
|
|
2332
|
+
// a root fiber to a child fiber in the previous microtick, because it was
|
|
2333
|
+
// embedded in a rendering coming from above, so the fiber will be rendered
|
|
2334
|
+
// in the next microtick anyway, so we should not render it again.
|
|
2335
|
+
if (this.fiber === fiber && (current || !fiber.parent)) {
|
|
2336
|
+
fiber.render();
|
|
2014
2337
|
}
|
|
2015
2338
|
}
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
this.willPatch = [];
|
|
2023
|
-
this.patched = [];
|
|
2024
|
-
this.mounted = [];
|
|
2025
|
-
// A fiber is typically locked when it is completing and the patch has not, or is being applied.
|
|
2026
|
-
// i.e.: render triggered in onWillUnmount or in willPatch will be delayed
|
|
2027
|
-
this.locked = false;
|
|
2339
|
+
destroy() {
|
|
2340
|
+
let shouldRemove = this.status === 1 /* MOUNTED */;
|
|
2341
|
+
this._destroy();
|
|
2342
|
+
if (shouldRemove) {
|
|
2343
|
+
this.bdom.remove();
|
|
2344
|
+
}
|
|
2028
2345
|
}
|
|
2029
|
-
|
|
2030
|
-
const
|
|
2031
|
-
this.
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
// Step 1: calling all willPatch lifecycle hooks
|
|
2035
|
-
for (current of this.willPatch) {
|
|
2036
|
-
// because of the asynchronous nature of the rendering, some parts of the
|
|
2037
|
-
// UI may have been rendered, then deleted in a followup rendering, and we
|
|
2038
|
-
// do not want to call onWillPatch in that case.
|
|
2039
|
-
let node = current.node;
|
|
2040
|
-
if (node.fiber === current) {
|
|
2041
|
-
const component = node.component;
|
|
2042
|
-
for (let cb of node.willPatch) {
|
|
2043
|
-
cb.call(component);
|
|
2044
|
-
}
|
|
2045
|
-
}
|
|
2346
|
+
_destroy() {
|
|
2347
|
+
const component = this.component;
|
|
2348
|
+
if (this.status === 1 /* MOUNTED */) {
|
|
2349
|
+
for (let cb of this.willUnmount) {
|
|
2350
|
+
cb.call(component);
|
|
2046
2351
|
}
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
if (current.appliedToDom) {
|
|
2056
|
-
for (let cb of current.node.mounted) {
|
|
2057
|
-
cb();
|
|
2058
|
-
}
|
|
2352
|
+
}
|
|
2353
|
+
for (let child of Object.values(this.children)) {
|
|
2354
|
+
child._destroy();
|
|
2355
|
+
}
|
|
2356
|
+
if (this.willDestroy.length) {
|
|
2357
|
+
try {
|
|
2358
|
+
for (let cb of this.willDestroy) {
|
|
2359
|
+
cb.call(component);
|
|
2059
2360
|
}
|
|
2060
2361
|
}
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
while ((current = patchedFibers.pop())) {
|
|
2064
|
-
current = current;
|
|
2065
|
-
if (current.appliedToDom) {
|
|
2066
|
-
for (let cb of current.node.patched) {
|
|
2067
|
-
cb();
|
|
2068
|
-
}
|
|
2069
|
-
}
|
|
2362
|
+
catch (e) {
|
|
2363
|
+
this.app.handleError({ error: e, node: this });
|
|
2070
2364
|
}
|
|
2071
2365
|
}
|
|
2072
|
-
|
|
2073
|
-
this.locked = false;
|
|
2074
|
-
handleError({ fiber: current || this, error: e });
|
|
2075
|
-
}
|
|
2076
|
-
}
|
|
2077
|
-
}
|
|
2078
|
-
class MountFiber extends RootFiber {
|
|
2079
|
-
constructor(node, target, options = {}) {
|
|
2080
|
-
super(node, null);
|
|
2081
|
-
this.target = target;
|
|
2082
|
-
this.position = options.position || "last-child";
|
|
2366
|
+
this.status = 2 /* DESTROYED */;
|
|
2083
2367
|
}
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
node.updateDom();
|
|
2095
|
-
}
|
|
2096
|
-
else {
|
|
2097
|
-
node.bdom = this.bdom;
|
|
2098
|
-
if (this.position === "last-child" || this.target.childNodes.length === 0) {
|
|
2099
|
-
mount$1(node.bdom, this.target);
|
|
2100
|
-
}
|
|
2101
|
-
else {
|
|
2102
|
-
const firstChild = this.target.childNodes[0];
|
|
2103
|
-
mount$1(node.bdom, this.target, firstChild);
|
|
2104
|
-
}
|
|
2105
|
-
}
|
|
2106
|
-
// unregistering the fiber before mounted since it can do another render
|
|
2107
|
-
// and that the current rendering is obviously completed
|
|
2108
|
-
node.fiber = null;
|
|
2109
|
-
node.status = 1 /* MOUNTED */;
|
|
2110
|
-
this.appliedToDom = true;
|
|
2111
|
-
let mountedFibers = this.mounted;
|
|
2112
|
-
while ((current = mountedFibers.pop())) {
|
|
2113
|
-
if (current.appliedToDom) {
|
|
2114
|
-
for (let cb of current.node.mounted) {
|
|
2115
|
-
cb();
|
|
2116
|
-
}
|
|
2117
|
-
}
|
|
2118
|
-
}
|
|
2119
|
-
}
|
|
2120
|
-
catch (e) {
|
|
2121
|
-
handleError({ fiber: current, error: e });
|
|
2368
|
+
async updateAndRender(props, parentFiber) {
|
|
2369
|
+
const rawProps = props;
|
|
2370
|
+
props = Object.assign({}, props);
|
|
2371
|
+
// update
|
|
2372
|
+
const fiber = makeChildFiber(this, parentFiber);
|
|
2373
|
+
this.fiber = fiber;
|
|
2374
|
+
const component = this.component;
|
|
2375
|
+
const defaultProps = component.constructor.defaultProps;
|
|
2376
|
+
if (defaultProps) {
|
|
2377
|
+
applyDefaultProps(props, defaultProps);
|
|
2122
2378
|
}
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
*
|
|
2129
|
-
* Note that this method does modify in place the props
|
|
2130
|
-
*/
|
|
2131
|
-
function applyDefaultProps(props, ComponentClass) {
|
|
2132
|
-
const defaultProps = ComponentClass.defaultProps;
|
|
2133
|
-
if (defaultProps) {
|
|
2134
|
-
for (let propName in defaultProps) {
|
|
2135
|
-
if (props[propName] === undefined) {
|
|
2136
|
-
props[propName] = defaultProps[propName];
|
|
2379
|
+
currentNode = this;
|
|
2380
|
+
for (const key in props) {
|
|
2381
|
+
const prop = props[key];
|
|
2382
|
+
if (prop && typeof prop === "object" && prop[TARGET]) {
|
|
2383
|
+
props[key] = useState(prop);
|
|
2137
2384
|
}
|
|
2138
2385
|
}
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
function getPropDescription(staticProps) {
|
|
2145
|
-
if (staticProps instanceof Array) {
|
|
2146
|
-
return Object.fromEntries(staticProps.map((p) => (p.endsWith("?") ? [p.slice(0, -1), false] : [p, true])));
|
|
2147
|
-
}
|
|
2148
|
-
return staticProps || { "*": true };
|
|
2149
|
-
}
|
|
2150
|
-
/**
|
|
2151
|
-
* Validate the component props (or next props) against the (static) props
|
|
2152
|
-
* description. This is potentially an expensive operation: it may needs to
|
|
2153
|
-
* visit recursively the props and all the children to check if they are valid.
|
|
2154
|
-
* This is why it is only done in 'dev' mode.
|
|
2155
|
-
*/
|
|
2156
|
-
function validateProps(name, props, parent) {
|
|
2157
|
-
const ComponentClass = typeof name !== "string"
|
|
2158
|
-
? name
|
|
2159
|
-
: parent.constructor.components[name];
|
|
2160
|
-
if (!ComponentClass) {
|
|
2161
|
-
// this is an error, wrong component. We silently return here instead so the
|
|
2162
|
-
// error is triggered by the usual path ('component' function)
|
|
2163
|
-
return;
|
|
2164
|
-
}
|
|
2165
|
-
applyDefaultProps(props, ComponentClass);
|
|
2166
|
-
const defaultProps = ComponentClass.defaultProps || {};
|
|
2167
|
-
let propsDef = getPropDescription(ComponentClass.props);
|
|
2168
|
-
const allowAdditionalProps = "*" in propsDef;
|
|
2169
|
-
for (let propName in propsDef) {
|
|
2170
|
-
if (propName === "*") {
|
|
2171
|
-
continue;
|
|
2172
|
-
}
|
|
2173
|
-
const propDef = propsDef[propName];
|
|
2174
|
-
let isMandatory = !!propDef;
|
|
2175
|
-
if (typeof propDef === "object" && "optional" in propDef) {
|
|
2176
|
-
isMandatory = !propDef.optional;
|
|
2386
|
+
currentNode = null;
|
|
2387
|
+
const prom = Promise.all(this.willUpdateProps.map((f) => f.call(component, props)));
|
|
2388
|
+
await prom;
|
|
2389
|
+
if (fiber !== this.fiber) {
|
|
2390
|
+
return;
|
|
2177
2391
|
}
|
|
2178
|
-
|
|
2179
|
-
|
|
2392
|
+
component.props = props;
|
|
2393
|
+
this.props = rawProps;
|
|
2394
|
+
fiber.render();
|
|
2395
|
+
const parentRoot = parentFiber.root;
|
|
2396
|
+
if (this.willPatch.length) {
|
|
2397
|
+
parentRoot.willPatch.push(fiber);
|
|
2180
2398
|
}
|
|
2181
|
-
if (
|
|
2182
|
-
|
|
2183
|
-
throw new Error(`Missing props '${propName}' (component '${ComponentClass.name}')`);
|
|
2184
|
-
}
|
|
2185
|
-
else {
|
|
2186
|
-
continue;
|
|
2187
|
-
}
|
|
2399
|
+
if (this.patched.length) {
|
|
2400
|
+
parentRoot.patched.push(fiber);
|
|
2188
2401
|
}
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2402
|
+
}
|
|
2403
|
+
/**
|
|
2404
|
+
* Finds a child that has dom that is not yet updated, and update it. This
|
|
2405
|
+
* method is meant to be used only in the context of repatching the dom after
|
|
2406
|
+
* a mounted hook failed and was handled.
|
|
2407
|
+
*/
|
|
2408
|
+
updateDom() {
|
|
2409
|
+
if (!this.fiber) {
|
|
2410
|
+
return;
|
|
2192
2411
|
}
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2412
|
+
if (this.bdom === this.fiber.bdom) {
|
|
2413
|
+
// If the error was handled by some child component, we need to find it to
|
|
2414
|
+
// apply its change
|
|
2415
|
+
for (let k in this.children) {
|
|
2416
|
+
const child = this.children[k];
|
|
2417
|
+
child.updateDom();
|
|
2418
|
+
}
|
|
2196
2419
|
}
|
|
2197
|
-
|
|
2198
|
-
|
|
2420
|
+
else {
|
|
2421
|
+
// if we get here, this is the component that handled the error and rerendered
|
|
2422
|
+
// itself, so we can simply patch the dom
|
|
2423
|
+
this.bdom.patch(this.fiber.bdom, false);
|
|
2424
|
+
this.fiber.appliedToDom = true;
|
|
2425
|
+
this.fiber = null;
|
|
2199
2426
|
}
|
|
2200
2427
|
}
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2428
|
+
// ---------------------------------------------------------------------------
|
|
2429
|
+
// Block DOM methods
|
|
2430
|
+
// ---------------------------------------------------------------------------
|
|
2431
|
+
firstNode() {
|
|
2432
|
+
const bdom = this.bdom;
|
|
2433
|
+
return bdom ? bdom.firstNode() : undefined;
|
|
2434
|
+
}
|
|
2435
|
+
mount(parent, anchor) {
|
|
2436
|
+
const bdom = this.fiber.bdom;
|
|
2437
|
+
this.bdom = bdom;
|
|
2438
|
+
bdom.mount(parent, anchor);
|
|
2439
|
+
this.status = 1 /* MOUNTED */;
|
|
2440
|
+
this.fiber.appliedToDom = true;
|
|
2441
|
+
this.children = this.fiber.childrenMap;
|
|
2442
|
+
this.fiber = null;
|
|
2443
|
+
}
|
|
2444
|
+
moveBefore(other, afterNode) {
|
|
2445
|
+
this.bdom.moveBefore(other ? other.bdom : null, afterNode);
|
|
2446
|
+
}
|
|
2447
|
+
patch() {
|
|
2448
|
+
if (this.fiber && this.fiber.parent) {
|
|
2449
|
+
// we only patch here renderings coming from above. renderings initiated
|
|
2450
|
+
// by the component will be patched independently in the appropriate
|
|
2451
|
+
// fiber.complete
|
|
2452
|
+
this._patch();
|
|
2206
2453
|
}
|
|
2207
2454
|
}
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
if (propDef === true) {
|
|
2214
|
-
return true;
|
|
2215
|
-
}
|
|
2216
|
-
if (typeof propDef === "function") {
|
|
2217
|
-
// Check if a value is constructed by some Constructor. Note that there is a
|
|
2218
|
-
// slight abuse of language: we want to consider primitive values as well.
|
|
2219
|
-
//
|
|
2220
|
-
// So, even though 1 is not an instance of Number, we want to consider that
|
|
2221
|
-
// it is valid.
|
|
2222
|
-
if (typeof prop === "object") {
|
|
2223
|
-
return prop instanceof propDef;
|
|
2224
|
-
}
|
|
2225
|
-
return typeof prop === propDef.name.toLowerCase();
|
|
2226
|
-
}
|
|
2227
|
-
else if (propDef instanceof Array) {
|
|
2228
|
-
// If this code is executed, this means that we want to check if a prop
|
|
2229
|
-
// matches at least one of its descriptor.
|
|
2230
|
-
let result = false;
|
|
2231
|
-
for (let i = 0, iLen = propDef.length; i < iLen; i++) {
|
|
2232
|
-
result = result || isValidProp(prop, propDef[i]);
|
|
2455
|
+
_patch() {
|
|
2456
|
+
let hasChildren = false;
|
|
2457
|
+
for (let _k in this.children) {
|
|
2458
|
+
hasChildren = true;
|
|
2459
|
+
break;
|
|
2233
2460
|
}
|
|
2234
|
-
|
|
2461
|
+
const fiber = this.fiber;
|
|
2462
|
+
this.children = fiber.childrenMap;
|
|
2463
|
+
this.bdom.patch(fiber.bdom, hasChildren);
|
|
2464
|
+
fiber.appliedToDom = true;
|
|
2465
|
+
this.fiber = null;
|
|
2235
2466
|
}
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
return true;
|
|
2467
|
+
beforeRemove() {
|
|
2468
|
+
this._destroy();
|
|
2239
2469
|
}
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
result = result && propDef.validate(prop);
|
|
2470
|
+
remove() {
|
|
2471
|
+
this.bdom.remove();
|
|
2243
2472
|
}
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2473
|
+
// ---------------------------------------------------------------------------
|
|
2474
|
+
// Some debug helpers
|
|
2475
|
+
// ---------------------------------------------------------------------------
|
|
2476
|
+
get name() {
|
|
2477
|
+
return this.component.constructor.name;
|
|
2248
2478
|
}
|
|
2249
|
-
|
|
2250
|
-
const
|
|
2251
|
-
|
|
2252
|
-
result = result && isValidProp(prop[key], shape[key]);
|
|
2253
|
-
}
|
|
2254
|
-
if (result) {
|
|
2255
|
-
for (let propName in prop) {
|
|
2256
|
-
if (!(propName in shape)) {
|
|
2257
|
-
throw new Error(`unknown prop '${propName}'`);
|
|
2258
|
-
}
|
|
2259
|
-
}
|
|
2260
|
-
}
|
|
2479
|
+
get subscriptions() {
|
|
2480
|
+
const render = batchedRenderFunctions.get(this);
|
|
2481
|
+
return render ? getSubscriptions(render) : [];
|
|
2261
2482
|
}
|
|
2262
|
-
return result;
|
|
2263
2483
|
}
|
|
2264
2484
|
|
|
2265
|
-
|
|
2266
|
-
function
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
return
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2485
|
+
const TIMEOUT = Symbol("timeout");
|
|
2486
|
+
function wrapError(fn, hookName) {
|
|
2487
|
+
const error = new OwlError(`The following error occurred in ${hookName}: `);
|
|
2488
|
+
const timeoutError = new OwlError(`${hookName}'s promise hasn't resolved after 3 seconds`);
|
|
2489
|
+
const node = getCurrent();
|
|
2490
|
+
return (...args) => {
|
|
2491
|
+
const onError = (cause) => {
|
|
2492
|
+
error.cause = cause;
|
|
2493
|
+
if (cause instanceof Error) {
|
|
2494
|
+
error.message += `"${cause.message}"`;
|
|
2495
|
+
}
|
|
2496
|
+
else {
|
|
2497
|
+
error.message = `Something that is not an Error was thrown in ${hookName} (see this Error's "cause" property)`;
|
|
2498
|
+
}
|
|
2499
|
+
throw error;
|
|
2500
|
+
};
|
|
2501
|
+
try {
|
|
2502
|
+
const result = fn(...args);
|
|
2503
|
+
if (result instanceof Promise) {
|
|
2504
|
+
if (hookName === "onWillStart" || hookName === "onWillUpdateProps") {
|
|
2505
|
+
const fiber = node.fiber;
|
|
2506
|
+
Promise.race([
|
|
2507
|
+
result.catch(() => { }),
|
|
2508
|
+
new Promise((resolve) => setTimeout(() => resolve(TIMEOUT), 3000)),
|
|
2509
|
+
]).then((res) => {
|
|
2510
|
+
if (res === TIMEOUT && node.fiber === fiber) {
|
|
2511
|
+
console.warn(timeoutError);
|
|
2512
|
+
}
|
|
2513
|
+
});
|
|
2514
|
+
}
|
|
2515
|
+
return result.catch(onError);
|
|
2516
|
+
}
|
|
2517
|
+
return result;
|
|
2518
|
+
}
|
|
2519
|
+
catch (cause) {
|
|
2520
|
+
onError(cause);
|
|
2521
|
+
}
|
|
2522
|
+
};
|
|
2274
2523
|
}
|
|
2275
2524
|
// -----------------------------------------------------------------------------
|
|
2276
|
-
//
|
|
2525
|
+
// hooks
|
|
2277
2526
|
// -----------------------------------------------------------------------------
|
|
2278
|
-
|
|
2279
|
-
/**
|
|
2280
|
-
* Creates a reactive object that will be observed by the current component.
|
|
2281
|
-
* Reading data from the returned object (eg during rendering) will cause the
|
|
2282
|
-
* component to subscribe to that data and be rerendered when it changes.
|
|
2283
|
-
*
|
|
2284
|
-
* @param state the state to observe
|
|
2285
|
-
* @returns a reactive object that will cause the component to re-render on
|
|
2286
|
-
* relevant changes
|
|
2287
|
-
* @see reactive
|
|
2288
|
-
*/
|
|
2289
|
-
function useState(state) {
|
|
2527
|
+
function onWillStart(fn) {
|
|
2290
2528
|
const node = getCurrent();
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
render = batched(node.render.bind(node));
|
|
2294
|
-
batchedRenderFunctions.set(node, render);
|
|
2295
|
-
// manual implementation of onWillDestroy to break cyclic dependency
|
|
2296
|
-
node.willDestroy.push(clearReactivesForCallback.bind(null, render));
|
|
2297
|
-
if (node.app.dev) {
|
|
2298
|
-
Object.defineProperty(node, "subscriptions", {
|
|
2299
|
-
get() {
|
|
2300
|
-
return getSubscriptions(render);
|
|
2301
|
-
},
|
|
2302
|
-
});
|
|
2303
|
-
}
|
|
2304
|
-
}
|
|
2305
|
-
return reactive(state, render);
|
|
2529
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
2530
|
+
node.willStart.push(decorate(fn.bind(node.component), "onWillStart"));
|
|
2306
2531
|
}
|
|
2307
|
-
function
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
}
|
|
2312
|
-
}
|
|
2313
|
-
return Object.keys(props1).length !== Object.keys(props2).length;
|
|
2532
|
+
function onWillUpdateProps(fn) {
|
|
2533
|
+
const node = getCurrent();
|
|
2534
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
2535
|
+
node.willUpdateProps.push(decorate(fn.bind(node.component), "onWillUpdateProps"));
|
|
2314
2536
|
}
|
|
2315
|
-
function
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
if (node.status < 1 /* MOUNTED */) {
|
|
2320
|
-
node.destroy();
|
|
2321
|
-
node = undefined;
|
|
2322
|
-
}
|
|
2323
|
-
else if (node.status === 2 /* DESTROYED */) {
|
|
2324
|
-
node = undefined;
|
|
2325
|
-
}
|
|
2326
|
-
}
|
|
2327
|
-
if (isDynamic && node && node.component.constructor !== name) {
|
|
2328
|
-
node = undefined;
|
|
2329
|
-
}
|
|
2330
|
-
const parentFiber = ctx.fiber;
|
|
2331
|
-
if (node) {
|
|
2332
|
-
let shouldRender = node.forceNextRender;
|
|
2333
|
-
if (shouldRender) {
|
|
2334
|
-
node.forceNextRender = false;
|
|
2335
|
-
}
|
|
2336
|
-
else {
|
|
2337
|
-
const currentProps = node.component.props[TARGET];
|
|
2338
|
-
shouldRender = parentFiber.deep || arePropsDifferent(currentProps, props);
|
|
2339
|
-
}
|
|
2340
|
-
if (shouldRender) {
|
|
2341
|
-
node.updateAndRender(props, parentFiber);
|
|
2342
|
-
}
|
|
2343
|
-
}
|
|
2344
|
-
else {
|
|
2345
|
-
// new component
|
|
2346
|
-
let C;
|
|
2347
|
-
if (isDynamic) {
|
|
2348
|
-
C = name;
|
|
2349
|
-
}
|
|
2350
|
-
else {
|
|
2351
|
-
C = parent.constructor.components[name];
|
|
2352
|
-
if (!C) {
|
|
2353
|
-
throw new Error(`Cannot find the definition of component "${name}"`);
|
|
2354
|
-
}
|
|
2355
|
-
}
|
|
2356
|
-
node = new ComponentNode(C, props, ctx.app, ctx);
|
|
2357
|
-
ctx.children[key] = node;
|
|
2358
|
-
node.initiateRender(new Fiber(node, parentFiber));
|
|
2359
|
-
}
|
|
2360
|
-
return node;
|
|
2537
|
+
function onMounted(fn) {
|
|
2538
|
+
const node = getCurrent();
|
|
2539
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
2540
|
+
node.mounted.push(decorate(fn.bind(node.component), "onMounted"));
|
|
2361
2541
|
}
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2542
|
+
function onWillPatch(fn) {
|
|
2543
|
+
const node = getCurrent();
|
|
2544
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
2545
|
+
node.willPatch.unshift(decorate(fn.bind(node.component), "onWillPatch"));
|
|
2546
|
+
}
|
|
2547
|
+
function onPatched(fn) {
|
|
2548
|
+
const node = getCurrent();
|
|
2549
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
2550
|
+
node.patched.push(decorate(fn.bind(node.component), "onPatched"));
|
|
2551
|
+
}
|
|
2552
|
+
function onWillUnmount(fn) {
|
|
2553
|
+
const node = getCurrent();
|
|
2554
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
2555
|
+
node.willUnmount.unshift(decorate(fn.bind(node.component), "onWillUnmount"));
|
|
2556
|
+
}
|
|
2557
|
+
function onWillDestroy(fn) {
|
|
2558
|
+
const node = getCurrent();
|
|
2559
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
2560
|
+
node.willDestroy.push(decorate(fn.bind(node.component), "onWillDestroy"));
|
|
2561
|
+
}
|
|
2562
|
+
function onWillRender(fn) {
|
|
2563
|
+
const node = getCurrent();
|
|
2564
|
+
const renderFn = node.renderFn;
|
|
2565
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
2566
|
+
fn = decorate(fn.bind(node.component), "onWillRender");
|
|
2567
|
+
node.renderFn = () => {
|
|
2568
|
+
fn();
|
|
2569
|
+
return renderFn();
|
|
2570
|
+
};
|
|
2571
|
+
}
|
|
2572
|
+
function onRendered(fn) {
|
|
2573
|
+
const node = getCurrent();
|
|
2574
|
+
const renderFn = node.renderFn;
|
|
2575
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
2576
|
+
fn = decorate(fn.bind(node.component), "onRendered");
|
|
2577
|
+
node.renderFn = () => {
|
|
2578
|
+
const result = renderFn();
|
|
2579
|
+
fn();
|
|
2580
|
+
return result;
|
|
2581
|
+
};
|
|
2582
|
+
}
|
|
2583
|
+
function onError(callback) {
|
|
2584
|
+
const node = getCurrent();
|
|
2585
|
+
let handlers = nodeErrorHandlers.get(node);
|
|
2586
|
+
if (!handlers) {
|
|
2587
|
+
handlers = [];
|
|
2588
|
+
nodeErrorHandlers.set(node, handlers);
|
|
2389
2589
|
}
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2590
|
+
handlers.push(callback.bind(node.component));
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2593
|
+
class Component {
|
|
2594
|
+
constructor(props, env, node) {
|
|
2595
|
+
this.props = props;
|
|
2596
|
+
this.env = env;
|
|
2597
|
+
this.__owl__ = node;
|
|
2394
2598
|
}
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2599
|
+
setup() { }
|
|
2600
|
+
render(deep = false) {
|
|
2601
|
+
this.__owl__.render(deep === true);
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2604
|
+
Component.template = "";
|
|
2605
|
+
|
|
2606
|
+
const VText = text("").constructor;
|
|
2607
|
+
class VPortal extends VText {
|
|
2608
|
+
constructor(selector, content) {
|
|
2609
|
+
super("");
|
|
2610
|
+
this.target = null;
|
|
2611
|
+
this.selector = selector;
|
|
2612
|
+
this.content = content;
|
|
2613
|
+
}
|
|
2614
|
+
mount(parent, anchor) {
|
|
2615
|
+
super.mount(parent, anchor);
|
|
2616
|
+
this.target = document.querySelector(this.selector);
|
|
2617
|
+
if (this.target) {
|
|
2618
|
+
this.content.mount(this.target, null);
|
|
2403
2619
|
}
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
return;
|
|
2620
|
+
else {
|
|
2621
|
+
this.content.mount(parent, anchor);
|
|
2407
2622
|
}
|
|
2408
|
-
|
|
2409
|
-
|
|
2623
|
+
}
|
|
2624
|
+
beforeRemove() {
|
|
2625
|
+
this.content.beforeRemove();
|
|
2626
|
+
}
|
|
2627
|
+
remove() {
|
|
2628
|
+
if (this.content) {
|
|
2629
|
+
super.remove();
|
|
2630
|
+
this.content.remove();
|
|
2631
|
+
this.content = null;
|
|
2410
2632
|
}
|
|
2411
2633
|
}
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
if (
|
|
2415
|
-
|
|
2416
|
-
// situation may have changed after the microtask tick
|
|
2417
|
-
current = this.fiber;
|
|
2634
|
+
patch(other) {
|
|
2635
|
+
super.patch(other);
|
|
2636
|
+
if (this.content) {
|
|
2637
|
+
this.content.patch(other.content, true);
|
|
2418
2638
|
}
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2639
|
+
else {
|
|
2640
|
+
this.content = other.content;
|
|
2641
|
+
this.content.mount(this.target, null);
|
|
2642
|
+
}
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2645
|
+
/**
|
|
2646
|
+
* kind of similar to <t t-slot="default"/>, but it wraps it around a VPortal
|
|
2647
|
+
*/
|
|
2648
|
+
function portalTemplate(app, bdom, helpers) {
|
|
2649
|
+
let { callSlot } = helpers;
|
|
2650
|
+
return function template(ctx, node, key = "") {
|
|
2651
|
+
return new VPortal(ctx.props.target, callSlot(ctx, node, key, "default", false, null));
|
|
2652
|
+
};
|
|
2653
|
+
}
|
|
2654
|
+
class Portal extends Component {
|
|
2655
|
+
setup() {
|
|
2656
|
+
const node = this.__owl__;
|
|
2657
|
+
onMounted(() => {
|
|
2658
|
+
const portal = node.bdom;
|
|
2659
|
+
if (!portal.target) {
|
|
2660
|
+
const target = document.querySelector(this.props.target);
|
|
2661
|
+
if (target) {
|
|
2662
|
+
portal.content.moveBefore(target, null);
|
|
2663
|
+
}
|
|
2664
|
+
else {
|
|
2665
|
+
throw new OwlError("invalid portal target");
|
|
2424
2666
|
}
|
|
2425
|
-
return;
|
|
2426
2667
|
}
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
}
|
|
2433
|
-
const fiber = makeRootFiber(this);
|
|
2434
|
-
fiber.deep = deep;
|
|
2435
|
-
this.fiber = fiber;
|
|
2436
|
-
this.app.scheduler.addFiber(fiber);
|
|
2437
|
-
await Promise.resolve();
|
|
2438
|
-
if (this.status === 2 /* DESTROYED */) {
|
|
2439
|
-
return;
|
|
2440
|
-
}
|
|
2441
|
-
// We only want to actually render the component if the following two
|
|
2442
|
-
// conditions are true:
|
|
2443
|
-
// * this.fiber: it could be null, in which case the render has been cancelled
|
|
2444
|
-
// * (current || !fiber.parent): if current is not null, this means that the
|
|
2445
|
-
// render function was called when a render was already occurring. In this
|
|
2446
|
-
// case, the pending rendering was cancelled, and the fiber needs to be
|
|
2447
|
-
// rendered to complete the work. If current is null, we check that the
|
|
2448
|
-
// fiber has no parent. If that is the case, the fiber was downgraded from
|
|
2449
|
-
// a root fiber to a child fiber in the previous microtick, because it was
|
|
2450
|
-
// embedded in a rendering coming from above, so the fiber will be rendered
|
|
2451
|
-
// in the next microtick anyway, so we should not render it again.
|
|
2452
|
-
if (this.fiber === fiber && (current || !fiber.parent)) {
|
|
2453
|
-
this._render(fiber);
|
|
2454
|
-
}
|
|
2668
|
+
});
|
|
2669
|
+
onWillUnmount(() => {
|
|
2670
|
+
const portal = node.bdom;
|
|
2671
|
+
portal.remove();
|
|
2672
|
+
});
|
|
2455
2673
|
}
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2674
|
+
}
|
|
2675
|
+
Portal.template = "__portal__";
|
|
2676
|
+
Portal.props = {
|
|
2677
|
+
target: {
|
|
2678
|
+
type: String,
|
|
2679
|
+
},
|
|
2680
|
+
slots: true,
|
|
2681
|
+
};
|
|
2682
|
+
|
|
2683
|
+
// -----------------------------------------------------------------------------
|
|
2684
|
+
// helpers
|
|
2685
|
+
// -----------------------------------------------------------------------------
|
|
2686
|
+
const isUnionType = (t) => Array.isArray(t);
|
|
2687
|
+
const isBaseType = (t) => typeof t !== "object";
|
|
2688
|
+
const isValueType = (t) => typeof t === "object" && t && "value" in t;
|
|
2689
|
+
function isOptional(t) {
|
|
2690
|
+
return typeof t === "object" && "optional" in t ? t.optional || false : false;
|
|
2691
|
+
}
|
|
2692
|
+
function describeType(type) {
|
|
2693
|
+
return type === "*" || type === true ? "value" : type.name.toLowerCase();
|
|
2694
|
+
}
|
|
2695
|
+
function describe(info) {
|
|
2696
|
+
if (isBaseType(info)) {
|
|
2697
|
+
return describeType(info);
|
|
2464
2698
|
}
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
this._destroy();
|
|
2468
|
-
if (shouldRemove) {
|
|
2469
|
-
this.bdom.remove();
|
|
2470
|
-
}
|
|
2699
|
+
else if (isUnionType(info)) {
|
|
2700
|
+
return info.map(describe).join(" or ");
|
|
2471
2701
|
}
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2702
|
+
else if (isValueType(info)) {
|
|
2703
|
+
return String(info.value);
|
|
2704
|
+
}
|
|
2705
|
+
if ("element" in info) {
|
|
2706
|
+
return `list of ${describe({ type: info.element, optional: false })}s`;
|
|
2707
|
+
}
|
|
2708
|
+
if ("shape" in info) {
|
|
2709
|
+
return `object`;
|
|
2710
|
+
}
|
|
2711
|
+
return describe(info.type || "*");
|
|
2712
|
+
}
|
|
2713
|
+
function toSchema(spec) {
|
|
2714
|
+
return Object.fromEntries(spec.map((e) => e.endsWith("?") ? [e.slice(0, -1), { optional: true }] : [e, { type: "*", optional: false }]));
|
|
2715
|
+
}
|
|
2716
|
+
/**
|
|
2717
|
+
* Main validate function
|
|
2718
|
+
*/
|
|
2719
|
+
function validate(obj, spec) {
|
|
2720
|
+
let errors = validateSchema(obj, spec);
|
|
2721
|
+
if (errors.length) {
|
|
2722
|
+
throw new OwlError("Invalid object: " + errors.join(", "));
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
/**
|
|
2726
|
+
* Helper validate function, to get the list of errors. useful if one want to
|
|
2727
|
+
* manipulate the errors without parsing an error object
|
|
2728
|
+
*/
|
|
2729
|
+
function validateSchema(obj, schema) {
|
|
2730
|
+
if (Array.isArray(schema)) {
|
|
2731
|
+
schema = toSchema(schema);
|
|
2732
|
+
}
|
|
2733
|
+
let errors = [];
|
|
2734
|
+
// check if each value in obj has correct shape
|
|
2735
|
+
for (let key in obj) {
|
|
2736
|
+
if (key in schema) {
|
|
2737
|
+
let result = validateType(key, obj[key], schema[key]);
|
|
2738
|
+
if (result) {
|
|
2739
|
+
errors.push(result);
|
|
2477
2740
|
}
|
|
2478
2741
|
}
|
|
2479
|
-
|
|
2480
|
-
|
|
2742
|
+
else if (!("*" in schema)) {
|
|
2743
|
+
errors.push(`unknown key '${key}'`);
|
|
2481
2744
|
}
|
|
2482
|
-
|
|
2483
|
-
|
|
2745
|
+
}
|
|
2746
|
+
// check that all specified keys are defined in obj
|
|
2747
|
+
for (let key in schema) {
|
|
2748
|
+
const spec = schema[key];
|
|
2749
|
+
if (key !== "*" && !isOptional(spec) && !(key in obj)) {
|
|
2750
|
+
const isObj = typeof spec === "object" && !Array.isArray(spec);
|
|
2751
|
+
const isAny = spec === "*" || (isObj && "type" in spec ? spec.type === "*" : isObj);
|
|
2752
|
+
let detail = isAny ? "" : ` (should be a ${describe(spec)})`;
|
|
2753
|
+
errors.push(`'${key}' is missing${detail}`);
|
|
2484
2754
|
}
|
|
2485
|
-
this.status = 2 /* DESTROYED */;
|
|
2486
2755
|
}
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
currentNode = null;
|
|
2496
|
-
const prom = Promise.all(this.willUpdateProps.map((f) => f.call(component, props)));
|
|
2497
|
-
await prom;
|
|
2498
|
-
if (fiber !== this.fiber) {
|
|
2499
|
-
return;
|
|
2756
|
+
return errors;
|
|
2757
|
+
}
|
|
2758
|
+
function validateBaseType(key, value, type) {
|
|
2759
|
+
if (typeof type === "function") {
|
|
2760
|
+
if (typeof value === "object") {
|
|
2761
|
+
if (!(value instanceof type)) {
|
|
2762
|
+
return `'${key}' is not a ${describeType(type)}`;
|
|
2763
|
+
}
|
|
2500
2764
|
}
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
const parentRoot = parentFiber.root;
|
|
2504
|
-
if (this.willPatch.length) {
|
|
2505
|
-
parentRoot.willPatch.push(fiber);
|
|
2765
|
+
else if (typeof value !== type.name.toLowerCase()) {
|
|
2766
|
+
return `'${key}' is not a ${describeType(type)}`;
|
|
2506
2767
|
}
|
|
2507
|
-
|
|
2508
|
-
|
|
2768
|
+
}
|
|
2769
|
+
return null;
|
|
2770
|
+
}
|
|
2771
|
+
function validateArrayType(key, value, descr) {
|
|
2772
|
+
if (!Array.isArray(value)) {
|
|
2773
|
+
return `'${key}' is not a list of ${describe(descr)}s`;
|
|
2774
|
+
}
|
|
2775
|
+
for (let i = 0; i < value.length; i++) {
|
|
2776
|
+
const error = validateType(`${key}[${i}]`, value[i], descr);
|
|
2777
|
+
if (error) {
|
|
2778
|
+
return error;
|
|
2509
2779
|
}
|
|
2510
2780
|
}
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2781
|
+
return null;
|
|
2782
|
+
}
|
|
2783
|
+
function validateType(key, value, descr) {
|
|
2784
|
+
if (value === undefined) {
|
|
2785
|
+
return isOptional(descr) ? null : `'${key}' is undefined (should be a ${describe(descr)})`;
|
|
2786
|
+
}
|
|
2787
|
+
else if (isBaseType(descr)) {
|
|
2788
|
+
return validateBaseType(key, value, descr);
|
|
2789
|
+
}
|
|
2790
|
+
else if (isValueType(descr)) {
|
|
2791
|
+
return value === descr.value ? null : `'${key}' is not equal to '${descr.value}'`;
|
|
2792
|
+
}
|
|
2793
|
+
else if (isUnionType(descr)) {
|
|
2794
|
+
let validDescr = descr.find((p) => !validateType(key, value, p));
|
|
2795
|
+
return validDescr ? null : `'${key}' is not a ${describe(descr)}`;
|
|
2796
|
+
}
|
|
2797
|
+
let result = null;
|
|
2798
|
+
if ("element" in descr) {
|
|
2799
|
+
result = validateArrayType(key, value, descr.element);
|
|
2800
|
+
}
|
|
2801
|
+
else if ("shape" in descr && !result) {
|
|
2802
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
2803
|
+
result = `'${key}' is not an object`;
|
|
2519
2804
|
}
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
const child = this.children[k];
|
|
2525
|
-
child.updateDom();
|
|
2805
|
+
else {
|
|
2806
|
+
const errors = validateSchema(value, descr.shape);
|
|
2807
|
+
if (errors.length) {
|
|
2808
|
+
result = `'${key}' has not the correct shape (${errors.join(", ")})`;
|
|
2526
2809
|
}
|
|
2527
2810
|
}
|
|
2528
|
-
else {
|
|
2529
|
-
// if we get here, this is the component that handled the error and rerendered
|
|
2530
|
-
// itself, so we can simply patch the dom
|
|
2531
|
-
this.bdom.patch(this.fiber.bdom, false);
|
|
2532
|
-
this.fiber.appliedToDom = true;
|
|
2533
|
-
this.fiber = null;
|
|
2534
|
-
}
|
|
2535
2811
|
}
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
// ---------------------------------------------------------------------------
|
|
2539
|
-
firstNode() {
|
|
2540
|
-
const bdom = this.bdom;
|
|
2541
|
-
return bdom ? bdom.firstNode() : undefined;
|
|
2812
|
+
if ("type" in descr && !result) {
|
|
2813
|
+
result = validateType(key, value, descr.type);
|
|
2542
2814
|
}
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
this.bdom = bdom;
|
|
2546
|
-
bdom.mount(parent, anchor);
|
|
2547
|
-
this.status = 1 /* MOUNTED */;
|
|
2548
|
-
this.fiber.appliedToDom = true;
|
|
2549
|
-
this.fiber = null;
|
|
2815
|
+
if ("validate" in descr && !result) {
|
|
2816
|
+
result = !descr.validate(value) ? `'${key}' is not valid` : null;
|
|
2550
2817
|
}
|
|
2551
|
-
|
|
2552
|
-
|
|
2818
|
+
return result;
|
|
2819
|
+
}
|
|
2820
|
+
|
|
2821
|
+
const ObjectCreate = Object.create;
|
|
2822
|
+
/**
|
|
2823
|
+
* This file contains utility functions that will be injected in each template,
|
|
2824
|
+
* to perform various useful tasks in the compiled code.
|
|
2825
|
+
*/
|
|
2826
|
+
function withDefault(value, defaultValue) {
|
|
2827
|
+
return value === undefined || value === null || value === false ? defaultValue : value;
|
|
2828
|
+
}
|
|
2829
|
+
function callSlot(ctx, parent, key, name, dynamic, extra, defaultContent) {
|
|
2830
|
+
key = key + "__slot_" + name;
|
|
2831
|
+
const slots = ctx.props.slots || {};
|
|
2832
|
+
const { __render, __ctx, __scope } = slots[name] || {};
|
|
2833
|
+
const slotScope = ObjectCreate(__ctx || {});
|
|
2834
|
+
if (__scope) {
|
|
2835
|
+
slotScope[__scope] = extra;
|
|
2553
2836
|
}
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2837
|
+
const slotBDom = __render ? __render.call(__ctx.__owl__.component, slotScope, parent, key) : null;
|
|
2838
|
+
if (defaultContent) {
|
|
2839
|
+
let child1 = undefined;
|
|
2840
|
+
let child2 = undefined;
|
|
2841
|
+
if (slotBDom) {
|
|
2842
|
+
child1 = dynamic ? toggler(name, slotBDom) : slotBDom;
|
|
2560
2843
|
}
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
const hasChildren = Object.keys(this.children).length > 0;
|
|
2564
|
-
this.bdom.patch(this.fiber.bdom, hasChildren);
|
|
2565
|
-
if (hasChildren) {
|
|
2566
|
-
this.cleanOutdatedChildren();
|
|
2844
|
+
else {
|
|
2845
|
+
child2 = defaultContent.call(ctx.__owl__.component, ctx, parent, key);
|
|
2567
2846
|
}
|
|
2568
|
-
|
|
2569
|
-
this.fiber = null;
|
|
2847
|
+
return multi([child1, child2]);
|
|
2570
2848
|
}
|
|
2571
|
-
|
|
2572
|
-
|
|
2849
|
+
return slotBDom || text("");
|
|
2850
|
+
}
|
|
2851
|
+
function capture(ctx) {
|
|
2852
|
+
const component = ctx.__owl__.component;
|
|
2853
|
+
const result = ObjectCreate(component);
|
|
2854
|
+
for (let k in ctx) {
|
|
2855
|
+
result[k] = ctx[k];
|
|
2573
2856
|
}
|
|
2574
|
-
|
|
2575
|
-
|
|
2857
|
+
return result;
|
|
2858
|
+
}
|
|
2859
|
+
function withKey(elem, k) {
|
|
2860
|
+
elem.key = k;
|
|
2861
|
+
return elem;
|
|
2862
|
+
}
|
|
2863
|
+
function prepareList(collection) {
|
|
2864
|
+
let keys;
|
|
2865
|
+
let values;
|
|
2866
|
+
if (Array.isArray(collection)) {
|
|
2867
|
+
keys = collection;
|
|
2868
|
+
values = collection;
|
|
2576
2869
|
}
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2870
|
+
else if (collection) {
|
|
2871
|
+
values = Object.keys(collection);
|
|
2872
|
+
keys = Object.values(collection);
|
|
2873
|
+
}
|
|
2874
|
+
else {
|
|
2875
|
+
throw new OwlError("Invalid loop expression");
|
|
2876
|
+
}
|
|
2877
|
+
const n = values.length;
|
|
2878
|
+
return [keys, values, n, new Array(n)];
|
|
2879
|
+
}
|
|
2880
|
+
const isBoundary = Symbol("isBoundary");
|
|
2881
|
+
function setContextValue(ctx, key, value) {
|
|
2882
|
+
const ctx0 = ctx;
|
|
2883
|
+
while (!ctx.hasOwnProperty(key) && !ctx.hasOwnProperty(isBoundary)) {
|
|
2884
|
+
const newCtx = ctx.__proto__;
|
|
2885
|
+
if (!newCtx) {
|
|
2886
|
+
ctx = ctx0;
|
|
2887
|
+
break;
|
|
2588
2888
|
}
|
|
2889
|
+
ctx = newCtx;
|
|
2589
2890
|
}
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2891
|
+
ctx[key] = value;
|
|
2892
|
+
}
|
|
2893
|
+
function toNumber(val) {
|
|
2894
|
+
const n = parseFloat(val);
|
|
2895
|
+
return isNaN(n) ? val : n;
|
|
2896
|
+
}
|
|
2897
|
+
function shallowEqual(l1, l2) {
|
|
2898
|
+
for (let i = 0, l = l1.length; i < l; i++) {
|
|
2899
|
+
if (l1[i] !== l2[i]) {
|
|
2900
|
+
return false;
|
|
2901
|
+
}
|
|
2600
2902
|
}
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2903
|
+
return true;
|
|
2904
|
+
}
|
|
2905
|
+
class LazyValue {
|
|
2906
|
+
constructor(fn, ctx, component, node, key) {
|
|
2907
|
+
this.fn = fn;
|
|
2908
|
+
this.ctx = capture(ctx);
|
|
2909
|
+
this.component = component;
|
|
2910
|
+
this.node = node;
|
|
2911
|
+
this.key = key;
|
|
2604
2912
|
}
|
|
2605
|
-
|
|
2606
|
-
this.
|
|
2913
|
+
evaluate() {
|
|
2914
|
+
return this.fn.call(this.component, this.ctx, this.node, this.key);
|
|
2607
2915
|
}
|
|
2608
|
-
|
|
2609
|
-
this.
|
|
2610
|
-
if (!this.isRunning) {
|
|
2611
|
-
this.start();
|
|
2612
|
-
}
|
|
2916
|
+
toString() {
|
|
2917
|
+
return this.evaluate().toString();
|
|
2613
2918
|
}
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2919
|
+
}
|
|
2920
|
+
/*
|
|
2921
|
+
* Safely outputs `value` as a block depending on the nature of `value`
|
|
2922
|
+
*/
|
|
2923
|
+
function safeOutput(value, defaultValue) {
|
|
2924
|
+
if (value === undefined) {
|
|
2925
|
+
return defaultValue ? toggler("default", defaultValue) : toggler("undefined", text(""));
|
|
2926
|
+
}
|
|
2927
|
+
let safeKey;
|
|
2928
|
+
let block;
|
|
2929
|
+
switch (typeof value) {
|
|
2930
|
+
case "object":
|
|
2931
|
+
if (value instanceof Markup) {
|
|
2932
|
+
safeKey = `string_safe`;
|
|
2933
|
+
block = html(value);
|
|
2623
2934
|
}
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
return;
|
|
2935
|
+
else if (value instanceof LazyValue) {
|
|
2936
|
+
safeKey = `lazy_value`;
|
|
2937
|
+
block = value.evaluate();
|
|
2628
2938
|
}
|
|
2629
|
-
if (
|
|
2630
|
-
|
|
2631
|
-
|
|
2939
|
+
else if (value instanceof String) {
|
|
2940
|
+
safeKey = "string_unsafe";
|
|
2941
|
+
block = text(value);
|
|
2632
2942
|
}
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
this.tasks.delete(fiber);
|
|
2943
|
+
else {
|
|
2944
|
+
// Assuming it is a block
|
|
2945
|
+
safeKey = "block_safe";
|
|
2946
|
+
block = value;
|
|
2638
2947
|
}
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2948
|
+
break;
|
|
2949
|
+
case "string":
|
|
2950
|
+
safeKey = "string_unsafe";
|
|
2951
|
+
block = text(value);
|
|
2952
|
+
break;
|
|
2953
|
+
default:
|
|
2954
|
+
safeKey = "string_unsafe";
|
|
2955
|
+
block = text(String(value));
|
|
2643
2956
|
}
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2957
|
+
return toggler(safeKey, block);
|
|
2958
|
+
}
|
|
2959
|
+
let boundFunctions = new WeakMap();
|
|
2960
|
+
const WeakMapGet = WeakMap.prototype.get;
|
|
2961
|
+
const WeakMapSet = WeakMap.prototype.set;
|
|
2962
|
+
function bind(ctx, fn) {
|
|
2963
|
+
let component = ctx.__owl__.component;
|
|
2964
|
+
let boundFnMap = WeakMapGet.call(boundFunctions, component);
|
|
2965
|
+
if (!boundFnMap) {
|
|
2966
|
+
boundFnMap = new WeakMap();
|
|
2967
|
+
WeakMapSet.call(boundFunctions, component, boundFnMap);
|
|
2651
2968
|
}
|
|
2969
|
+
let boundFn = WeakMapGet.call(boundFnMap, fn);
|
|
2970
|
+
if (!boundFn) {
|
|
2971
|
+
boundFn = fn.bind(component);
|
|
2972
|
+
WeakMapSet.call(boundFnMap, fn, boundFn);
|
|
2973
|
+
}
|
|
2974
|
+
return boundFn;
|
|
2975
|
+
}
|
|
2976
|
+
function multiRefSetter(refs, name) {
|
|
2977
|
+
let count = 0;
|
|
2978
|
+
return (el) => {
|
|
2979
|
+
if (el) {
|
|
2980
|
+
count++;
|
|
2981
|
+
if (count > 1) {
|
|
2982
|
+
throw new OwlError("Cannot have 2 elements with same ref name at the same time");
|
|
2983
|
+
}
|
|
2984
|
+
}
|
|
2985
|
+
if (count === 0 || el) {
|
|
2986
|
+
refs[name] = el;
|
|
2987
|
+
}
|
|
2988
|
+
};
|
|
2652
2989
|
}
|
|
2653
|
-
// capture the value of requestAnimationFrame as soon as possible, to avoid
|
|
2654
|
-
// interactions with other code, such as test frameworks that override them
|
|
2655
|
-
Scheduler.requestAnimationFrame = window.requestAnimationFrame.bind(window);
|
|
2656
|
-
|
|
2657
2990
|
/**
|
|
2658
|
-
*
|
|
2659
|
-
*
|
|
2660
|
-
*
|
|
2661
|
-
*
|
|
2662
|
-
* to rewrite some variables. For example, if a template has
|
|
2663
|
-
*
|
|
2664
|
-
* ```xml
|
|
2665
|
-
* <t t-if="computeSomething({val: state.val})">...</t>
|
|
2666
|
-
* ```
|
|
2667
|
-
*
|
|
2668
|
-
* this needs to be translated in something like this:
|
|
2669
|
-
*
|
|
2670
|
-
* ```js
|
|
2671
|
-
* if (context["computeSomething"]({val: context["state"].val})) { ... }
|
|
2672
|
-
* ```
|
|
2673
|
-
*
|
|
2674
|
-
* This file contains the implementation of an extremely naive tokenizer/parser
|
|
2675
|
-
* and evaluator for javascript expressions. The supported grammar is basically
|
|
2676
|
-
* only expressive enough to understand the shape of objects, of arrays, and
|
|
2677
|
-
* various operators.
|
|
2991
|
+
* Validate the component props (or next props) against the (static) props
|
|
2992
|
+
* description. This is potentially an expensive operation: it may needs to
|
|
2993
|
+
* visit recursively the props and all the children to check if they are valid.
|
|
2994
|
+
* This is why it is only done in 'dev' mode.
|
|
2678
2995
|
*/
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
gte: ">=",
|
|
2688
|
-
lt: "<",
|
|
2689
|
-
lte: "<=",
|
|
2690
|
-
});
|
|
2691
|
-
const STATIC_TOKEN_MAP = Object.assign(Object.create(null), {
|
|
2692
|
-
"{": "LEFT_BRACE",
|
|
2693
|
-
"}": "RIGHT_BRACE",
|
|
2694
|
-
"[": "LEFT_BRACKET",
|
|
2695
|
-
"]": "RIGHT_BRACKET",
|
|
2696
|
-
":": "COLON",
|
|
2697
|
-
",": "COMMA",
|
|
2698
|
-
"(": "LEFT_PAREN",
|
|
2699
|
-
")": "RIGHT_PAREN",
|
|
2700
|
-
});
|
|
2701
|
-
// note that the space after typeof is relevant. It makes sure that the formatted
|
|
2702
|
-
// expression has a space after typeof
|
|
2703
|
-
const OPERATORS = "...,.,===,==,+,!==,!=,!,||,&&,>=,>,<=,<,?,-,*,/,%,typeof ,=>,=,;,in ".split(",");
|
|
2704
|
-
let tokenizeString = function (expr) {
|
|
2705
|
-
let s = expr[0];
|
|
2706
|
-
let start = s;
|
|
2707
|
-
if (s !== "'" && s !== '"' && s !== "`") {
|
|
2708
|
-
return false;
|
|
2996
|
+
function validateProps(name, props, comp) {
|
|
2997
|
+
const ComponentClass = typeof name !== "string"
|
|
2998
|
+
? name
|
|
2999
|
+
: comp.constructor.components[name];
|
|
3000
|
+
if (!ComponentClass) {
|
|
3001
|
+
// this is an error, wrong component. We silently return here instead so the
|
|
3002
|
+
// error is triggered by the usual path ('component' function)
|
|
3003
|
+
return;
|
|
2709
3004
|
}
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
3005
|
+
const schema = ComponentClass.props;
|
|
3006
|
+
if (!schema) {
|
|
3007
|
+
if (comp.__owl__.app.warnIfNoStaticProps) {
|
|
3008
|
+
console.warn(`Component '${ComponentClass.name}' does not have a static props description`);
|
|
3009
|
+
}
|
|
3010
|
+
return;
|
|
3011
|
+
}
|
|
3012
|
+
const defaultProps = ComponentClass.defaultProps;
|
|
3013
|
+
if (defaultProps) {
|
|
3014
|
+
let isMandatory = (name) => Array.isArray(schema)
|
|
3015
|
+
? schema.includes(name)
|
|
3016
|
+
: name in schema && !("*" in schema) && !isOptional(schema[name]);
|
|
3017
|
+
for (let p in defaultProps) {
|
|
3018
|
+
if (isMandatory(p)) {
|
|
3019
|
+
throw new OwlError(`A default value cannot be defined for a mandatory prop (name: '${p}', component: ${ComponentClass.name})`);
|
|
2720
3020
|
}
|
|
2721
|
-
s += cur;
|
|
2722
3021
|
}
|
|
2723
|
-
i++;
|
|
2724
3022
|
}
|
|
2725
|
-
|
|
2726
|
-
|
|
3023
|
+
const errors = validateSchema(props, schema);
|
|
3024
|
+
if (errors.length) {
|
|
3025
|
+
throw new OwlError(`Invalid props for component '${ComponentClass.name}': ` + errors.join(", "));
|
|
3026
|
+
}
|
|
3027
|
+
}
|
|
3028
|
+
const helpers = {
|
|
3029
|
+
withDefault,
|
|
3030
|
+
zero: Symbol("zero"),
|
|
3031
|
+
isBoundary,
|
|
3032
|
+
callSlot,
|
|
3033
|
+
capture,
|
|
3034
|
+
withKey,
|
|
3035
|
+
prepareList,
|
|
3036
|
+
setContextValue,
|
|
3037
|
+
multiRefSetter,
|
|
3038
|
+
shallowEqual,
|
|
3039
|
+
toNumber,
|
|
3040
|
+
validateProps,
|
|
3041
|
+
LazyValue,
|
|
3042
|
+
safeOutput,
|
|
3043
|
+
bind,
|
|
3044
|
+
createCatcher,
|
|
3045
|
+
markRaw,
|
|
3046
|
+
OwlError,
|
|
3047
|
+
};
|
|
3048
|
+
|
|
3049
|
+
const bdom = { text, createBlock, list, multi, html, toggler, comment };
|
|
3050
|
+
function parseXML$1(xml) {
|
|
3051
|
+
const parser = new DOMParser();
|
|
3052
|
+
const doc = parser.parseFromString(xml, "text/xml");
|
|
3053
|
+
if (doc.getElementsByTagName("parsererror").length) {
|
|
3054
|
+
let msg = "Invalid XML in template.";
|
|
3055
|
+
const parsererrorText = doc.getElementsByTagName("parsererror")[0].textContent;
|
|
3056
|
+
if (parsererrorText) {
|
|
3057
|
+
msg += "\nThe parser has produced the following error message:\n" + parsererrorText;
|
|
3058
|
+
const re = /\d+/g;
|
|
3059
|
+
const firstMatch = re.exec(parsererrorText);
|
|
3060
|
+
if (firstMatch) {
|
|
3061
|
+
const lineNumber = Number(firstMatch[0]);
|
|
3062
|
+
const line = xml.split("\n")[lineNumber - 1];
|
|
3063
|
+
const secondMatch = re.exec(parsererrorText);
|
|
3064
|
+
if (line && secondMatch) {
|
|
3065
|
+
const columnIndex = Number(secondMatch[0]) - 1;
|
|
3066
|
+
if (line[columnIndex]) {
|
|
3067
|
+
msg +=
|
|
3068
|
+
`\nThe error might be located at xml line ${lineNumber} column ${columnIndex}\n` +
|
|
3069
|
+
`${line}\n${"-".repeat(columnIndex - 1)}^`;
|
|
3070
|
+
}
|
|
3071
|
+
}
|
|
3072
|
+
}
|
|
3073
|
+
}
|
|
3074
|
+
throw new OwlError(msg);
|
|
3075
|
+
}
|
|
3076
|
+
return doc;
|
|
3077
|
+
}
|
|
3078
|
+
class TemplateSet {
|
|
3079
|
+
constructor(config = {}) {
|
|
3080
|
+
this.rawTemplates = Object.create(globalTemplates);
|
|
3081
|
+
this.templates = {};
|
|
3082
|
+
this.Portal = Portal;
|
|
3083
|
+
this.dev = config.dev || false;
|
|
3084
|
+
this.translateFn = config.translateFn;
|
|
3085
|
+
this.translatableAttributes = config.translatableAttributes;
|
|
3086
|
+
if (config.templates) {
|
|
3087
|
+
this.addTemplates(config.templates);
|
|
3088
|
+
}
|
|
3089
|
+
}
|
|
3090
|
+
static registerTemplate(name, fn) {
|
|
3091
|
+
globalTemplates[name] = fn;
|
|
3092
|
+
}
|
|
3093
|
+
addTemplate(name, template) {
|
|
3094
|
+
if (name in this.rawTemplates) {
|
|
3095
|
+
const rawTemplate = this.rawTemplates[name];
|
|
3096
|
+
const currentAsString = typeof rawTemplate === "string"
|
|
3097
|
+
? rawTemplate
|
|
3098
|
+
: rawTemplate instanceof Element
|
|
3099
|
+
? rawTemplate.outerHTML
|
|
3100
|
+
: rawTemplate.toString();
|
|
3101
|
+
const newAsString = typeof template === "string" ? template : template.outerHTML;
|
|
3102
|
+
if (currentAsString === newAsString) {
|
|
3103
|
+
return;
|
|
3104
|
+
}
|
|
3105
|
+
throw new OwlError(`Template ${name} already defined with different content`);
|
|
3106
|
+
}
|
|
3107
|
+
this.rawTemplates[name] = template;
|
|
3108
|
+
}
|
|
3109
|
+
addTemplates(xml) {
|
|
3110
|
+
if (!xml) {
|
|
3111
|
+
// empty string
|
|
3112
|
+
return;
|
|
3113
|
+
}
|
|
3114
|
+
xml = xml instanceof Document ? xml : parseXML$1(xml);
|
|
3115
|
+
for (const template of xml.querySelectorAll("[t-name]")) {
|
|
3116
|
+
const name = template.getAttribute("t-name");
|
|
3117
|
+
this.addTemplate(name, template);
|
|
3118
|
+
}
|
|
3119
|
+
}
|
|
3120
|
+
getTemplate(name) {
|
|
3121
|
+
if (!(name in this.templates)) {
|
|
3122
|
+
const rawTemplate = this.rawTemplates[name];
|
|
3123
|
+
if (rawTemplate === undefined) {
|
|
3124
|
+
let extraInfo = "";
|
|
3125
|
+
try {
|
|
3126
|
+
const componentName = getCurrent().component.constructor.name;
|
|
3127
|
+
extraInfo = ` (for component "${componentName}")`;
|
|
3128
|
+
}
|
|
3129
|
+
catch { }
|
|
3130
|
+
throw new OwlError(`Missing template: "${name}"${extraInfo}`);
|
|
3131
|
+
}
|
|
3132
|
+
const isFn = typeof rawTemplate === "function" && !(rawTemplate instanceof Element);
|
|
3133
|
+
const templateFn = isFn ? rawTemplate : this._compileTemplate(name, rawTemplate);
|
|
3134
|
+
// first add a function to lazily get the template, in case there is a
|
|
3135
|
+
// recursive call to the template name
|
|
3136
|
+
const templates = this.templates;
|
|
3137
|
+
this.templates[name] = function (context, parent) {
|
|
3138
|
+
return templates[name].call(this, context, parent);
|
|
3139
|
+
};
|
|
3140
|
+
const template = templateFn(this, bdom, helpers);
|
|
3141
|
+
this.templates[name] = template;
|
|
3142
|
+
}
|
|
3143
|
+
return this.templates[name];
|
|
3144
|
+
}
|
|
3145
|
+
_compileTemplate(name, template) {
|
|
3146
|
+
throw new OwlError(`Unable to compile a template. Please use owl full build instead`);
|
|
3147
|
+
}
|
|
3148
|
+
callTemplate(owner, subTemplate, ctx, parent, key) {
|
|
3149
|
+
const template = this.getTemplate(subTemplate);
|
|
3150
|
+
return toggler(subTemplate, template.call(owner, ctx, parent, key));
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
// -----------------------------------------------------------------------------
|
|
3154
|
+
// xml tag helper
|
|
3155
|
+
// -----------------------------------------------------------------------------
|
|
3156
|
+
const globalTemplates = {};
|
|
3157
|
+
function xml(...args) {
|
|
3158
|
+
const name = `__template__${xml.nextId++}`;
|
|
3159
|
+
const value = String.raw(...args);
|
|
3160
|
+
globalTemplates[name] = value;
|
|
3161
|
+
return name;
|
|
3162
|
+
}
|
|
3163
|
+
xml.nextId = 1;
|
|
3164
|
+
TemplateSet.registerTemplate("__portal__", portalTemplate);
|
|
3165
|
+
|
|
3166
|
+
/**
|
|
3167
|
+
* Owl QWeb Expression Parser
|
|
3168
|
+
*
|
|
3169
|
+
* Owl needs in various contexts to be able to understand the structure of a
|
|
3170
|
+
* string representing a javascript expression. The usual goal is to be able
|
|
3171
|
+
* to rewrite some variables. For example, if a template has
|
|
3172
|
+
*
|
|
3173
|
+
* ```xml
|
|
3174
|
+
* <t t-if="computeSomething({val: state.val})">...</t>
|
|
3175
|
+
* ```
|
|
3176
|
+
*
|
|
3177
|
+
* this needs to be translated in something like this:
|
|
3178
|
+
*
|
|
3179
|
+
* ```js
|
|
3180
|
+
* if (context["computeSomething"]({val: context["state"].val})) { ... }
|
|
3181
|
+
* ```
|
|
3182
|
+
*
|
|
3183
|
+
* This file contains the implementation of an extremely naive tokenizer/parser
|
|
3184
|
+
* and evaluator for javascript expressions. The supported grammar is basically
|
|
3185
|
+
* only expressive enough to understand the shape of objects, of arrays, and
|
|
3186
|
+
* various operators.
|
|
3187
|
+
*/
|
|
3188
|
+
//------------------------------------------------------------------------------
|
|
3189
|
+
// Misc types, constants and helpers
|
|
3190
|
+
//------------------------------------------------------------------------------
|
|
3191
|
+
const RESERVED_WORDS = "true,false,NaN,null,undefined,debugger,console,window,in,instanceof,new,function,return,this,eval,void,Math,RegExp,Array,Object,Date".split(",");
|
|
3192
|
+
const WORD_REPLACEMENT = Object.assign(Object.create(null), {
|
|
3193
|
+
and: "&&",
|
|
3194
|
+
or: "||",
|
|
3195
|
+
gt: ">",
|
|
3196
|
+
gte: ">=",
|
|
3197
|
+
lt: "<",
|
|
3198
|
+
lte: "<=",
|
|
3199
|
+
});
|
|
3200
|
+
const STATIC_TOKEN_MAP = Object.assign(Object.create(null), {
|
|
3201
|
+
"{": "LEFT_BRACE",
|
|
3202
|
+
"}": "RIGHT_BRACE",
|
|
3203
|
+
"[": "LEFT_BRACKET",
|
|
3204
|
+
"]": "RIGHT_BRACKET",
|
|
3205
|
+
":": "COLON",
|
|
3206
|
+
",": "COMMA",
|
|
3207
|
+
"(": "LEFT_PAREN",
|
|
3208
|
+
")": "RIGHT_PAREN",
|
|
3209
|
+
});
|
|
3210
|
+
// note that the space after typeof is relevant. It makes sure that the formatted
|
|
3211
|
+
// expression has a space after typeof. Currently we don't support delete and void
|
|
3212
|
+
const OPERATORS = "...,.,===,==,+,!==,!=,!,||,&&,>=,>,<=,<,?,-,*,/,%,typeof ,=>,=,;,in ,new ,|,&,^,~".split(",");
|
|
3213
|
+
let tokenizeString = function (expr) {
|
|
3214
|
+
let s = expr[0];
|
|
3215
|
+
let start = s;
|
|
3216
|
+
if (s !== "'" && s !== '"' && s !== "`") {
|
|
3217
|
+
return false;
|
|
3218
|
+
}
|
|
3219
|
+
let i = 1;
|
|
3220
|
+
let cur;
|
|
3221
|
+
while (expr[i] && expr[i] !== start) {
|
|
3222
|
+
cur = expr[i];
|
|
3223
|
+
s += cur;
|
|
3224
|
+
if (cur === "\\") {
|
|
3225
|
+
i++;
|
|
3226
|
+
cur = expr[i];
|
|
3227
|
+
if (!cur) {
|
|
3228
|
+
throw new OwlError("Invalid expression");
|
|
3229
|
+
}
|
|
3230
|
+
s += cur;
|
|
3231
|
+
}
|
|
3232
|
+
i++;
|
|
3233
|
+
}
|
|
3234
|
+
if (expr[i] !== start) {
|
|
3235
|
+
throw new OwlError("Invalid expression");
|
|
2727
3236
|
}
|
|
2728
3237
|
s += start;
|
|
2729
3238
|
if (start === "`") {
|
|
@@ -2830,7 +3339,7 @@ function tokenize(expr) {
|
|
|
2830
3339
|
error = e; // Silence all errors and throw a generic error below
|
|
2831
3340
|
}
|
|
2832
3341
|
if (current.length || error) {
|
|
2833
|
-
throw new
|
|
3342
|
+
throw new OwlError(`Tokenizer error: could not tokenize \`${expr}\``);
|
|
2834
3343
|
}
|
|
2835
3344
|
return result;
|
|
2836
3345
|
}
|
|
@@ -2941,26 +3450,35 @@ function compileExprToArray(expr) {
|
|
|
2941
3450
|
}
|
|
2942
3451
|
return tokens;
|
|
2943
3452
|
}
|
|
3453
|
+
// Leading spaces are trimmed during tokenization, so they need to be added back for some values
|
|
3454
|
+
const paddedValues = new Map([["in ", " in "]]);
|
|
2944
3455
|
function compileExpr(expr) {
|
|
2945
3456
|
return compileExprToArray(expr)
|
|
2946
|
-
.map((t) => t.value)
|
|
3457
|
+
.map((t) => paddedValues.get(t.value) || t.value)
|
|
2947
3458
|
.join("");
|
|
2948
3459
|
}
|
|
2949
|
-
const INTERP_REGEXP = /\{\{.*?\}\}/g;
|
|
2950
|
-
|
|
2951
|
-
function interpolate(s) {
|
|
3460
|
+
const INTERP_REGEXP = /\{\{.*?\}\}|\#\{.*?\}/g;
|
|
3461
|
+
function replaceDynamicParts(s, replacer) {
|
|
2952
3462
|
let matches = s.match(INTERP_REGEXP);
|
|
2953
3463
|
if (matches && matches[0].length === s.length) {
|
|
2954
|
-
return `(${
|
|
3464
|
+
return `(${replacer(s.slice(2, matches[0][0] === "{" ? -2 : -1))})`;
|
|
2955
3465
|
}
|
|
2956
|
-
let r = s.replace(
|
|
3466
|
+
let r = s.replace(INTERP_REGEXP, (s) => "${" + replacer(s.slice(2, s[0] === "{" ? -2 : -1)) + "}");
|
|
2957
3467
|
return "`" + r + "`";
|
|
3468
|
+
}
|
|
3469
|
+
function interpolate(s) {
|
|
3470
|
+
return replaceDynamicParts(s, compileExpr);
|
|
2958
3471
|
}
|
|
2959
3472
|
|
|
2960
3473
|
// using a non-html document so that <inner/outer>HTML serializes as XML instead
|
|
2961
3474
|
// of HTML (as we will parse it as xml later)
|
|
2962
3475
|
const xmlDoc = document.implementation.createDocument(null, null, null);
|
|
2963
3476
|
const MODS = new Set(["stop", "capture", "prevent", "self", "synthetic"]);
|
|
3477
|
+
let nextDataIds = {};
|
|
3478
|
+
function generateId(prefix = "") {
|
|
3479
|
+
nextDataIds[prefix] = (nextDataIds[prefix] || 0) + 1;
|
|
3480
|
+
return prefix + nextDataIds[prefix];
|
|
3481
|
+
}
|
|
2964
3482
|
// -----------------------------------------------------------------------------
|
|
2965
3483
|
// BlockDescription
|
|
2966
3484
|
// -----------------------------------------------------------------------------
|
|
@@ -2979,12 +3497,8 @@ class BlockDescription {
|
|
|
2979
3497
|
this.target = target;
|
|
2980
3498
|
this.type = type;
|
|
2981
3499
|
}
|
|
2982
|
-
static generateId(prefix) {
|
|
2983
|
-
this.nextDataIds[prefix] = (this.nextDataIds[prefix] || 0) + 1;
|
|
2984
|
-
return prefix + this.nextDataIds[prefix];
|
|
2985
|
-
}
|
|
2986
3500
|
insertData(str, prefix = "d") {
|
|
2987
|
-
const id =
|
|
3501
|
+
const id = generateId(prefix);
|
|
2988
3502
|
this.target.addLine(`let ${id} = ${str};`);
|
|
2989
3503
|
return this.data.push(id) - 1;
|
|
2990
3504
|
}
|
|
@@ -3022,7 +3536,6 @@ class BlockDescription {
|
|
|
3022
3536
|
}
|
|
3023
3537
|
}
|
|
3024
3538
|
BlockDescription.nextBlockId = 1;
|
|
3025
|
-
BlockDescription.nextDataIds = {};
|
|
3026
3539
|
function createContext(parentCtx, params) {
|
|
3027
3540
|
return Object.assign({
|
|
3028
3541
|
block: null,
|
|
@@ -3084,19 +3597,26 @@ class CodeTarget {
|
|
|
3084
3597
|
result.push(`}`);
|
|
3085
3598
|
return result.join("\n ");
|
|
3086
3599
|
}
|
|
3600
|
+
currentKey(ctx) {
|
|
3601
|
+
let key = this.loopLevel ? `key${this.loopLevel}` : "key";
|
|
3602
|
+
if (ctx.tKeyExpr) {
|
|
3603
|
+
key = `${ctx.tKeyExpr} + ${key}`;
|
|
3604
|
+
}
|
|
3605
|
+
return key;
|
|
3606
|
+
}
|
|
3087
3607
|
}
|
|
3088
3608
|
const TRANSLATABLE_ATTRS = ["label", "title", "placeholder", "alt"];
|
|
3089
3609
|
const translationRE = /^(\s*)([\s\S]+?)(\s*)$/;
|
|
3090
3610
|
class CodeGenerator {
|
|
3091
3611
|
constructor(ast, options) {
|
|
3092
3612
|
this.blocks = [];
|
|
3093
|
-
this.ids = {};
|
|
3094
3613
|
this.nextBlockId = 1;
|
|
3095
3614
|
this.isDebug = false;
|
|
3096
3615
|
this.targets = [];
|
|
3097
3616
|
this.target = new CodeTarget("template");
|
|
3098
3617
|
this.translatableAttributes = TRANSLATABLE_ATTRS;
|
|
3099
3618
|
this.staticDefs = [];
|
|
3619
|
+
this.slotNames = new Set();
|
|
3100
3620
|
this.helpers = new Set();
|
|
3101
3621
|
this.translateFn = options.translateFn || ((s) => s);
|
|
3102
3622
|
if (options.translatableAttributes) {
|
|
@@ -3120,7 +3640,7 @@ class CodeGenerator {
|
|
|
3120
3640
|
const ast = this.ast;
|
|
3121
3641
|
this.isDebug = ast.type === 12 /* TDebug */;
|
|
3122
3642
|
BlockDescription.nextBlockId = 1;
|
|
3123
|
-
|
|
3643
|
+
nextDataIds = {};
|
|
3124
3644
|
this.compileAST(ast, {
|
|
3125
3645
|
block: null,
|
|
3126
3646
|
index: 0,
|
|
@@ -3130,9 +3650,7 @@ class CodeGenerator {
|
|
|
3130
3650
|
tKeyExpr: null,
|
|
3131
3651
|
});
|
|
3132
3652
|
// define blocks and utility functions
|
|
3133
|
-
let mainCode = [
|
|
3134
|
-
` let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;`,
|
|
3135
|
-
];
|
|
3653
|
+
let mainCode = [` let { text, createBlock, list, multi, html, toggler, comment } = bdom;`];
|
|
3136
3654
|
if (this.helpers.size) {
|
|
3137
3655
|
mainCode.push(`let { ${[...this.helpers].join(", ")} } = helpers;`);
|
|
3138
3656
|
}
|
|
@@ -3148,6 +3666,7 @@ class CodeGenerator {
|
|
|
3148
3666
|
for (let block of this.blocks) {
|
|
3149
3667
|
if (block.dom) {
|
|
3150
3668
|
let xmlString = block.asXmlString();
|
|
3669
|
+
xmlString = xmlString.replace(/`/g, "\\`");
|
|
3151
3670
|
if (block.dynamicTagName) {
|
|
3152
3671
|
xmlString = xmlString.replace(/^<\w+/, `<\${tag || '${block.dom.nodeName}'}`);
|
|
3153
3672
|
xmlString = xmlString.replace(/\w+>$/, `\${tag || '${block.dom.nodeName}'}>`);
|
|
@@ -3177,7 +3696,7 @@ class CodeGenerator {
|
|
|
3177
3696
|
return code;
|
|
3178
3697
|
}
|
|
3179
3698
|
compileInNewTarget(prefix, ast, ctx, on) {
|
|
3180
|
-
const name =
|
|
3699
|
+
const name = generateId(prefix);
|
|
3181
3700
|
const initialTarget = this.target;
|
|
3182
3701
|
const target = new CodeTarget(name, on);
|
|
3183
3702
|
this.targets.push(target);
|
|
@@ -3192,12 +3711,8 @@ class CodeGenerator {
|
|
|
3192
3711
|
define(varName, expr) {
|
|
3193
3712
|
this.addLine(`const ${varName} = ${expr};`);
|
|
3194
3713
|
}
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
return prefix + this.ids[prefix];
|
|
3198
|
-
}
|
|
3199
|
-
insertAnchor(block) {
|
|
3200
|
-
const tag = `block-child-${block.children.length}`;
|
|
3714
|
+
insertAnchor(block, index = block.children.length) {
|
|
3715
|
+
const tag = `block-child-${index}`;
|
|
3201
3716
|
const anchor = xmlDoc.createElement(tag);
|
|
3202
3717
|
block.insert(anchor);
|
|
3203
3718
|
}
|
|
@@ -3218,18 +3733,14 @@ class CodeGenerator {
|
|
|
3218
3733
|
}
|
|
3219
3734
|
insertBlock(expression, block, ctx) {
|
|
3220
3735
|
let blockExpr = block.generateExpr(expression);
|
|
3221
|
-
const tKeyExpr = ctx.tKeyExpr;
|
|
3222
3736
|
if (block.parentVar) {
|
|
3223
|
-
let
|
|
3224
|
-
if (tKeyExpr) {
|
|
3225
|
-
keyArg = `${tKeyExpr} + ${keyArg}`;
|
|
3226
|
-
}
|
|
3737
|
+
let key = this.target.currentKey(ctx);
|
|
3227
3738
|
this.helpers.add("withKey");
|
|
3228
|
-
this.addLine(`${block.parentVar}[${ctx.index}] = withKey(${blockExpr}, ${
|
|
3739
|
+
this.addLine(`${block.parentVar}[${ctx.index}] = withKey(${blockExpr}, ${key});`);
|
|
3229
3740
|
return;
|
|
3230
3741
|
}
|
|
3231
|
-
if (tKeyExpr) {
|
|
3232
|
-
blockExpr = `toggler(${tKeyExpr}, ${blockExpr})`;
|
|
3742
|
+
if (ctx.tKeyExpr) {
|
|
3743
|
+
blockExpr = `toggler(${ctx.tKeyExpr}, ${blockExpr})`;
|
|
3233
3744
|
}
|
|
3234
3745
|
if (block.isRoot && !ctx.preventRoot) {
|
|
3235
3746
|
if (this.target.on) {
|
|
@@ -3264,7 +3775,7 @@ class CodeGenerator {
|
|
|
3264
3775
|
.map((tok) => {
|
|
3265
3776
|
if (tok.varName && !tok.isLocal) {
|
|
3266
3777
|
if (!mapping.has(tok.varName)) {
|
|
3267
|
-
const varId =
|
|
3778
|
+
const varId = generateId("v");
|
|
3268
3779
|
mapping.set(tok.varName, varId);
|
|
3269
3780
|
this.define(varId, tok.value);
|
|
3270
3781
|
}
|
|
@@ -3274,74 +3785,62 @@ class CodeGenerator {
|
|
|
3274
3785
|
})
|
|
3275
3786
|
.join("");
|
|
3276
3787
|
}
|
|
3788
|
+
/**
|
|
3789
|
+
* @returns the newly created block name, if any
|
|
3790
|
+
*/
|
|
3277
3791
|
compileAST(ast, ctx) {
|
|
3278
3792
|
switch (ast.type) {
|
|
3279
3793
|
case 1 /* Comment */:
|
|
3280
|
-
this.compileComment(ast, ctx);
|
|
3281
|
-
break;
|
|
3794
|
+
return this.compileComment(ast, ctx);
|
|
3282
3795
|
case 0 /* Text */:
|
|
3283
|
-
this.compileText(ast, ctx);
|
|
3284
|
-
break;
|
|
3796
|
+
return this.compileText(ast, ctx);
|
|
3285
3797
|
case 2 /* DomNode */:
|
|
3286
|
-
this.compileTDomNode(ast, ctx);
|
|
3287
|
-
break;
|
|
3798
|
+
return this.compileTDomNode(ast, ctx);
|
|
3288
3799
|
case 4 /* TEsc */:
|
|
3289
|
-
this.compileTEsc(ast, ctx);
|
|
3290
|
-
break;
|
|
3800
|
+
return this.compileTEsc(ast, ctx);
|
|
3291
3801
|
case 8 /* TOut */:
|
|
3292
|
-
this.compileTOut(ast, ctx);
|
|
3293
|
-
break;
|
|
3802
|
+
return this.compileTOut(ast, ctx);
|
|
3294
3803
|
case 5 /* TIf */:
|
|
3295
|
-
this.compileTIf(ast, ctx);
|
|
3296
|
-
break;
|
|
3804
|
+
return this.compileTIf(ast, ctx);
|
|
3297
3805
|
case 9 /* TForEach */:
|
|
3298
|
-
this.compileTForeach(ast, ctx);
|
|
3299
|
-
break;
|
|
3806
|
+
return this.compileTForeach(ast, ctx);
|
|
3300
3807
|
case 10 /* TKey */:
|
|
3301
|
-
this.compileTKey(ast, ctx);
|
|
3302
|
-
break;
|
|
3808
|
+
return this.compileTKey(ast, ctx);
|
|
3303
3809
|
case 3 /* Multi */:
|
|
3304
|
-
this.compileMulti(ast, ctx);
|
|
3305
|
-
break;
|
|
3810
|
+
return this.compileMulti(ast, ctx);
|
|
3306
3811
|
case 7 /* TCall */:
|
|
3307
|
-
this.compileTCall(ast, ctx);
|
|
3308
|
-
break;
|
|
3812
|
+
return this.compileTCall(ast, ctx);
|
|
3309
3813
|
case 15 /* TCallBlock */:
|
|
3310
|
-
this.compileTCallBlock(ast, ctx);
|
|
3311
|
-
break;
|
|
3814
|
+
return this.compileTCallBlock(ast, ctx);
|
|
3312
3815
|
case 6 /* TSet */:
|
|
3313
|
-
this.compileTSet(ast, ctx);
|
|
3314
|
-
break;
|
|
3816
|
+
return this.compileTSet(ast, ctx);
|
|
3315
3817
|
case 11 /* TComponent */:
|
|
3316
|
-
this.compileComponent(ast, ctx);
|
|
3317
|
-
break;
|
|
3818
|
+
return this.compileComponent(ast, ctx);
|
|
3318
3819
|
case 12 /* TDebug */:
|
|
3319
|
-
this.compileDebug(ast, ctx);
|
|
3320
|
-
break;
|
|
3820
|
+
return this.compileDebug(ast, ctx);
|
|
3321
3821
|
case 13 /* TLog */:
|
|
3322
|
-
this.compileLog(ast, ctx);
|
|
3323
|
-
break;
|
|
3822
|
+
return this.compileLog(ast, ctx);
|
|
3324
3823
|
case 14 /* TSlot */:
|
|
3325
|
-
this.compileTSlot(ast, ctx);
|
|
3326
|
-
break;
|
|
3824
|
+
return this.compileTSlot(ast, ctx);
|
|
3327
3825
|
case 16 /* TTranslation */:
|
|
3328
|
-
this.compileTTranslation(ast, ctx);
|
|
3329
|
-
break;
|
|
3826
|
+
return this.compileTTranslation(ast, ctx);
|
|
3330
3827
|
case 17 /* TPortal */:
|
|
3331
|
-
this.compileTPortal(ast, ctx);
|
|
3828
|
+
return this.compileTPortal(ast, ctx);
|
|
3332
3829
|
}
|
|
3333
3830
|
}
|
|
3334
3831
|
compileDebug(ast, ctx) {
|
|
3335
3832
|
this.addLine(`debugger;`);
|
|
3336
3833
|
if (ast.content) {
|
|
3337
|
-
this.compileAST(ast.content, ctx);
|
|
3834
|
+
return this.compileAST(ast.content, ctx);
|
|
3338
3835
|
}
|
|
3836
|
+
return null;
|
|
3339
3837
|
}
|
|
3340
3838
|
compileLog(ast, ctx) {
|
|
3341
3839
|
this.addLine(`console.log(${compileExpr(ast.expr)});`);
|
|
3342
3840
|
if (ast.content) {
|
|
3343
|
-
this.compileAST(ast.content, ctx);
|
|
3841
|
+
return this.compileAST(ast.content, ctx);
|
|
3344
3842
|
}
|
|
3843
|
+
return null;
|
|
3345
3844
|
}
|
|
3346
3845
|
compileComment(ast, ctx) {
|
|
3347
3846
|
let { block, forceNewBlock } = ctx;
|
|
@@ -3357,6 +3856,7 @@ class CodeGenerator {
|
|
|
3357
3856
|
const text = xmlDoc.createComment(ast.value);
|
|
3358
3857
|
block.insert(text);
|
|
3359
3858
|
}
|
|
3859
|
+
return block.varName;
|
|
3360
3860
|
}
|
|
3361
3861
|
compileText(ast, ctx) {
|
|
3362
3862
|
let { block, forceNewBlock } = ctx;
|
|
@@ -3376,6 +3876,7 @@ class CodeGenerator {
|
|
|
3376
3876
|
const createFn = ast.type === 0 /* Text */ ? xmlDoc.createTextNode : xmlDoc.createComment;
|
|
3377
3877
|
block.insert(createFn.call(xmlDoc, value));
|
|
3378
3878
|
}
|
|
3879
|
+
return block.varName;
|
|
3379
3880
|
}
|
|
3380
3881
|
generateHandlerCode(rawEvent, handler) {
|
|
3381
3882
|
const modifiers = rawEvent
|
|
@@ -3383,7 +3884,7 @@ class CodeGenerator {
|
|
|
3383
3884
|
.slice(1)
|
|
3384
3885
|
.map((m) => {
|
|
3385
3886
|
if (!MODS.has(m)) {
|
|
3386
|
-
throw new
|
|
3887
|
+
throw new OwlError(`Unknown event modifier: '${m}'`);
|
|
3387
3888
|
}
|
|
3388
3889
|
return `"${m}"`;
|
|
3389
3890
|
});
|
|
@@ -3404,7 +3905,7 @@ class CodeGenerator {
|
|
|
3404
3905
|
block = this.createBlock(block, "block", ctx);
|
|
3405
3906
|
this.blocks.push(block);
|
|
3406
3907
|
if (ast.dynamicTag) {
|
|
3407
|
-
const tagExpr =
|
|
3908
|
+
const tagExpr = generateId("tag");
|
|
3408
3909
|
this.define(tagExpr, compileExpr(ast.dynamicTag));
|
|
3409
3910
|
block.dynamicTagName = tagExpr;
|
|
3410
3911
|
}
|
|
@@ -3425,13 +3926,23 @@ class CodeGenerator {
|
|
|
3425
3926
|
attrs["block-attribute-" + idx] = attrName;
|
|
3426
3927
|
}
|
|
3427
3928
|
else if (key.startsWith("t-att")) {
|
|
3929
|
+
attrName = key === "t-att" ? null : key.slice(6);
|
|
3428
3930
|
expr = compileExpr(ast.attrs[key]);
|
|
3931
|
+
if (attrName && isProp(ast.tag, attrName)) {
|
|
3932
|
+
// we force a new string or new boolean to bypass the equality check in blockdom when patching same value
|
|
3933
|
+
if (attrName === "value") {
|
|
3934
|
+
// When the expression is falsy, fall back to an empty string
|
|
3935
|
+
expr = `new String((${expr}) || "")`;
|
|
3936
|
+
}
|
|
3937
|
+
else {
|
|
3938
|
+
expr = `new Boolean(${expr})`;
|
|
3939
|
+
}
|
|
3940
|
+
}
|
|
3429
3941
|
const idx = block.insertData(expr, "attr");
|
|
3430
3942
|
if (key === "t-att") {
|
|
3431
3943
|
attrs[`block-attributes`] = String(idx);
|
|
3432
3944
|
}
|
|
3433
3945
|
else {
|
|
3434
|
-
attrName = key.slice(6);
|
|
3435
3946
|
attrs[`block-attribute-${idx}`] = attrName;
|
|
3436
3947
|
}
|
|
3437
3948
|
}
|
|
@@ -3459,8 +3970,8 @@ class CodeGenerator {
|
|
|
3459
3970
|
this.target.hasRef = true;
|
|
3460
3971
|
const isDynamic = INTERP_REGEXP.test(ast.ref);
|
|
3461
3972
|
if (isDynamic) {
|
|
3462
|
-
const str = ast.ref
|
|
3463
|
-
const idx = block.insertData(`(el) => refs[
|
|
3973
|
+
const str = replaceDynamicParts(ast.ref, (expr) => this.captureExpression(expr, true));
|
|
3974
|
+
const idx = block.insertData(`(el) => refs[${str}] = el`, "ref");
|
|
3464
3975
|
attrs["block-ref"] = String(idx);
|
|
3465
3976
|
}
|
|
3466
3977
|
else {
|
|
@@ -3474,7 +3985,7 @@ class CodeGenerator {
|
|
|
3474
3985
|
info[1] = `multiRefSetter(refs, \`${name}\`)`;
|
|
3475
3986
|
}
|
|
3476
3987
|
else {
|
|
3477
|
-
let id =
|
|
3988
|
+
let id = generateId("ref");
|
|
3478
3989
|
this.target.refInfo[name] = [id, `(el) => refs[\`${name}\`] = el`];
|
|
3479
3990
|
const index = block.data.push(id) - 1;
|
|
3480
3991
|
attrs["block-ref"] = String(index);
|
|
@@ -3486,10 +3997,10 @@ class CodeGenerator {
|
|
|
3486
3997
|
if (ast.model) {
|
|
3487
3998
|
const { hasDynamicChildren, baseExpr, expr, eventType, shouldNumberize, shouldTrim, targetAttr, specialInitTargetAttr, } = ast.model;
|
|
3488
3999
|
const baseExpression = compileExpr(baseExpr);
|
|
3489
|
-
const bExprId =
|
|
4000
|
+
const bExprId = generateId("bExpr");
|
|
3490
4001
|
this.define(bExprId, baseExpression);
|
|
3491
4002
|
const expression = compileExpr(expr);
|
|
3492
|
-
const exprId =
|
|
4003
|
+
const exprId = generateId("expr");
|
|
3493
4004
|
this.define(exprId, expression);
|
|
3494
4005
|
const fullExpression = `${bExprId}[${exprId}]`;
|
|
3495
4006
|
let idx;
|
|
@@ -3498,7 +4009,7 @@ class CodeGenerator {
|
|
|
3498
4009
|
attrs[`block-attribute-${idx}`] = specialInitTargetAttr;
|
|
3499
4010
|
}
|
|
3500
4011
|
else if (hasDynamicChildren) {
|
|
3501
|
-
const bValueId =
|
|
4012
|
+
const bValueId = generateId("bValue");
|
|
3502
4013
|
tModelSelectedExpr = `${bValueId}`;
|
|
3503
4014
|
this.define(tModelSelectedExpr, fullExpression);
|
|
3504
4015
|
}
|
|
@@ -3558,6 +4069,7 @@ class CodeGenerator {
|
|
|
3558
4069
|
this.addLine(`let ${block.children.map((c) => c.varName)};`, codeIdx);
|
|
3559
4070
|
}
|
|
3560
4071
|
}
|
|
4072
|
+
return block.varName;
|
|
3561
4073
|
}
|
|
3562
4074
|
compileTEsc(ast, ctx) {
|
|
3563
4075
|
let { block, forceNewBlock } = ctx;
|
|
@@ -3582,6 +4094,7 @@ class CodeGenerator {
|
|
|
3582
4094
|
const text = xmlDoc.createElement(`block-text-${idx}`);
|
|
3583
4095
|
block.insert(text);
|
|
3584
4096
|
}
|
|
4097
|
+
return block.varName;
|
|
3585
4098
|
}
|
|
3586
4099
|
compileTOut(ast, ctx) {
|
|
3587
4100
|
let { block } = ctx;
|
|
@@ -3589,20 +4102,38 @@ class CodeGenerator {
|
|
|
3589
4102
|
this.insertAnchor(block);
|
|
3590
4103
|
}
|
|
3591
4104
|
block = this.createBlock(block, "html", ctx);
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
4105
|
+
let blockStr;
|
|
4106
|
+
if (ast.expr === "0") {
|
|
4107
|
+
this.helpers.add("zero");
|
|
4108
|
+
blockStr = `ctx[zero]`;
|
|
4109
|
+
}
|
|
4110
|
+
else if (ast.body) {
|
|
4111
|
+
let bodyValue = null;
|
|
4112
|
+
bodyValue = BlockDescription.nextBlockId;
|
|
3596
4113
|
const subCtx = createContext(ctx);
|
|
3597
4114
|
this.compileAST({ type: 3 /* Multi */, content: ast.body }, subCtx);
|
|
3598
|
-
this.helpers.add("
|
|
3599
|
-
|
|
4115
|
+
this.helpers.add("safeOutput");
|
|
4116
|
+
blockStr = `safeOutput(${compileExpr(ast.expr)}, b${bodyValue})`;
|
|
3600
4117
|
}
|
|
3601
|
-
|
|
4118
|
+
else {
|
|
4119
|
+
this.helpers.add("safeOutput");
|
|
4120
|
+
blockStr = `safeOutput(${compileExpr(ast.expr)})`;
|
|
4121
|
+
}
|
|
4122
|
+
this.insertBlock(blockStr, block, ctx);
|
|
4123
|
+
return block.varName;
|
|
4124
|
+
}
|
|
4125
|
+
compileTIfBranch(content, block, ctx) {
|
|
4126
|
+
this.target.indentLevel++;
|
|
4127
|
+
let childN = block.children.length;
|
|
4128
|
+
this.compileAST(content, createContext(ctx, { block, index: ctx.index }));
|
|
4129
|
+
if (block.children.length > childN) {
|
|
4130
|
+
// we have some content => need to insert an anchor at correct index
|
|
4131
|
+
this.insertAnchor(block, childN);
|
|
4132
|
+
}
|
|
4133
|
+
this.target.indentLevel--;
|
|
3602
4134
|
}
|
|
3603
4135
|
compileTIf(ast, ctx, nextNode) {
|
|
3604
|
-
let { block, forceNewBlock
|
|
3605
|
-
let currentIndex = index;
|
|
4136
|
+
let { block, forceNewBlock } = ctx;
|
|
3606
4137
|
const codeIdx = this.target.code.length;
|
|
3607
4138
|
const isNewBlock = !block || (block.type !== "multi" && forceNewBlock);
|
|
3608
4139
|
if (block) {
|
|
@@ -3612,28 +4143,16 @@ class CodeGenerator {
|
|
|
3612
4143
|
block = this.createBlock(block, "multi", ctx);
|
|
3613
4144
|
}
|
|
3614
4145
|
this.addLine(`if (${compileExpr(ast.condition)}) {`);
|
|
3615
|
-
this.
|
|
3616
|
-
this.insertAnchor(block);
|
|
3617
|
-
const subCtx = createContext(ctx, { block, index: currentIndex });
|
|
3618
|
-
this.compileAST(ast.content, subCtx);
|
|
3619
|
-
this.target.indentLevel--;
|
|
4146
|
+
this.compileTIfBranch(ast.content, block, ctx);
|
|
3620
4147
|
if (ast.tElif) {
|
|
3621
4148
|
for (let clause of ast.tElif) {
|
|
3622
4149
|
this.addLine(`} else if (${compileExpr(clause.condition)}) {`);
|
|
3623
|
-
this.
|
|
3624
|
-
this.insertAnchor(block);
|
|
3625
|
-
const subCtx = createContext(ctx, { block, index: currentIndex });
|
|
3626
|
-
this.compileAST(clause.content, subCtx);
|
|
3627
|
-
this.target.indentLevel--;
|
|
4150
|
+
this.compileTIfBranch(clause.content, block, ctx);
|
|
3628
4151
|
}
|
|
3629
4152
|
}
|
|
3630
4153
|
if (ast.tElse) {
|
|
3631
4154
|
this.addLine(`} else {`);
|
|
3632
|
-
this.
|
|
3633
|
-
this.insertAnchor(block);
|
|
3634
|
-
const subCtx = createContext(ctx, { block, index: currentIndex });
|
|
3635
|
-
this.compileAST(ast.tElse, subCtx);
|
|
3636
|
-
this.target.indentLevel--;
|
|
4155
|
+
this.compileTIfBranch(ast.tElse, block, ctx);
|
|
3637
4156
|
}
|
|
3638
4157
|
this.addLine("}");
|
|
3639
4158
|
if (isNewBlock) {
|
|
@@ -3656,6 +4175,7 @@ class CodeGenerator {
|
|
|
3656
4175
|
const args = block.children.map((c) => c.varName).join(", ");
|
|
3657
4176
|
this.insertBlock(`multi([${args}])`, block, ctx);
|
|
3658
4177
|
}
|
|
4178
|
+
return block.varName;
|
|
3659
4179
|
}
|
|
3660
4180
|
compileTForeach(ast, ctx) {
|
|
3661
4181
|
let { block } = ctx;
|
|
@@ -3694,13 +4214,14 @@ class CodeGenerator {
|
|
|
3694
4214
|
this.define(`key${this.target.loopLevel}`, ast.key ? compileExpr(ast.key) : loopVar);
|
|
3695
4215
|
if (this.dev) {
|
|
3696
4216
|
// Throw error on duplicate keys in dev mode
|
|
3697
|
-
this.
|
|
4217
|
+
this.helpers.add("OwlError");
|
|
4218
|
+
this.addLine(`if (keys${block.id}.has(key${this.target.loopLevel})) { throw new OwlError(\`Got duplicate key in t-foreach: \${key${this.target.loopLevel}}\`)}`);
|
|
3698
4219
|
this.addLine(`keys${block.id}.add(key${this.target.loopLevel});`);
|
|
3699
4220
|
}
|
|
3700
4221
|
let id;
|
|
3701
4222
|
if (ast.memo) {
|
|
3702
4223
|
this.target.hasCache = true;
|
|
3703
|
-
id =
|
|
4224
|
+
id = generateId();
|
|
3704
4225
|
this.define(`memo${id}`, compileExpr(ast.memo));
|
|
3705
4226
|
this.define(`vnode${id}`, `cache[key${this.target.loopLevel}];`);
|
|
3706
4227
|
this.addLine(`if (vnode${id}) {`);
|
|
@@ -3727,16 +4248,17 @@ class CodeGenerator {
|
|
|
3727
4248
|
this.addLine(`ctx = ctx.__proto__;`);
|
|
3728
4249
|
}
|
|
3729
4250
|
this.insertBlock("l", block, ctx);
|
|
4251
|
+
return block.varName;
|
|
3730
4252
|
}
|
|
3731
4253
|
compileTKey(ast, ctx) {
|
|
3732
|
-
const tKeyExpr =
|
|
4254
|
+
const tKeyExpr = generateId("tKey_");
|
|
3733
4255
|
this.define(tKeyExpr, compileExpr(ast.expr));
|
|
3734
4256
|
ctx = createContext(ctx, {
|
|
3735
4257
|
tKeyExpr,
|
|
3736
4258
|
block: ctx.block,
|
|
3737
4259
|
index: ctx.index,
|
|
3738
4260
|
});
|
|
3739
|
-
this.compileAST(ast.content, ctx);
|
|
4261
|
+
return this.compileAST(ast.content, ctx);
|
|
3740
4262
|
}
|
|
3741
4263
|
compileMulti(ast, ctx) {
|
|
3742
4264
|
let { block, forceNewBlock } = ctx;
|
|
@@ -3744,11 +4266,13 @@ class CodeGenerator {
|
|
|
3744
4266
|
let codeIdx = this.target.code.length;
|
|
3745
4267
|
if (isNewBlock) {
|
|
3746
4268
|
const n = ast.content.filter((c) => c.type !== 6 /* TSet */).length;
|
|
4269
|
+
let result = null;
|
|
3747
4270
|
if (n <= 1) {
|
|
3748
4271
|
for (let child of ast.content) {
|
|
3749
|
-
this.compileAST(child, ctx);
|
|
4272
|
+
const blockName = this.compileAST(child, ctx);
|
|
4273
|
+
result = result || blockName;
|
|
3750
4274
|
}
|
|
3751
|
-
return;
|
|
4275
|
+
return result;
|
|
3752
4276
|
}
|
|
3753
4277
|
block = this.createBlock(block, "multi", ctx);
|
|
3754
4278
|
}
|
|
@@ -3788,19 +4312,24 @@ class CodeGenerator {
|
|
|
3788
4312
|
const args = block.children.map((c) => c.varName).join(", ");
|
|
3789
4313
|
this.insertBlock(`multi([${args}])`, block, ctx);
|
|
3790
4314
|
}
|
|
4315
|
+
return block.varName;
|
|
3791
4316
|
}
|
|
3792
4317
|
compileTCall(ast, ctx) {
|
|
3793
4318
|
let { block, forceNewBlock } = ctx;
|
|
4319
|
+
let ctxVar = ctx.ctxVar || "ctx";
|
|
4320
|
+
if (ast.context) {
|
|
4321
|
+
ctxVar = generateId("ctx");
|
|
4322
|
+
this.addLine(`let ${ctxVar} = ${compileExpr(ast.context)};`);
|
|
4323
|
+
}
|
|
3794
4324
|
if (ast.body) {
|
|
3795
|
-
this.addLine(
|
|
3796
|
-
this.addLine(
|
|
4325
|
+
this.addLine(`${ctxVar} = Object.create(${ctxVar});`);
|
|
4326
|
+
this.addLine(`${ctxVar}[isBoundary] = 1;`);
|
|
3797
4327
|
this.helpers.add("isBoundary");
|
|
3798
|
-
const
|
|
3799
|
-
const
|
|
3800
|
-
|
|
3801
|
-
if (nextId !== BlockDescription.nextBlockId) {
|
|
4328
|
+
const subCtx = createContext(ctx, { preventRoot: true, ctxVar });
|
|
4329
|
+
const bl = this.compileMulti({ type: 3 /* Multi */, content: ast.body }, subCtx);
|
|
4330
|
+
if (bl) {
|
|
3802
4331
|
this.helpers.add("zero");
|
|
3803
|
-
this.addLine(
|
|
4332
|
+
this.addLine(`${ctxVar}[zero] = ${bl};`);
|
|
3804
4333
|
}
|
|
3805
4334
|
}
|
|
3806
4335
|
const isDynamic = INTERP_REGEXP.test(ast.name);
|
|
@@ -3812,28 +4341,30 @@ class CodeGenerator {
|
|
|
3812
4341
|
}
|
|
3813
4342
|
const key = `key + \`${this.generateComponentKey()}\``;
|
|
3814
4343
|
if (isDynamic) {
|
|
3815
|
-
const templateVar =
|
|
4344
|
+
const templateVar = generateId("template");
|
|
4345
|
+
if (!this.staticDefs.find((d) => d.id === "call")) {
|
|
4346
|
+
this.staticDefs.push({ id: "call", expr: `app.callTemplate.bind(app)` });
|
|
4347
|
+
}
|
|
3816
4348
|
this.define(templateVar, subTemplate);
|
|
3817
4349
|
block = this.createBlock(block, "multi", ctx);
|
|
3818
|
-
this.
|
|
3819
|
-
this.insertBlock(`call(this, ${templateVar}, ctx, node, ${key})`, block, {
|
|
4350
|
+
this.insertBlock(`call(this, ${templateVar}, ${ctxVar}, node, ${key})`, block, {
|
|
3820
4351
|
...ctx,
|
|
3821
4352
|
forceNewBlock: !block,
|
|
3822
4353
|
});
|
|
3823
4354
|
}
|
|
3824
4355
|
else {
|
|
3825
|
-
const id =
|
|
3826
|
-
this.
|
|
3827
|
-
this.staticDefs.push({ id, expr: `getTemplate(${subTemplate})` });
|
|
4356
|
+
const id = generateId(`callTemplate_`);
|
|
4357
|
+
this.staticDefs.push({ id, expr: `app.getTemplate(${subTemplate})` });
|
|
3828
4358
|
block = this.createBlock(block, "multi", ctx);
|
|
3829
|
-
this.insertBlock(`${id}.call(this,
|
|
4359
|
+
this.insertBlock(`${id}.call(this, ${ctxVar}, node, ${key})`, block, {
|
|
3830
4360
|
...ctx,
|
|
3831
4361
|
forceNewBlock: !block,
|
|
3832
4362
|
});
|
|
3833
4363
|
}
|
|
3834
4364
|
if (ast.body && !ctx.isLast) {
|
|
3835
|
-
this.addLine(
|
|
4365
|
+
this.addLine(`${ctxVar} = ${ctxVar}.__proto__;`);
|
|
3836
4366
|
}
|
|
4367
|
+
return block.varName;
|
|
3837
4368
|
}
|
|
3838
4369
|
compileTCallBlock(ast, ctx) {
|
|
3839
4370
|
let { block, forceNewBlock } = ctx;
|
|
@@ -3844,6 +4375,7 @@ class CodeGenerator {
|
|
|
3844
4375
|
}
|
|
3845
4376
|
block = this.createBlock(block, "multi", ctx);
|
|
3846
4377
|
this.insertBlock(compileExpr(ast.name), block, { ...ctx, forceNewBlock: !block });
|
|
4378
|
+
return block.varName;
|
|
3847
4379
|
}
|
|
3848
4380
|
compileTSet(ast, ctx) {
|
|
3849
4381
|
this.target.shouldProtectScope = true;
|
|
@@ -3853,7 +4385,8 @@ class CodeGenerator {
|
|
|
3853
4385
|
this.helpers.add("LazyValue");
|
|
3854
4386
|
const bodyAst = { type: 3 /* Multi */, content: ast.body };
|
|
3855
4387
|
const name = this.compileInNewTarget("value", bodyAst, ctx);
|
|
3856
|
-
let
|
|
4388
|
+
let key = this.target.currentKey(ctx);
|
|
4389
|
+
let value = `new LazyValue(${name}, ctx, this, node, ${key})`;
|
|
3857
4390
|
value = ast.value ? (value ? `withDefault(${expr}, ${value})` : expr) : value;
|
|
3858
4391
|
this.addLine(`ctx[\`${ast.name}\`] = ${value};`);
|
|
3859
4392
|
}
|
|
@@ -3871,11 +4404,12 @@ class CodeGenerator {
|
|
|
3871
4404
|
value = expr;
|
|
3872
4405
|
}
|
|
3873
4406
|
this.helpers.add("setContextValue");
|
|
3874
|
-
this.addLine(`setContextValue(ctx, "${ast.name}", ${value});`);
|
|
4407
|
+
this.addLine(`setContextValue(${ctx.ctxVar || "ctx"}, "${ast.name}", ${value});`);
|
|
3875
4408
|
}
|
|
4409
|
+
return null;
|
|
3876
4410
|
}
|
|
3877
4411
|
generateComponentKey() {
|
|
3878
|
-
const parts = [
|
|
4412
|
+
const parts = [generateId("__")];
|
|
3879
4413
|
for (let i = 0; i < this.target.loopLevel; i++) {
|
|
3880
4414
|
parts.push(`\${key${i + 1}}`);
|
|
3881
4415
|
}
|
|
@@ -3902,48 +4436,50 @@ class CodeGenerator {
|
|
|
3902
4436
|
value = `bind(ctx, ${value || undefined})`;
|
|
3903
4437
|
}
|
|
3904
4438
|
else {
|
|
3905
|
-
throw new
|
|
4439
|
+
throw new OwlError("Invalid prop suffix");
|
|
3906
4440
|
}
|
|
3907
4441
|
}
|
|
3908
4442
|
name = /^[a-z_]+$/i.test(name) ? name : `'${name}'`;
|
|
3909
4443
|
return `${name}: ${value || undefined}`;
|
|
3910
4444
|
}
|
|
3911
4445
|
formatPropObject(obj) {
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
4446
|
+
return Object.entries(obj).map(([k, v]) => this.formatProp(k, v));
|
|
4447
|
+
}
|
|
4448
|
+
getPropString(props, dynProps) {
|
|
4449
|
+
let propString = `{${props.join(",")}}`;
|
|
4450
|
+
if (dynProps) {
|
|
4451
|
+
propString = `Object.assign({}, ${compileExpr(dynProps)}${props.length ? ", " + propString : ""})`;
|
|
3915
4452
|
}
|
|
3916
|
-
return
|
|
4453
|
+
return propString;
|
|
3917
4454
|
}
|
|
3918
4455
|
compileComponent(ast, ctx) {
|
|
3919
4456
|
let { block } = ctx;
|
|
3920
4457
|
// props
|
|
3921
4458
|
const hasSlotsProp = "slots" in (ast.props || {});
|
|
3922
|
-
const props = [];
|
|
3923
|
-
const propExpr = this.formatPropObject(ast.props || {});
|
|
3924
|
-
if (propExpr) {
|
|
3925
|
-
props.push(propExpr);
|
|
3926
|
-
}
|
|
4459
|
+
const props = ast.props ? this.formatPropObject(ast.props) : [];
|
|
3927
4460
|
// slots
|
|
3928
4461
|
let slotDef = "";
|
|
3929
4462
|
if (ast.slots) {
|
|
3930
4463
|
let ctxStr = "ctx";
|
|
3931
4464
|
if (this.target.loopLevel || !this.hasSafeContext) {
|
|
3932
|
-
ctxStr =
|
|
4465
|
+
ctxStr = generateId("ctx");
|
|
3933
4466
|
this.helpers.add("capture");
|
|
3934
4467
|
this.define(ctxStr, `capture(ctx)`);
|
|
3935
4468
|
}
|
|
3936
4469
|
let slotStr = [];
|
|
3937
4470
|
for (let slotName in ast.slots) {
|
|
3938
4471
|
const slotAst = ast.slots[slotName];
|
|
3939
|
-
const
|
|
3940
|
-
|
|
4472
|
+
const params = [];
|
|
4473
|
+
if (slotAst.content) {
|
|
4474
|
+
const name = this.compileInNewTarget("slot", slotAst.content, ctx, slotAst.on);
|
|
4475
|
+
params.push(`__render: ${name}, __ctx: ${ctxStr}`);
|
|
4476
|
+
}
|
|
3941
4477
|
const scope = ast.slots[slotName].scope;
|
|
3942
4478
|
if (scope) {
|
|
3943
4479
|
params.push(`__scope: "${scope}"`);
|
|
3944
4480
|
}
|
|
3945
4481
|
if (ast.slots[slotName].attrs) {
|
|
3946
|
-
params.push(this.formatPropObject(ast.slots[slotName].attrs));
|
|
4482
|
+
params.push(...this.formatPropObject(ast.slots[slotName].attrs));
|
|
3947
4483
|
}
|
|
3948
4484
|
const slotInfo = `{${params.join(", ")}}`;
|
|
3949
4485
|
slotStr.push(`'${slotName}': ${slotInfo}`);
|
|
@@ -3954,14 +4490,10 @@ class CodeGenerator {
|
|
|
3954
4490
|
this.helpers.add("markRaw");
|
|
3955
4491
|
props.push(`slots: markRaw(${slotDef})`);
|
|
3956
4492
|
}
|
|
3957
|
-
|
|
3958
|
-
let propString = propStr;
|
|
3959
|
-
if (ast.dynamicProps) {
|
|
3960
|
-
propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)}${props.length ? ", " + propStr : ""})`;
|
|
3961
|
-
}
|
|
4493
|
+
let propString = this.getPropString(props, ast.dynamicProps);
|
|
3962
4494
|
let propVar;
|
|
3963
4495
|
if ((slotDef && (ast.dynamicProps || hasSlotsProp)) || this.dev) {
|
|
3964
|
-
propVar =
|
|
4496
|
+
propVar = generateId("props");
|
|
3965
4497
|
this.define(propVar, propString);
|
|
3966
4498
|
propString = propVar;
|
|
3967
4499
|
}
|
|
@@ -3973,14 +4505,14 @@ class CodeGenerator {
|
|
|
3973
4505
|
const key = this.generateComponentKey();
|
|
3974
4506
|
let expr;
|
|
3975
4507
|
if (ast.isDynamic) {
|
|
3976
|
-
expr =
|
|
4508
|
+
expr = generateId("Comp");
|
|
3977
4509
|
this.define(expr, compileExpr(ast.name));
|
|
3978
4510
|
}
|
|
3979
4511
|
else {
|
|
3980
4512
|
expr = `\`${ast.name}\``;
|
|
3981
4513
|
}
|
|
3982
4514
|
if (this.dev) {
|
|
3983
|
-
this.addLine(`helpers.validateProps(${expr}, ${propVar},
|
|
4515
|
+
this.addLine(`helpers.validateProps(${expr}, ${propVar}, this);`);
|
|
3984
4516
|
}
|
|
3985
4517
|
if (block && (ctx.forceNewBlock === false || ctx.tKeyExpr)) {
|
|
3986
4518
|
// todo: check the forcenewblock condition
|
|
@@ -3990,8 +4522,12 @@ class CodeGenerator {
|
|
|
3990
4522
|
if (ctx.tKeyExpr) {
|
|
3991
4523
|
keyArg = `${ctx.tKeyExpr} + ${keyArg}`;
|
|
3992
4524
|
}
|
|
3993
|
-
|
|
3994
|
-
|
|
4525
|
+
let id = generateId("comp");
|
|
4526
|
+
this.staticDefs.push({
|
|
4527
|
+
id,
|
|
4528
|
+
expr: `app.createComponent(${ast.isDynamic ? null : expr}, ${!ast.isDynamic}, ${!!ast.slots}, ${!!ast.dynamicProps}, ${!ast.props && !ast.dynamicProps})`,
|
|
4529
|
+
});
|
|
4530
|
+
let blockExpr = `${id}(${propString}, ${keyArg}, node, this, ${ast.isDynamic ? expr : null})`;
|
|
3995
4531
|
if (ast.isDynamic) {
|
|
3996
4532
|
blockExpr = `toggler(${expr}, ${blockExpr})`;
|
|
3997
4533
|
}
|
|
@@ -4001,14 +4537,15 @@ class CodeGenerator {
|
|
|
4001
4537
|
}
|
|
4002
4538
|
block = this.createBlock(block, "multi", ctx);
|
|
4003
4539
|
this.insertBlock(blockExpr, block, ctx);
|
|
4540
|
+
return block.varName;
|
|
4004
4541
|
}
|
|
4005
4542
|
wrapWithEventCatcher(expr, on) {
|
|
4006
4543
|
this.helpers.add("createCatcher");
|
|
4007
|
-
let name =
|
|
4544
|
+
let name = generateId("catcher");
|
|
4008
4545
|
let spec = {};
|
|
4009
4546
|
let handlers = [];
|
|
4010
4547
|
for (let ev in on) {
|
|
4011
|
-
let handlerId =
|
|
4548
|
+
let handlerId = generateId("hdlr");
|
|
4012
4549
|
let idx = handlers.push(handlerId) - 1;
|
|
4013
4550
|
spec[ev] = idx;
|
|
4014
4551
|
const handler = this.generateHandlerCode(ev, on[ev]);
|
|
@@ -4023,26 +4560,39 @@ class CodeGenerator {
|
|
|
4023
4560
|
let blockString;
|
|
4024
4561
|
let slotName;
|
|
4025
4562
|
let dynamic = false;
|
|
4563
|
+
let isMultiple = false;
|
|
4026
4564
|
if (ast.name.match(INTERP_REGEXP)) {
|
|
4027
4565
|
dynamic = true;
|
|
4566
|
+
isMultiple = true;
|
|
4028
4567
|
slotName = interpolate(ast.name);
|
|
4029
4568
|
}
|
|
4030
4569
|
else {
|
|
4031
4570
|
slotName = "'" + ast.name + "'";
|
|
4571
|
+
isMultiple = isMultiple || this.slotNames.has(ast.name);
|
|
4572
|
+
this.slotNames.add(ast.name);
|
|
4032
4573
|
}
|
|
4033
|
-
const
|
|
4574
|
+
const dynProps = ast.attrs ? ast.attrs["t-props"] : null;
|
|
4575
|
+
if (ast.attrs) {
|
|
4576
|
+
delete ast.attrs["t-props"];
|
|
4577
|
+
}
|
|
4578
|
+
let key = this.target.loopLevel ? `key${this.target.loopLevel}` : "key";
|
|
4579
|
+
if (isMultiple) {
|
|
4580
|
+
key = `${key} + \`${this.generateComponentKey()}\``;
|
|
4581
|
+
}
|
|
4582
|
+
const props = ast.attrs ? this.formatPropObject(ast.attrs) : [];
|
|
4583
|
+
const scope = this.getPropString(props, dynProps);
|
|
4034
4584
|
if (ast.defaultContent) {
|
|
4035
4585
|
const name = this.compileInNewTarget("defaultContent", ast.defaultContent, ctx);
|
|
4036
|
-
blockString = `callSlot(ctx, node, key, ${slotName}, ${dynamic}, ${scope}, ${name})`;
|
|
4586
|
+
blockString = `callSlot(ctx, node, ${key}, ${slotName}, ${dynamic}, ${scope}, ${name})`;
|
|
4037
4587
|
}
|
|
4038
4588
|
else {
|
|
4039
4589
|
if (dynamic) {
|
|
4040
|
-
let name =
|
|
4590
|
+
let name = generateId("slot");
|
|
4041
4591
|
this.define(name, slotName);
|
|
4042
|
-
blockString = `toggler(${name}, callSlot(ctx, node, key, ${name}
|
|
4592
|
+
blockString = `toggler(${name}, callSlot(ctx, node, ${key}, ${name}, ${dynamic}, ${scope}))`;
|
|
4043
4593
|
}
|
|
4044
4594
|
else {
|
|
4045
|
-
blockString = `callSlot(ctx, node, key, ${slotName}, ${dynamic}, ${scope})`;
|
|
4595
|
+
blockString = `callSlot(ctx, node, ${key}, ${slotName}, ${dynamic}, ${scope})`;
|
|
4046
4596
|
}
|
|
4047
4597
|
}
|
|
4048
4598
|
// event handling
|
|
@@ -4054,42 +4604,50 @@ class CodeGenerator {
|
|
|
4054
4604
|
}
|
|
4055
4605
|
block = this.createBlock(block, "multi", ctx);
|
|
4056
4606
|
this.insertBlock(blockString, block, { ...ctx, forceNewBlock: false });
|
|
4607
|
+
return block.varName;
|
|
4057
4608
|
}
|
|
4058
4609
|
compileTTranslation(ast, ctx) {
|
|
4059
4610
|
if (ast.content) {
|
|
4060
|
-
this.compileAST(ast.content, Object.assign({}, ctx, { translate: false }));
|
|
4611
|
+
return this.compileAST(ast.content, Object.assign({}, ctx, { translate: false }));
|
|
4061
4612
|
}
|
|
4613
|
+
return null;
|
|
4062
4614
|
}
|
|
4063
4615
|
compileTPortal(ast, ctx) {
|
|
4064
|
-
this.
|
|
4616
|
+
if (!this.staticDefs.find((d) => d.id === "Portal")) {
|
|
4617
|
+
this.staticDefs.push({ id: "Portal", expr: `app.Portal` });
|
|
4618
|
+
}
|
|
4065
4619
|
let { block } = ctx;
|
|
4066
4620
|
const name = this.compileInNewTarget("slot", ast.content, ctx);
|
|
4067
4621
|
const key = this.generateComponentKey();
|
|
4068
4622
|
let ctxStr = "ctx";
|
|
4069
4623
|
if (this.target.loopLevel || !this.hasSafeContext) {
|
|
4070
|
-
ctxStr =
|
|
4624
|
+
ctxStr = generateId("ctx");
|
|
4071
4625
|
this.helpers.add("capture");
|
|
4072
|
-
this.define(ctxStr, `capture(ctx)
|
|
4626
|
+
this.define(ctxStr, `capture(ctx)`);
|
|
4073
4627
|
}
|
|
4074
|
-
|
|
4628
|
+
let id = generateId("comp");
|
|
4629
|
+
this.staticDefs.push({
|
|
4630
|
+
id,
|
|
4631
|
+
expr: `app.createComponent(null, false, true, false, false)`,
|
|
4632
|
+
});
|
|
4633
|
+
const target = compileExpr(ast.target);
|
|
4634
|
+
const blockString = `${id}({target: ${target},slots: {'default': {__render: ${name}, __ctx: ${ctxStr}}}}, key + \`${key}\`, node, ctx, Portal)`;
|
|
4075
4635
|
if (block) {
|
|
4076
4636
|
this.insertAnchor(block);
|
|
4077
4637
|
}
|
|
4078
4638
|
block = this.createBlock(block, "multi", ctx);
|
|
4079
4639
|
this.insertBlock(blockString, block, { ...ctx, forceNewBlock: false });
|
|
4640
|
+
return block.varName;
|
|
4080
4641
|
}
|
|
4081
4642
|
}
|
|
4082
4643
|
|
|
4083
|
-
// -----------------------------------------------------------------------------
|
|
4084
|
-
// AST Type definition
|
|
4085
|
-
// -----------------------------------------------------------------------------
|
|
4086
4644
|
// -----------------------------------------------------------------------------
|
|
4087
4645
|
// Parser
|
|
4088
4646
|
// -----------------------------------------------------------------------------
|
|
4089
4647
|
const cache = new WeakMap();
|
|
4090
4648
|
function parse(xml) {
|
|
4091
4649
|
if (typeof xml === "string") {
|
|
4092
|
-
const elem = parseXML
|
|
4650
|
+
const elem = parseXML(`<t>${xml}</t>`).firstChild;
|
|
4093
4651
|
return _parse(elem);
|
|
4094
4652
|
}
|
|
4095
4653
|
let ast = cache.get(xml);
|
|
@@ -4191,7 +4749,7 @@ function parseDOMNode(node, ctx) {
|
|
|
4191
4749
|
return null;
|
|
4192
4750
|
}
|
|
4193
4751
|
if (tagName.startsWith("block-")) {
|
|
4194
|
-
throw new
|
|
4752
|
+
throw new OwlError(`Invalid tag name: '${tagName}'`);
|
|
4195
4753
|
}
|
|
4196
4754
|
ctx = Object.assign({}, ctx);
|
|
4197
4755
|
if (tagName === "pre") {
|
|
@@ -4210,14 +4768,14 @@ function parseDOMNode(node, ctx) {
|
|
|
4210
4768
|
const value = node.getAttribute(attr);
|
|
4211
4769
|
if (attr.startsWith("t-on")) {
|
|
4212
4770
|
if (attr === "t-on") {
|
|
4213
|
-
throw new
|
|
4771
|
+
throw new OwlError("Missing event name with t-on directive");
|
|
4214
4772
|
}
|
|
4215
4773
|
on = on || {};
|
|
4216
4774
|
on[attr.slice(5)] = value;
|
|
4217
4775
|
}
|
|
4218
4776
|
else if (attr.startsWith("t-model")) {
|
|
4219
4777
|
if (!["input", "select", "textarea"].includes(tagName)) {
|
|
4220
|
-
throw new
|
|
4778
|
+
throw new OwlError("The t-model directive only works with <input>, <textarea> and <select>");
|
|
4221
4779
|
}
|
|
4222
4780
|
let baseExpr, expr;
|
|
4223
4781
|
if (hasDotAtTheEnd.test(value)) {
|
|
@@ -4231,7 +4789,7 @@ function parseDOMNode(node, ctx) {
|
|
|
4231
4789
|
expr = value.slice(index + 1, -1);
|
|
4232
4790
|
}
|
|
4233
4791
|
else {
|
|
4234
|
-
throw new
|
|
4792
|
+
throw new OwlError(`Invalid t-model expression: "${value}" (it should be assignable)`);
|
|
4235
4793
|
}
|
|
4236
4794
|
const typeAttr = node.getAttribute("type");
|
|
4237
4795
|
const isInput = tagName === "input";
|
|
@@ -4261,11 +4819,11 @@ function parseDOMNode(node, ctx) {
|
|
|
4261
4819
|
}
|
|
4262
4820
|
}
|
|
4263
4821
|
else if (attr.startsWith("block-")) {
|
|
4264
|
-
throw new
|
|
4822
|
+
throw new OwlError(`Invalid attribute: '${attr}'`);
|
|
4265
4823
|
}
|
|
4266
4824
|
else if (attr !== "t-name") {
|
|
4267
4825
|
if (attr.startsWith("t-") && !attr.startsWith("t-att")) {
|
|
4268
|
-
throw new
|
|
4826
|
+
throw new OwlError(`Unknown QWeb directive: '${attr}'`);
|
|
4269
4827
|
}
|
|
4270
4828
|
const tModel = ctx.tModelInfo;
|
|
4271
4829
|
if (tModel && ["t-att-value", "t-attf-value"].includes(attr)) {
|
|
@@ -4316,7 +4874,7 @@ function parseTEscNode(node, ctx) {
|
|
|
4316
4874
|
};
|
|
4317
4875
|
}
|
|
4318
4876
|
if (ast.type === 11 /* TComponent */) {
|
|
4319
|
-
throw new
|
|
4877
|
+
throw new OwlError("t-esc is not supported on Component nodes");
|
|
4320
4878
|
}
|
|
4321
4879
|
return tesc;
|
|
4322
4880
|
}
|
|
@@ -4364,7 +4922,7 @@ function parseTForEach(node, ctx) {
|
|
|
4364
4922
|
node.removeAttribute("t-as");
|
|
4365
4923
|
const key = node.getAttribute("t-key");
|
|
4366
4924
|
if (!key) {
|
|
4367
|
-
throw new
|
|
4925
|
+
throw new OwlError(`"Directive t-foreach should always be used with a t-key!" (expression: t-foreach="${collection}" t-as="${elem}")`);
|
|
4368
4926
|
}
|
|
4369
4927
|
node.removeAttribute("t-key");
|
|
4370
4928
|
const memo = node.getAttribute("t-memo") || "";
|
|
@@ -4411,10 +4969,12 @@ function parseTCall(node, ctx) {
|
|
|
4411
4969
|
return null;
|
|
4412
4970
|
}
|
|
4413
4971
|
const subTemplate = node.getAttribute("t-call");
|
|
4972
|
+
const context = node.getAttribute("t-call-context");
|
|
4414
4973
|
node.removeAttribute("t-call");
|
|
4974
|
+
node.removeAttribute("t-call-context");
|
|
4415
4975
|
if (node.tagName !== "t") {
|
|
4416
4976
|
const ast = parseNode(node, ctx);
|
|
4417
|
-
const tcall = { type: 7 /* TCall */, name: subTemplate, body: null };
|
|
4977
|
+
const tcall = { type: 7 /* TCall */, name: subTemplate, body: null, context };
|
|
4418
4978
|
if (ast && ast.type === 2 /* DomNode */) {
|
|
4419
4979
|
ast.content = [tcall];
|
|
4420
4980
|
return ast;
|
|
@@ -4431,6 +4991,7 @@ function parseTCall(node, ctx) {
|
|
|
4431
4991
|
type: 7 /* TCall */,
|
|
4432
4992
|
name: subTemplate,
|
|
4433
4993
|
body: body.length ? body : null,
|
|
4994
|
+
context,
|
|
4434
4995
|
};
|
|
4435
4996
|
}
|
|
4436
4997
|
// -----------------------------------------------------------------------------
|
|
@@ -4519,662 +5080,274 @@ const directiveErrorMap = new Map([
|
|
|
4519
5080
|
function parseComponent(node, ctx) {
|
|
4520
5081
|
let name = node.tagName;
|
|
4521
5082
|
const firstLetter = name[0];
|
|
4522
|
-
let isDynamic = node.hasAttribute("t-component");
|
|
4523
|
-
if (isDynamic && name !== "t") {
|
|
4524
|
-
throw new
|
|
4525
|
-
}
|
|
4526
|
-
if (!(firstLetter === firstLetter.toUpperCase() || isDynamic)) {
|
|
4527
|
-
return null;
|
|
4528
|
-
}
|
|
4529
|
-
if (isDynamic) {
|
|
4530
|
-
name = node.getAttribute("t-component");
|
|
4531
|
-
node.removeAttribute("t-component");
|
|
4532
|
-
}
|
|
4533
|
-
const dynamicProps = node.getAttribute("t-props");
|
|
4534
|
-
node.removeAttribute("t-props");
|
|
4535
|
-
const defaultSlotScope = node.getAttribute("t-slot-scope");
|
|
4536
|
-
node.removeAttribute("t-slot-scope");
|
|
4537
|
-
let on = null;
|
|
4538
|
-
let props = null;
|
|
4539
|
-
for (let name of node.getAttributeNames()) {
|
|
4540
|
-
const value = node.getAttribute(name);
|
|
4541
|
-
if (name.startsWith("t-")) {
|
|
4542
|
-
if (name.startsWith("t-on-")) {
|
|
4543
|
-
on = on || {};
|
|
4544
|
-
on[name.slice(5)] = value;
|
|
4545
|
-
}
|
|
4546
|
-
else {
|
|
4547
|
-
const message = directiveErrorMap.get(name.split("-").slice(0, 2).join("-"));
|
|
4548
|
-
throw new Error(message || `unsupported directive on Component: ${name}`);
|
|
4549
|
-
}
|
|
4550
|
-
}
|
|
4551
|
-
else {
|
|
4552
|
-
props = props || {};
|
|
4553
|
-
props[name] = value;
|
|
4554
|
-
}
|
|
4555
|
-
}
|
|
4556
|
-
let slots = null;
|
|
4557
|
-
if (node.hasChildNodes()) {
|
|
4558
|
-
const clone = node.cloneNode(true);
|
|
4559
|
-
// named slots
|
|
4560
|
-
const slotNodes = Array.from(clone.querySelectorAll("[t-set-slot]"));
|
|
4561
|
-
for (let slotNode of slotNodes) {
|
|
4562
|
-
if (slotNode.tagName !== "t") {
|
|
4563
|
-
throw new Error(`Directive 't-set-slot' can only be used on <t> nodes (used on a <${slotNode.tagName}>)`);
|
|
4564
|
-
}
|
|
4565
|
-
const name = slotNode.getAttribute("t-set-slot");
|
|
4566
|
-
// check if this is defined in a sub component (in which case it should
|
|
4567
|
-
// be ignored)
|
|
4568
|
-
let el = slotNode.parentElement;
|
|
4569
|
-
let isInSubComponent = false;
|
|
4570
|
-
while (el !== clone) {
|
|
4571
|
-
if (el.hasAttribute("t-component") || el.tagName[0] === el.tagName[0].toUpperCase()) {
|
|
4572
|
-
isInSubComponent = true;
|
|
4573
|
-
break;
|
|
4574
|
-
}
|
|
4575
|
-
el = el.parentElement;
|
|
4576
|
-
}
|
|
4577
|
-
if (isInSubComponent) {
|
|
4578
|
-
continue;
|
|
4579
|
-
}
|
|
4580
|
-
slotNode.removeAttribute("t-set-slot");
|
|
4581
|
-
slotNode.remove();
|
|
4582
|
-
const slotAst = parseNode(slotNode, ctx);
|
|
4583
|
-
if (slotAst) {
|
|
4584
|
-
let on = null;
|
|
4585
|
-
let attrs = null;
|
|
4586
|
-
let scope = null;
|
|
4587
|
-
for (let attributeName of slotNode.getAttributeNames()) {
|
|
4588
|
-
const value = slotNode.getAttribute(attributeName);
|
|
4589
|
-
if (attributeName === "t-slot-scope") {
|
|
4590
|
-
scope = value;
|
|
4591
|
-
continue;
|
|
4592
|
-
}
|
|
4593
|
-
else if (attributeName.startsWith("t-on-")) {
|
|
4594
|
-
on = on || {};
|
|
4595
|
-
on[attributeName.slice(5)] = value;
|
|
4596
|
-
}
|
|
4597
|
-
else {
|
|
4598
|
-
attrs = attrs || {};
|
|
4599
|
-
attrs[attributeName] = value;
|
|
4600
|
-
}
|
|
4601
|
-
}
|
|
4602
|
-
slots = slots || {};
|
|
4603
|
-
slots[name] = { content: slotAst, on, attrs, scope };
|
|
4604
|
-
}
|
|
4605
|
-
}
|
|
4606
|
-
// default slot
|
|
4607
|
-
const defaultContent = parseChildNodes(clone, ctx);
|
|
4608
|
-
if (defaultContent) {
|
|
4609
|
-
slots = slots || {};
|
|
4610
|
-
slots.default = { content: defaultContent, on, attrs: null, scope: defaultSlotScope };
|
|
4611
|
-
}
|
|
4612
|
-
}
|
|
4613
|
-
return { type: 11 /* TComponent */, name, isDynamic, dynamicProps, props, slots, on };
|
|
4614
|
-
}
|
|
4615
|
-
// -----------------------------------------------------------------------------
|
|
4616
|
-
// Slots
|
|
4617
|
-
// -----------------------------------------------------------------------------
|
|
4618
|
-
function parseTSlot(node, ctx) {
|
|
4619
|
-
if (!node.hasAttribute("t-slot")) {
|
|
4620
|
-
return null;
|
|
4621
|
-
}
|
|
4622
|
-
const name = node.getAttribute("t-slot");
|
|
4623
|
-
node.removeAttribute("t-slot");
|
|
4624
|
-
let attrs = null;
|
|
4625
|
-
let on = null;
|
|
4626
|
-
for (let attributeName of node.getAttributeNames()) {
|
|
4627
|
-
const value = node.getAttribute(attributeName);
|
|
4628
|
-
if (attributeName.startsWith("t-on-")) {
|
|
4629
|
-
on = on || {};
|
|
4630
|
-
on[attributeName.slice(5)] = value;
|
|
4631
|
-
}
|
|
4632
|
-
else {
|
|
4633
|
-
attrs = attrs || {};
|
|
4634
|
-
attrs[attributeName] = value;
|
|
4635
|
-
}
|
|
4636
|
-
}
|
|
4637
|
-
return {
|
|
4638
|
-
type: 14 /* TSlot */,
|
|
4639
|
-
name,
|
|
4640
|
-
attrs,
|
|
4641
|
-
on,
|
|
4642
|
-
defaultContent: parseChildNodes(node, ctx),
|
|
4643
|
-
};
|
|
4644
|
-
}
|
|
4645
|
-
function parseTTranslation(node, ctx) {
|
|
4646
|
-
if (node.getAttribute("t-translation") !== "off") {
|
|
4647
|
-
return null;
|
|
4648
|
-
}
|
|
4649
|
-
node.removeAttribute("t-translation");
|
|
4650
|
-
return {
|
|
4651
|
-
type: 16 /* TTranslation */,
|
|
4652
|
-
content: parseNode(node, ctx),
|
|
4653
|
-
};
|
|
4654
|
-
}
|
|
4655
|
-
// -----------------------------------------------------------------------------
|
|
4656
|
-
// Portal
|
|
4657
|
-
// -----------------------------------------------------------------------------
|
|
4658
|
-
function parseTPortal(node, ctx) {
|
|
4659
|
-
if (!node.hasAttribute("t-portal")) {
|
|
4660
|
-
return null;
|
|
4661
|
-
}
|
|
4662
|
-
const target = node.getAttribute("t-portal");
|
|
4663
|
-
node.removeAttribute("t-portal");
|
|
4664
|
-
const content = parseNode(node, ctx);
|
|
4665
|
-
if (!content) {
|
|
4666
|
-
return {
|
|
4667
|
-
type: 0 /* Text */,
|
|
4668
|
-
value: "",
|
|
4669
|
-
};
|
|
4670
|
-
}
|
|
4671
|
-
return {
|
|
4672
|
-
type: 17 /* TPortal */,
|
|
4673
|
-
target,
|
|
4674
|
-
content,
|
|
4675
|
-
};
|
|
4676
|
-
}
|
|
4677
|
-
// -----------------------------------------------------------------------------
|
|
4678
|
-
// helpers
|
|
4679
|
-
// -----------------------------------------------------------------------------
|
|
4680
|
-
/**
|
|
4681
|
-
* Parse all the child nodes of a given node and return a list of ast elements
|
|
4682
|
-
*/
|
|
4683
|
-
function parseChildren(node, ctx) {
|
|
4684
|
-
const children = [];
|
|
4685
|
-
for (let child of node.childNodes) {
|
|
4686
|
-
const childAst = parseNode(child, ctx);
|
|
4687
|
-
if (childAst) {
|
|
4688
|
-
if (childAst.type === 3 /* Multi */) {
|
|
4689
|
-
children.push(...childAst.content);
|
|
4690
|
-
}
|
|
4691
|
-
else {
|
|
4692
|
-
children.push(childAst);
|
|
4693
|
-
}
|
|
4694
|
-
}
|
|
4695
|
-
}
|
|
4696
|
-
return children;
|
|
4697
|
-
}
|
|
4698
|
-
/**
|
|
4699
|
-
* Parse all the child nodes of a given node and return an ast if possible.
|
|
4700
|
-
* In the case there are multiple children, they are wrapped in a astmulti.
|
|
4701
|
-
*/
|
|
4702
|
-
function parseChildNodes(node, ctx) {
|
|
4703
|
-
const children = parseChildren(node, ctx);
|
|
4704
|
-
switch (children.length) {
|
|
4705
|
-
case 0:
|
|
4706
|
-
return null;
|
|
4707
|
-
case 1:
|
|
4708
|
-
return children[0];
|
|
4709
|
-
default:
|
|
4710
|
-
return { type: 3 /* Multi */, content: children };
|
|
4711
|
-
}
|
|
4712
|
-
}
|
|
4713
|
-
/**
|
|
4714
|
-
* Normalizes the content of an Element so that t-if/t-elif/t-else directives
|
|
4715
|
-
* immediately follow one another (by removing empty text nodes or comments).
|
|
4716
|
-
* Throws an error when a conditional branching statement is malformed. This
|
|
4717
|
-
* function modifies the Element in place.
|
|
4718
|
-
*
|
|
4719
|
-
* @param el the element containing the tree that should be normalized
|
|
4720
|
-
*/
|
|
4721
|
-
function normalizeTIf(el) {
|
|
4722
|
-
let tbranch = el.querySelectorAll("[t-elif], [t-else]");
|
|
4723
|
-
for (let i = 0, ilen = tbranch.length; i < ilen; i++) {
|
|
4724
|
-
let node = tbranch[i];
|
|
4725
|
-
let prevElem = node.previousElementSibling;
|
|
4726
|
-
let pattr = (name) => prevElem.getAttribute(name);
|
|
4727
|
-
let nattr = (name) => +!!node.getAttribute(name);
|
|
4728
|
-
if (prevElem && (pattr("t-if") || pattr("t-elif"))) {
|
|
4729
|
-
if (pattr("t-foreach")) {
|
|
4730
|
-
throw new Error("t-if cannot stay at the same level as t-foreach when using t-elif or t-else");
|
|
4731
|
-
}
|
|
4732
|
-
if (["t-if", "t-elif", "t-else"].map(nattr).reduce(function (a, b) {
|
|
4733
|
-
return a + b;
|
|
4734
|
-
}) > 1) {
|
|
4735
|
-
throw new Error("Only one conditional branching directive is allowed per node");
|
|
4736
|
-
}
|
|
4737
|
-
// All text (with only spaces) and comment nodes (nodeType 8) between
|
|
4738
|
-
// branch nodes are removed
|
|
4739
|
-
let textNode;
|
|
4740
|
-
while ((textNode = node.previousSibling) !== prevElem) {
|
|
4741
|
-
if (textNode.nodeValue.trim().length && textNode.nodeType !== 8) {
|
|
4742
|
-
throw new Error("text is not allowed between branching directives");
|
|
4743
|
-
}
|
|
4744
|
-
textNode.remove();
|
|
4745
|
-
}
|
|
4746
|
-
}
|
|
4747
|
-
else {
|
|
4748
|
-
throw new Error("t-elif and t-else directives must be preceded by a t-if or t-elif directive");
|
|
4749
|
-
}
|
|
4750
|
-
}
|
|
4751
|
-
}
|
|
4752
|
-
/**
|
|
4753
|
-
* Normalizes the content of an Element so that t-esc directives on components
|
|
4754
|
-
* are removed and instead places a <t t-esc=""> as the default slot of the
|
|
4755
|
-
* component. Also throws if the component already has content. This function
|
|
4756
|
-
* modifies the Element in place.
|
|
4757
|
-
*
|
|
4758
|
-
* @param el the element containing the tree that should be normalized
|
|
4759
|
-
*/
|
|
4760
|
-
function normalizeTEsc(el) {
|
|
4761
|
-
const elements = [...el.querySelectorAll("[t-esc]")].filter((el) => el.tagName[0] === el.tagName[0].toUpperCase() || el.hasAttribute("t-component"));
|
|
4762
|
-
for (const el of elements) {
|
|
4763
|
-
if (el.childNodes.length) {
|
|
4764
|
-
throw new Error("Cannot have t-esc on a component that already has content");
|
|
4765
|
-
}
|
|
4766
|
-
const value = el.getAttribute("t-esc");
|
|
4767
|
-
el.removeAttribute("t-esc");
|
|
4768
|
-
const t = el.ownerDocument.createElement("t");
|
|
4769
|
-
if (value != null) {
|
|
4770
|
-
t.setAttribute("t-esc", value);
|
|
4771
|
-
}
|
|
4772
|
-
el.appendChild(t);
|
|
4773
|
-
}
|
|
4774
|
-
}
|
|
4775
|
-
/**
|
|
4776
|
-
* Normalizes the tree inside a given element and do some preliminary validation
|
|
4777
|
-
* on it. This function modifies the Element in place.
|
|
4778
|
-
*
|
|
4779
|
-
* @param el the element containing the tree that should be normalized
|
|
4780
|
-
*/
|
|
4781
|
-
function normalizeXML(el) {
|
|
4782
|
-
normalizeTIf(el);
|
|
4783
|
-
normalizeTEsc(el);
|
|
4784
|
-
}
|
|
4785
|
-
/**
|
|
4786
|
-
* Parses an XML string into an XML document, throwing errors on parser errors
|
|
4787
|
-
* instead of returning an XML document containing the parseerror.
|
|
4788
|
-
*
|
|
4789
|
-
* @param xml the string to parse
|
|
4790
|
-
* @returns an XML document corresponding to the content of the string
|
|
4791
|
-
*/
|
|
4792
|
-
function parseXML$1(xml) {
|
|
4793
|
-
const parser = new DOMParser();
|
|
4794
|
-
const doc = parser.parseFromString(xml, "text/xml");
|
|
4795
|
-
if (doc.getElementsByTagName("parsererror").length) {
|
|
4796
|
-
let msg = "Invalid XML in template.";
|
|
4797
|
-
const parsererrorText = doc.getElementsByTagName("parsererror")[0].textContent;
|
|
4798
|
-
if (parsererrorText) {
|
|
4799
|
-
msg += "\nThe parser has produced the following error message:\n" + parsererrorText;
|
|
4800
|
-
const re = /\d+/g;
|
|
4801
|
-
const firstMatch = re.exec(parsererrorText);
|
|
4802
|
-
if (firstMatch) {
|
|
4803
|
-
const lineNumber = Number(firstMatch[0]);
|
|
4804
|
-
const line = xml.split("\n")[lineNumber - 1];
|
|
4805
|
-
const secondMatch = re.exec(parsererrorText);
|
|
4806
|
-
if (line && secondMatch) {
|
|
4807
|
-
const columnIndex = Number(secondMatch[0]) - 1;
|
|
4808
|
-
if (line[columnIndex]) {
|
|
4809
|
-
msg +=
|
|
4810
|
-
`\nThe error might be located at xml line ${lineNumber} column ${columnIndex}\n` +
|
|
4811
|
-
`${line}\n${"-".repeat(columnIndex - 1)}^`;
|
|
4812
|
-
}
|
|
4813
|
-
}
|
|
4814
|
-
}
|
|
4815
|
-
}
|
|
4816
|
-
throw new Error(msg);
|
|
4817
|
-
}
|
|
4818
|
-
return doc;
|
|
4819
|
-
}
|
|
4820
|
-
|
|
4821
|
-
function compile(template, options = {}) {
|
|
4822
|
-
// parsing
|
|
4823
|
-
const ast = parse(template);
|
|
4824
|
-
// some work
|
|
4825
|
-
const hasSafeContext = template instanceof Node
|
|
4826
|
-
? !(template instanceof Element) || template.querySelector("[t-set], [t-call]") === null
|
|
4827
|
-
: !template.includes("t-set") && !template.includes("t-call");
|
|
4828
|
-
// code generation
|
|
4829
|
-
const codeGenerator = new CodeGenerator(ast, { ...options, hasSafeContext });
|
|
4830
|
-
const code = codeGenerator.generateCode();
|
|
4831
|
-
// template function
|
|
4832
|
-
return new Function("bdom, helpers", code);
|
|
4833
|
-
}
|
|
4834
|
-
|
|
4835
|
-
function wrapError(fn, hookName) {
|
|
4836
|
-
const error = new Error(`The following error occurred in ${hookName}: `);
|
|
4837
|
-
return (...args) => {
|
|
4838
|
-
try {
|
|
4839
|
-
const result = fn(...args);
|
|
4840
|
-
if (result instanceof Promise) {
|
|
4841
|
-
return result.catch((cause) => {
|
|
4842
|
-
error.cause = cause;
|
|
4843
|
-
if (cause instanceof Error) {
|
|
4844
|
-
error.message += `"${cause.message}"`;
|
|
4845
|
-
}
|
|
4846
|
-
throw error;
|
|
4847
|
-
});
|
|
4848
|
-
}
|
|
4849
|
-
return result;
|
|
4850
|
-
}
|
|
4851
|
-
catch (cause) {
|
|
4852
|
-
if (cause instanceof Error) {
|
|
4853
|
-
error.message += `"${cause.message}"`;
|
|
4854
|
-
}
|
|
4855
|
-
throw error;
|
|
4856
|
-
}
|
|
4857
|
-
};
|
|
4858
|
-
}
|
|
4859
|
-
// -----------------------------------------------------------------------------
|
|
4860
|
-
// hooks
|
|
4861
|
-
// -----------------------------------------------------------------------------
|
|
4862
|
-
function onWillStart(fn) {
|
|
4863
|
-
const node = getCurrent();
|
|
4864
|
-
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4865
|
-
node.willStart.push(decorate(fn.bind(node.component), "onWillStart"));
|
|
4866
|
-
}
|
|
4867
|
-
function onWillUpdateProps(fn) {
|
|
4868
|
-
const node = getCurrent();
|
|
4869
|
-
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4870
|
-
node.willUpdateProps.push(decorate(fn.bind(node.component), "onWillUpdateProps"));
|
|
4871
|
-
}
|
|
4872
|
-
function onMounted(fn) {
|
|
4873
|
-
const node = getCurrent();
|
|
4874
|
-
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4875
|
-
node.mounted.push(decorate(fn.bind(node.component), "onMounted"));
|
|
4876
|
-
}
|
|
4877
|
-
function onWillPatch(fn) {
|
|
4878
|
-
const node = getCurrent();
|
|
4879
|
-
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4880
|
-
node.willPatch.unshift(decorate(fn.bind(node.component), "onWillPatch"));
|
|
4881
|
-
}
|
|
4882
|
-
function onPatched(fn) {
|
|
4883
|
-
const node = getCurrent();
|
|
4884
|
-
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4885
|
-
node.patched.push(decorate(fn.bind(node.component), "onPatched"));
|
|
4886
|
-
}
|
|
4887
|
-
function onWillUnmount(fn) {
|
|
4888
|
-
const node = getCurrent();
|
|
4889
|
-
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4890
|
-
node.willUnmount.unshift(decorate(fn.bind(node.component), "onWillUnmount"));
|
|
4891
|
-
}
|
|
4892
|
-
function onWillDestroy(fn) {
|
|
4893
|
-
const node = getCurrent();
|
|
4894
|
-
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4895
|
-
node.willDestroy.push(decorate(fn.bind(node.component), "onWillDestroy"));
|
|
4896
|
-
}
|
|
4897
|
-
function onWillRender(fn) {
|
|
4898
|
-
const node = getCurrent();
|
|
4899
|
-
const renderFn = node.renderFn;
|
|
4900
|
-
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4901
|
-
fn = decorate(fn.bind(node.component), "onWillRender");
|
|
4902
|
-
node.renderFn = () => {
|
|
4903
|
-
fn();
|
|
4904
|
-
return renderFn();
|
|
4905
|
-
};
|
|
4906
|
-
}
|
|
4907
|
-
function onRendered(fn) {
|
|
4908
|
-
const node = getCurrent();
|
|
4909
|
-
const renderFn = node.renderFn;
|
|
4910
|
-
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4911
|
-
fn = decorate(fn.bind(node.component), "onRendered");
|
|
4912
|
-
node.renderFn = () => {
|
|
4913
|
-
const result = renderFn();
|
|
4914
|
-
fn();
|
|
4915
|
-
return result;
|
|
4916
|
-
};
|
|
4917
|
-
}
|
|
4918
|
-
function onError(callback) {
|
|
4919
|
-
const node = getCurrent();
|
|
4920
|
-
let handlers = nodeErrorHandlers.get(node);
|
|
4921
|
-
if (!handlers) {
|
|
4922
|
-
handlers = [];
|
|
4923
|
-
nodeErrorHandlers.set(node, handlers);
|
|
4924
|
-
}
|
|
4925
|
-
handlers.push(callback.bind(node.component));
|
|
4926
|
-
}
|
|
4927
|
-
|
|
4928
|
-
class Component {
|
|
4929
|
-
constructor(props, env, node) {
|
|
4930
|
-
this.props = props;
|
|
4931
|
-
this.env = env;
|
|
4932
|
-
this.__owl__ = node;
|
|
4933
|
-
}
|
|
4934
|
-
setup() { }
|
|
4935
|
-
render(deep = false) {
|
|
4936
|
-
this.__owl__.render(deep);
|
|
4937
|
-
}
|
|
4938
|
-
}
|
|
4939
|
-
Component.template = "";
|
|
4940
|
-
|
|
4941
|
-
const VText = text("").constructor;
|
|
4942
|
-
class VPortal extends VText {
|
|
4943
|
-
constructor(selector, realBDom) {
|
|
4944
|
-
super("");
|
|
4945
|
-
this.target = null;
|
|
4946
|
-
this.selector = selector;
|
|
4947
|
-
this.realBDom = realBDom;
|
|
4948
|
-
}
|
|
4949
|
-
mount(parent, anchor) {
|
|
4950
|
-
super.mount(parent, anchor);
|
|
4951
|
-
this.target = document.querySelector(this.selector);
|
|
4952
|
-
if (!this.target) {
|
|
4953
|
-
let el = this.el;
|
|
4954
|
-
while (el && el.parentElement instanceof HTMLElement) {
|
|
4955
|
-
el = el.parentElement;
|
|
4956
|
-
}
|
|
4957
|
-
this.target = el && el.querySelector(this.selector);
|
|
4958
|
-
if (!this.target) {
|
|
4959
|
-
throw new Error("invalid portal target");
|
|
4960
|
-
}
|
|
4961
|
-
}
|
|
4962
|
-
this.realBDom.mount(this.target, null);
|
|
5083
|
+
let isDynamic = node.hasAttribute("t-component");
|
|
5084
|
+
if (isDynamic && name !== "t") {
|
|
5085
|
+
throw new OwlError(`Directive 't-component' can only be used on <t> nodes (used on a <${name}>)`);
|
|
4963
5086
|
}
|
|
4964
|
-
|
|
4965
|
-
|
|
5087
|
+
if (!(firstLetter === firstLetter.toUpperCase() || isDynamic)) {
|
|
5088
|
+
return null;
|
|
4966
5089
|
}
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
this.realBDom.remove();
|
|
4971
|
-
this.realBDom = null;
|
|
4972
|
-
}
|
|
5090
|
+
if (isDynamic) {
|
|
5091
|
+
name = node.getAttribute("t-component");
|
|
5092
|
+
node.removeAttribute("t-component");
|
|
4973
5093
|
}
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
5094
|
+
const dynamicProps = node.getAttribute("t-props");
|
|
5095
|
+
node.removeAttribute("t-props");
|
|
5096
|
+
const defaultSlotScope = node.getAttribute("t-slot-scope");
|
|
5097
|
+
node.removeAttribute("t-slot-scope");
|
|
5098
|
+
let on = null;
|
|
5099
|
+
let props = null;
|
|
5100
|
+
for (let name of node.getAttributeNames()) {
|
|
5101
|
+
const value = node.getAttribute(name);
|
|
5102
|
+
if (name.startsWith("t-")) {
|
|
5103
|
+
if (name.startsWith("t-on-")) {
|
|
5104
|
+
on = on || {};
|
|
5105
|
+
on[name.slice(5)] = value;
|
|
5106
|
+
}
|
|
5107
|
+
else {
|
|
5108
|
+
const message = directiveErrorMap.get(name.split("-").slice(0, 2).join("-"));
|
|
5109
|
+
throw new OwlError(message || `unsupported directive on Component: ${name}`);
|
|
5110
|
+
}
|
|
4978
5111
|
}
|
|
4979
5112
|
else {
|
|
4980
|
-
|
|
4981
|
-
|
|
5113
|
+
props = props || {};
|
|
5114
|
+
props[name] = value;
|
|
4982
5115
|
}
|
|
4983
5116
|
}
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
const
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
node.bdom.remove();
|
|
5117
|
+
let slots = null;
|
|
5118
|
+
if (node.hasChildNodes()) {
|
|
5119
|
+
const clone = node.cloneNode(true);
|
|
5120
|
+
// named slots
|
|
5121
|
+
const slotNodes = Array.from(clone.querySelectorAll("[t-set-slot]"));
|
|
5122
|
+
for (let slotNode of slotNodes) {
|
|
5123
|
+
if (slotNode.tagName !== "t") {
|
|
5124
|
+
throw new OwlError(`Directive 't-set-slot' can only be used on <t> nodes (used on a <${slotNode.tagName}>)`);
|
|
4993
5125
|
}
|
|
4994
|
-
|
|
5126
|
+
const name = slotNode.getAttribute("t-set-slot");
|
|
5127
|
+
// check if this is defined in a sub component (in which case it should
|
|
5128
|
+
// be ignored)
|
|
5129
|
+
let el = slotNode.parentElement;
|
|
5130
|
+
let isInSubComponent = false;
|
|
5131
|
+
while (el !== clone) {
|
|
5132
|
+
if (el.hasAttribute("t-component") || el.tagName[0] === el.tagName[0].toUpperCase()) {
|
|
5133
|
+
isInSubComponent = true;
|
|
5134
|
+
break;
|
|
5135
|
+
}
|
|
5136
|
+
el = el.parentElement;
|
|
5137
|
+
}
|
|
5138
|
+
if (isInSubComponent) {
|
|
5139
|
+
continue;
|
|
5140
|
+
}
|
|
5141
|
+
slotNode.removeAttribute("t-set-slot");
|
|
5142
|
+
slotNode.remove();
|
|
5143
|
+
const slotAst = parseNode(slotNode, ctx);
|
|
5144
|
+
let on = null;
|
|
5145
|
+
let attrs = null;
|
|
5146
|
+
let scope = null;
|
|
5147
|
+
for (let attributeName of slotNode.getAttributeNames()) {
|
|
5148
|
+
const value = slotNode.getAttribute(attributeName);
|
|
5149
|
+
if (attributeName === "t-slot-scope") {
|
|
5150
|
+
scope = value;
|
|
5151
|
+
continue;
|
|
5152
|
+
}
|
|
5153
|
+
else if (attributeName.startsWith("t-on-")) {
|
|
5154
|
+
on = on || {};
|
|
5155
|
+
on[attributeName.slice(5)] = value;
|
|
5156
|
+
}
|
|
5157
|
+
else {
|
|
5158
|
+
attrs = attrs || {};
|
|
5159
|
+
attrs[attributeName] = value;
|
|
5160
|
+
}
|
|
5161
|
+
}
|
|
5162
|
+
slots = slots || {};
|
|
5163
|
+
slots[name] = { content: slotAst, on, attrs, scope };
|
|
5164
|
+
}
|
|
5165
|
+
// default slot
|
|
5166
|
+
const defaultContent = parseChildNodes(clone, ctx);
|
|
5167
|
+
if (defaultContent) {
|
|
5168
|
+
slots = slots || {};
|
|
5169
|
+
slots.default = { content: defaultContent, on, attrs: null, scope: defaultSlotScope };
|
|
5170
|
+
}
|
|
4995
5171
|
}
|
|
5172
|
+
return { type: 11 /* TComponent */, name, isDynamic, dynamicProps, props, slots, on };
|
|
4996
5173
|
}
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
};
|
|
5004
|
-
|
|
5005
|
-
/**
|
|
5006
|
-
* This file contains utility functions that will be injected in each template,
|
|
5007
|
-
* to perform various useful tasks in the compiled code.
|
|
5008
|
-
*/
|
|
5009
|
-
function withDefault(value, defaultValue) {
|
|
5010
|
-
return value === undefined || value === null || value === false ? defaultValue : value;
|
|
5011
|
-
}
|
|
5012
|
-
function callSlot(ctx, parent, key, name, dynamic, extra, defaultContent) {
|
|
5013
|
-
key = key + "__slot_" + name;
|
|
5014
|
-
const slots = ctx.props[TARGET].slots || {};
|
|
5015
|
-
const { __render, __ctx, __scope } = slots[name] || {};
|
|
5016
|
-
const slotScope = Object.create(__ctx || {});
|
|
5017
|
-
if (__scope) {
|
|
5018
|
-
slotScope[__scope] = extra;
|
|
5174
|
+
// -----------------------------------------------------------------------------
|
|
5175
|
+
// Slots
|
|
5176
|
+
// -----------------------------------------------------------------------------
|
|
5177
|
+
function parseTSlot(node, ctx) {
|
|
5178
|
+
if (!node.hasAttribute("t-slot")) {
|
|
5179
|
+
return null;
|
|
5019
5180
|
}
|
|
5020
|
-
const
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5181
|
+
const name = node.getAttribute("t-slot");
|
|
5182
|
+
node.removeAttribute("t-slot");
|
|
5183
|
+
let attrs = null;
|
|
5184
|
+
let on = null;
|
|
5185
|
+
for (let attributeName of node.getAttributeNames()) {
|
|
5186
|
+
const value = node.getAttribute(attributeName);
|
|
5187
|
+
if (attributeName.startsWith("t-on-")) {
|
|
5188
|
+
on = on || {};
|
|
5189
|
+
on[attributeName.slice(5)] = value;
|
|
5026
5190
|
}
|
|
5027
5191
|
else {
|
|
5028
|
-
|
|
5192
|
+
attrs = attrs || {};
|
|
5193
|
+
attrs[attributeName] = value;
|
|
5029
5194
|
}
|
|
5030
|
-
return multi([child1, child2]);
|
|
5031
5195
|
}
|
|
5032
|
-
return
|
|
5196
|
+
return {
|
|
5197
|
+
type: 14 /* TSlot */,
|
|
5198
|
+
name,
|
|
5199
|
+
attrs,
|
|
5200
|
+
on,
|
|
5201
|
+
defaultContent: parseChildNodes(node, ctx),
|
|
5202
|
+
};
|
|
5033
5203
|
}
|
|
5034
|
-
function
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
for (let k in ctx) {
|
|
5038
|
-
result[k] = ctx[k];
|
|
5204
|
+
function parseTTranslation(node, ctx) {
|
|
5205
|
+
if (node.getAttribute("t-translation") !== "off") {
|
|
5206
|
+
return null;
|
|
5039
5207
|
}
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5208
|
+
node.removeAttribute("t-translation");
|
|
5209
|
+
return {
|
|
5210
|
+
type: 16 /* TTranslation */,
|
|
5211
|
+
content: parseNode(node, ctx),
|
|
5212
|
+
};
|
|
5045
5213
|
}
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
}
|
|
5053
|
-
else if (collection) {
|
|
5054
|
-
values = Object.keys(collection);
|
|
5055
|
-
keys = Object.values(collection);
|
|
5214
|
+
// -----------------------------------------------------------------------------
|
|
5215
|
+
// Portal
|
|
5216
|
+
// -----------------------------------------------------------------------------
|
|
5217
|
+
function parseTPortal(node, ctx) {
|
|
5218
|
+
if (!node.hasAttribute("t-portal")) {
|
|
5219
|
+
return null;
|
|
5056
5220
|
}
|
|
5057
|
-
|
|
5058
|
-
|
|
5221
|
+
const target = node.getAttribute("t-portal");
|
|
5222
|
+
node.removeAttribute("t-portal");
|
|
5223
|
+
const content = parseNode(node, ctx);
|
|
5224
|
+
if (!content) {
|
|
5225
|
+
return {
|
|
5226
|
+
type: 0 /* Text */,
|
|
5227
|
+
value: "",
|
|
5228
|
+
};
|
|
5059
5229
|
}
|
|
5060
|
-
|
|
5061
|
-
|
|
5230
|
+
return {
|
|
5231
|
+
type: 17 /* TPortal */,
|
|
5232
|
+
target,
|
|
5233
|
+
content,
|
|
5234
|
+
};
|
|
5062
5235
|
}
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5236
|
+
// -----------------------------------------------------------------------------
|
|
5237
|
+
// helpers
|
|
5238
|
+
// -----------------------------------------------------------------------------
|
|
5239
|
+
/**
|
|
5240
|
+
* Parse all the child nodes of a given node and return a list of ast elements
|
|
5241
|
+
*/
|
|
5242
|
+
function parseChildren(node, ctx) {
|
|
5243
|
+
const children = [];
|
|
5244
|
+
for (let child of node.childNodes) {
|
|
5245
|
+
const childAst = parseNode(child, ctx);
|
|
5246
|
+
if (childAst) {
|
|
5247
|
+
if (childAst.type === 3 /* Multi */) {
|
|
5248
|
+
children.push(...childAst.content);
|
|
5249
|
+
}
|
|
5250
|
+
else {
|
|
5251
|
+
children.push(childAst);
|
|
5252
|
+
}
|
|
5071
5253
|
}
|
|
5072
|
-
ctx = newCtx;
|
|
5073
5254
|
}
|
|
5074
|
-
|
|
5255
|
+
return children;
|
|
5075
5256
|
}
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5257
|
+
/**
|
|
5258
|
+
* Parse all the child nodes of a given node and return an ast if possible.
|
|
5259
|
+
* In the case there are multiple children, they are wrapped in a astmulti.
|
|
5260
|
+
*/
|
|
5261
|
+
function parseChildNodes(node, ctx) {
|
|
5262
|
+
const children = parseChildren(node, ctx);
|
|
5263
|
+
switch (children.length) {
|
|
5264
|
+
case 0:
|
|
5265
|
+
return null;
|
|
5266
|
+
case 1:
|
|
5267
|
+
return children[0];
|
|
5268
|
+
default:
|
|
5269
|
+
return { type: 3 /* Multi */, content: children };
|
|
5270
|
+
}
|
|
5079
5271
|
}
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5272
|
+
/**
|
|
5273
|
+
* Normalizes the content of an Element so that t-if/t-elif/t-else directives
|
|
5274
|
+
* immediately follow one another (by removing empty text nodes or comments).
|
|
5275
|
+
* Throws an error when a conditional branching statement is malformed. This
|
|
5276
|
+
* function modifies the Element in place.
|
|
5277
|
+
*
|
|
5278
|
+
* @param el the element containing the tree that should be normalized
|
|
5279
|
+
*/
|
|
5280
|
+
function normalizeTIf(el) {
|
|
5281
|
+
let tbranch = el.querySelectorAll("[t-elif], [t-else]");
|
|
5282
|
+
for (let i = 0, ilen = tbranch.length; i < ilen; i++) {
|
|
5283
|
+
let node = tbranch[i];
|
|
5284
|
+
let prevElem = node.previousElementSibling;
|
|
5285
|
+
let pattr = (name) => prevElem.getAttribute(name);
|
|
5286
|
+
let nattr = (name) => +!!node.getAttribute(name);
|
|
5287
|
+
if (prevElem && (pattr("t-if") || pattr("t-elif"))) {
|
|
5288
|
+
if (pattr("t-foreach")) {
|
|
5289
|
+
throw new OwlError("t-if cannot stay at the same level as t-foreach when using t-elif or t-else");
|
|
5290
|
+
}
|
|
5291
|
+
if (["t-if", "t-elif", "t-else"].map(nattr).reduce(function (a, b) {
|
|
5292
|
+
return a + b;
|
|
5293
|
+
}) > 1) {
|
|
5294
|
+
throw new OwlError("Only one conditional branching directive is allowed per node");
|
|
5295
|
+
}
|
|
5296
|
+
// All text (with only spaces) and comment nodes (nodeType 8) between
|
|
5297
|
+
// branch nodes are removed
|
|
5298
|
+
let textNode;
|
|
5299
|
+
while ((textNode = node.previousSibling) !== prevElem) {
|
|
5300
|
+
if (textNode.nodeValue.trim().length && textNode.nodeType !== 8) {
|
|
5301
|
+
throw new OwlError("text is not allowed between branching directives");
|
|
5302
|
+
}
|
|
5303
|
+
textNode.remove();
|
|
5304
|
+
}
|
|
5305
|
+
}
|
|
5306
|
+
else {
|
|
5307
|
+
throw new OwlError("t-elif and t-else directives must be preceded by a t-if or t-elif directive");
|
|
5084
5308
|
}
|
|
5085
|
-
}
|
|
5086
|
-
return true;
|
|
5087
|
-
}
|
|
5088
|
-
class LazyValue {
|
|
5089
|
-
constructor(fn, ctx, node) {
|
|
5090
|
-
this.fn = fn;
|
|
5091
|
-
this.ctx = capture(ctx);
|
|
5092
|
-
this.node = node;
|
|
5093
|
-
}
|
|
5094
|
-
evaluate() {
|
|
5095
|
-
return this.fn(this.ctx, this.node);
|
|
5096
|
-
}
|
|
5097
|
-
toString() {
|
|
5098
|
-
return this.evaluate().toString();
|
|
5099
5309
|
}
|
|
5100
5310
|
}
|
|
5101
|
-
|
|
5102
|
-
*
|
|
5311
|
+
/**
|
|
5312
|
+
* Normalizes the content of an Element so that t-esc directives on components
|
|
5313
|
+
* are removed and instead places a <t t-esc=""> as the default slot of the
|
|
5314
|
+
* component. Also throws if the component already has content. This function
|
|
5315
|
+
* modifies the Element in place.
|
|
5316
|
+
*
|
|
5317
|
+
* @param el the element containing the tree that should be normalized
|
|
5103
5318
|
*/
|
|
5104
|
-
function
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
let block;
|
|
5110
|
-
if (value instanceof Markup) {
|
|
5111
|
-
safeKey = `string_safe`;
|
|
5112
|
-
block = html(value);
|
|
5113
|
-
}
|
|
5114
|
-
else if (value instanceof LazyValue) {
|
|
5115
|
-
safeKey = `lazy_value`;
|
|
5116
|
-
block = value.evaluate();
|
|
5117
|
-
}
|
|
5118
|
-
else if (value instanceof String || typeof value === "string") {
|
|
5119
|
-
safeKey = "string_unsafe";
|
|
5120
|
-
block = text(value);
|
|
5121
|
-
}
|
|
5122
|
-
else {
|
|
5123
|
-
// Assuming it is a block
|
|
5124
|
-
safeKey = "block_safe";
|
|
5125
|
-
block = value;
|
|
5126
|
-
}
|
|
5127
|
-
return toggler(safeKey, block);
|
|
5128
|
-
}
|
|
5129
|
-
let boundFunctions = new WeakMap();
|
|
5130
|
-
function bind(ctx, fn) {
|
|
5131
|
-
let component = ctx.__owl__.component;
|
|
5132
|
-
let boundFnMap = boundFunctions.get(component);
|
|
5133
|
-
if (!boundFnMap) {
|
|
5134
|
-
boundFnMap = new WeakMap();
|
|
5135
|
-
boundFunctions.set(component, boundFnMap);
|
|
5136
|
-
}
|
|
5137
|
-
let boundFn = boundFnMap.get(fn);
|
|
5138
|
-
if (!boundFn) {
|
|
5139
|
-
boundFn = fn.bind(component);
|
|
5140
|
-
boundFnMap.set(fn, boundFn);
|
|
5141
|
-
}
|
|
5142
|
-
return boundFn;
|
|
5143
|
-
}
|
|
5144
|
-
function multiRefSetter(refs, name) {
|
|
5145
|
-
let count = 0;
|
|
5146
|
-
return (el) => {
|
|
5147
|
-
if (el) {
|
|
5148
|
-
count++;
|
|
5149
|
-
if (count > 1) {
|
|
5150
|
-
throw new Error("Cannot have 2 elements with same ref name at the same time");
|
|
5151
|
-
}
|
|
5319
|
+
function normalizeTEsc(el) {
|
|
5320
|
+
const elements = [...el.querySelectorAll("[t-esc]")].filter((el) => el.tagName[0] === el.tagName[0].toUpperCase() || el.hasAttribute("t-component"));
|
|
5321
|
+
for (const el of elements) {
|
|
5322
|
+
if (el.childNodes.length) {
|
|
5323
|
+
throw new OwlError("Cannot have t-esc on a component that already has content");
|
|
5152
5324
|
}
|
|
5153
|
-
|
|
5154
|
-
|
|
5325
|
+
const value = el.getAttribute("t-esc");
|
|
5326
|
+
el.removeAttribute("t-esc");
|
|
5327
|
+
const t = el.ownerDocument.createElement("t");
|
|
5328
|
+
if (value != null) {
|
|
5329
|
+
t.setAttribute("t-esc", value);
|
|
5155
5330
|
}
|
|
5156
|
-
|
|
5331
|
+
el.appendChild(t);
|
|
5332
|
+
}
|
|
5157
5333
|
}
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
};
|
|
5176
|
-
|
|
5177
|
-
const bdom = { text, createBlock, list, multi, html, toggler, component, comment };
|
|
5334
|
+
/**
|
|
5335
|
+
* Normalizes the tree inside a given element and do some preliminary validation
|
|
5336
|
+
* on it. This function modifies the Element in place.
|
|
5337
|
+
*
|
|
5338
|
+
* @param el the element containing the tree that should be normalized
|
|
5339
|
+
*/
|
|
5340
|
+
function normalizeXML(el) {
|
|
5341
|
+
normalizeTIf(el);
|
|
5342
|
+
normalizeTEsc(el);
|
|
5343
|
+
}
|
|
5344
|
+
/**
|
|
5345
|
+
* Parses an XML string into an XML document, throwing errors on parser errors
|
|
5346
|
+
* instead of returning an XML document containing the parseerror.
|
|
5347
|
+
*
|
|
5348
|
+
* @param xml the string to parse
|
|
5349
|
+
* @returns an XML document corresponding to the content of the string
|
|
5350
|
+
*/
|
|
5178
5351
|
function parseXML(xml) {
|
|
5179
5352
|
const parser = new DOMParser();
|
|
5180
5353
|
const doc = parser.parseFromString(xml, "text/xml");
|
|
@@ -5199,87 +5372,134 @@ function parseXML(xml) {
|
|
|
5199
5372
|
}
|
|
5200
5373
|
}
|
|
5201
5374
|
}
|
|
5202
|
-
throw new
|
|
5375
|
+
throw new OwlError(msg);
|
|
5203
5376
|
}
|
|
5204
5377
|
return doc;
|
|
5205
|
-
}
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
5378
|
+
}
|
|
5379
|
+
|
|
5380
|
+
function compile(template, options = {}) {
|
|
5381
|
+
// parsing
|
|
5382
|
+
const ast = parse(template);
|
|
5383
|
+
// some work
|
|
5384
|
+
const hasSafeContext = template instanceof Node
|
|
5385
|
+
? !(template instanceof Element) || template.querySelector("[t-set], [t-call]") === null
|
|
5386
|
+
: !template.includes("t-set") && !template.includes("t-call");
|
|
5387
|
+
// code generation
|
|
5388
|
+
const codeGenerator = new CodeGenerator(ast, { ...options, hasSafeContext });
|
|
5389
|
+
const code = codeGenerator.generateCode();
|
|
5390
|
+
// template function
|
|
5391
|
+
return new Function("app, bdom, helpers", code);
|
|
5392
|
+
}
|
|
5393
|
+
|
|
5394
|
+
const mainEventHandler = (data, ev, currentTarget) => {
|
|
5395
|
+
const { data: _data, modifiers } = filterOutModifiersFromData(data);
|
|
5396
|
+
data = _data;
|
|
5397
|
+
let stopped = false;
|
|
5398
|
+
if (modifiers.length) {
|
|
5399
|
+
let selfMode = false;
|
|
5400
|
+
const isSelf = ev.target === currentTarget;
|
|
5401
|
+
for (const mod of modifiers) {
|
|
5402
|
+
switch (mod) {
|
|
5403
|
+
case "self":
|
|
5404
|
+
selfMode = true;
|
|
5405
|
+
if (isSelf) {
|
|
5406
|
+
continue;
|
|
5407
|
+
}
|
|
5408
|
+
else {
|
|
5409
|
+
return stopped;
|
|
5410
|
+
}
|
|
5411
|
+
case "prevent":
|
|
5412
|
+
if ((selfMode && isSelf) || !selfMode)
|
|
5413
|
+
ev.preventDefault();
|
|
5414
|
+
continue;
|
|
5415
|
+
case "stop":
|
|
5416
|
+
if ((selfMode && isSelf) || !selfMode)
|
|
5417
|
+
ev.stopPropagation();
|
|
5418
|
+
stopped = true;
|
|
5419
|
+
continue;
|
|
5420
|
+
}
|
|
5236
5421
|
}
|
|
5237
|
-
this.rawTemplates[name] = template;
|
|
5238
5422
|
}
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5423
|
+
// If handler is empty, the array slot 0 will also be empty, and data will not have the property 0
|
|
5424
|
+
// We check this rather than data[0] being truthy (or typeof function) so that it crashes
|
|
5425
|
+
// as expected when there is a handler expression that evaluates to a falsy value
|
|
5426
|
+
if (Object.hasOwnProperty.call(data, 0)) {
|
|
5427
|
+
const handler = data[0];
|
|
5428
|
+
if (typeof handler !== "function") {
|
|
5429
|
+
throw new OwlError(`Invalid handler (expected a function, received: '${handler}')`);
|
|
5243
5430
|
}
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
this.addTemplate(name, template, options);
|
|
5431
|
+
let node = data[1] ? data[1].__owl__ : null;
|
|
5432
|
+
if (node ? node.status === 1 /* MOUNTED */ : true) {
|
|
5433
|
+
handler.call(node ? node.component : null, ev);
|
|
5248
5434
|
}
|
|
5249
5435
|
}
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
|
|
5436
|
+
return stopped;
|
|
5437
|
+
};
|
|
5438
|
+
|
|
5439
|
+
// -----------------------------------------------------------------------------
|
|
5440
|
+
// Scheduler
|
|
5441
|
+
// -----------------------------------------------------------------------------
|
|
5442
|
+
class Scheduler {
|
|
5443
|
+
constructor() {
|
|
5444
|
+
this.tasks = new Set();
|
|
5445
|
+
this.frame = 0;
|
|
5446
|
+
this.delayedRenders = [];
|
|
5447
|
+
this.requestAnimationFrame = Scheduler.requestAnimationFrame;
|
|
5448
|
+
}
|
|
5449
|
+
addFiber(fiber) {
|
|
5450
|
+
this.tasks.add(fiber.root);
|
|
5451
|
+
}
|
|
5452
|
+
/**
|
|
5453
|
+
* Process all current tasks. This only applies to the fibers that are ready.
|
|
5454
|
+
* Other tasks are left unchanged.
|
|
5455
|
+
*/
|
|
5456
|
+
flush() {
|
|
5457
|
+
if (this.delayedRenders.length) {
|
|
5458
|
+
let renders = this.delayedRenders;
|
|
5459
|
+
this.delayedRenders = [];
|
|
5460
|
+
for (let f of renders) {
|
|
5461
|
+
if (f.root && f.node.status !== 2 /* DESTROYED */ && f.node.fiber === f) {
|
|
5462
|
+
f.render();
|
|
5258
5463
|
}
|
|
5259
|
-
catch { }
|
|
5260
|
-
throw new Error(`Missing template: "${name}"${extraInfo}`);
|
|
5261
5464
|
}
|
|
5262
|
-
const templateFn = this._compileTemplate(name, rawTemplate);
|
|
5263
|
-
// first add a function to lazily get the template, in case there is a
|
|
5264
|
-
// recursive call to the template name
|
|
5265
|
-
const templates = this.templates;
|
|
5266
|
-
this.templates[name] = function (context, parent) {
|
|
5267
|
-
return templates[name].call(this, context, parent);
|
|
5268
|
-
};
|
|
5269
|
-
const template = templateFn(bdom, this.helpers);
|
|
5270
|
-
this.templates[name] = template;
|
|
5271
5465
|
}
|
|
5272
|
-
|
|
5466
|
+
if (this.frame === 0) {
|
|
5467
|
+
this.frame = this.requestAnimationFrame(() => {
|
|
5468
|
+
this.frame = 0;
|
|
5469
|
+
this.tasks.forEach((fiber) => this.processFiber(fiber));
|
|
5470
|
+
for (let task of this.tasks) {
|
|
5471
|
+
if (task.node.status === 2 /* DESTROYED */) {
|
|
5472
|
+
this.tasks.delete(task);
|
|
5473
|
+
}
|
|
5474
|
+
}
|
|
5475
|
+
});
|
|
5476
|
+
}
|
|
5273
5477
|
}
|
|
5274
|
-
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
5478
|
+
processFiber(fiber) {
|
|
5479
|
+
if (fiber.root !== fiber) {
|
|
5480
|
+
this.tasks.delete(fiber);
|
|
5481
|
+
return;
|
|
5482
|
+
}
|
|
5483
|
+
const hasError = fibersInError.has(fiber);
|
|
5484
|
+
if (hasError && fiber.counter !== 0) {
|
|
5485
|
+
this.tasks.delete(fiber);
|
|
5486
|
+
return;
|
|
5487
|
+
}
|
|
5488
|
+
if (fiber.node.status === 2 /* DESTROYED */) {
|
|
5489
|
+
this.tasks.delete(fiber);
|
|
5490
|
+
return;
|
|
5491
|
+
}
|
|
5492
|
+
if (fiber.counter === 0) {
|
|
5493
|
+
if (!hasError) {
|
|
5494
|
+
fiber.complete();
|
|
5495
|
+
}
|
|
5496
|
+
this.tasks.delete(fiber);
|
|
5497
|
+
}
|
|
5281
5498
|
}
|
|
5282
|
-
}
|
|
5499
|
+
}
|
|
5500
|
+
// capture the value of requestAnimationFrame as soon as possible, to avoid
|
|
5501
|
+
// interactions with other code, such as test frameworks that override them
|
|
5502
|
+
Scheduler.requestAnimationFrame = window.requestAnimationFrame.bind(window);
|
|
5283
5503
|
|
|
5284
5504
|
let hasBeenLogged = false;
|
|
5285
5505
|
const DEV_MSG = () => {
|
|
@@ -5298,6 +5518,7 @@ class App extends TemplateSet {
|
|
|
5298
5518
|
if (config.test) {
|
|
5299
5519
|
this.dev = true;
|
|
5300
5520
|
}
|
|
5521
|
+
this.warnIfNoStaticProps = config.warnIfNoStaticProps || false;
|
|
5301
5522
|
if (this.dev && !config.test && !hasBeenLogged) {
|
|
5302
5523
|
console.info(DEV_MSG());
|
|
5303
5524
|
hasBeenLogged = true;
|
|
@@ -5309,13 +5530,16 @@ class App extends TemplateSet {
|
|
|
5309
5530
|
}
|
|
5310
5531
|
mount(target, options) {
|
|
5311
5532
|
App.validateTarget(target);
|
|
5533
|
+
if (this.dev) {
|
|
5534
|
+
validateProps(this.Root, this.props, { __owl__: { app: this } });
|
|
5535
|
+
}
|
|
5312
5536
|
const node = this.makeNode(this.Root, this.props);
|
|
5313
5537
|
const prom = this.mountNode(node, target, options);
|
|
5314
5538
|
this.root = node;
|
|
5315
5539
|
return prom;
|
|
5316
5540
|
}
|
|
5317
5541
|
makeNode(Component, props) {
|
|
5318
|
-
return new ComponentNode(Component, props, this);
|
|
5542
|
+
return new ComponentNode(Component, props, this, null, null);
|
|
5319
5543
|
}
|
|
5320
5544
|
mountNode(node, target, options) {
|
|
5321
5545
|
const promise = new Promise((resolve, reject) => {
|
|
@@ -5333,10 +5557,7 @@ class App extends TemplateSet {
|
|
|
5333
5557
|
nodeErrorHandlers.set(node, handlers);
|
|
5334
5558
|
}
|
|
5335
5559
|
handlers.unshift((e) => {
|
|
5336
|
-
if (isResolved) {
|
|
5337
|
-
console.error(e);
|
|
5338
|
-
}
|
|
5339
|
-
else {
|
|
5560
|
+
if (!isResolved) {
|
|
5340
5561
|
reject(e);
|
|
5341
5562
|
}
|
|
5342
5563
|
throw e;
|
|
@@ -5347,9 +5568,62 @@ class App extends TemplateSet {
|
|
|
5347
5568
|
}
|
|
5348
5569
|
destroy() {
|
|
5349
5570
|
if (this.root) {
|
|
5571
|
+
this.scheduler.flush();
|
|
5350
5572
|
this.root.destroy();
|
|
5351
5573
|
}
|
|
5352
5574
|
}
|
|
5575
|
+
createComponent(name, isStatic, hasSlotsProp, hasDynamicPropList, hasNoProp) {
|
|
5576
|
+
const isDynamic = !isStatic;
|
|
5577
|
+
function _arePropsDifferent(props1, props2) {
|
|
5578
|
+
for (let k in props1) {
|
|
5579
|
+
if (props1[k] !== props2[k]) {
|
|
5580
|
+
return true;
|
|
5581
|
+
}
|
|
5582
|
+
}
|
|
5583
|
+
return hasDynamicPropList && Object.keys(props1).length !== Object.keys(props2).length;
|
|
5584
|
+
}
|
|
5585
|
+
const arePropsDifferent = hasSlotsProp
|
|
5586
|
+
? (_1, _2) => true
|
|
5587
|
+
: hasNoProp
|
|
5588
|
+
? (_1, _2) => false
|
|
5589
|
+
: _arePropsDifferent;
|
|
5590
|
+
const updateAndRender = ComponentNode.prototype.updateAndRender;
|
|
5591
|
+
const initiateRender = ComponentNode.prototype.initiateRender;
|
|
5592
|
+
return (props, key, ctx, parent, C) => {
|
|
5593
|
+
let children = ctx.children;
|
|
5594
|
+
let node = children[key];
|
|
5595
|
+
if (isDynamic && node && node.component.constructor !== C) {
|
|
5596
|
+
node = undefined;
|
|
5597
|
+
}
|
|
5598
|
+
const parentFiber = ctx.fiber;
|
|
5599
|
+
if (node) {
|
|
5600
|
+
if (arePropsDifferent(node.props, props) || parentFiber.deep || node.forceNextRender) {
|
|
5601
|
+
node.forceNextRender = false;
|
|
5602
|
+
updateAndRender.call(node, props, parentFiber);
|
|
5603
|
+
}
|
|
5604
|
+
}
|
|
5605
|
+
else {
|
|
5606
|
+
// new component
|
|
5607
|
+
if (isStatic) {
|
|
5608
|
+
C = parent.constructor.components[name];
|
|
5609
|
+
if (!C) {
|
|
5610
|
+
throw new OwlError(`Cannot find the definition of component "${name}"`);
|
|
5611
|
+
}
|
|
5612
|
+
else if (!(C.prototype instanceof Component)) {
|
|
5613
|
+
throw new OwlError(`"${name}" is not a Component. It must inherit from the Component class`);
|
|
5614
|
+
}
|
|
5615
|
+
}
|
|
5616
|
+
node = new ComponentNode(C, props, this, ctx, key);
|
|
5617
|
+
children[key] = node;
|
|
5618
|
+
initiateRender.call(node, new Fiber(node, parentFiber));
|
|
5619
|
+
}
|
|
5620
|
+
parentFiber.childrenMap[key] = node;
|
|
5621
|
+
return node;
|
|
5622
|
+
};
|
|
5623
|
+
}
|
|
5624
|
+
handleError(...args) {
|
|
5625
|
+
return handleError(...args);
|
|
5626
|
+
}
|
|
5353
5627
|
}
|
|
5354
5628
|
App.validateTarget = validateTarget;
|
|
5355
5629
|
async function mount(C, target, config = {}) {
|
|
@@ -5412,10 +5686,6 @@ function useChildSubEnv(envExtension) {
|
|
|
5412
5686
|
const node = getCurrent();
|
|
5413
5687
|
node.childEnv = extendEnv(node.childEnv, envExtension);
|
|
5414
5688
|
}
|
|
5415
|
-
// -----------------------------------------------------------------------------
|
|
5416
|
-
// useEffect
|
|
5417
|
-
// -----------------------------------------------------------------------------
|
|
5418
|
-
const NO_OP = () => { };
|
|
5419
5689
|
/**
|
|
5420
5690
|
* This hook will run a callback when a component is mounted and patched, and
|
|
5421
5691
|
* will run a cleanup function before patching and before unmounting the
|
|
@@ -5433,18 +5703,20 @@ function useEffect(effect, computeDependencies = () => [NaN]) {
|
|
|
5433
5703
|
let dependencies;
|
|
5434
5704
|
onMounted(() => {
|
|
5435
5705
|
dependencies = computeDependencies();
|
|
5436
|
-
cleanup = effect(...dependencies)
|
|
5706
|
+
cleanup = effect(...dependencies);
|
|
5437
5707
|
});
|
|
5438
5708
|
onPatched(() => {
|
|
5439
5709
|
const newDeps = computeDependencies();
|
|
5440
5710
|
const shouldReapply = newDeps.some((val, i) => val !== dependencies[i]);
|
|
5441
5711
|
if (shouldReapply) {
|
|
5442
5712
|
dependencies = newDeps;
|
|
5443
|
-
cleanup
|
|
5444
|
-
|
|
5713
|
+
if (cleanup) {
|
|
5714
|
+
cleanup();
|
|
5715
|
+
}
|
|
5716
|
+
cleanup = effect(...dependencies);
|
|
5445
5717
|
}
|
|
5446
5718
|
});
|
|
5447
|
-
onWillUnmount(() => cleanup());
|
|
5719
|
+
onWillUnmount(() => cleanup && cleanup());
|
|
5448
5720
|
}
|
|
5449
5721
|
// -----------------------------------------------------------------------------
|
|
5450
5722
|
// useExternalListener
|
|
@@ -5488,10 +5760,19 @@ const blockDom = {
|
|
|
5488
5760
|
};
|
|
5489
5761
|
const __info__ = {};
|
|
5490
5762
|
|
|
5491
|
-
|
|
5763
|
+
TemplateSet.prototype._compileTemplate = function _compileTemplate(name, template) {
|
|
5764
|
+
return compile(template, {
|
|
5765
|
+
name,
|
|
5766
|
+
dev: this.dev,
|
|
5767
|
+
translateFn: this.translateFn,
|
|
5768
|
+
translatableAttributes: this.translatableAttributes,
|
|
5769
|
+
});
|
|
5770
|
+
};
|
|
5771
|
+
|
|
5772
|
+
export { App, Component, EventBus, OwlError, __info__, blockDom, loadFile, markRaw, markup, mount, onError, onMounted, onPatched, onRendered, onWillDestroy, onWillPatch, onWillRender, onWillStart, onWillUnmount, onWillUpdateProps, reactive, status, toRaw, useChildSubEnv, useComponent, useEffect, useEnv, useExternalListener, useRef, useState, useSubEnv, validate, whenReady, xml };
|
|
5492
5773
|
|
|
5493
5774
|
|
|
5494
|
-
__info__.version = '2.0.0
|
|
5495
|
-
__info__.date = '2022-
|
|
5496
|
-
__info__.hash = '
|
|
5775
|
+
__info__.version = '2.0.0';
|
|
5776
|
+
__info__.date = '2022-10-07T13:28:10.216Z';
|
|
5777
|
+
__info__.hash = 'a1f2282';
|
|
5497
5778
|
__info__.url = 'https://github.com/odoo/owl';
|