@odoo/owl 3.0.0-alpha.20 → 3.0.0-alpha.21
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 +100 -110
- package/dist/compile_templates.mjs +84 -80
- package/dist/compiler.js +2356 -2356
- package/dist/owl-devtools.zip +0 -0
- package/dist/owl.cjs.js +1596 -1459
- package/dist/owl.cjs.runtime.js +4362 -3952
- package/dist/owl.es.js +1596 -1459
- package/dist/owl.es.runtime.js +4360 -3950
- package/dist/owl.iife.js +1596 -1459
- package/dist/owl.iife.min.js +1 -1
- package/dist/owl.iife.runtime.js +4363 -3953
- package/dist/owl.iife.runtime.min.js +1 -1
- package/dist/types/common/types.d.ts +1 -1
- package/dist/types/compiler/code_generator.d.ts +1 -2
- package/dist/types/compiler/inline_expressions.d.ts +7 -24
- package/dist/types/compiler/parser.d.ts +3 -8
- package/dist/types/owl.d.ts +33 -27
- package/dist/types/runtime/blockdom/attributes.d.ts +2 -0
- package/dist/types/runtime/component_node.d.ts +2 -0
- package/dist/types/runtime/error_handling.d.ts +13 -13
- package/dist/types/runtime/fibers.d.ts +37 -37
- package/dist/types/runtime/plugin_manager.d.ts +2 -0
- package/dist/types/runtime/portal.d.ts +15 -12
- package/dist/types/runtime/reactivity/computations.d.ts +1 -0
- package/dist/types/runtime/reactivity.d.ts +57 -57
- package/dist/types/runtime/rendering/scheduler.d.ts +1 -1
- package/dist/types/runtime/rendering/template_helpers.d.ts +2 -2
- package/dist/types/runtime/scheduler.d.ts +21 -21
- package/dist/types/runtime/types.d.ts +3 -0
- package/dist/types/version.d.ts +1 -1
- package/package.json +24 -1
- package/dist/types/runtime/cancellableContext.d.ts +0 -15
- package/dist/types/runtime/cancellablePromise.d.ts +0 -15
- package/dist/types/runtime/executionContext.d.ts +0 -0
- package/dist/types/runtime/listOperation.d.ts +0 -1
- package/dist/types/runtime/model.d.ts +0 -48
- package/dist/types/runtime/plugins.d.ts +0 -36
- package/dist/types/runtime/reactivity/async_computed.d.ts +0 -1
- package/dist/types/runtime/reactivity/derived.d.ts +0 -7
- package/dist/types/runtime/reactivity/reactivity.d.ts +0 -46
- package/dist/types/runtime/reactivity/signals.d.ts +0 -30
- package/dist/types/runtime/reactivity/state.d.ts +0 -48
- package/dist/types/runtime/relationalModel/discussModel.d.ts +0 -19
- package/dist/types/runtime/relationalModel/discussModelTypes.d.ts +0 -22
- package/dist/types/runtime/relationalModel/field.d.ts +0 -20
- package/dist/types/runtime/relationalModel/model.d.ts +0 -59
- package/dist/types/runtime/relationalModel/modelData.d.ts +0 -18
- package/dist/types/runtime/relationalModel/modelRegistry.d.ts +0 -3
- package/dist/types/runtime/relationalModel/modelUtils.d.ts +0 -4
- package/dist/types/runtime/relationalModel/store.d.ts +0 -16
- package/dist/types/runtime/relationalModel/types.d.ts +0 -83
- package/dist/types/runtime/relationalModel/util.d.ts +0 -1
- package/dist/types/runtime/relationalModel/web/WebDataPoint.d.ts +0 -25
- package/dist/types/runtime/relationalModel/web/WebRecord.d.ts +0 -131
- package/dist/types/runtime/relationalModel/web/WebStaticList.d.ts +0 -63
- package/dist/types/runtime/relationalModel/web/webModel.d.ts +0 -5
- package/dist/types/runtime/relationalModel/web/webModelTypes.d.ts +0 -139
- package/dist/types/runtime/signals.d.ts +0 -19
- package/dist/types/runtime/suspense.d.ts +0 -5
- package/dist/types/runtime/task.d.ts +0 -12
- package/dist/types/utils/registry.d.ts +0 -15
package/dist/owl.iife.js
CHANGED
|
@@ -130,10 +130,16 @@
|
|
|
130
130
|
return new VToggler(key, child);
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
133
|
+
let elemSetAttribute;
|
|
134
|
+
let removeAttribute;
|
|
135
|
+
let tokenListAdd;
|
|
136
|
+
let tokenListRemove;
|
|
137
|
+
if (typeof Element !== "undefined") {
|
|
138
|
+
({ setAttribute: elemSetAttribute, removeAttribute } = Element.prototype);
|
|
139
|
+
const tokenList = DOMTokenList.prototype;
|
|
140
|
+
tokenListAdd = tokenList.add;
|
|
141
|
+
tokenListRemove = tokenList.remove;
|
|
142
|
+
}
|
|
137
143
|
const isArray = Array.isArray;
|
|
138
144
|
const { split, trim } = String.prototype;
|
|
139
145
|
const wordRegexp = /\s+/;
|
|
@@ -165,6 +171,9 @@
|
|
|
165
171
|
if (attrs[0] === "class") {
|
|
166
172
|
setClass.call(this, attrs[1]);
|
|
167
173
|
}
|
|
174
|
+
else if (attrs[0] === "style") {
|
|
175
|
+
setStyle.call(this, attrs[1]);
|
|
176
|
+
}
|
|
168
177
|
else {
|
|
169
178
|
setAttribute.call(this, attrs[0], attrs[1]);
|
|
170
179
|
}
|
|
@@ -174,6 +183,9 @@
|
|
|
174
183
|
if (k === "class") {
|
|
175
184
|
setClass.call(this, attrs[k]);
|
|
176
185
|
}
|
|
186
|
+
else if (k === "style") {
|
|
187
|
+
setStyle.call(this, attrs[k]);
|
|
188
|
+
}
|
|
177
189
|
else {
|
|
178
190
|
setAttribute.call(this, k, attrs[k]);
|
|
179
191
|
}
|
|
@@ -191,6 +203,9 @@
|
|
|
191
203
|
if (name === "class") {
|
|
192
204
|
updateClass.call(this, val, oldAttrs[1]);
|
|
193
205
|
}
|
|
206
|
+
else if (name === "style") {
|
|
207
|
+
updateStyle.call(this, val, oldAttrs[1]);
|
|
208
|
+
}
|
|
194
209
|
else {
|
|
195
210
|
setAttribute.call(this, name, val);
|
|
196
211
|
}
|
|
@@ -206,6 +221,9 @@
|
|
|
206
221
|
if (k === "class") {
|
|
207
222
|
updateClass.call(this, "", oldAttrs[k]);
|
|
208
223
|
}
|
|
224
|
+
else if (k === "style") {
|
|
225
|
+
updateStyle.call(this, "", oldAttrs[k]);
|
|
226
|
+
}
|
|
209
227
|
else {
|
|
210
228
|
removeAttribute.call(this, k);
|
|
211
229
|
}
|
|
@@ -217,6 +235,9 @@
|
|
|
217
235
|
if (k === "class") {
|
|
218
236
|
updateClass.call(this, val, oldAttrs[k]);
|
|
219
237
|
}
|
|
238
|
+
else if (k === "style") {
|
|
239
|
+
updateStyle.call(this, val, oldAttrs[k]);
|
|
240
|
+
}
|
|
220
241
|
else {
|
|
221
242
|
setAttribute.call(this, k, val);
|
|
222
243
|
}
|
|
@@ -289,6 +310,80 @@
|
|
|
289
310
|
}
|
|
290
311
|
}
|
|
291
312
|
}
|
|
313
|
+
// ---------------------------------------------------------------------------
|
|
314
|
+
// Style
|
|
315
|
+
// ---------------------------------------------------------------------------
|
|
316
|
+
const CSS_PROP_CACHE = {};
|
|
317
|
+
function toKebabCase(prop) {
|
|
318
|
+
if (prop in CSS_PROP_CACHE) {
|
|
319
|
+
return CSS_PROP_CACHE[prop];
|
|
320
|
+
}
|
|
321
|
+
const result = prop.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase());
|
|
322
|
+
CSS_PROP_CACHE[prop] = result;
|
|
323
|
+
return result;
|
|
324
|
+
}
|
|
325
|
+
function toStyleObj(expr) {
|
|
326
|
+
const result = {};
|
|
327
|
+
switch (typeof expr) {
|
|
328
|
+
case "string": {
|
|
329
|
+
const str = trim.call(expr);
|
|
330
|
+
if (!str) {
|
|
331
|
+
return {};
|
|
332
|
+
}
|
|
333
|
+
const parts = str.split(";");
|
|
334
|
+
for (let part of parts) {
|
|
335
|
+
part = trim.call(part);
|
|
336
|
+
if (!part) {
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
const colonIdx = part.indexOf(":");
|
|
340
|
+
if (colonIdx === -1) {
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
const prop = trim.call(part.slice(0, colonIdx));
|
|
344
|
+
const value = trim.call(part.slice(colonIdx + 1));
|
|
345
|
+
if (prop && value) {
|
|
346
|
+
result[prop] = value;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return result;
|
|
350
|
+
}
|
|
351
|
+
case "object":
|
|
352
|
+
for (let prop in expr) {
|
|
353
|
+
const value = expr[prop];
|
|
354
|
+
if (value || value === 0) {
|
|
355
|
+
result[toKebabCase(prop)] = String(value);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return result;
|
|
359
|
+
default:
|
|
360
|
+
return {};
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
function setStyle(val) {
|
|
364
|
+
val = val === "" ? {} : toStyleObj(val);
|
|
365
|
+
const style = this.style;
|
|
366
|
+
for (let prop in val) {
|
|
367
|
+
style.setProperty(prop, val[prop]);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
function updateStyle(val, oldVal) {
|
|
371
|
+
oldVal = oldVal === "" ? {} : toStyleObj(oldVal);
|
|
372
|
+
val = val === "" ? {} : toStyleObj(val);
|
|
373
|
+
const style = this.style;
|
|
374
|
+
// remove old styles
|
|
375
|
+
for (let prop in oldVal) {
|
|
376
|
+
if (!(prop in val)) {
|
|
377
|
+
style.removeProperty(prop);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
// set new/changed styles
|
|
381
|
+
for (let prop in val) {
|
|
382
|
+
if (val[prop] !== oldVal[prop]) {
|
|
383
|
+
style.setProperty(prop, val[prop]);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
292
387
|
|
|
293
388
|
/**
|
|
294
389
|
* Creates a batched version of a callback so that all calls to it in the same
|
|
@@ -424,16 +519,17 @@
|
|
|
424
519
|
function createEventHandler(rawEvent) {
|
|
425
520
|
const eventName = rawEvent.split(".")[0];
|
|
426
521
|
const capture = rawEvent.includes(".capture");
|
|
522
|
+
const passive = rawEvent.includes(".passive");
|
|
427
523
|
if (rawEvent.includes(".synthetic")) {
|
|
428
|
-
return createSyntheticHandler(eventName, capture);
|
|
524
|
+
return createSyntheticHandler(eventName, capture, passive);
|
|
429
525
|
}
|
|
430
526
|
else {
|
|
431
|
-
return createElementHandler(eventName, capture);
|
|
527
|
+
return createElementHandler(eventName, capture, passive);
|
|
432
528
|
}
|
|
433
529
|
}
|
|
434
530
|
// Native listener
|
|
435
531
|
let nextNativeEventId = 1;
|
|
436
|
-
function createElementHandler(evName, capture = false) {
|
|
532
|
+
function createElementHandler(evName, capture = false, passive = false) {
|
|
437
533
|
let eventKey = `__event__${evName}_${nextNativeEventId++}`;
|
|
438
534
|
if (capture) {
|
|
439
535
|
eventKey = `${eventKey}_capture`;
|
|
@@ -447,13 +543,14 @@
|
|
|
447
543
|
return;
|
|
448
544
|
config$1.mainEventHandler(data, ev, currentTarget);
|
|
449
545
|
}
|
|
546
|
+
const options = { capture, passive };
|
|
450
547
|
function setup(data) {
|
|
451
548
|
this[eventKey] = data;
|
|
452
|
-
this.addEventListener(evName, listener,
|
|
549
|
+
this.addEventListener(evName, listener, options);
|
|
453
550
|
}
|
|
454
551
|
function remove() {
|
|
455
552
|
delete this[eventKey];
|
|
456
|
-
this.removeEventListener(evName, listener,
|
|
553
|
+
this.removeEventListener(evName, listener, options);
|
|
457
554
|
}
|
|
458
555
|
function update(data) {
|
|
459
556
|
this[eventKey] = data;
|
|
@@ -463,12 +560,12 @@
|
|
|
463
560
|
// Synthetic handler: a form of event delegation that allows placing only one
|
|
464
561
|
// listener per event type.
|
|
465
562
|
let nextSyntheticEventId = 1;
|
|
466
|
-
function createSyntheticHandler(evName, capture = false) {
|
|
563
|
+
function createSyntheticHandler(evName, capture = false, passive = false) {
|
|
467
564
|
let eventKey = `__event__synthetic_${evName}`;
|
|
468
565
|
if (capture) {
|
|
469
566
|
eventKey = `${eventKey}_capture`;
|
|
470
567
|
}
|
|
471
|
-
setupSyntheticEvent(evName, eventKey, capture);
|
|
568
|
+
setupSyntheticEvent(evName, eventKey, capture, passive);
|
|
472
569
|
const currentId = nextSyntheticEventId++;
|
|
473
570
|
function setup(data) {
|
|
474
571
|
const _data = this[eventKey] || {};
|
|
@@ -495,21 +592,27 @@
|
|
|
495
592
|
}
|
|
496
593
|
}
|
|
497
594
|
const CONFIGURED_SYNTHETIC_EVENTS = {};
|
|
498
|
-
function setupSyntheticEvent(evName, eventKey, capture = false) {
|
|
595
|
+
function setupSyntheticEvent(evName, eventKey, capture = false, passive = false) {
|
|
499
596
|
if (CONFIGURED_SYNTHETIC_EVENTS[eventKey]) {
|
|
500
597
|
return;
|
|
501
598
|
}
|
|
502
599
|
document.addEventListener(evName, (event) => nativeToSyntheticEvent(eventKey, event), {
|
|
503
600
|
capture,
|
|
601
|
+
passive,
|
|
504
602
|
});
|
|
505
603
|
CONFIGURED_SYNTHETIC_EVENTS[eventKey] = true;
|
|
506
604
|
}
|
|
507
605
|
|
|
508
606
|
const getDescriptor$3 = (o, p) => Object.getOwnPropertyDescriptor(o, p);
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
607
|
+
let nodeInsertBefore$3;
|
|
608
|
+
let nodeSetTextContent$1;
|
|
609
|
+
let nodeRemoveChild$3;
|
|
610
|
+
if (typeof Node !== "undefined") {
|
|
611
|
+
const nodeProto = Node.prototype;
|
|
612
|
+
nodeInsertBefore$3 = nodeProto.insertBefore;
|
|
613
|
+
nodeSetTextContent$1 = getDescriptor$3(nodeProto, "textContent").set;
|
|
614
|
+
nodeRemoveChild$3 = nodeProto.removeChild;
|
|
615
|
+
}
|
|
513
616
|
// -----------------------------------------------------------------------------
|
|
514
617
|
// Multi NODE
|
|
515
618
|
// -----------------------------------------------------------------------------
|
|
@@ -649,11 +752,15 @@
|
|
|
649
752
|
}
|
|
650
753
|
|
|
651
754
|
const getDescriptor$2 = (o, p) => Object.getOwnPropertyDescriptor(o, p);
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
755
|
+
let nodeInsertBefore$2;
|
|
756
|
+
let characterDataSetData$1;
|
|
757
|
+
let nodeRemoveChild$2;
|
|
758
|
+
if (typeof Node !== "undefined") {
|
|
759
|
+
const nodeProto = Node.prototype;
|
|
760
|
+
nodeInsertBefore$2 = nodeProto.insertBefore;
|
|
761
|
+
nodeRemoveChild$2 = nodeProto.removeChild;
|
|
762
|
+
characterDataSetData$1 = getDescriptor$2(CharacterData.prototype, "data").set;
|
|
763
|
+
}
|
|
657
764
|
class VSimpleNode {
|
|
658
765
|
text;
|
|
659
766
|
parentEl;
|
|
@@ -684,7 +791,7 @@
|
|
|
684
791
|
return this.text;
|
|
685
792
|
}
|
|
686
793
|
}
|
|
687
|
-
|
|
794
|
+
class VText extends VSimpleNode {
|
|
688
795
|
mount(parent, afterNode) {
|
|
689
796
|
this.mountNode(document.createTextNode(toText(this.text)), parent, afterNode);
|
|
690
797
|
}
|
|
@@ -695,7 +802,7 @@
|
|
|
695
802
|
this.text = text2;
|
|
696
803
|
}
|
|
697
804
|
}
|
|
698
|
-
}
|
|
805
|
+
}
|
|
699
806
|
class VComment extends VSimpleNode {
|
|
700
807
|
mount(parent, afterNode) {
|
|
701
808
|
this.mountNode(document.createComment(toText(this.text)), parent, afterNode);
|
|
@@ -703,7 +810,7 @@
|
|
|
703
810
|
patch() { }
|
|
704
811
|
}
|
|
705
812
|
function text(str) {
|
|
706
|
-
return new VText
|
|
813
|
+
return new VText(str);
|
|
707
814
|
}
|
|
708
815
|
function comment(str) {
|
|
709
816
|
return new VComment(str);
|
|
@@ -722,12 +829,18 @@
|
|
|
722
829
|
}
|
|
723
830
|
|
|
724
831
|
const getDescriptor$1 = (o, p) => Object.getOwnPropertyDescriptor(o, p);
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
832
|
+
let nodeProto;
|
|
833
|
+
let elementProto;
|
|
834
|
+
let characterDataSetData;
|
|
835
|
+
let nodeGetFirstChild;
|
|
836
|
+
let nodeGetNextSibling;
|
|
837
|
+
if (typeof Node !== "undefined") {
|
|
838
|
+
nodeProto = Node.prototype;
|
|
839
|
+
elementProto = Element.prototype;
|
|
840
|
+
characterDataSetData = getDescriptor$1(CharacterData.prototype, "data").set;
|
|
841
|
+
nodeGetFirstChild = getDescriptor$1(nodeProto, "firstChild").get;
|
|
842
|
+
nodeGetNextSibling = getDescriptor$1(nodeProto, "nextSibling").get;
|
|
843
|
+
}
|
|
731
844
|
const NO_OP = () => { };
|
|
732
845
|
function makePropSetter(name) {
|
|
733
846
|
return function setProp(value) {
|
|
@@ -1022,6 +1135,10 @@
|
|
|
1022
1135
|
setter = setClass;
|
|
1023
1136
|
updater = updateClass;
|
|
1024
1137
|
}
|
|
1138
|
+
else if (info.name === "style") {
|
|
1139
|
+
setter = setStyle;
|
|
1140
|
+
updater = updateStyle;
|
|
1141
|
+
}
|
|
1025
1142
|
else {
|
|
1026
1143
|
setter = createAttrUpdater(info.name);
|
|
1027
1144
|
updater = setter;
|
|
@@ -1107,8 +1224,8 @@
|
|
|
1107
1224
|
(((c.afterRefIdx ?? 0) & 0x7fff) << 16));
|
|
1108
1225
|
// these values are defined here to make them faster to lookup in the class
|
|
1109
1226
|
// block scope
|
|
1110
|
-
const nodeCloneNode = nodeProto
|
|
1111
|
-
const nodeInsertBefore = nodeProto
|
|
1227
|
+
const nodeCloneNode = nodeProto.cloneNode;
|
|
1228
|
+
const nodeInsertBefore = nodeProto.insertBefore;
|
|
1112
1229
|
const elementRemove = elementProto.remove;
|
|
1113
1230
|
class Block {
|
|
1114
1231
|
el;
|
|
@@ -1164,8 +1281,7 @@
|
|
|
1164
1281
|
locSetters[i].call(refs[locRefIdxs[i]], data[i]);
|
|
1165
1282
|
}
|
|
1166
1283
|
}
|
|
1167
|
-
|
|
1168
|
-
// preparing all children
|
|
1284
|
+
// preparing all children (off-DOM, before inserting el into the live document)
|
|
1169
1285
|
if (childN) {
|
|
1170
1286
|
const children = this.children;
|
|
1171
1287
|
for (let i = 0; i < childN; i++) {
|
|
@@ -1179,6 +1295,7 @@
|
|
|
1179
1295
|
}
|
|
1180
1296
|
}
|
|
1181
1297
|
}
|
|
1298
|
+
nodeInsertBefore.call(parent, el, afterNode);
|
|
1182
1299
|
this.el = el;
|
|
1183
1300
|
this.parentEl = parent;
|
|
1184
1301
|
if (cbRefs.length) {
|
|
@@ -1256,11 +1373,17 @@
|
|
|
1256
1373
|
}
|
|
1257
1374
|
|
|
1258
1375
|
const getDescriptor = (o, p) => Object.getOwnPropertyDescriptor(o, p);
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1376
|
+
let nodeInsertBefore$1;
|
|
1377
|
+
let nodeAppendChild;
|
|
1378
|
+
let nodeRemoveChild$1;
|
|
1379
|
+
let nodeSetTextContent;
|
|
1380
|
+
if (typeof Node !== "undefined") {
|
|
1381
|
+
const nodeProto = Node.prototype;
|
|
1382
|
+
nodeInsertBefore$1 = nodeProto.insertBefore;
|
|
1383
|
+
nodeAppendChild = nodeProto.appendChild;
|
|
1384
|
+
nodeRemoveChild$1 = nodeProto.removeChild;
|
|
1385
|
+
nodeSetTextContent = getDescriptor(nodeProto, "textContent").set;
|
|
1386
|
+
}
|
|
1264
1387
|
// -----------------------------------------------------------------------------
|
|
1265
1388
|
// List Node
|
|
1266
1389
|
// -----------------------------------------------------------------------------
|
|
@@ -1476,9 +1599,13 @@
|
|
|
1476
1599
|
return mapping;
|
|
1477
1600
|
}
|
|
1478
1601
|
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1602
|
+
let nodeInsertBefore;
|
|
1603
|
+
let nodeRemoveChild;
|
|
1604
|
+
if (typeof Node !== "undefined") {
|
|
1605
|
+
const nodeProto = Node.prototype;
|
|
1606
|
+
nodeInsertBefore = nodeProto.insertBefore;
|
|
1607
|
+
nodeRemoveChild = nodeProto.removeChild;
|
|
1608
|
+
}
|
|
1482
1609
|
class VHtml {
|
|
1483
1610
|
html;
|
|
1484
1611
|
parentEl;
|
|
@@ -1734,127 +1861,6 @@
|
|
|
1734
1861
|
setup() { }
|
|
1735
1862
|
}
|
|
1736
1863
|
|
|
1737
|
-
// Maps fibers to thrown errors
|
|
1738
|
-
const fibersInError = new WeakMap();
|
|
1739
|
-
const nodeErrorHandlers = new WeakMap();
|
|
1740
|
-
function destroyApp(app, error) {
|
|
1741
|
-
try {
|
|
1742
|
-
app.destroy();
|
|
1743
|
-
}
|
|
1744
|
-
catch (e) {
|
|
1745
|
-
// mute all errors here because we are in a corrupted state anyway
|
|
1746
|
-
}
|
|
1747
|
-
const e = Object.assign(new OwlError(`[Owl] Unhandled error. Destroying the root component`), {
|
|
1748
|
-
cause: error,
|
|
1749
|
-
});
|
|
1750
|
-
return e;
|
|
1751
|
-
}
|
|
1752
|
-
function _handleError(node, error) {
|
|
1753
|
-
if (!node) {
|
|
1754
|
-
return false;
|
|
1755
|
-
}
|
|
1756
|
-
const fiber = node.fiber;
|
|
1757
|
-
if (fiber) {
|
|
1758
|
-
fibersInError.set(fiber, error);
|
|
1759
|
-
}
|
|
1760
|
-
const errorHandlers = nodeErrorHandlers.get(node);
|
|
1761
|
-
if (errorHandlers) {
|
|
1762
|
-
let handled = false;
|
|
1763
|
-
// execute in the opposite order
|
|
1764
|
-
const finalize = () => destroyApp(node.app, error);
|
|
1765
|
-
for (let i = errorHandlers.length - 1; i >= 0; i--) {
|
|
1766
|
-
try {
|
|
1767
|
-
errorHandlers[i](error, finalize);
|
|
1768
|
-
handled = true;
|
|
1769
|
-
break;
|
|
1770
|
-
}
|
|
1771
|
-
catch (e) {
|
|
1772
|
-
error = e;
|
|
1773
|
-
}
|
|
1774
|
-
}
|
|
1775
|
-
if (handled) {
|
|
1776
|
-
return true;
|
|
1777
|
-
}
|
|
1778
|
-
}
|
|
1779
|
-
return _handleError(node.parent, error);
|
|
1780
|
-
}
|
|
1781
|
-
function handleError(params) {
|
|
1782
|
-
let { error } = params;
|
|
1783
|
-
const node = "node" in params ? params.node : params.fiber.node;
|
|
1784
|
-
const fiber = "fiber" in params ? params.fiber : node.fiber;
|
|
1785
|
-
if (fiber) {
|
|
1786
|
-
// resets the fibers on components if possible. This is important so that
|
|
1787
|
-
// new renderings can be properly included in the initial one, if any.
|
|
1788
|
-
let current = fiber;
|
|
1789
|
-
do {
|
|
1790
|
-
current.node.fiber = current;
|
|
1791
|
-
fibersInError.set(current, error);
|
|
1792
|
-
current = current.parent;
|
|
1793
|
-
} while (current);
|
|
1794
|
-
fibersInError.set(fiber.root, error);
|
|
1795
|
-
}
|
|
1796
|
-
const handled = _handleError(node, error);
|
|
1797
|
-
if (!handled) {
|
|
1798
|
-
throw destroyApp(node.app, error);
|
|
1799
|
-
}
|
|
1800
|
-
}
|
|
1801
|
-
|
|
1802
|
-
// -----------------------------------------------------------------------------
|
|
1803
|
-
// hooks
|
|
1804
|
-
// -----------------------------------------------------------------------------
|
|
1805
|
-
function decorate(node, f, hookName) {
|
|
1806
|
-
const result = f.bind(node.component);
|
|
1807
|
-
if (node.app.dev) {
|
|
1808
|
-
const suffix = f.name ? ` <${f.name}>` : "";
|
|
1809
|
-
Reflect.defineProperty(result, "name", {
|
|
1810
|
-
value: hookName + suffix,
|
|
1811
|
-
});
|
|
1812
|
-
}
|
|
1813
|
-
return result;
|
|
1814
|
-
}
|
|
1815
|
-
function onWillStart(fn) {
|
|
1816
|
-
const { node } = getContext("component");
|
|
1817
|
-
node.willStart.push(decorate(node, fn, "onWillStart"));
|
|
1818
|
-
}
|
|
1819
|
-
function onWillUpdateProps(fn) {
|
|
1820
|
-
const { node } = getContext("component");
|
|
1821
|
-
node.willUpdateProps.push(decorate(node, fn, "onWillUpdateProps"));
|
|
1822
|
-
}
|
|
1823
|
-
function onMounted(fn) {
|
|
1824
|
-
const { node } = getContext("component");
|
|
1825
|
-
node.mounted.push(decorate(node, fn, "onMounted"));
|
|
1826
|
-
}
|
|
1827
|
-
function onWillPatch(fn) {
|
|
1828
|
-
const { node } = getContext("component");
|
|
1829
|
-
node.willPatch.unshift(decorate(node, fn, "onWillPatch"));
|
|
1830
|
-
}
|
|
1831
|
-
function onPatched(fn) {
|
|
1832
|
-
const { node } = getContext("component");
|
|
1833
|
-
node.patched.push(decorate(node, fn, "onPatched"));
|
|
1834
|
-
}
|
|
1835
|
-
function onWillUnmount(fn) {
|
|
1836
|
-
const { node } = getContext("component");
|
|
1837
|
-
node.willUnmount.unshift(decorate(node, fn, "onWillUnmount"));
|
|
1838
|
-
}
|
|
1839
|
-
function onWillDestroy(fn) {
|
|
1840
|
-
const context = getContext();
|
|
1841
|
-
if (context.type === "component") {
|
|
1842
|
-
context.node.willDestroy.unshift(decorate(context.node, fn, "onWillDestroy"));
|
|
1843
|
-
}
|
|
1844
|
-
else {
|
|
1845
|
-
context.manager.onDestroyCb.push(fn);
|
|
1846
|
-
}
|
|
1847
|
-
}
|
|
1848
|
-
function onError(callback) {
|
|
1849
|
-
const { node } = getContext("component");
|
|
1850
|
-
let handlers = nodeErrorHandlers.get(node);
|
|
1851
|
-
if (!handlers) {
|
|
1852
|
-
handlers = [];
|
|
1853
|
-
nodeErrorHandlers.set(node, handlers);
|
|
1854
|
-
}
|
|
1855
|
-
handlers.push(callback.bind(node.component));
|
|
1856
|
-
}
|
|
1857
|
-
|
|
1858
1864
|
var ComputationState;
|
|
1859
1865
|
(function (ComputationState) {
|
|
1860
1866
|
ComputationState[ComputationState["EXECUTED"] = 0] = "EXECUTED";
|
|
@@ -1947,6 +1953,20 @@
|
|
|
1947
1953
|
}
|
|
1948
1954
|
sources.clear();
|
|
1949
1955
|
}
|
|
1956
|
+
function disposeComputation(computation) {
|
|
1957
|
+
for (const source of computation.sources) {
|
|
1958
|
+
source.observers.delete(computation);
|
|
1959
|
+
// Recursively dispose derived computations that lost all observers
|
|
1960
|
+
if ("compute" in source &&
|
|
1961
|
+
source.isDerived &&
|
|
1962
|
+
source.observers.size === 0) {
|
|
1963
|
+
disposeComputation(source);
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
computation.sources.clear();
|
|
1967
|
+
// Mark as stale so it recomputes correctly if ever re-used (shared computed case)
|
|
1968
|
+
computation.state = ComputationState.STALE;
|
|
1969
|
+
}
|
|
1950
1970
|
function markDownstream(computation) {
|
|
1951
1971
|
const stack = [computation];
|
|
1952
1972
|
let current;
|
|
@@ -1978,668 +1998,332 @@
|
|
|
1978
1998
|
return result;
|
|
1979
1999
|
}
|
|
1980
2000
|
|
|
1981
|
-
|
|
1982
|
-
const
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
const numberType = function validateNumber(context) {
|
|
1988
|
-
if (typeof context.value !== "number") {
|
|
1989
|
-
context.addIssue({ message: "value is not a number" });
|
|
2001
|
+
// Maps fibers to thrown errors
|
|
2002
|
+
const fibersInError = new WeakMap();
|
|
2003
|
+
const nodeErrorHandlers = new WeakMap();
|
|
2004
|
+
function destroyApp(app, error) {
|
|
2005
|
+
try {
|
|
2006
|
+
app.destroy();
|
|
1990
2007
|
}
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
if (typeof context.value !== "string") {
|
|
1994
|
-
context.addIssue({ message: "value is not a string" });
|
|
2008
|
+
catch (e) {
|
|
2009
|
+
// mute all errors here because we are in a corrupted state anyway
|
|
1995
2010
|
}
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
context.addIssue({ message: "value is not an array" });
|
|
2001
|
-
return;
|
|
2002
|
-
}
|
|
2003
|
-
if (!elementType) {
|
|
2004
|
-
return;
|
|
2005
|
-
}
|
|
2006
|
-
for (let index = 0; index < context.value.length; index++) {
|
|
2007
|
-
context.withKey(index).validate(elementType);
|
|
2008
|
-
}
|
|
2009
|
-
};
|
|
2010
|
-
}
|
|
2011
|
-
function constructorType(constructor) {
|
|
2012
|
-
return function validateConstructor(context) {
|
|
2013
|
-
if (!(typeof context.value === "function") ||
|
|
2014
|
-
!(context.value === constructor || context.value.prototype instanceof constructor)) {
|
|
2015
|
-
context.addIssue({ message: `value is not '${constructor.name}' or an extension` });
|
|
2016
|
-
}
|
|
2017
|
-
};
|
|
2011
|
+
const e = Object.assign(new OwlError(`[Owl] Unhandled error. Destroying the root component`), {
|
|
2012
|
+
cause: error,
|
|
2013
|
+
});
|
|
2014
|
+
return e;
|
|
2018
2015
|
}
|
|
2019
|
-
function
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2016
|
+
function _handleError(node, error) {
|
|
2017
|
+
if (!node) {
|
|
2018
|
+
return false;
|
|
2019
|
+
}
|
|
2020
|
+
const fiber = node.fiber;
|
|
2021
|
+
if (fiber) {
|
|
2022
|
+
fibersInError.set(fiber, error);
|
|
2023
|
+
}
|
|
2024
|
+
const errorHandlers = nodeErrorHandlers.get(node);
|
|
2025
|
+
if (errorHandlers) {
|
|
2026
|
+
let handled = false;
|
|
2027
|
+
// execute in the opposite order
|
|
2028
|
+
const finalize = () => destroyApp(node.app, error);
|
|
2029
|
+
for (let i = errorHandlers.length - 1; i >= 0; i--) {
|
|
2030
|
+
try {
|
|
2031
|
+
errorHandlers[i](error, finalize);
|
|
2032
|
+
handled = true;
|
|
2033
|
+
break;
|
|
2034
|
+
}
|
|
2035
|
+
catch (e) {
|
|
2036
|
+
error = e;
|
|
2037
|
+
}
|
|
2024
2038
|
}
|
|
2025
|
-
if (
|
|
2026
|
-
|
|
2039
|
+
if (handled) {
|
|
2040
|
+
return true;
|
|
2027
2041
|
}
|
|
2028
|
-
}
|
|
2042
|
+
}
|
|
2043
|
+
return _handleError(node.parent, error);
|
|
2029
2044
|
}
|
|
2030
|
-
function
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
};
|
|
2050
|
-
}
|
|
2051
|
-
function literalType(literal) {
|
|
2052
|
-
return function validateLiteral(context) {
|
|
2053
|
-
if (context.value !== literal) {
|
|
2054
|
-
context.addIssue({
|
|
2055
|
-
message: `value is not equal to ${typeof literal === "string" ? `'${literal}'` : literal}`,
|
|
2056
|
-
});
|
|
2057
|
-
}
|
|
2058
|
-
};
|
|
2045
|
+
function handleError(params) {
|
|
2046
|
+
let { error } = params;
|
|
2047
|
+
const node = "node" in params ? params.node : params.fiber.node;
|
|
2048
|
+
const fiber = "fiber" in params ? params.fiber : node.fiber;
|
|
2049
|
+
if (fiber) {
|
|
2050
|
+
// resets the fibers on components if possible. This is important so that
|
|
2051
|
+
// new renderings can be properly included in the initial one, if any.
|
|
2052
|
+
let current = fiber;
|
|
2053
|
+
do {
|
|
2054
|
+
current.node.fiber = current;
|
|
2055
|
+
fibersInError.set(current, error);
|
|
2056
|
+
current = current.parent;
|
|
2057
|
+
} while (current);
|
|
2058
|
+
fibersInError.set(fiber.root, error);
|
|
2059
|
+
}
|
|
2060
|
+
const handled = _handleError(node, error);
|
|
2061
|
+
if (!handled) {
|
|
2062
|
+
throw destroyApp(node.app, error);
|
|
2063
|
+
}
|
|
2059
2064
|
}
|
|
2060
|
-
|
|
2061
|
-
|
|
2065
|
+
|
|
2066
|
+
function makeChildFiber(node, parent) {
|
|
2067
|
+
let current = node.fiber;
|
|
2068
|
+
if (current) {
|
|
2069
|
+
cancelFibers(current.children);
|
|
2070
|
+
current.root = null;
|
|
2071
|
+
}
|
|
2072
|
+
return new Fiber(node, parent);
|
|
2062
2073
|
}
|
|
2063
|
-
function
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2074
|
+
function makeRootFiber(node) {
|
|
2075
|
+
let current = node.fiber;
|
|
2076
|
+
if (current) {
|
|
2077
|
+
let root = current.root;
|
|
2078
|
+
// lock root fiber because canceling children fibers may destroy components,
|
|
2079
|
+
// which means any arbitrary code can be run in onWillDestroy, which may
|
|
2080
|
+
// trigger new renderings
|
|
2081
|
+
root.locked = true;
|
|
2082
|
+
root.setCounter(root.counter + 1 - cancelFibers(current.children));
|
|
2083
|
+
root.locked = false;
|
|
2084
|
+
current.children = [];
|
|
2085
|
+
current.childrenMap = {};
|
|
2086
|
+
current.bdom = null;
|
|
2087
|
+
if (fibersInError.has(current)) {
|
|
2088
|
+
fibersInError.delete(current);
|
|
2089
|
+
fibersInError.delete(root);
|
|
2090
|
+
current.appliedToDom = false;
|
|
2091
|
+
if (current instanceof RootFiber) {
|
|
2092
|
+
// it is possible that this fiber is a fiber that crashed while being
|
|
2093
|
+
// mounted, so the mounted list is possibly corrupted. We restore it to
|
|
2094
|
+
// its normal initial state (which is empty list or a list with a mount
|
|
2095
|
+
// fiber.
|
|
2096
|
+
current.mounted = current instanceof MountFiber ? [current] : [];
|
|
2070
2097
|
}
|
|
2071
|
-
continue;
|
|
2072
2098
|
}
|
|
2073
|
-
|
|
2099
|
+
return current;
|
|
2074
2100
|
}
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
missingKeys,
|
|
2079
|
-
});
|
|
2101
|
+
const fiber = new RootFiber(node, null);
|
|
2102
|
+
if (node.willPatch.length) {
|
|
2103
|
+
fiber.willPatch.push(fiber);
|
|
2080
2104
|
}
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
const missingKeys = keys.filter((key) => {
|
|
2084
|
-
if (key.endsWith("?")) {
|
|
2085
|
-
return false;
|
|
2086
|
-
}
|
|
2087
|
-
return !(key in context.value);
|
|
2088
|
-
});
|
|
2089
|
-
if (missingKeys.length) {
|
|
2090
|
-
context.addIssue({
|
|
2091
|
-
message: "object value have missing keys",
|
|
2092
|
-
missingKeys,
|
|
2093
|
-
});
|
|
2105
|
+
if (node.patched.length) {
|
|
2106
|
+
fiber.patched.push(fiber);
|
|
2094
2107
|
}
|
|
2108
|
+
return fiber;
|
|
2095
2109
|
}
|
|
2096
|
-
function
|
|
2097
|
-
|
|
2098
|
-
if (typeof context.value !== "object" ||
|
|
2099
|
-
Array.isArray(context.value) ||
|
|
2100
|
-
context.value === null) {
|
|
2101
|
-
context.addIssue({ message: "value is not an object" });
|
|
2102
|
-
return;
|
|
2103
|
-
}
|
|
2104
|
-
if (!schema) {
|
|
2105
|
-
return;
|
|
2106
|
-
}
|
|
2107
|
-
if (Array.isArray(schema)) {
|
|
2108
|
-
validateObjectKeys(context, schema);
|
|
2109
|
-
}
|
|
2110
|
-
else {
|
|
2111
|
-
validateObjectShape(context, schema);
|
|
2112
|
-
}
|
|
2113
|
-
};
|
|
2114
|
-
}
|
|
2115
|
-
function promiseType(type) {
|
|
2116
|
-
return function validatePromise(context) {
|
|
2117
|
-
if (!(context.value instanceof Promise)) {
|
|
2118
|
-
context.addIssue({ message: "value is not a promise" });
|
|
2119
|
-
}
|
|
2120
|
-
};
|
|
2110
|
+
function throwOnRender() {
|
|
2111
|
+
throw new OwlError("Attempted to render cancelled fiber");
|
|
2121
2112
|
}
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2113
|
+
/**
|
|
2114
|
+
* @returns number of not-yet rendered fibers cancelled
|
|
2115
|
+
*/
|
|
2116
|
+
function cancelFibers(fibers) {
|
|
2117
|
+
let result = 0;
|
|
2118
|
+
for (let fiber of fibers) {
|
|
2119
|
+
let node = fiber.node;
|
|
2120
|
+
fiber.render = throwOnRender;
|
|
2121
|
+
if (node.status === 0 /* STATUS.NEW */) {
|
|
2122
|
+
node.cancel();
|
|
2129
2123
|
}
|
|
2130
|
-
|
|
2131
|
-
|
|
2124
|
+
node.fiber = null;
|
|
2125
|
+
if (fiber.bdom) {
|
|
2126
|
+
// if fiber has been rendered, this means that the component props have
|
|
2127
|
+
// been updated. however, this fiber will not be patched to the dom, so
|
|
2128
|
+
// it could happen that the next render compare the current props with
|
|
2129
|
+
// the same props, and skip the render completely. With the next line,
|
|
2130
|
+
// we kindly request the component code to force a render, so it works as
|
|
2131
|
+
// expected.
|
|
2132
|
+
node.forceNextRender = true;
|
|
2132
2133
|
}
|
|
2133
|
-
|
|
2134
|
-
|
|
2134
|
+
else {
|
|
2135
|
+
result++;
|
|
2135
2136
|
}
|
|
2136
|
-
|
|
2137
|
+
result += cancelFibers(fiber.children);
|
|
2138
|
+
}
|
|
2139
|
+
return result;
|
|
2137
2140
|
}
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2141
|
+
class Fiber {
|
|
2142
|
+
node;
|
|
2143
|
+
bdom = null;
|
|
2144
|
+
root; // A Fiber that has been replaced by another has no root
|
|
2145
|
+
parent;
|
|
2146
|
+
children = [];
|
|
2147
|
+
appliedToDom = false;
|
|
2148
|
+
deep = false;
|
|
2149
|
+
childrenMap = {};
|
|
2150
|
+
constructor(node, parent) {
|
|
2151
|
+
this.node = node;
|
|
2152
|
+
this.parent = parent;
|
|
2153
|
+
if (parent) {
|
|
2154
|
+
this.deep = parent.deep;
|
|
2155
|
+
const root = parent.root;
|
|
2156
|
+
root.setCounter(root.counter + 1);
|
|
2157
|
+
this.root = root;
|
|
2158
|
+
parent.children.push(this);
|
|
2147
2159
|
}
|
|
2148
|
-
|
|
2149
|
-
|
|
2160
|
+
else {
|
|
2161
|
+
this.root = this;
|
|
2150
2162
|
}
|
|
2151
|
-
}
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
let
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
+
}
|
|
2164
|
+
render() {
|
|
2165
|
+
// if some parent has a fiber => register in followup
|
|
2166
|
+
let prev = this.root.node;
|
|
2167
|
+
let scheduler = prev.app.scheduler;
|
|
2168
|
+
let current = prev.parent;
|
|
2169
|
+
while (current) {
|
|
2170
|
+
if (current.fiber) {
|
|
2171
|
+
let root = current.fiber.root;
|
|
2172
|
+
if (root.counter === 0 && prev.parentKey in current.fiber.childrenMap) {
|
|
2173
|
+
current = root.node;
|
|
2174
|
+
}
|
|
2175
|
+
else {
|
|
2176
|
+
scheduler.delayedRenders.push(this);
|
|
2177
|
+
return;
|
|
2178
|
+
}
|
|
2163
2179
|
}
|
|
2164
|
-
|
|
2180
|
+
prev = current;
|
|
2181
|
+
current = current.parent;
|
|
2165
2182
|
}
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2183
|
+
// there are no current rendering from above => we can render
|
|
2184
|
+
this._render();
|
|
2185
|
+
}
|
|
2186
|
+
_render() {
|
|
2187
|
+
const node = this.node;
|
|
2188
|
+
const root = this.root;
|
|
2189
|
+
if (root) {
|
|
2190
|
+
// todo: should use updateComputation somewhere else.
|
|
2191
|
+
const c = getCurrentComputation();
|
|
2192
|
+
removeSources(node.signalComputation);
|
|
2193
|
+
setComputation(node.signalComputation);
|
|
2194
|
+
try {
|
|
2195
|
+
this.bdom = true;
|
|
2196
|
+
this.bdom = node.renderFn();
|
|
2197
|
+
}
|
|
2198
|
+
catch (e) {
|
|
2199
|
+
node.app.handleError({ node, error: e });
|
|
2200
|
+
}
|
|
2201
|
+
setComputation(c);
|
|
2202
|
+
root.setCounter(root.counter - 1);
|
|
2176
2203
|
}
|
|
2177
|
-
}
|
|
2204
|
+
}
|
|
2178
2205
|
}
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
if (issues.length) {
|
|
2207
|
-
const issueStrings = JSON.stringify(issues, (key, value) => {
|
|
2208
|
-
if (typeof value === "function") {
|
|
2209
|
-
return value.name;
|
|
2206
|
+
class RootFiber extends Fiber {
|
|
2207
|
+
counter = 1;
|
|
2208
|
+
// only add stuff in this if they have registered some hooks
|
|
2209
|
+
willPatch = [];
|
|
2210
|
+
patched = [];
|
|
2211
|
+
mounted = [];
|
|
2212
|
+
// A fiber is typically locked when it is completing and the patch has not, or is being applied.
|
|
2213
|
+
// i.e.: render triggered in onWillUnmount or in willPatch will be delayed
|
|
2214
|
+
locked = false;
|
|
2215
|
+
complete() {
|
|
2216
|
+
const node = this.node;
|
|
2217
|
+
this.locked = true;
|
|
2218
|
+
let current = undefined;
|
|
2219
|
+
let mountedFibers = this.mounted;
|
|
2220
|
+
try {
|
|
2221
|
+
// Step 1: calling all willPatch lifecycle hooks
|
|
2222
|
+
for (current of this.willPatch) {
|
|
2223
|
+
// because of the asynchronous nature of the rendering, some parts of the
|
|
2224
|
+
// UI may have been rendered, then deleted in a followup rendering, and we
|
|
2225
|
+
// do not want to call onWillPatch in that case.
|
|
2226
|
+
let node = current.node;
|
|
2227
|
+
if (node.fiber === current) {
|
|
2228
|
+
const component = node.component;
|
|
2229
|
+
for (let cb of node.willPatch) {
|
|
2230
|
+
cb.call(component);
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2210
2233
|
}
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
},
|
|
2224
|
-
addIssue(issue) {
|
|
2225
|
-
issues.push({
|
|
2226
|
-
received: this.value,
|
|
2227
|
-
path: this.path,
|
|
2228
|
-
...issue,
|
|
2229
|
-
});
|
|
2230
|
-
},
|
|
2231
|
-
mergeIssues(newIssues) {
|
|
2232
|
-
issues.push(...newIssues);
|
|
2233
|
-
},
|
|
2234
|
-
validate(type) {
|
|
2235
|
-
type(this);
|
|
2236
|
-
if (!this.isValid && parent) {
|
|
2237
|
-
parent.issueDepth = this.issueDepth + 1;
|
|
2234
|
+
current = undefined;
|
|
2235
|
+
// Step 2: patching the dom
|
|
2236
|
+
node._patch();
|
|
2237
|
+
this.locked = false;
|
|
2238
|
+
// Step 4: calling all mounted lifecycle hooks
|
|
2239
|
+
while ((current = mountedFibers.pop())) {
|
|
2240
|
+
current = current;
|
|
2241
|
+
if (current.appliedToDom) {
|
|
2242
|
+
for (let cb of current.node.mounted) {
|
|
2243
|
+
cb();
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2238
2246
|
}
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
function validateType(value, validation) {
|
|
2249
|
-
const issues = [];
|
|
2250
|
-
validation(createContext$1(issues, value, []));
|
|
2251
|
-
return issues;
|
|
2252
|
-
}
|
|
2253
|
-
|
|
2254
|
-
function validateObjectWithDefaults(schema, defaultValues) {
|
|
2255
|
-
const keys = Array.isArray(schema) ? schema : Object.keys(schema);
|
|
2256
|
-
const mandatoryDefaultedKeys = keys.filter((key) => !key.endsWith("?") && key in defaultValues);
|
|
2257
|
-
return (context) => {
|
|
2258
|
-
if (mandatoryDefaultedKeys.length) {
|
|
2259
|
-
context.addIssue({
|
|
2260
|
-
message: "props have default values on mandatory keys",
|
|
2261
|
-
keys: mandatoryDefaultedKeys,
|
|
2262
|
-
});
|
|
2263
|
-
}
|
|
2264
|
-
context.validate(types.object(schema));
|
|
2265
|
-
};
|
|
2266
|
-
}
|
|
2267
|
-
function props(type, defaults) {
|
|
2268
|
-
const { node, app, componentName } = getContext("component");
|
|
2269
|
-
function getProp(key) {
|
|
2270
|
-
if (node.props[key] === undefined && defaults) {
|
|
2271
|
-
return defaults[key];
|
|
2272
|
-
}
|
|
2273
|
-
return node.props[key];
|
|
2274
|
-
}
|
|
2275
|
-
const result = Object.create(null);
|
|
2276
|
-
function applyPropGetters(keys) {
|
|
2277
|
-
for (const key of keys) {
|
|
2278
|
-
Reflect.defineProperty(result, key, {
|
|
2279
|
-
enumerable: true,
|
|
2280
|
-
get: getProp.bind(null, key),
|
|
2281
|
-
});
|
|
2282
|
-
}
|
|
2283
|
-
}
|
|
2284
|
-
if (type) {
|
|
2285
|
-
const keys = (Array.isArray(type) ? type : Object.keys(type)).map((key) => key.endsWith("?") ? key.slice(0, -1) : key);
|
|
2286
|
-
applyPropGetters(keys);
|
|
2287
|
-
if (app.dev) {
|
|
2288
|
-
const validation = defaults ? validateObjectWithDefaults(type, defaults) : types.object(type);
|
|
2289
|
-
assertType(node.props, validation, `Invalid component props (${componentName})`);
|
|
2290
|
-
node.willUpdateProps.push((np) => {
|
|
2291
|
-
assertType(np, validation, `Invalid component props (${componentName})`);
|
|
2292
|
-
});
|
|
2293
|
-
}
|
|
2294
|
-
}
|
|
2295
|
-
else {
|
|
2296
|
-
applyPropGetters(Object.keys(node.props));
|
|
2297
|
-
node.willUpdateProps.push((np) => {
|
|
2298
|
-
for (let key in result) {
|
|
2299
|
-
Reflect.deleteProperty(result, key);
|
|
2247
|
+
// Step 5: calling all patched hooks
|
|
2248
|
+
let patchedFibers = this.patched;
|
|
2249
|
+
while ((current = patchedFibers.pop())) {
|
|
2250
|
+
current = current;
|
|
2251
|
+
if (current.appliedToDom) {
|
|
2252
|
+
for (let cb of current.node.patched) {
|
|
2253
|
+
cb();
|
|
2254
|
+
}
|
|
2255
|
+
}
|
|
2300
2256
|
}
|
|
2301
|
-
applyPropGetters(Object.keys(np));
|
|
2302
|
-
});
|
|
2303
|
-
}
|
|
2304
|
-
return result;
|
|
2305
|
-
}
|
|
2306
|
-
|
|
2307
|
-
const VText = text("").constructor;
|
|
2308
|
-
class VPortal extends VText {
|
|
2309
|
-
content;
|
|
2310
|
-
selector;
|
|
2311
|
-
target = null;
|
|
2312
|
-
constructor(selector, content) {
|
|
2313
|
-
super("");
|
|
2314
|
-
this.selector = selector;
|
|
2315
|
-
this.content = content;
|
|
2316
|
-
}
|
|
2317
|
-
mount(parent, anchor) {
|
|
2318
|
-
super.mount(parent, anchor);
|
|
2319
|
-
this.target = document.querySelector(this.selector);
|
|
2320
|
-
if (this.target) {
|
|
2321
|
-
this.content.mount(this.target, null);
|
|
2322
|
-
}
|
|
2323
|
-
else {
|
|
2324
|
-
this.content.mount(parent, anchor);
|
|
2325
2257
|
}
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2258
|
+
catch (e) {
|
|
2259
|
+
// if mountedFibers is not empty, this means that a crash occured while
|
|
2260
|
+
// calling the mounted hooks of some component. So, there may still be
|
|
2261
|
+
// some component that have been mounted, but for which the mounted hooks
|
|
2262
|
+
// have not been called. Here, we remove the willUnmount hooks for these
|
|
2263
|
+
// specific component to prevent a worse situation (willUnmount being
|
|
2264
|
+
// called even though mounted has not been called)
|
|
2265
|
+
for (let fiber of mountedFibers) {
|
|
2266
|
+
fiber.node.willUnmount = [];
|
|
2267
|
+
}
|
|
2268
|
+
this.locked = false;
|
|
2269
|
+
node.app.handleError({ fiber: current || this, error: e });
|
|
2335
2270
|
}
|
|
2336
2271
|
}
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
if (
|
|
2340
|
-
this.
|
|
2341
|
-
}
|
|
2342
|
-
else {
|
|
2343
|
-
this.content = other.content;
|
|
2344
|
-
this.content.mount(this.target, null);
|
|
2272
|
+
setCounter(newValue) {
|
|
2273
|
+
this.counter = newValue;
|
|
2274
|
+
if (newValue === 0) {
|
|
2275
|
+
this.node.app.scheduler.flush();
|
|
2345
2276
|
}
|
|
2346
2277
|
}
|
|
2347
2278
|
}
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
}
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2279
|
+
class MountFiber extends RootFiber {
|
|
2280
|
+
target;
|
|
2281
|
+
position;
|
|
2282
|
+
constructor(node, target, options = {}) {
|
|
2283
|
+
super(node, null);
|
|
2284
|
+
this.target = target;
|
|
2285
|
+
this.position = options.position || "last-child";
|
|
2286
|
+
}
|
|
2287
|
+
complete() {
|
|
2288
|
+
let current = this;
|
|
2289
|
+
try {
|
|
2290
|
+
const node = this.node;
|
|
2291
|
+
node.children = this.childrenMap;
|
|
2292
|
+
node.app.constructor.validateTarget(this.target);
|
|
2293
|
+
if (node.bdom) {
|
|
2294
|
+
// this is a complicated situation: if we mount a fiber with an existing
|
|
2295
|
+
// bdom, this means that this same fiber was already completed, mounted,
|
|
2296
|
+
// but a crash occurred in some mounted hook. Then, it was handled and
|
|
2297
|
+
// the new rendering is being applied.
|
|
2298
|
+
node.updateDom();
|
|
2299
|
+
}
|
|
2300
|
+
else {
|
|
2301
|
+
node.bdom = this.bdom;
|
|
2302
|
+
if (this.position === "last-child" || this.target.childNodes.length === 0) {
|
|
2303
|
+
mount$1(node.bdom, this.target);
|
|
2370
2304
|
}
|
|
2371
2305
|
else {
|
|
2372
|
-
|
|
2306
|
+
const firstChild = this.target.childNodes[0];
|
|
2307
|
+
mount$1(node.bdom, this.target, firstChild);
|
|
2373
2308
|
}
|
|
2374
2309
|
}
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
let current = node.fiber;
|
|
2393
|
-
if (current) {
|
|
2394
|
-
let root = current.root;
|
|
2395
|
-
// lock root fiber because canceling children fibers may destroy components,
|
|
2396
|
-
// which means any arbitrary code can be run in onWillDestroy, which may
|
|
2397
|
-
// trigger new renderings
|
|
2398
|
-
root.locked = true;
|
|
2399
|
-
root.setCounter(root.counter + 1 - cancelFibers(current.children));
|
|
2400
|
-
root.locked = false;
|
|
2401
|
-
current.children = [];
|
|
2402
|
-
current.childrenMap = {};
|
|
2403
|
-
current.bdom = null;
|
|
2404
|
-
if (fibersInError.has(current)) {
|
|
2405
|
-
fibersInError.delete(current);
|
|
2406
|
-
fibersInError.delete(root);
|
|
2407
|
-
current.appliedToDom = false;
|
|
2408
|
-
if (current instanceof RootFiber) {
|
|
2409
|
-
// it is possible that this fiber is a fiber that crashed while being
|
|
2410
|
-
// mounted, so the mounted list is possibly corrupted. We restore it to
|
|
2411
|
-
// its normal initial state (which is empty list or a list with a mount
|
|
2412
|
-
// fiber.
|
|
2413
|
-
current.mounted = current instanceof MountFiber ? [current] : [];
|
|
2414
|
-
}
|
|
2415
|
-
}
|
|
2416
|
-
return current;
|
|
2417
|
-
}
|
|
2418
|
-
const fiber = new RootFiber(node, null);
|
|
2419
|
-
if (node.willPatch.length) {
|
|
2420
|
-
fiber.willPatch.push(fiber);
|
|
2421
|
-
}
|
|
2422
|
-
if (node.patched.length) {
|
|
2423
|
-
fiber.patched.push(fiber);
|
|
2424
|
-
}
|
|
2425
|
-
return fiber;
|
|
2426
|
-
}
|
|
2427
|
-
function throwOnRender() {
|
|
2428
|
-
throw new OwlError("Attempted to render cancelled fiber");
|
|
2429
|
-
}
|
|
2430
|
-
/**
|
|
2431
|
-
* @returns number of not-yet rendered fibers cancelled
|
|
2432
|
-
*/
|
|
2433
|
-
function cancelFibers(fibers) {
|
|
2434
|
-
let result = 0;
|
|
2435
|
-
for (let fiber of fibers) {
|
|
2436
|
-
let node = fiber.node;
|
|
2437
|
-
fiber.render = throwOnRender;
|
|
2438
|
-
if (node.status === 0 /* STATUS.NEW */) {
|
|
2439
|
-
node.cancel();
|
|
2440
|
-
}
|
|
2441
|
-
node.fiber = null;
|
|
2442
|
-
if (fiber.bdom) {
|
|
2443
|
-
// if fiber has been rendered, this means that the component props have
|
|
2444
|
-
// been updated. however, this fiber will not be patched to the dom, so
|
|
2445
|
-
// it could happen that the next render compare the current props with
|
|
2446
|
-
// the same props, and skip the render completely. With the next line,
|
|
2447
|
-
// we kindly request the component code to force a render, so it works as
|
|
2448
|
-
// expected.
|
|
2449
|
-
node.forceNextRender = true;
|
|
2450
|
-
}
|
|
2451
|
-
else {
|
|
2452
|
-
result++;
|
|
2453
|
-
}
|
|
2454
|
-
result += cancelFibers(fiber.children);
|
|
2455
|
-
}
|
|
2456
|
-
return result;
|
|
2457
|
-
}
|
|
2458
|
-
class Fiber {
|
|
2459
|
-
node;
|
|
2460
|
-
bdom = null;
|
|
2461
|
-
root; // A Fiber that has been replaced by another has no root
|
|
2462
|
-
parent;
|
|
2463
|
-
children = [];
|
|
2464
|
-
appliedToDom = false;
|
|
2465
|
-
deep = false;
|
|
2466
|
-
childrenMap = {};
|
|
2467
|
-
constructor(node, parent) {
|
|
2468
|
-
this.node = node;
|
|
2469
|
-
this.parent = parent;
|
|
2470
|
-
if (parent) {
|
|
2471
|
-
this.deep = parent.deep;
|
|
2472
|
-
const root = parent.root;
|
|
2473
|
-
root.setCounter(root.counter + 1);
|
|
2474
|
-
this.root = root;
|
|
2475
|
-
parent.children.push(this);
|
|
2476
|
-
}
|
|
2477
|
-
else {
|
|
2478
|
-
this.root = this;
|
|
2479
|
-
}
|
|
2480
|
-
}
|
|
2481
|
-
render() {
|
|
2482
|
-
// if some parent has a fiber => register in followup
|
|
2483
|
-
let prev = this.root.node;
|
|
2484
|
-
let scheduler = prev.app.scheduler;
|
|
2485
|
-
let current = prev.parent;
|
|
2486
|
-
while (current) {
|
|
2487
|
-
if (current.fiber) {
|
|
2488
|
-
let root = current.fiber.root;
|
|
2489
|
-
if (root.counter === 0 && prev.parentKey in current.fiber.childrenMap) {
|
|
2490
|
-
current = root.node;
|
|
2491
|
-
}
|
|
2492
|
-
else {
|
|
2493
|
-
scheduler.delayedRenders.push(this);
|
|
2494
|
-
return;
|
|
2495
|
-
}
|
|
2496
|
-
}
|
|
2497
|
-
prev = current;
|
|
2498
|
-
current = current.parent;
|
|
2499
|
-
}
|
|
2500
|
-
// there are no current rendering from above => we can render
|
|
2501
|
-
this._render();
|
|
2502
|
-
}
|
|
2503
|
-
_render() {
|
|
2504
|
-
const node = this.node;
|
|
2505
|
-
const root = this.root;
|
|
2506
|
-
if (root) {
|
|
2507
|
-
// todo: should use updateComputation somewhere else.
|
|
2508
|
-
const c = getCurrentComputation();
|
|
2509
|
-
setComputation(node.signalComputation);
|
|
2510
|
-
try {
|
|
2511
|
-
this.bdom = true;
|
|
2512
|
-
this.bdom = node.renderFn();
|
|
2513
|
-
}
|
|
2514
|
-
catch (e) {
|
|
2515
|
-
node.app.handleError({ node, error: e });
|
|
2516
|
-
}
|
|
2517
|
-
setComputation(c);
|
|
2518
|
-
root.setCounter(root.counter - 1);
|
|
2519
|
-
}
|
|
2520
|
-
}
|
|
2521
|
-
}
|
|
2522
|
-
class RootFiber extends Fiber {
|
|
2523
|
-
counter = 1;
|
|
2524
|
-
// only add stuff in this if they have registered some hooks
|
|
2525
|
-
willPatch = [];
|
|
2526
|
-
patched = [];
|
|
2527
|
-
mounted = [];
|
|
2528
|
-
// A fiber is typically locked when it is completing and the patch has not, or is being applied.
|
|
2529
|
-
// i.e.: render triggered in onWillUnmount or in willPatch will be delayed
|
|
2530
|
-
locked = false;
|
|
2531
|
-
complete() {
|
|
2532
|
-
const node = this.node;
|
|
2533
|
-
this.locked = true;
|
|
2534
|
-
let current = undefined;
|
|
2535
|
-
let mountedFibers = this.mounted;
|
|
2536
|
-
try {
|
|
2537
|
-
// Step 1: calling all willPatch lifecycle hooks
|
|
2538
|
-
for (current of this.willPatch) {
|
|
2539
|
-
// because of the asynchronous nature of the rendering, some parts of the
|
|
2540
|
-
// UI may have been rendered, then deleted in a followup rendering, and we
|
|
2541
|
-
// do not want to call onWillPatch in that case.
|
|
2542
|
-
let node = current.node;
|
|
2543
|
-
if (node.fiber === current) {
|
|
2544
|
-
const component = node.component;
|
|
2545
|
-
for (let cb of node.willPatch) {
|
|
2546
|
-
cb.call(component);
|
|
2547
|
-
}
|
|
2548
|
-
}
|
|
2549
|
-
}
|
|
2550
|
-
current = undefined;
|
|
2551
|
-
// Step 2: patching the dom
|
|
2552
|
-
node._patch();
|
|
2553
|
-
this.locked = false;
|
|
2554
|
-
// Step 4: calling all mounted lifecycle hooks
|
|
2555
|
-
while ((current = mountedFibers.pop())) {
|
|
2556
|
-
current = current;
|
|
2557
|
-
if (current.appliedToDom) {
|
|
2558
|
-
for (let cb of current.node.mounted) {
|
|
2559
|
-
cb();
|
|
2560
|
-
}
|
|
2561
|
-
}
|
|
2562
|
-
}
|
|
2563
|
-
// Step 5: calling all patched hooks
|
|
2564
|
-
let patchedFibers = this.patched;
|
|
2565
|
-
while ((current = patchedFibers.pop())) {
|
|
2566
|
-
current = current;
|
|
2567
|
-
if (current.appliedToDom) {
|
|
2568
|
-
for (let cb of current.node.patched) {
|
|
2569
|
-
cb();
|
|
2570
|
-
}
|
|
2571
|
-
}
|
|
2572
|
-
}
|
|
2573
|
-
}
|
|
2574
|
-
catch (e) {
|
|
2575
|
-
// if mountedFibers is not empty, this means that a crash occured while
|
|
2576
|
-
// calling the mounted hooks of some component. So, there may still be
|
|
2577
|
-
// some component that have been mounted, but for which the mounted hooks
|
|
2578
|
-
// have not been called. Here, we remove the willUnmount hooks for these
|
|
2579
|
-
// specific component to prevent a worse situation (willUnmount being
|
|
2580
|
-
// called even though mounted has not been called)
|
|
2581
|
-
for (let fiber of mountedFibers) {
|
|
2582
|
-
fiber.node.willUnmount = [];
|
|
2583
|
-
}
|
|
2584
|
-
this.locked = false;
|
|
2585
|
-
node.app.handleError({ fiber: current || this, error: e });
|
|
2586
|
-
}
|
|
2587
|
-
}
|
|
2588
|
-
setCounter(newValue) {
|
|
2589
|
-
this.counter = newValue;
|
|
2590
|
-
if (newValue === 0) {
|
|
2591
|
-
this.node.app.scheduler.flush();
|
|
2592
|
-
}
|
|
2593
|
-
}
|
|
2594
|
-
}
|
|
2595
|
-
class MountFiber extends RootFiber {
|
|
2596
|
-
target;
|
|
2597
|
-
position;
|
|
2598
|
-
constructor(node, target, options = {}) {
|
|
2599
|
-
super(node, null);
|
|
2600
|
-
this.target = target;
|
|
2601
|
-
this.position = options.position || "last-child";
|
|
2602
|
-
}
|
|
2603
|
-
complete() {
|
|
2604
|
-
let current = this;
|
|
2605
|
-
try {
|
|
2606
|
-
const node = this.node;
|
|
2607
|
-
node.children = this.childrenMap;
|
|
2608
|
-
node.app.constructor.validateTarget(this.target);
|
|
2609
|
-
if (node.bdom) {
|
|
2610
|
-
// this is a complicated situation: if we mount a fiber with an existing
|
|
2611
|
-
// bdom, this means that this same fiber was already completed, mounted,
|
|
2612
|
-
// but a crash occurred in some mounted hook. Then, it was handled and
|
|
2613
|
-
// the new rendering is being applied.
|
|
2614
|
-
node.updateDom();
|
|
2615
|
-
}
|
|
2616
|
-
else {
|
|
2617
|
-
node.bdom = this.bdom;
|
|
2618
|
-
if (this.position === "last-child" || this.target.childNodes.length === 0) {
|
|
2619
|
-
mount$1(node.bdom, this.target);
|
|
2620
|
-
}
|
|
2621
|
-
else {
|
|
2622
|
-
const firstChild = this.target.childNodes[0];
|
|
2623
|
-
mount$1(node.bdom, this.target, firstChild);
|
|
2624
|
-
}
|
|
2625
|
-
}
|
|
2626
|
-
// unregistering the fiber before mounted since it can do another render
|
|
2627
|
-
// and that the current rendering is obviously completed
|
|
2628
|
-
node.fiber = null;
|
|
2629
|
-
node.status = 1 /* STATUS.MOUNTED */;
|
|
2630
|
-
this.appliedToDom = true;
|
|
2631
|
-
let mountedFibers = this.mounted;
|
|
2632
|
-
while ((current = mountedFibers.pop())) {
|
|
2633
|
-
if (current.appliedToDom) {
|
|
2634
|
-
for (let cb of current.node.mounted) {
|
|
2635
|
-
cb();
|
|
2636
|
-
}
|
|
2637
|
-
}
|
|
2638
|
-
}
|
|
2639
|
-
}
|
|
2640
|
-
catch (e) {
|
|
2641
|
-
this.node.app.handleError({ fiber: current, error: e });
|
|
2642
|
-
}
|
|
2310
|
+
// unregistering the fiber before mounted since it can do another render
|
|
2311
|
+
// and that the current rendering is obviously completed
|
|
2312
|
+
node.fiber = null;
|
|
2313
|
+
node.status = 1 /* STATUS.MOUNTED */;
|
|
2314
|
+
this.appliedToDom = true;
|
|
2315
|
+
let mountedFibers = this.mounted;
|
|
2316
|
+
while ((current = mountedFibers.pop())) {
|
|
2317
|
+
if (current.appliedToDom) {
|
|
2318
|
+
for (let cb of current.node.mounted) {
|
|
2319
|
+
cb();
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2324
|
+
catch (e) {
|
|
2325
|
+
this.node.app.handleError({ fiber: current, error: e });
|
|
2326
|
+
}
|
|
2643
2327
|
}
|
|
2644
2328
|
}
|
|
2645
2329
|
|
|
@@ -2653,6 +2337,7 @@
|
|
|
2653
2337
|
forceNextRender = false;
|
|
2654
2338
|
parentKey;
|
|
2655
2339
|
props;
|
|
2340
|
+
defaultProps = {};
|
|
2656
2341
|
renderFn;
|
|
2657
2342
|
parent;
|
|
2658
2343
|
children = Object.create(null);
|
|
@@ -2664,6 +2349,7 @@
|
|
|
2664
2349
|
patched = [];
|
|
2665
2350
|
willDestroy = [];
|
|
2666
2351
|
signalComputation;
|
|
2352
|
+
computations = [];
|
|
2667
2353
|
pluginManager;
|
|
2668
2354
|
constructor(C, props, app, parent, parentKey) {
|
|
2669
2355
|
this.app = app;
|
|
@@ -2671,7 +2357,7 @@
|
|
|
2671
2357
|
this.parentKey = parentKey;
|
|
2672
2358
|
this.pluginManager = parent ? parent.pluginManager : app.pluginManager;
|
|
2673
2359
|
this.signalComputation = createComputation(() => this.render(false), false, ComputationState.EXECUTED);
|
|
2674
|
-
this.props =
|
|
2360
|
+
this.props = props;
|
|
2675
2361
|
contextStack.push({
|
|
2676
2362
|
type: "component",
|
|
2677
2363
|
app,
|
|
@@ -2807,10 +2493,19 @@
|
|
|
2807
2493
|
this.app.handleError({ error: e, node: this });
|
|
2808
2494
|
}
|
|
2809
2495
|
}
|
|
2496
|
+
for (const computation of this.computations) {
|
|
2497
|
+
disposeComputation(computation);
|
|
2498
|
+
}
|
|
2499
|
+
disposeComputation(this.signalComputation);
|
|
2810
2500
|
this.status = 3 /* STATUS.DESTROYED */;
|
|
2811
2501
|
}
|
|
2812
2502
|
async updateAndRender(props, parentFiber) {
|
|
2813
2503
|
props = Object.assign({}, props);
|
|
2504
|
+
for (const key in this.defaultProps) {
|
|
2505
|
+
if (props[key] === undefined) {
|
|
2506
|
+
props[key] = this.defaultProps[key];
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2814
2509
|
// update
|
|
2815
2510
|
const fiber = makeChildFiber(this, parentFiber);
|
|
2816
2511
|
this.fiber = fiber;
|
|
@@ -3439,6 +3134,13 @@
|
|
|
3439
3134
|
}
|
|
3440
3135
|
};
|
|
3441
3136
|
}
|
|
3137
|
+
function callHandler(fn, ctx, ev) {
|
|
3138
|
+
if (typeof fn !== "function") {
|
|
3139
|
+
throw new OwlError(`Invalid handler expression: the \`t-on\` expression should evaluate to a function, but got '${typeof fn}'. ` +
|
|
3140
|
+
`Did you mean to use an arrow function? (e.g. \`t-on-click="() => expr"\`)`);
|
|
3141
|
+
}
|
|
3142
|
+
fn.call(ctx["this"], ev);
|
|
3143
|
+
}
|
|
3442
3144
|
function modelExpr(value) {
|
|
3443
3145
|
if (typeof value !== "function" || typeof value.set !== "function") {
|
|
3444
3146
|
throw new OwlError(`Invalid t-model expression: expression should evaluate to a function with a 'set' method defined on it`);
|
|
@@ -3533,8 +3235,8 @@
|
|
|
3533
3235
|
createRef,
|
|
3534
3236
|
modelExpr,
|
|
3535
3237
|
createComponent,
|
|
3536
|
-
Portal,
|
|
3537
3238
|
callTemplate,
|
|
3239
|
+
callHandler,
|
|
3538
3240
|
};
|
|
3539
3241
|
|
|
3540
3242
|
const bdom = { text, createBlock, list, multi, html, toggler, comment };
|
|
@@ -3642,7 +3344,6 @@
|
|
|
3642
3344
|
return name;
|
|
3643
3345
|
}
|
|
3644
3346
|
xml.nextId = 1;
|
|
3645
|
-
TemplateSet.registerTemplate("__portal__", portalTemplate);
|
|
3646
3347
|
|
|
3647
3348
|
/**
|
|
3648
3349
|
* Owl QWeb Expression Parser
|
|
@@ -3854,11 +3555,18 @@
|
|
|
3854
3555
|
* the arrow operator, then we add the current (or some previous tokens) token to
|
|
3855
3556
|
* the list of variables so it does not get replaced by a lookup in the context
|
|
3856
3557
|
*/
|
|
3857
|
-
|
|
3558
|
+
// Leading spaces are trimmed during tokenization, so they need to be added back for some values
|
|
3559
|
+
const paddedValues = new Map([["in ", " in "]]);
|
|
3560
|
+
/**
|
|
3561
|
+
* Processes a javascript expression: compiles variable lookups and detects
|
|
3562
|
+
* top-level arrow functions with their free variables, all in a single pass.
|
|
3563
|
+
*/
|
|
3564
|
+
function processExpr(expr) {
|
|
3858
3565
|
const localVars = new Set();
|
|
3859
3566
|
const tokens = tokenize(expr);
|
|
3860
3567
|
let i = 0;
|
|
3861
3568
|
let stack = []; // to track last opening (, [ or {
|
|
3569
|
+
let topLevelArrowIndex = -1;
|
|
3862
3570
|
while (i < tokens.length) {
|
|
3863
3571
|
let token = tokens[i];
|
|
3864
3572
|
let prevToken = tokens[i - 1];
|
|
@@ -3899,18 +3607,21 @@
|
|
|
3899
3607
|
token.value = token.replace((expr) => compileExpr(expr));
|
|
3900
3608
|
}
|
|
3901
3609
|
if (nextToken && nextToken.type === "OPERATOR" && nextToken.value === "=>") {
|
|
3610
|
+
if (stack.length === 0) {
|
|
3611
|
+
topLevelArrowIndex = i + 1;
|
|
3612
|
+
}
|
|
3902
3613
|
if (token.type === "RIGHT_PAREN") {
|
|
3903
3614
|
let j = i - 1;
|
|
3904
3615
|
while (j > 0 && tokens[j].type !== "LEFT_PAREN") {
|
|
3905
3616
|
if (tokens[j].type === "SYMBOL" && tokens[j].originalValue) {
|
|
3906
3617
|
tokens[j].value = tokens[j].originalValue;
|
|
3907
|
-
localVars.add(tokens[j].value);
|
|
3618
|
+
localVars.add(tokens[j].value);
|
|
3908
3619
|
}
|
|
3909
3620
|
j--;
|
|
3910
3621
|
}
|
|
3911
3622
|
}
|
|
3912
3623
|
else {
|
|
3913
|
-
localVars.add(token.value);
|
|
3624
|
+
localVars.add(token.value);
|
|
3914
3625
|
}
|
|
3915
3626
|
}
|
|
3916
3627
|
if (isVar) {
|
|
@@ -3931,14 +3642,24 @@
|
|
|
3931
3642
|
token.isLocal = true;
|
|
3932
3643
|
}
|
|
3933
3644
|
}
|
|
3934
|
-
|
|
3645
|
+
// Collect free variables from arrow function body
|
|
3646
|
+
let freeVariables = null;
|
|
3647
|
+
if (topLevelArrowIndex !== -1) {
|
|
3648
|
+
freeVariables = [];
|
|
3649
|
+
const seen = new Set();
|
|
3650
|
+
for (let i = topLevelArrowIndex + 1; i < tokens.length; i++) {
|
|
3651
|
+
const t = tokens[i];
|
|
3652
|
+
if (t.varName && !t.isLocal && t.varName !== "this" && !seen.has(t.varName)) {
|
|
3653
|
+
seen.add(t.varName);
|
|
3654
|
+
freeVariables.push(t.varName);
|
|
3655
|
+
}
|
|
3656
|
+
}
|
|
3657
|
+
}
|
|
3658
|
+
const compiled = tokens.map((t) => paddedValues.get(t.value) || t.value).join("");
|
|
3659
|
+
return { expr: compiled, freeVariables };
|
|
3935
3660
|
}
|
|
3936
|
-
// Leading spaces are trimmed during tokenization, so they need to be added back for some values
|
|
3937
|
-
const paddedValues = new Map([["in ", " in "]]);
|
|
3938
3661
|
function compileExpr(expr) {
|
|
3939
|
-
return
|
|
3940
|
-
.map((t) => paddedValues.get(t.value) || t.value)
|
|
3941
|
-
.join("");
|
|
3662
|
+
return processExpr(expr).expr;
|
|
3942
3663
|
}
|
|
3943
3664
|
const INTERP_REGEXP = /\{\{.*?\}\}|\#\{.*?\}/g;
|
|
3944
3665
|
function replaceDynamicParts(s, replacer) {
|
|
@@ -3957,8 +3678,11 @@
|
|
|
3957
3678
|
const whitespaceRE = /\s+/g;
|
|
3958
3679
|
// using a non-html document so that <inner/outer>HTML serializes as XML instead
|
|
3959
3680
|
// of HTML (as we will parse it as xml later)
|
|
3960
|
-
|
|
3961
|
-
|
|
3681
|
+
let xmlDoc;
|
|
3682
|
+
if (typeof document !== "undefined") {
|
|
3683
|
+
xmlDoc = document.implementation.createDocument(null, null, null);
|
|
3684
|
+
}
|
|
3685
|
+
const MODS = new Set(["stop", "capture", "prevent", "self", "synthetic", "passive"]);
|
|
3962
3686
|
let nextDataIds = {};
|
|
3963
3687
|
function generateId(prefix = "") {
|
|
3964
3688
|
nextDataIds[prefix] = (nextDataIds[prefix] || 0) + 1;
|
|
@@ -4056,7 +3780,7 @@
|
|
|
4056
3780
|
return t.innerHTML;
|
|
4057
3781
|
}
|
|
4058
3782
|
}
|
|
4059
|
-
function createContext(parentCtx, params) {
|
|
3783
|
+
function createContext$1(parentCtx, params) {
|
|
4060
3784
|
return Object.assign({
|
|
4061
3785
|
block: null,
|
|
4062
3786
|
index: 0,
|
|
@@ -4224,7 +3948,7 @@
|
|
|
4224
3948
|
const target = new CodeTarget(name, on);
|
|
4225
3949
|
this.targets.push(target);
|
|
4226
3950
|
this.target = target;
|
|
4227
|
-
this.compileAST(ast, createContext(ctx));
|
|
3951
|
+
this.compileAST(ast, createContext$1(ctx));
|
|
4228
3952
|
this.target = initialTarget;
|
|
4229
3953
|
return name;
|
|
4230
3954
|
}
|
|
@@ -4318,8 +4042,6 @@
|
|
|
4318
4042
|
return this.compileTTranslation(ast, ctx);
|
|
4319
4043
|
case 16 /* ASTType.TTranslationContext */:
|
|
4320
4044
|
return this.compileTTranslationContext(ast, ctx);
|
|
4321
|
-
case 17 /* ASTType.TPortal */:
|
|
4322
|
-
return this.compileTPortal(ast, ctx);
|
|
4323
4045
|
}
|
|
4324
4046
|
}
|
|
4325
4047
|
compileDebug(ast, ctx) {
|
|
@@ -4405,7 +4127,8 @@
|
|
|
4405
4127
|
hoistedExpr = `(ctx,${bareArrowMatch[1]})=>${rest}`;
|
|
4406
4128
|
}
|
|
4407
4129
|
else {
|
|
4408
|
-
|
|
4130
|
+
this.helpers.add("callHandler");
|
|
4131
|
+
hoistedExpr = `(ctx, ev) => callHandler(${compiled}, ctx, ev)`;
|
|
4409
4132
|
}
|
|
4410
4133
|
const id = generateId("hdlr_fn");
|
|
4411
4134
|
this.staticDefs.push({ id, expr: hoistedExpr });
|
|
@@ -4483,11 +4206,22 @@
|
|
|
4483
4206
|
// t-model
|
|
4484
4207
|
let tModelSelectedExpr;
|
|
4485
4208
|
if (ast.model) {
|
|
4486
|
-
const { hasDynamicChildren, expr, eventType, shouldNumberize, shouldTrim, targetAttr, specialInitTargetAttr, } = ast.model;
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4209
|
+
const { hasDynamicChildren, expr, eventType, shouldNumberize, shouldTrim, targetAttr, specialInitTargetAttr, isProxy, } = ast.model;
|
|
4210
|
+
let readExpr;
|
|
4211
|
+
let writeExpr;
|
|
4212
|
+
if (isProxy) {
|
|
4213
|
+
const expression = compileExpr(expr);
|
|
4214
|
+
readExpr = expression;
|
|
4215
|
+
writeExpr = (value) => `${expression} = ${value}`;
|
|
4216
|
+
}
|
|
4217
|
+
else {
|
|
4218
|
+
const exprId = generateId("expr");
|
|
4219
|
+
const expression = compileExpr(expr);
|
|
4220
|
+
this.helpers.add("modelExpr");
|
|
4221
|
+
this.define(exprId, `modelExpr(${expression})`);
|
|
4222
|
+
readExpr = `${exprId}()`;
|
|
4223
|
+
writeExpr = (value) => `${exprId}.set(${value})`;
|
|
4224
|
+
}
|
|
4491
4225
|
let idx;
|
|
4492
4226
|
if (specialInitTargetAttr) {
|
|
4493
4227
|
let targetExpr = targetAttr in attrs && `'${attrs[targetAttr]}'`;
|
|
@@ -4498,23 +4232,23 @@
|
|
|
4498
4232
|
targetExpr = compileExpr(dynamicTgExpr);
|
|
4499
4233
|
}
|
|
4500
4234
|
}
|
|
4501
|
-
idx = block.insertData(`${
|
|
4235
|
+
idx = block.insertData(`${readExpr} === ${targetExpr}`, "prop");
|
|
4502
4236
|
attrs[`block-property-${idx}`] = specialInitTargetAttr;
|
|
4503
4237
|
}
|
|
4504
4238
|
else if (hasDynamicChildren) {
|
|
4505
4239
|
const bValueId = generateId("bValue");
|
|
4506
4240
|
tModelSelectedExpr = `${bValueId}`;
|
|
4507
|
-
this.define(tModelSelectedExpr,
|
|
4241
|
+
this.define(tModelSelectedExpr, readExpr);
|
|
4508
4242
|
}
|
|
4509
4243
|
else {
|
|
4510
|
-
idx = block.insertData(
|
|
4244
|
+
idx = block.insertData(readExpr, "prop");
|
|
4511
4245
|
attrs[`block-property-${idx}`] = targetAttr;
|
|
4512
4246
|
}
|
|
4513
4247
|
this.helpers.add("toNumber");
|
|
4514
4248
|
let valueCode = `ev.target.${targetAttr}`;
|
|
4515
4249
|
valueCode = shouldTrim ? `${valueCode}.trim()` : valueCode;
|
|
4516
4250
|
valueCode = shouldNumberize ? `toNumber(${valueCode})` : valueCode;
|
|
4517
|
-
const handler = `[(ctx, ev) => { ${
|
|
4251
|
+
const handler = `[(ctx, ev) => { ${writeExpr(valueCode)}; }, ctx]`;
|
|
4518
4252
|
idx = block.insertData(handler, "hdlr");
|
|
4519
4253
|
attrs[`block-handler-${idx}`] = eventType;
|
|
4520
4254
|
}
|
|
@@ -4548,7 +4282,7 @@
|
|
|
4548
4282
|
const children = ast.content;
|
|
4549
4283
|
for (let i = 0; i < children.length; i++) {
|
|
4550
4284
|
const child = ast.content[i];
|
|
4551
|
-
const subCtx = createContext(ctx, {
|
|
4285
|
+
const subCtx = createContext$1(ctx, {
|
|
4552
4286
|
block,
|
|
4553
4287
|
index: block.childNumber,
|
|
4554
4288
|
forceNewBlock: false,
|
|
@@ -4604,7 +4338,7 @@
|
|
|
4604
4338
|
else if (ast.body) {
|
|
4605
4339
|
let bodyValue = null;
|
|
4606
4340
|
bodyValue = BlockDescription.nextBlockId;
|
|
4607
|
-
const subCtx = createContext(ctx);
|
|
4341
|
+
const subCtx = createContext$1(ctx);
|
|
4608
4342
|
this.compileAST({ type: 3 /* ASTType.Multi */, content: ast.body }, subCtx);
|
|
4609
4343
|
this.helpers.add("safeOutput");
|
|
4610
4344
|
blockStr = `safeOutput(${compileExpr(ast.expr)}, b${bodyValue})`;
|
|
@@ -4619,7 +4353,7 @@
|
|
|
4619
4353
|
compileTIfBranch(content, block, ctx) {
|
|
4620
4354
|
this.target.indentLevel++;
|
|
4621
4355
|
let childN = block.children.length;
|
|
4622
|
-
this.compileAST(content, createContext(ctx, { block, index: ctx.index }));
|
|
4356
|
+
this.compileAST(content, createContext$1(ctx, { block, index: ctx.index }));
|
|
4623
4357
|
if (block.children.length > childN) {
|
|
4624
4358
|
// we have some content => need to insert an anchor at correct index
|
|
4625
4359
|
this.insertAnchor(block, childN);
|
|
@@ -4715,7 +4449,7 @@
|
|
|
4715
4449
|
this.addLine(`if (keys${block.id}.has(String(key${this.target.loopLevel}))) { throw new OwlError(\`Got duplicate key in t-foreach: \${key${this.target.loopLevel}}\`)}`);
|
|
4716
4450
|
this.addLine(`keys${block.id}.add(String(key${this.target.loopLevel}));`);
|
|
4717
4451
|
}
|
|
4718
|
-
const subCtx = createContext(ctx, { block, index: loopVar });
|
|
4452
|
+
const subCtx = createContext$1(ctx, { block, index: loopVar });
|
|
4719
4453
|
this.compileAST(ast.body, subCtx);
|
|
4720
4454
|
this.target.indentLevel--;
|
|
4721
4455
|
this.target.loopLevel--;
|
|
@@ -4727,7 +4461,7 @@
|
|
|
4727
4461
|
compileTKey(ast, ctx) {
|
|
4728
4462
|
const tKeyExpr = generateId("tKey_");
|
|
4729
4463
|
this.define(tKeyExpr, compileExpr(ast.expr));
|
|
4730
|
-
ctx = createContext(ctx, {
|
|
4464
|
+
ctx = createContext$1(ctx, {
|
|
4731
4465
|
tKeyExpr,
|
|
4732
4466
|
block: ctx.block,
|
|
4733
4467
|
index: ctx.index,
|
|
@@ -4754,7 +4488,7 @@
|
|
|
4754
4488
|
for (let i = 0, l = ast.content.length; i < l; i++) {
|
|
4755
4489
|
const child = ast.content[i];
|
|
4756
4490
|
const forceNewBlock = !child.hasNoRepresentation;
|
|
4757
|
-
const subCtx = createContext(ctx, {
|
|
4491
|
+
const subCtx = createContext$1(ctx, {
|
|
4758
4492
|
block,
|
|
4759
4493
|
index,
|
|
4760
4494
|
forceNewBlock,
|
|
@@ -4807,12 +4541,11 @@
|
|
|
4807
4541
|
if (ast.context) {
|
|
4808
4542
|
const dynCtxVar = generateId("ctx");
|
|
4809
4543
|
this.addLine(`const ${dynCtxVar} = ${compileExpr(ast.context)};`);
|
|
4810
|
-
if (
|
|
4811
|
-
ctxExpr = `Object.assign({
|
|
4544
|
+
if (attrs.length) {
|
|
4545
|
+
ctxExpr = `Object.assign({this: ${dynCtxVar}}, ${ctxString})`;
|
|
4812
4546
|
}
|
|
4813
4547
|
else {
|
|
4814
|
-
|
|
4815
|
-
ctxExpr = `Object.assign({}, ${dynCtxVar}, ${thisCtx}${attrs.length ? ", " + ctxString : ""})`;
|
|
4548
|
+
ctxExpr = `{this: ${dynCtxVar}}`;
|
|
4816
4549
|
}
|
|
4817
4550
|
}
|
|
4818
4551
|
else {
|
|
@@ -4957,9 +4690,29 @@
|
|
|
4957
4690
|
let { block } = ctx;
|
|
4958
4691
|
// props
|
|
4959
4692
|
const hasSlotsProp = "slots" in (ast.props || {});
|
|
4960
|
-
const props =
|
|
4961
|
-
|
|
4962
|
-
|
|
4693
|
+
const props = [];
|
|
4694
|
+
const propList = [];
|
|
4695
|
+
for (let p in ast.props || {}) {
|
|
4696
|
+
let [name, suffix] = p.split(".");
|
|
4697
|
+
if (suffix) {
|
|
4698
|
+
// .alike, .bind, .translate — delegate to formatProp, no propList entry
|
|
4699
|
+
props.push(this.formatProp(p, ast.props[p], ast.propsTranslationCtx, ctx.translationCtx));
|
|
4700
|
+
continue;
|
|
4701
|
+
}
|
|
4702
|
+
const { expr: compiledValue, freeVariables } = processExpr(ast.props[p]);
|
|
4703
|
+
const propName = /^[a-z_]+$/i.test(name) ? name : `'${name}'`;
|
|
4704
|
+
props.push(`${propName}: ${compiledValue || undefined}`);
|
|
4705
|
+
if (freeVariables) {
|
|
4706
|
+
for (const varName of freeVariables) {
|
|
4707
|
+
const syntheticKey = `\x01${name}.${varName}`;
|
|
4708
|
+
propList.push(`"${syntheticKey}"`);
|
|
4709
|
+
props.push(`"${syntheticKey}": ctx['${varName}']`);
|
|
4710
|
+
}
|
|
4711
|
+
}
|
|
4712
|
+
else {
|
|
4713
|
+
propList.push(`"${name}"`);
|
|
4714
|
+
}
|
|
4715
|
+
}
|
|
4963
4716
|
// slots
|
|
4964
4717
|
let slotDef = "";
|
|
4965
4718
|
if (ast.slots) {
|
|
@@ -5016,13 +4769,6 @@
|
|
|
5016
4769
|
keyArg = `${ctx.tKeyExpr} + ${keyArg}`;
|
|
5017
4770
|
}
|
|
5018
4771
|
let id = generateId("comp");
|
|
5019
|
-
const propList = [];
|
|
5020
|
-
for (let p in ast.props || {}) {
|
|
5021
|
-
let [name, suffix] = p.split(".");
|
|
5022
|
-
if (!suffix) {
|
|
5023
|
-
propList.push(`"${name}"`);
|
|
5024
|
-
}
|
|
5025
|
-
}
|
|
5026
4772
|
this.helpers.add("createComponent");
|
|
5027
4773
|
this.staticDefs.push({
|
|
5028
4774
|
id,
|
|
@@ -5127,26 +4873,6 @@
|
|
|
5127
4873
|
}
|
|
5128
4874
|
return null;
|
|
5129
4875
|
}
|
|
5130
|
-
compileTPortal(ast, ctx) {
|
|
5131
|
-
this.helpers.add("Portal");
|
|
5132
|
-
let { block } = ctx;
|
|
5133
|
-
const name = this.compileInNewTarget("slot", ast.content, ctx);
|
|
5134
|
-
let id = generateId("comp");
|
|
5135
|
-
this.helpers.add("createComponent");
|
|
5136
|
-
this.staticDefs.push({
|
|
5137
|
-
id,
|
|
5138
|
-
expr: `createComponent(app, null, false, true, false, false)`,
|
|
5139
|
-
});
|
|
5140
|
-
const target = compileExpr(ast.target);
|
|
5141
|
-
const key = this.generateComponentKey();
|
|
5142
|
-
const blockString = `${id}({target: ${target},slots: {'default': {__render: ${name}.bind(this), __ctx: ctx}}}, ${key}, node, ctx, Portal)`;
|
|
5143
|
-
if (block) {
|
|
5144
|
-
this.insertAnchor(block);
|
|
5145
|
-
}
|
|
5146
|
-
block = this.createBlock(block, "multi", ctx);
|
|
5147
|
-
this.insertBlock(blockString, block, { ...ctx, forceNewBlock: false });
|
|
5148
|
-
return block.varName;
|
|
5149
|
-
}
|
|
5150
4876
|
}
|
|
5151
4877
|
|
|
5152
4878
|
// -----------------------------------------------------------------------------
|
|
@@ -5182,7 +4908,6 @@
|
|
|
5182
4908
|
parseTDebugLog(node, ctx) ||
|
|
5183
4909
|
parseTForEach(node, ctx) ||
|
|
5184
4910
|
parseTIf(node, ctx) ||
|
|
5185
|
-
parseTPortal(node, ctx) ||
|
|
5186
4911
|
parseTTranslation(node, ctx) ||
|
|
5187
4912
|
parseTTranslationContext(node, ctx) ||
|
|
5188
4913
|
parseTCall(node, ctx) ||
|
|
@@ -5329,6 +5054,7 @@
|
|
|
5329
5054
|
const hasTrimMod = attr.includes(".trim");
|
|
5330
5055
|
const hasLazyMod = hasTrimMod || attr.includes(".lazy");
|
|
5331
5056
|
const hasNumberMod = attr.includes(".number");
|
|
5057
|
+
const hasProxyMod = attr.includes(".proxy");
|
|
5332
5058
|
const eventType = isRadioInput ? "click" : isSelect || hasLazyMod ? "change" : "input";
|
|
5333
5059
|
model = {
|
|
5334
5060
|
expr: value,
|
|
@@ -5338,6 +5064,7 @@
|
|
|
5338
5064
|
hasDynamicChildren: false,
|
|
5339
5065
|
shouldTrim: hasTrimMod,
|
|
5340
5066
|
shouldNumberize: hasNumberMod,
|
|
5067
|
+
isProxy: hasProxyMod,
|
|
5341
5068
|
};
|
|
5342
5069
|
if (isSelect) {
|
|
5343
5070
|
// don't pollute the original ctx
|
|
@@ -5809,694 +5536,1104 @@
|
|
|
5809
5536
|
return wrapInTTranslationContextAST(result, translationCtx);
|
|
5810
5537
|
}
|
|
5811
5538
|
// -----------------------------------------------------------------------------
|
|
5812
|
-
//
|
|
5539
|
+
// helpers
|
|
5813
5540
|
// -----------------------------------------------------------------------------
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5541
|
+
/**
|
|
5542
|
+
* Parse all the child nodes of a given node and return a list of ast elements
|
|
5543
|
+
*/
|
|
5544
|
+
function parseChildren(node, ctx) {
|
|
5545
|
+
const children = [];
|
|
5546
|
+
for (let child of node.childNodes) {
|
|
5547
|
+
const childAst = parseNode(child, ctx);
|
|
5548
|
+
if (childAst) {
|
|
5549
|
+
if (childAst.type === 3 /* ASTType.Multi */) {
|
|
5550
|
+
children.push(...childAst.content);
|
|
5551
|
+
}
|
|
5552
|
+
else {
|
|
5553
|
+
children.push(childAst);
|
|
5554
|
+
}
|
|
5555
|
+
}
|
|
5817
5556
|
}
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
5824
|
-
|
|
5825
|
-
|
|
5557
|
+
return children;
|
|
5558
|
+
}
|
|
5559
|
+
function makeASTMulti(children) {
|
|
5560
|
+
const ast = { type: 3 /* ASTType.Multi */, content: children };
|
|
5561
|
+
if (children.every((c) => c.hasNoRepresentation)) {
|
|
5562
|
+
ast.hasNoRepresentation = true;
|
|
5563
|
+
}
|
|
5564
|
+
return ast;
|
|
5565
|
+
}
|
|
5566
|
+
/**
|
|
5567
|
+
* Parse all the child nodes of a given node and return an ast if possible.
|
|
5568
|
+
* In the case there are multiple children, they are wrapped in a astmulti.
|
|
5569
|
+
*/
|
|
5570
|
+
function parseChildNodes(node, ctx) {
|
|
5571
|
+
const children = parseChildren(node, ctx);
|
|
5572
|
+
switch (children.length) {
|
|
5573
|
+
case 0:
|
|
5574
|
+
return null;
|
|
5575
|
+
case 1:
|
|
5576
|
+
return children[0];
|
|
5577
|
+
default:
|
|
5578
|
+
return makeASTMulti(children);
|
|
5579
|
+
}
|
|
5580
|
+
}
|
|
5581
|
+
/**
|
|
5582
|
+
* Normalizes the content of an Element so that t-if/t-elif/t-else directives
|
|
5583
|
+
* immediately follow one another (by removing empty text nodes or comments).
|
|
5584
|
+
* Throws an error when a conditional branching statement is malformed. This
|
|
5585
|
+
* function modifies the Element in place.
|
|
5586
|
+
*
|
|
5587
|
+
* @param el the element containing the tree that should be normalized
|
|
5588
|
+
*/
|
|
5589
|
+
function normalizeTIf(el) {
|
|
5590
|
+
let tbranch = el.querySelectorAll("[t-elif], [t-else]");
|
|
5591
|
+
for (let i = 0, ilen = tbranch.length; i < ilen; i++) {
|
|
5592
|
+
let node = tbranch[i];
|
|
5593
|
+
let prevElem = node.previousElementSibling;
|
|
5594
|
+
let pattr = (name) => prevElem.getAttribute(name);
|
|
5595
|
+
let nattr = (name) => +!!node.getAttribute(name);
|
|
5596
|
+
if (prevElem && (pattr("t-if") || pattr("t-elif"))) {
|
|
5597
|
+
if (pattr("t-foreach")) {
|
|
5598
|
+
throw new OwlError("t-if cannot stay at the same level as t-foreach when using t-elif or t-else");
|
|
5599
|
+
}
|
|
5600
|
+
if (["t-if", "t-elif", "t-else"].map(nattr).reduce(function (a, b) {
|
|
5601
|
+
return a + b;
|
|
5602
|
+
}) > 1) {
|
|
5603
|
+
throw new OwlError("Only one conditional branching directive is allowed per node");
|
|
5604
|
+
}
|
|
5605
|
+
// All text (with only spaces) and comment nodes (nodeType 8) between
|
|
5606
|
+
// branch nodes are removed
|
|
5607
|
+
let textNode;
|
|
5608
|
+
while ((textNode = node.previousSibling) !== prevElem) {
|
|
5609
|
+
if (textNode.nodeValue.trim().length && textNode.nodeType !== 8) {
|
|
5610
|
+
throw new OwlError("text is not allowed between branching directives");
|
|
5611
|
+
}
|
|
5612
|
+
textNode.remove();
|
|
5613
|
+
}
|
|
5614
|
+
}
|
|
5615
|
+
else {
|
|
5616
|
+
throw new OwlError("t-elif and t-else directives must be preceded by a t-if or t-elif directive");
|
|
5617
|
+
}
|
|
5618
|
+
}
|
|
5619
|
+
}
|
|
5620
|
+
/**
|
|
5621
|
+
* Normalizes the content of an Element so that t-out directives on components
|
|
5622
|
+
* are removed and instead places a <t t-out=""> as the default slot of the
|
|
5623
|
+
* component. Also throws if the component already has content. This function
|
|
5624
|
+
* modifies the Element in place.
|
|
5625
|
+
*
|
|
5626
|
+
* @param el the element containing the tree that should be normalized
|
|
5627
|
+
*/
|
|
5628
|
+
function normalizeTOut(el) {
|
|
5629
|
+
const elements = [...el.querySelectorAll(`[t-out]`)].filter((el) => el.tagName[0] === el.tagName[0].toUpperCase() || el.hasAttribute("t-component"));
|
|
5630
|
+
for (const el of elements) {
|
|
5631
|
+
if (el.childNodes.length) {
|
|
5632
|
+
throw new OwlError(`Cannot have t-out on a component that already has content`);
|
|
5633
|
+
}
|
|
5634
|
+
const value = el.getAttribute("t-out");
|
|
5635
|
+
el.removeAttribute("t-out");
|
|
5636
|
+
const t = el.ownerDocument.createElement("t");
|
|
5637
|
+
if (value != null) {
|
|
5638
|
+
t.setAttribute("t-out", value);
|
|
5639
|
+
}
|
|
5640
|
+
el.appendChild(t);
|
|
5641
|
+
}
|
|
5642
|
+
}
|
|
5643
|
+
/**
|
|
5644
|
+
* Normalizes the tree inside a given element and do some preliminary validation
|
|
5645
|
+
* on it. This function modifies the Element in place.
|
|
5646
|
+
*
|
|
5647
|
+
* @param el the element containing the tree that should be normalized
|
|
5648
|
+
*/
|
|
5649
|
+
function normalizeXML(el) {
|
|
5650
|
+
normalizeTIf(el);
|
|
5651
|
+
normalizeTOut(el);
|
|
5652
|
+
}
|
|
5653
|
+
|
|
5654
|
+
function compile(template, options = {
|
|
5655
|
+
hasGlobalValues: false,
|
|
5656
|
+
}) {
|
|
5657
|
+
// parsing
|
|
5658
|
+
const ast = parse(template, options.customDirectives);
|
|
5659
|
+
// code generation
|
|
5660
|
+
const codeGenerator = new CodeGenerator(ast, options);
|
|
5661
|
+
const code = codeGenerator.generateCode();
|
|
5662
|
+
// template function
|
|
5663
|
+
try {
|
|
5664
|
+
return new Function("app, bdom, helpers", code);
|
|
5665
|
+
}
|
|
5666
|
+
catch (originalError) {
|
|
5667
|
+
const { name } = options;
|
|
5668
|
+
const nameStr = name ? `template "${name}"` : "anonymous template";
|
|
5669
|
+
const err = new OwlError(`Failed to compile ${nameStr}: ${originalError.message}\n\ngenerated code:\nfunction(app, bdom, helpers) {\n${code}\n}`);
|
|
5670
|
+
err.cause = originalError;
|
|
5671
|
+
throw err;
|
|
5672
|
+
}
|
|
5673
|
+
}
|
|
5674
|
+
|
|
5675
|
+
// do not modify manually. This file is generated by the release script.
|
|
5676
|
+
const version = "3.0.0-alpha.21";
|
|
5677
|
+
|
|
5678
|
+
function effect(fn) {
|
|
5679
|
+
const computation = createComputation(() => {
|
|
5680
|
+
// In case the cleanup read an atom.
|
|
5681
|
+
// todo: test it
|
|
5682
|
+
setComputation(undefined);
|
|
5683
|
+
unsubscribeEffect(computation);
|
|
5684
|
+
setComputation(computation);
|
|
5685
|
+
return fn();
|
|
5686
|
+
}, false);
|
|
5687
|
+
getCurrentComputation()?.observers.add(computation);
|
|
5688
|
+
updateComputation(computation);
|
|
5689
|
+
// Remove sources and unsubscribe
|
|
5690
|
+
return function cleanupEffect() {
|
|
5691
|
+
// In case the cleanup read an atom.
|
|
5692
|
+
// todo: test it
|
|
5693
|
+
const previousComputation = getCurrentComputation();
|
|
5694
|
+
setComputation(undefined);
|
|
5695
|
+
unsubscribeEffect(computation);
|
|
5696
|
+
setComputation(previousComputation);
|
|
5697
|
+
};
|
|
5698
|
+
}
|
|
5699
|
+
function unsubscribeEffect(effect) {
|
|
5700
|
+
removeSources(effect);
|
|
5701
|
+
cleanupEffect(effect);
|
|
5702
|
+
for (const childEffect of effect.observers) {
|
|
5703
|
+
// Consider it executed to avoid it's re-execution
|
|
5704
|
+
// todo: make a test for it
|
|
5705
|
+
childEffect.state = ComputationState.EXECUTED;
|
|
5706
|
+
removeSources(childEffect);
|
|
5707
|
+
unsubscribeEffect(childEffect);
|
|
5708
|
+
}
|
|
5709
|
+
effect.observers.clear();
|
|
5710
|
+
}
|
|
5711
|
+
function cleanupEffect(effect) {
|
|
5712
|
+
// the computation.value of an effect is a cleanup function
|
|
5713
|
+
const cleanupFn = effect.value;
|
|
5714
|
+
if (cleanupFn && typeof cleanupFn === "function") {
|
|
5715
|
+
cleanupFn();
|
|
5716
|
+
effect.value = undefined;
|
|
5717
|
+
}
|
|
5718
|
+
}
|
|
5719
|
+
|
|
5720
|
+
class Plugin {
|
|
5721
|
+
static _shadowId;
|
|
5722
|
+
static get id() {
|
|
5723
|
+
return this._shadowId ?? this.name;
|
|
5724
|
+
}
|
|
5725
|
+
static set id(shadowId) {
|
|
5726
|
+
this._shadowId = shadowId;
|
|
5727
|
+
}
|
|
5728
|
+
__owl__;
|
|
5729
|
+
constructor(manager) {
|
|
5730
|
+
this.__owl__ = manager;
|
|
5731
|
+
}
|
|
5732
|
+
setup() { }
|
|
5733
|
+
}
|
|
5734
|
+
class PluginManager {
|
|
5735
|
+
app;
|
|
5736
|
+
config;
|
|
5737
|
+
onDestroyCb = [];
|
|
5738
|
+
computations = [];
|
|
5739
|
+
plugins;
|
|
5740
|
+
status = 0 /* STATUS.NEW */;
|
|
5741
|
+
constructor(app, options = {}) {
|
|
5742
|
+
this.app = app;
|
|
5743
|
+
this.config = options.config ?? {};
|
|
5744
|
+
if (options.parent) {
|
|
5745
|
+
const parent = options.parent;
|
|
5746
|
+
parent.onDestroyCb.push(() => this.destroy());
|
|
5747
|
+
this.plugins = Object.create(parent.plugins);
|
|
5748
|
+
}
|
|
5749
|
+
else {
|
|
5750
|
+
this.plugins = {};
|
|
5751
|
+
}
|
|
5752
|
+
}
|
|
5753
|
+
destroy() {
|
|
5754
|
+
const cbs = this.onDestroyCb;
|
|
5755
|
+
while (cbs.length) {
|
|
5756
|
+
cbs.pop()();
|
|
5757
|
+
}
|
|
5758
|
+
for (const computation of this.computations) {
|
|
5759
|
+
disposeComputation(computation);
|
|
5760
|
+
}
|
|
5761
|
+
this.status = 3 /* STATUS.DESTROYED */;
|
|
5762
|
+
}
|
|
5763
|
+
getPluginById(id) {
|
|
5764
|
+
return this.plugins[id] || null;
|
|
5765
|
+
}
|
|
5766
|
+
getPlugin(pluginConstructor) {
|
|
5767
|
+
return this.getPluginById(pluginConstructor.id);
|
|
5768
|
+
}
|
|
5769
|
+
startPlugin(pluginConstructor) {
|
|
5770
|
+
if (!pluginConstructor.id) {
|
|
5771
|
+
throw new OwlError(`Plugin "${pluginConstructor.name}" has no id`);
|
|
5772
|
+
}
|
|
5773
|
+
if (this.plugins.hasOwnProperty(pluginConstructor.id)) {
|
|
5774
|
+
const existingPluginType = this.getPluginById(pluginConstructor.id).constructor;
|
|
5775
|
+
if (existingPluginType !== pluginConstructor) {
|
|
5776
|
+
throw new OwlError(`Trying to start a plugin with the same id as an other plugin (id: '${pluginConstructor.id}', existing plugin: '${existingPluginType.name}', starting plugin: '${pluginConstructor.name}')`);
|
|
5777
|
+
}
|
|
5778
|
+
return null;
|
|
5779
|
+
}
|
|
5780
|
+
const plugin = new pluginConstructor(this);
|
|
5781
|
+
this.plugins[pluginConstructor.id] = plugin;
|
|
5782
|
+
plugin.setup();
|
|
5783
|
+
return plugin;
|
|
5784
|
+
}
|
|
5785
|
+
startPlugins(pluginConstructors) {
|
|
5786
|
+
contextStack.push({
|
|
5787
|
+
type: "plugin",
|
|
5788
|
+
app: this.app,
|
|
5789
|
+
manager: this,
|
|
5790
|
+
get status() {
|
|
5791
|
+
return this.manager.status;
|
|
5792
|
+
},
|
|
5793
|
+
});
|
|
5794
|
+
try {
|
|
5795
|
+
for (const pluginConstructor of pluginConstructors) {
|
|
5796
|
+
this.startPlugin(pluginConstructor);
|
|
5797
|
+
}
|
|
5798
|
+
}
|
|
5799
|
+
finally {
|
|
5800
|
+
contextStack.pop();
|
|
5801
|
+
}
|
|
5802
|
+
this.status = 1 /* STATUS.MOUNTED */;
|
|
5803
|
+
}
|
|
5804
|
+
}
|
|
5805
|
+
function startPlugins(manager, plugins) {
|
|
5806
|
+
if (Array.isArray(plugins)) {
|
|
5807
|
+
manager.startPlugins(plugins);
|
|
5808
|
+
}
|
|
5809
|
+
else {
|
|
5810
|
+
manager.onDestroyCb.push(effect(() => {
|
|
5811
|
+
const pluginItems = plugins.items();
|
|
5812
|
+
untrack(() => manager.startPlugins(pluginItems));
|
|
5813
|
+
}));
|
|
5814
|
+
}
|
|
5815
|
+
}
|
|
5816
|
+
|
|
5817
|
+
// -----------------------------------------------------------------------------
|
|
5818
|
+
// Scheduler
|
|
5819
|
+
// -----------------------------------------------------------------------------
|
|
5820
|
+
let requestAnimationFrame;
|
|
5821
|
+
if (typeof window !== "undefined") {
|
|
5822
|
+
requestAnimationFrame = window.requestAnimationFrame.bind(window);
|
|
5823
|
+
}
|
|
5824
|
+
class Scheduler {
|
|
5825
|
+
// capture the value of requestAnimationFrame as soon as possible, to avoid
|
|
5826
|
+
// interactions with other code, such as test frameworks that override them
|
|
5827
|
+
static requestAnimationFrame = requestAnimationFrame;
|
|
5828
|
+
tasks = new Set();
|
|
5829
|
+
requestAnimationFrame;
|
|
5830
|
+
frame = 0;
|
|
5831
|
+
delayedRenders = [];
|
|
5832
|
+
cancelledNodes = new Set();
|
|
5833
|
+
processing = false;
|
|
5834
|
+
constructor() {
|
|
5835
|
+
this.requestAnimationFrame = Scheduler.requestAnimationFrame;
|
|
5836
|
+
}
|
|
5837
|
+
addFiber(fiber) {
|
|
5838
|
+
this.tasks.add(fiber.root);
|
|
5839
|
+
}
|
|
5840
|
+
scheduleDestroy(node) {
|
|
5841
|
+
this.cancelledNodes.add(node);
|
|
5842
|
+
if (this.frame === 0) {
|
|
5843
|
+
this.frame = this.requestAnimationFrame(() => this.processTasks());
|
|
5844
|
+
}
|
|
5845
|
+
}
|
|
5846
|
+
/**
|
|
5847
|
+
* Process all current tasks. This only applies to the fibers that are ready.
|
|
5848
|
+
* Other tasks are left unchanged.
|
|
5849
|
+
*/
|
|
5850
|
+
flush() {
|
|
5851
|
+
if (this.delayedRenders.length) {
|
|
5852
|
+
let renders = this.delayedRenders;
|
|
5853
|
+
this.delayedRenders = [];
|
|
5854
|
+
for (let f of renders) {
|
|
5855
|
+
if (f.root && f.node.status !== 3 /* STATUS.DESTROYED */ && f.node.fiber === f) {
|
|
5856
|
+
f.render();
|
|
5857
|
+
}
|
|
5858
|
+
}
|
|
5859
|
+
}
|
|
5860
|
+
if (this.frame === 0) {
|
|
5861
|
+
this.frame = this.requestAnimationFrame(() => this.processTasks());
|
|
5862
|
+
}
|
|
5863
|
+
}
|
|
5864
|
+
processTasks() {
|
|
5865
|
+
if (this.processing) {
|
|
5866
|
+
return;
|
|
5867
|
+
}
|
|
5868
|
+
this.processing = true;
|
|
5869
|
+
this.frame = 0;
|
|
5870
|
+
for (let node of this.cancelledNodes) {
|
|
5871
|
+
node._destroy();
|
|
5872
|
+
}
|
|
5873
|
+
this.cancelledNodes.clear();
|
|
5874
|
+
for (let task of this.tasks) {
|
|
5875
|
+
this.processFiber(task);
|
|
5876
|
+
}
|
|
5877
|
+
for (let task of this.tasks) {
|
|
5878
|
+
if (task.node.status === 3 /* STATUS.DESTROYED */) {
|
|
5879
|
+
this.tasks.delete(task);
|
|
5880
|
+
}
|
|
5881
|
+
}
|
|
5882
|
+
this.processing = false;
|
|
5883
|
+
}
|
|
5884
|
+
processFiber(fiber) {
|
|
5885
|
+
if (fiber.root !== fiber) {
|
|
5886
|
+
this.tasks.delete(fiber);
|
|
5887
|
+
return;
|
|
5888
|
+
}
|
|
5889
|
+
const hasError = fibersInError.has(fiber);
|
|
5890
|
+
if (hasError && fiber.counter !== 0) {
|
|
5891
|
+
this.tasks.delete(fiber);
|
|
5892
|
+
return;
|
|
5893
|
+
}
|
|
5894
|
+
if (fiber.node.status === 3 /* STATUS.DESTROYED */) {
|
|
5895
|
+
this.tasks.delete(fiber);
|
|
5896
|
+
return;
|
|
5897
|
+
}
|
|
5898
|
+
if (fiber.counter === 0) {
|
|
5899
|
+
if (!hasError) {
|
|
5900
|
+
fiber.complete();
|
|
5901
|
+
}
|
|
5902
|
+
// at this point, the fiber should have been applied to the DOM, so we can
|
|
5903
|
+
// remove it from the task list. If it is not the case, it means that there
|
|
5904
|
+
// was an error and an error handler triggered a new rendering that recycled
|
|
5905
|
+
// the fiber, so in that case, we actually want to keep the fiber around,
|
|
5906
|
+
// otherwise it will just be ignored.
|
|
5907
|
+
if (fiber.appliedToDom) {
|
|
5908
|
+
this.tasks.delete(fiber);
|
|
5909
|
+
}
|
|
5910
|
+
}
|
|
5911
|
+
}
|
|
5912
|
+
}
|
|
5913
|
+
|
|
5914
|
+
let hasBeenLogged = false;
|
|
5915
|
+
const apps = new Set();
|
|
5916
|
+
if (typeof window !== "undefined") {
|
|
5917
|
+
window.__OWL_DEVTOOLS__ ||= { apps, Fiber, RootFiber, toRaw, proxy };
|
|
5918
|
+
}
|
|
5919
|
+
class App extends TemplateSet {
|
|
5920
|
+
static validateTarget = validateTarget;
|
|
5921
|
+
static apps = apps;
|
|
5922
|
+
static version = version;
|
|
5923
|
+
name;
|
|
5924
|
+
scheduler = new Scheduler();
|
|
5925
|
+
roots = new Set();
|
|
5926
|
+
pluginManager;
|
|
5927
|
+
constructor(config = {}) {
|
|
5928
|
+
super(config);
|
|
5929
|
+
this.name = config.name || "";
|
|
5930
|
+
apps.add(this);
|
|
5931
|
+
this.pluginManager = new PluginManager(this, { config: config.config });
|
|
5932
|
+
if (config.plugins) {
|
|
5933
|
+
startPlugins(this.pluginManager, config.plugins);
|
|
5934
|
+
}
|
|
5935
|
+
if (config.test) {
|
|
5936
|
+
this.dev = true;
|
|
5937
|
+
}
|
|
5938
|
+
if (this.dev && !config.test && !hasBeenLogged) {
|
|
5939
|
+
console.info(`Owl is running in 'dev' mode.`);
|
|
5940
|
+
hasBeenLogged = true;
|
|
5941
|
+
}
|
|
5942
|
+
}
|
|
5943
|
+
createRoot(Root, config = {}) {
|
|
5944
|
+
const props = config.props || {};
|
|
5945
|
+
let resolve;
|
|
5946
|
+
let reject;
|
|
5947
|
+
const promise = new Promise((res, rej) => {
|
|
5948
|
+
resolve = res;
|
|
5949
|
+
reject = rej;
|
|
5950
|
+
});
|
|
5951
|
+
const restore = saveContext();
|
|
5952
|
+
let node;
|
|
5953
|
+
let error = null;
|
|
5954
|
+
try {
|
|
5955
|
+
node = new ComponentNode(Root, props, this, null, null);
|
|
5956
|
+
}
|
|
5957
|
+
catch (e) {
|
|
5958
|
+
error = e;
|
|
5959
|
+
reject(e);
|
|
5960
|
+
}
|
|
5961
|
+
finally {
|
|
5962
|
+
restore();
|
|
5963
|
+
}
|
|
5964
|
+
const root = {
|
|
5965
|
+
node: node,
|
|
5966
|
+
promise,
|
|
5967
|
+
mount: (target, options) => {
|
|
5968
|
+
if (error) {
|
|
5969
|
+
return promise;
|
|
5970
|
+
}
|
|
5971
|
+
App.validateTarget(target);
|
|
5972
|
+
this.mountNode(node, target, resolve, reject, options);
|
|
5973
|
+
return promise;
|
|
5974
|
+
},
|
|
5975
|
+
destroy: () => {
|
|
5976
|
+
this.roots.delete(root);
|
|
5977
|
+
node.destroy();
|
|
5978
|
+
this.scheduler.processTasks();
|
|
5979
|
+
},
|
|
5980
|
+
};
|
|
5981
|
+
this.roots.add(root);
|
|
5982
|
+
return root;
|
|
5983
|
+
}
|
|
5984
|
+
mountNode(node, target, resolve, reject, options) {
|
|
5985
|
+
// Manually add the last resort error handler on the node
|
|
5986
|
+
let handlers = nodeErrorHandlers.get(node);
|
|
5987
|
+
if (!handlers) {
|
|
5988
|
+
handlers = [];
|
|
5989
|
+
nodeErrorHandlers.set(node, handlers);
|
|
5990
|
+
}
|
|
5991
|
+
handlers.unshift((e, finalize) => {
|
|
5992
|
+
const finalError = finalize();
|
|
5993
|
+
reject(finalError);
|
|
5994
|
+
});
|
|
5995
|
+
// manually set a onMounted callback.
|
|
5996
|
+
// that way, we are independant from the current node.
|
|
5997
|
+
node.mounted.push(() => {
|
|
5998
|
+
resolve(node.component);
|
|
5999
|
+
handlers.shift();
|
|
6000
|
+
});
|
|
6001
|
+
node.mountComponent(target, options);
|
|
6002
|
+
}
|
|
6003
|
+
destroy() {
|
|
6004
|
+
for (let root of this.roots) {
|
|
6005
|
+
root.destroy();
|
|
6006
|
+
}
|
|
6007
|
+
this.pluginManager.destroy();
|
|
6008
|
+
this.scheduler.processTasks();
|
|
6009
|
+
apps.delete(this);
|
|
6010
|
+
}
|
|
6011
|
+
handleError(...args) {
|
|
6012
|
+
return handleError(...args);
|
|
6013
|
+
}
|
|
6014
|
+
}
|
|
6015
|
+
async function mount(C, target, config = {}) {
|
|
6016
|
+
const app = new App(config);
|
|
6017
|
+
const root = app.createRoot(C, config);
|
|
6018
|
+
return root.mount(target, config);
|
|
6019
|
+
}
|
|
6020
|
+
|
|
6021
|
+
const mainEventHandler = (data, ev, currentTarget) => {
|
|
6022
|
+
const { data: _data, modifiers } = filterOutModifiersFromData(data);
|
|
6023
|
+
data = _data;
|
|
6024
|
+
let stopped = false;
|
|
6025
|
+
if (modifiers.length) {
|
|
6026
|
+
let selfMode = false;
|
|
6027
|
+
const isSelf = ev.target === currentTarget;
|
|
6028
|
+
for (const mod of modifiers) {
|
|
6029
|
+
switch (mod) {
|
|
6030
|
+
case "self":
|
|
6031
|
+
selfMode = true;
|
|
6032
|
+
if (isSelf) {
|
|
6033
|
+
continue;
|
|
6034
|
+
}
|
|
6035
|
+
else {
|
|
6036
|
+
return stopped;
|
|
6037
|
+
}
|
|
6038
|
+
case "prevent":
|
|
6039
|
+
if ((selfMode && isSelf) || !selfMode)
|
|
6040
|
+
ev.preventDefault();
|
|
6041
|
+
continue;
|
|
6042
|
+
case "stop":
|
|
6043
|
+
if ((selfMode && isSelf) || !selfMode)
|
|
6044
|
+
ev.stopPropagation();
|
|
6045
|
+
stopped = true;
|
|
6046
|
+
continue;
|
|
6047
|
+
}
|
|
6048
|
+
}
|
|
6049
|
+
}
|
|
6050
|
+
// If handler is empty, the array slot 0 will also be empty, and data will not have the property 0
|
|
6051
|
+
// We check this rather than data[0] being truthy (or typeof function) so that it crashes
|
|
6052
|
+
// as expected when there is a handler expression that evaluates to a falsy value
|
|
6053
|
+
if (Object.hasOwnProperty.call(data, 0)) {
|
|
6054
|
+
const handler = data[0];
|
|
6055
|
+
if (typeof handler !== "function") {
|
|
6056
|
+
throw new OwlError(`Invalid handler (expected a function, received: '${handler}')`);
|
|
6057
|
+
}
|
|
6058
|
+
let node = data[1] ? data[1].__owl__ : null;
|
|
6059
|
+
if (node ? node.status === 1 /* STATUS.MOUNTED */ : true) {
|
|
6060
|
+
handler(data[1], ev);
|
|
6061
|
+
}
|
|
5826
6062
|
}
|
|
5827
|
-
return
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
content,
|
|
5831
|
-
};
|
|
5832
|
-
}
|
|
6063
|
+
return stopped;
|
|
6064
|
+
};
|
|
6065
|
+
|
|
5833
6066
|
// -----------------------------------------------------------------------------
|
|
5834
|
-
//
|
|
6067
|
+
// hooks
|
|
5835
6068
|
// -----------------------------------------------------------------------------
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
if (childAst) {
|
|
5844
|
-
if (childAst.type === 3 /* ASTType.Multi */) {
|
|
5845
|
-
children.push(...childAst.content);
|
|
5846
|
-
}
|
|
5847
|
-
else {
|
|
5848
|
-
children.push(childAst);
|
|
5849
|
-
}
|
|
5850
|
-
}
|
|
6069
|
+
function decorate(node, f, hookName) {
|
|
6070
|
+
const result = f.bind(node.component);
|
|
6071
|
+
if (node.app.dev) {
|
|
6072
|
+
const suffix = f.name ? ` <${f.name}>` : "";
|
|
6073
|
+
Reflect.defineProperty(result, "name", {
|
|
6074
|
+
value: hookName + suffix,
|
|
6075
|
+
});
|
|
5851
6076
|
}
|
|
5852
|
-
return
|
|
6077
|
+
return result;
|
|
5853
6078
|
}
|
|
5854
|
-
function
|
|
5855
|
-
const
|
|
5856
|
-
|
|
5857
|
-
|
|
6079
|
+
function onWillStart(fn) {
|
|
6080
|
+
const { node } = getContext("component");
|
|
6081
|
+
node.willStart.push(decorate(node, fn, "onWillStart"));
|
|
6082
|
+
}
|
|
6083
|
+
function onWillUpdateProps(fn) {
|
|
6084
|
+
const { node } = getContext("component");
|
|
6085
|
+
node.willUpdateProps.push(decorate(node, fn, "onWillUpdateProps"));
|
|
6086
|
+
}
|
|
6087
|
+
function onMounted(fn) {
|
|
6088
|
+
const { node } = getContext("component");
|
|
6089
|
+
node.mounted.push(decorate(node, fn, "onMounted"));
|
|
6090
|
+
}
|
|
6091
|
+
function onWillPatch(fn) {
|
|
6092
|
+
const { node } = getContext("component");
|
|
6093
|
+
node.willPatch.unshift(decorate(node, fn, "onWillPatch"));
|
|
6094
|
+
}
|
|
6095
|
+
function onPatched(fn) {
|
|
6096
|
+
const { node } = getContext("component");
|
|
6097
|
+
node.patched.push(decorate(node, fn, "onPatched"));
|
|
6098
|
+
}
|
|
6099
|
+
function onWillUnmount(fn) {
|
|
6100
|
+
const { node } = getContext("component");
|
|
6101
|
+
node.willUnmount.unshift(decorate(node, fn, "onWillUnmount"));
|
|
6102
|
+
}
|
|
6103
|
+
function onWillDestroy(fn) {
|
|
6104
|
+
const context = getContext();
|
|
6105
|
+
if (context.type === "component") {
|
|
6106
|
+
context.node.willDestroy.unshift(decorate(context.node, fn, "onWillDestroy"));
|
|
6107
|
+
}
|
|
6108
|
+
else {
|
|
6109
|
+
context.manager.onDestroyCb.push(fn);
|
|
5858
6110
|
}
|
|
5859
|
-
return ast;
|
|
5860
6111
|
}
|
|
5861
|
-
|
|
5862
|
-
|
|
5863
|
-
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
switch (children.length) {
|
|
5868
|
-
case 0:
|
|
5869
|
-
return null;
|
|
5870
|
-
case 1:
|
|
5871
|
-
return children[0];
|
|
5872
|
-
default:
|
|
5873
|
-
return makeASTMulti(children);
|
|
6112
|
+
function onError(callback) {
|
|
6113
|
+
const { node } = getContext("component");
|
|
6114
|
+
let handlers = nodeErrorHandlers.get(node);
|
|
6115
|
+
if (!handlers) {
|
|
6116
|
+
handlers = [];
|
|
6117
|
+
nodeErrorHandlers.set(node, handlers);
|
|
5874
6118
|
}
|
|
6119
|
+
handlers.push(callback.bind(node.component));
|
|
5875
6120
|
}
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
* @param el the element containing the tree that should be normalized
|
|
5883
|
-
*/
|
|
5884
|
-
function normalizeTIf(el) {
|
|
5885
|
-
let tbranch = el.querySelectorAll("[t-elif], [t-else]");
|
|
5886
|
-
for (let i = 0, ilen = tbranch.length; i < ilen; i++) {
|
|
5887
|
-
let node = tbranch[i];
|
|
5888
|
-
let prevElem = node.previousElementSibling;
|
|
5889
|
-
let pattr = (name) => prevElem.getAttribute(name);
|
|
5890
|
-
let nattr = (name) => +!!node.getAttribute(name);
|
|
5891
|
-
if (prevElem && (pattr("t-if") || pattr("t-elif"))) {
|
|
5892
|
-
if (pattr("t-foreach")) {
|
|
5893
|
-
throw new OwlError("t-if cannot stay at the same level as t-foreach when using t-elif or t-else");
|
|
5894
|
-
}
|
|
5895
|
-
if (["t-if", "t-elif", "t-else"].map(nattr).reduce(function (a, b) {
|
|
5896
|
-
return a + b;
|
|
5897
|
-
}) > 1) {
|
|
5898
|
-
throw new OwlError("Only one conditional branching directive is allowed per node");
|
|
5899
|
-
}
|
|
5900
|
-
// All text (with only spaces) and comment nodes (nodeType 8) between
|
|
5901
|
-
// branch nodes are removed
|
|
5902
|
-
let textNode;
|
|
5903
|
-
while ((textNode = node.previousSibling) !== prevElem) {
|
|
5904
|
-
if (textNode.nodeValue.trim().length && textNode.nodeType !== 8) {
|
|
5905
|
-
throw new OwlError("text is not allowed between branching directives");
|
|
5906
|
-
}
|
|
5907
|
-
textNode.remove();
|
|
5908
|
-
}
|
|
6121
|
+
|
|
6122
|
+
function computed(getter, options = {}) {
|
|
6123
|
+
const computation = createComputation(() => {
|
|
6124
|
+
const newValue = getter();
|
|
6125
|
+
if (!Object.is(computation.value, newValue)) {
|
|
6126
|
+
onWriteAtom(computation);
|
|
5909
6127
|
}
|
|
5910
|
-
|
|
5911
|
-
|
|
6128
|
+
return newValue;
|
|
6129
|
+
}, true);
|
|
6130
|
+
function readComputed() {
|
|
6131
|
+
if (computation.state !== ComputationState.EXECUTED) {
|
|
6132
|
+
updateComputation(computation);
|
|
5912
6133
|
}
|
|
6134
|
+
onReadAtom(computation);
|
|
6135
|
+
return computation.value;
|
|
5913
6136
|
}
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
*
|
|
5921
|
-
* @param el the element containing the tree that should be normalized
|
|
5922
|
-
*/
|
|
5923
|
-
function normalizeTOut(el) {
|
|
5924
|
-
const elements = [...el.querySelectorAll(`[t-out]`)].filter((el) => el.tagName[0] === el.tagName[0].toUpperCase() || el.hasAttribute("t-component"));
|
|
5925
|
-
for (const el of elements) {
|
|
5926
|
-
if (el.childNodes.length) {
|
|
5927
|
-
throw new OwlError(`Cannot have t-out on a component that already has content`);
|
|
6137
|
+
readComputed[atomSymbol] = computation;
|
|
6138
|
+
readComputed.set = options.set ?? (() => { });
|
|
6139
|
+
const context = contextStack.at(-1);
|
|
6140
|
+
if (context) {
|
|
6141
|
+
if (context.type === "component") {
|
|
6142
|
+
context.node.computations.push(computation);
|
|
5928
6143
|
}
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
const t = el.ownerDocument.createElement("t");
|
|
5932
|
-
if (value != null) {
|
|
5933
|
-
t.setAttribute("t-out", value);
|
|
6144
|
+
else if (context.type === "plugin") {
|
|
6145
|
+
context.manager.computations.push(computation);
|
|
5934
6146
|
}
|
|
5935
|
-
el.appendChild(t);
|
|
5936
|
-
}
|
|
5937
|
-
}
|
|
5938
|
-
/**
|
|
5939
|
-
* Normalizes the tree inside a given element and do some preliminary validation
|
|
5940
|
-
* on it. This function modifies the Element in place.
|
|
5941
|
-
*
|
|
5942
|
-
* @param el the element containing the tree that should be normalized
|
|
5943
|
-
*/
|
|
5944
|
-
function normalizeXML(el) {
|
|
5945
|
-
normalizeTIf(el);
|
|
5946
|
-
normalizeTOut(el);
|
|
5947
|
-
}
|
|
5948
|
-
|
|
5949
|
-
function compile(template, options = {
|
|
5950
|
-
hasGlobalValues: false,
|
|
5951
|
-
}) {
|
|
5952
|
-
// parsing
|
|
5953
|
-
const ast = parse(template, options.customDirectives);
|
|
5954
|
-
// code generation
|
|
5955
|
-
const codeGenerator = new CodeGenerator(ast, options);
|
|
5956
|
-
const code = codeGenerator.generateCode();
|
|
5957
|
-
// template function
|
|
5958
|
-
try {
|
|
5959
|
-
return new Function("app, bdom, helpers", code);
|
|
5960
|
-
}
|
|
5961
|
-
catch (originalError) {
|
|
5962
|
-
const { name } = options;
|
|
5963
|
-
const nameStr = name ? `template "${name}"` : "anonymous template";
|
|
5964
|
-
const err = new OwlError(`Failed to compile ${nameStr}: ${originalError.message}\n\ngenerated code:\nfunction(app, bdom, helpers) {\n${code}\n}`);
|
|
5965
|
-
err.cause = originalError;
|
|
5966
|
-
throw err;
|
|
5967
6147
|
}
|
|
6148
|
+
return readComputed;
|
|
5968
6149
|
}
|
|
5969
6150
|
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
|
|
5987
|
-
|
|
5988
|
-
|
|
5989
|
-
setComputation(undefined);
|
|
5990
|
-
unsubscribeEffect(computation);
|
|
5991
|
-
setComputation(previousComputation);
|
|
6151
|
+
function buildSignal(value, set) {
|
|
6152
|
+
const atom = {
|
|
6153
|
+
type: "signal",
|
|
6154
|
+
value,
|
|
6155
|
+
observers: new Set(),
|
|
6156
|
+
};
|
|
6157
|
+
let readValue = set(atom);
|
|
6158
|
+
const readSignal = () => {
|
|
6159
|
+
onReadAtom(atom);
|
|
6160
|
+
return readValue;
|
|
6161
|
+
};
|
|
6162
|
+
readSignal[atomSymbol] = atom;
|
|
6163
|
+
readSignal.set = function writeSignal(newValue) {
|
|
6164
|
+
if (Object.is(atom.value, newValue)) {
|
|
6165
|
+
return;
|
|
6166
|
+
}
|
|
6167
|
+
atom.value = newValue;
|
|
6168
|
+
readValue = set(atom);
|
|
6169
|
+
onWriteAtom(atom);
|
|
5992
6170
|
};
|
|
6171
|
+
return readSignal;
|
|
5993
6172
|
}
|
|
5994
|
-
function
|
|
5995
|
-
|
|
5996
|
-
|
|
5997
|
-
for (const childEffect of effect.observers) {
|
|
5998
|
-
// Consider it executed to avoid it's re-execution
|
|
5999
|
-
// todo: make a test for it
|
|
6000
|
-
childEffect.state = ComputationState.EXECUTED;
|
|
6001
|
-
removeSources(childEffect);
|
|
6002
|
-
unsubscribeEffect(childEffect);
|
|
6173
|
+
function invalidateSignal(signal) {
|
|
6174
|
+
if (typeof signal !== "function" || signal[atomSymbol]?.type !== "signal") {
|
|
6175
|
+
throw new OwlError(`Value is not a signal (${signal})`);
|
|
6003
6176
|
}
|
|
6004
|
-
|
|
6177
|
+
onWriteAtom(signal[atomSymbol]);
|
|
6178
|
+
}
|
|
6179
|
+
function signalArray(initialValue) {
|
|
6180
|
+
return buildSignal(initialValue, (atom) => proxifyTarget(atom.value, atom));
|
|
6181
|
+
}
|
|
6182
|
+
function signalObject(initialValue) {
|
|
6183
|
+
return buildSignal(initialValue, (atom) => proxifyTarget(atom.value, atom));
|
|
6184
|
+
}
|
|
6185
|
+
function signalMap(initialValue) {
|
|
6186
|
+
return buildSignal(initialValue, (atom) => proxifyTarget(atom.value, atom));
|
|
6005
6187
|
}
|
|
6006
|
-
function
|
|
6007
|
-
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
|
|
6011
|
-
effect.value = undefined;
|
|
6012
|
-
}
|
|
6188
|
+
function signalSet(initialValue) {
|
|
6189
|
+
return buildSignal(initialValue, (atom) => proxifyTarget(atom.value, atom));
|
|
6190
|
+
}
|
|
6191
|
+
function signal(value) {
|
|
6192
|
+
return buildSignal(value, (atom) => atom.value);
|
|
6013
6193
|
}
|
|
6194
|
+
signal.invalidate = invalidateSignal;
|
|
6195
|
+
signal.Array = signalArray;
|
|
6196
|
+
signal.Map = signalMap;
|
|
6197
|
+
signal.Object = signalObject;
|
|
6198
|
+
signal.Set = signalSet;
|
|
6014
6199
|
|
|
6015
|
-
|
|
6016
|
-
|
|
6017
|
-
|
|
6018
|
-
|
|
6019
|
-
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
this.__owl__ = manager;
|
|
6200
|
+
function assertType(value, validation, errorMessage = "Value does not match the type") {
|
|
6201
|
+
const issues = validateType(value, validation);
|
|
6202
|
+
if (issues.length) {
|
|
6203
|
+
const issueStrings = JSON.stringify(issues, (key, value) => {
|
|
6204
|
+
if (typeof value === "function") {
|
|
6205
|
+
return value.name;
|
|
6206
|
+
}
|
|
6207
|
+
return value;
|
|
6208
|
+
}, 2);
|
|
6209
|
+
throw new OwlError(`${errorMessage}\n${issueStrings}`);
|
|
6026
6210
|
}
|
|
6027
|
-
setup() { }
|
|
6028
6211
|
}
|
|
6029
|
-
|
|
6030
|
-
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6212
|
+
function createContext(issues, value, path, parent) {
|
|
6213
|
+
return {
|
|
6214
|
+
issueDepth: 0,
|
|
6215
|
+
path,
|
|
6216
|
+
value,
|
|
6217
|
+
get isValid() {
|
|
6218
|
+
return !issues.length;
|
|
6219
|
+
},
|
|
6220
|
+
addIssue(issue) {
|
|
6221
|
+
issues.push({
|
|
6222
|
+
received: this.value,
|
|
6223
|
+
path: this.path,
|
|
6224
|
+
...issue,
|
|
6225
|
+
});
|
|
6226
|
+
},
|
|
6227
|
+
mergeIssues(newIssues) {
|
|
6228
|
+
issues.push(...newIssues);
|
|
6229
|
+
},
|
|
6230
|
+
validate(type) {
|
|
6231
|
+
type(this);
|
|
6232
|
+
if (!this.isValid && parent) {
|
|
6233
|
+
parent.issueDepth = this.issueDepth + 1;
|
|
6234
|
+
}
|
|
6235
|
+
},
|
|
6236
|
+
withIssues(issues) {
|
|
6237
|
+
return createContext(issues, this.value, this.path, this);
|
|
6238
|
+
},
|
|
6239
|
+
withKey(key) {
|
|
6240
|
+
return createContext(issues, this.value[key], this.path.concat(key), this);
|
|
6241
|
+
},
|
|
6242
|
+
};
|
|
6243
|
+
}
|
|
6244
|
+
function validateType(value, validation) {
|
|
6245
|
+
const issues = [];
|
|
6246
|
+
validation(createContext(issues, value, []));
|
|
6247
|
+
return issues;
|
|
6248
|
+
}
|
|
6249
|
+
|
|
6250
|
+
class Resource {
|
|
6251
|
+
_items = signal.Array([]);
|
|
6252
|
+
_name;
|
|
6253
|
+
_validation;
|
|
6254
|
+
constructor(options = {}) {
|
|
6255
|
+
this._name = options.name;
|
|
6256
|
+
this._validation = options.validation;
|
|
6046
6257
|
}
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6258
|
+
items = computed(() => {
|
|
6259
|
+
return this._items()
|
|
6260
|
+
.sort((el1, el2) => el1[0] - el2[0])
|
|
6261
|
+
.map((elem) => elem[1]);
|
|
6262
|
+
});
|
|
6263
|
+
add(item, options = {}) {
|
|
6264
|
+
if (this._validation) {
|
|
6265
|
+
const info = this._name ? ` (resource '${this._name}')` : "";
|
|
6266
|
+
assertType(item, this._validation, `Resource item does not match the type${info}`);
|
|
6051
6267
|
}
|
|
6052
|
-
this.
|
|
6053
|
-
|
|
6054
|
-
getPluginById(id) {
|
|
6055
|
-
return this.plugins[id] || null;
|
|
6056
|
-
}
|
|
6057
|
-
getPlugin(pluginConstructor) {
|
|
6058
|
-
return this.getPluginById(pluginConstructor.id);
|
|
6268
|
+
this._items().push([options.sequence ?? 50, item]);
|
|
6269
|
+
return this;
|
|
6059
6270
|
}
|
|
6060
|
-
|
|
6061
|
-
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
if (this.plugins.hasOwnProperty(pluginConstructor.id)) {
|
|
6065
|
-
const existingPluginType = this.getPluginById(pluginConstructor.id).constructor;
|
|
6066
|
-
if (existingPluginType !== pluginConstructor) {
|
|
6067
|
-
throw new OwlError(`Trying to start a plugin with the same id as an other plugin (id: '${pluginConstructor.id}', existing plugin: '${existingPluginType.name}', starting plugin: '${pluginConstructor.name}')`);
|
|
6068
|
-
}
|
|
6069
|
-
return null;
|
|
6070
|
-
}
|
|
6071
|
-
const plugin = new pluginConstructor(this);
|
|
6072
|
-
this.plugins[pluginConstructor.id] = plugin;
|
|
6073
|
-
plugin.setup();
|
|
6074
|
-
return plugin;
|
|
6271
|
+
delete(item) {
|
|
6272
|
+
const items = this._items().filter(([seq, val]) => val !== item);
|
|
6273
|
+
this._items.set(items);
|
|
6274
|
+
return this;
|
|
6075
6275
|
}
|
|
6076
|
-
|
|
6077
|
-
|
|
6078
|
-
type: "plugin",
|
|
6079
|
-
app: this.app,
|
|
6080
|
-
manager: this,
|
|
6081
|
-
get status() {
|
|
6082
|
-
return this.manager.status;
|
|
6083
|
-
},
|
|
6084
|
-
});
|
|
6085
|
-
try {
|
|
6086
|
-
for (const pluginConstructor of pluginConstructors) {
|
|
6087
|
-
this.startPlugin(pluginConstructor);
|
|
6088
|
-
}
|
|
6089
|
-
}
|
|
6090
|
-
finally {
|
|
6091
|
-
contextStack.pop();
|
|
6092
|
-
}
|
|
6093
|
-
this.status = 1 /* STATUS.MOUNTED */;
|
|
6276
|
+
has(item) {
|
|
6277
|
+
return this._items().some(([s, value]) => value === item);
|
|
6094
6278
|
}
|
|
6095
6279
|
}
|
|
6096
|
-
function
|
|
6097
|
-
|
|
6098
|
-
|
|
6099
|
-
}
|
|
6100
|
-
else {
|
|
6101
|
-
manager.onDestroyCb.push(effect(() => {
|
|
6102
|
-
const pluginItems = plugins.items();
|
|
6103
|
-
untrack(() => manager.startPlugins(pluginItems));
|
|
6104
|
-
}));
|
|
6280
|
+
function useResource(r, elements) {
|
|
6281
|
+
for (let elem of elements) {
|
|
6282
|
+
r.add(elem);
|
|
6105
6283
|
}
|
|
6284
|
+
onWillDestroy(() => {
|
|
6285
|
+
for (let elem of elements) {
|
|
6286
|
+
r.delete(elem);
|
|
6287
|
+
}
|
|
6288
|
+
});
|
|
6106
6289
|
}
|
|
6107
6290
|
|
|
6108
|
-
|
|
6109
|
-
|
|
6110
|
-
|
|
6111
|
-
|
|
6112
|
-
|
|
6113
|
-
|
|
6114
|
-
|
|
6115
|
-
tasks = new Set();
|
|
6116
|
-
requestAnimationFrame;
|
|
6117
|
-
frame = 0;
|
|
6118
|
-
delayedRenders = [];
|
|
6119
|
-
cancelledNodes = new Set();
|
|
6120
|
-
processing = false;
|
|
6121
|
-
constructor() {
|
|
6122
|
-
this.requestAnimationFrame = Scheduler.requestAnimationFrame;
|
|
6123
|
-
}
|
|
6124
|
-
addFiber(fiber) {
|
|
6125
|
-
this.tasks.add(fiber.root);
|
|
6291
|
+
class Registry {
|
|
6292
|
+
_map = signal.Object(Object.create(null));
|
|
6293
|
+
_name;
|
|
6294
|
+
_validation;
|
|
6295
|
+
constructor(options = {}) {
|
|
6296
|
+
this._name = options.name || "registry";
|
|
6297
|
+
this._validation = options.validation;
|
|
6126
6298
|
}
|
|
6127
|
-
|
|
6128
|
-
this.
|
|
6129
|
-
|
|
6130
|
-
|
|
6299
|
+
entries = computed(() => {
|
|
6300
|
+
const entries = Object.entries(this._map())
|
|
6301
|
+
.sort((el1, el2) => el1[1][0] - el2[1][0])
|
|
6302
|
+
.map(([str, elem]) => [str, elem[1]]);
|
|
6303
|
+
return entries;
|
|
6304
|
+
});
|
|
6305
|
+
items = computed(() => this.entries().map((e) => e[1]));
|
|
6306
|
+
addById(item, options = {}) {
|
|
6307
|
+
if (!item.id) {
|
|
6308
|
+
throw new OwlError(`Item should have an id key (registry '${this._name}')`);
|
|
6131
6309
|
}
|
|
6310
|
+
return this.add(item.id, item, { sequence: options.sequence ?? 50 });
|
|
6132
6311
|
}
|
|
6133
|
-
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
flush() {
|
|
6138
|
-
if (this.delayedRenders.length) {
|
|
6139
|
-
let renders = this.delayedRenders;
|
|
6140
|
-
this.delayedRenders = [];
|
|
6141
|
-
for (let f of renders) {
|
|
6142
|
-
if (f.root && f.node.status !== 3 /* STATUS.DESTROYED */ && f.node.fiber === f) {
|
|
6143
|
-
f.render();
|
|
6144
|
-
}
|
|
6145
|
-
}
|
|
6146
|
-
}
|
|
6147
|
-
if (this.frame === 0) {
|
|
6148
|
-
this.frame = this.requestAnimationFrame(() => this.processTasks());
|
|
6312
|
+
add(key, value, options = {}) {
|
|
6313
|
+
if (this._validation) {
|
|
6314
|
+
const info = this._name ? ` (registry '${this._name}', key: '${key}')` : ` (key: '${key}')`;
|
|
6315
|
+
assertType(value, this._validation, `Registry entry does not match the type${info}`);
|
|
6149
6316
|
}
|
|
6317
|
+
this._map()[key] = [options.sequence ?? 50, value];
|
|
6318
|
+
return this;
|
|
6150
6319
|
}
|
|
6151
|
-
|
|
6152
|
-
|
|
6153
|
-
|
|
6154
|
-
|
|
6155
|
-
this.processing = true;
|
|
6156
|
-
this.frame = 0;
|
|
6157
|
-
for (let node of this.cancelledNodes) {
|
|
6158
|
-
node._destroy();
|
|
6159
|
-
}
|
|
6160
|
-
this.cancelledNodes.clear();
|
|
6161
|
-
for (let task of this.tasks) {
|
|
6162
|
-
this.processFiber(task);
|
|
6163
|
-
}
|
|
6164
|
-
for (let task of this.tasks) {
|
|
6165
|
-
if (task.node.status === 3 /* STATUS.DESTROYED */) {
|
|
6166
|
-
this.tasks.delete(task);
|
|
6167
|
-
}
|
|
6320
|
+
get(key, defaultValue) {
|
|
6321
|
+
const hasKey = key in this._map();
|
|
6322
|
+
if (!hasKey && arguments.length < 2) {
|
|
6323
|
+
throw new Error(`KeyNotFoundError: Cannot find key "${key}" (registry '${this._name}')`);
|
|
6168
6324
|
}
|
|
6169
|
-
this.
|
|
6325
|
+
return hasKey ? this._map()[key][1] : defaultValue;
|
|
6170
6326
|
}
|
|
6171
|
-
|
|
6172
|
-
|
|
6173
|
-
|
|
6327
|
+
delete(key) {
|
|
6328
|
+
delete this._map()[key];
|
|
6329
|
+
}
|
|
6330
|
+
has(key) {
|
|
6331
|
+
return key in this._map();
|
|
6332
|
+
}
|
|
6333
|
+
}
|
|
6334
|
+
|
|
6335
|
+
const anyType = function validateAny() { };
|
|
6336
|
+
const booleanType = function validateBoolean(context) {
|
|
6337
|
+
if (typeof context.value !== "boolean") {
|
|
6338
|
+
context.addIssue({ message: "value is not a boolean" });
|
|
6339
|
+
}
|
|
6340
|
+
};
|
|
6341
|
+
const numberType = function validateNumber(context) {
|
|
6342
|
+
if (typeof context.value !== "number") {
|
|
6343
|
+
context.addIssue({ message: "value is not a number" });
|
|
6344
|
+
}
|
|
6345
|
+
};
|
|
6346
|
+
const stringType = function validateString(context) {
|
|
6347
|
+
if (typeof context.value !== "string") {
|
|
6348
|
+
context.addIssue({ message: "value is not a string" });
|
|
6349
|
+
}
|
|
6350
|
+
};
|
|
6351
|
+
function arrayType(elementType) {
|
|
6352
|
+
return function validateArray(context) {
|
|
6353
|
+
if (!Array.isArray(context.value)) {
|
|
6354
|
+
context.addIssue({ message: "value is not an array" });
|
|
6174
6355
|
return;
|
|
6175
6356
|
}
|
|
6176
|
-
|
|
6177
|
-
if (hasError && fiber.counter !== 0) {
|
|
6178
|
-
this.tasks.delete(fiber);
|
|
6357
|
+
if (!elementType) {
|
|
6179
6358
|
return;
|
|
6180
6359
|
}
|
|
6181
|
-
|
|
6182
|
-
|
|
6183
|
-
return;
|
|
6360
|
+
for (let index = 0; index < context.value.length; index++) {
|
|
6361
|
+
context.withKey(index).validate(elementType);
|
|
6184
6362
|
}
|
|
6185
|
-
|
|
6186
|
-
|
|
6187
|
-
|
|
6188
|
-
|
|
6189
|
-
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
// the fiber, so in that case, we actually want to keep the fiber around,
|
|
6193
|
-
// otherwise it will just be ignored.
|
|
6194
|
-
if (fiber.appliedToDom) {
|
|
6195
|
-
this.tasks.delete(fiber);
|
|
6196
|
-
}
|
|
6363
|
+
};
|
|
6364
|
+
}
|
|
6365
|
+
function constructorType(constructor) {
|
|
6366
|
+
return function validateConstructor(context) {
|
|
6367
|
+
if (!(typeof context.value === "function") ||
|
|
6368
|
+
!(context.value === constructor || context.value.prototype instanceof constructor)) {
|
|
6369
|
+
context.addIssue({ message: `value is not '${constructor.name}' or an extension` });
|
|
6197
6370
|
}
|
|
6198
|
-
}
|
|
6371
|
+
};
|
|
6199
6372
|
}
|
|
6200
|
-
|
|
6201
|
-
|
|
6202
|
-
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
static validateTarget = validateTarget;
|
|
6206
|
-
static apps = apps;
|
|
6207
|
-
static version = version;
|
|
6208
|
-
name;
|
|
6209
|
-
scheduler = new Scheduler();
|
|
6210
|
-
roots = new Set();
|
|
6211
|
-
pluginManager;
|
|
6212
|
-
constructor(config = {}) {
|
|
6213
|
-
super(config);
|
|
6214
|
-
this.name = config.name || "";
|
|
6215
|
-
apps.add(this);
|
|
6216
|
-
this.pluginManager = new PluginManager(this, { config: config.config });
|
|
6217
|
-
if (config.plugins) {
|
|
6218
|
-
startPlugins(this.pluginManager, config.plugins);
|
|
6373
|
+
function customValidator(type, validator, errorMessage = "value does not match custom validation") {
|
|
6374
|
+
return function validateCustom(context) {
|
|
6375
|
+
context.validate(type);
|
|
6376
|
+
if (!context.isValid) {
|
|
6377
|
+
return;
|
|
6219
6378
|
}
|
|
6220
|
-
if (
|
|
6221
|
-
|
|
6379
|
+
if (!validator(context.value)) {
|
|
6380
|
+
context.addIssue({ message: errorMessage });
|
|
6222
6381
|
}
|
|
6223
|
-
|
|
6224
|
-
|
|
6225
|
-
|
|
6382
|
+
};
|
|
6383
|
+
}
|
|
6384
|
+
function functionType(parameters = [], result = undefined) {
|
|
6385
|
+
return function validateFunction(context) {
|
|
6386
|
+
if (typeof context.value !== "function") {
|
|
6387
|
+
context.addIssue({ message: "value is not a function" });
|
|
6226
6388
|
}
|
|
6227
|
-
}
|
|
6228
|
-
|
|
6229
|
-
|
|
6230
|
-
|
|
6231
|
-
|
|
6232
|
-
|
|
6233
|
-
resolve = res;
|
|
6234
|
-
reject = rej;
|
|
6235
|
-
});
|
|
6236
|
-
const restore = saveContext();
|
|
6237
|
-
let node;
|
|
6238
|
-
let error = null;
|
|
6239
|
-
try {
|
|
6240
|
-
node = new ComponentNode(Root, props, this, null, null);
|
|
6389
|
+
};
|
|
6390
|
+
}
|
|
6391
|
+
function instanceType(constructor) {
|
|
6392
|
+
return function validateInstanceType(context) {
|
|
6393
|
+
if (!(context.value instanceof constructor)) {
|
|
6394
|
+
context.addIssue({ message: `value is not an instance of '${constructor.name}'` });
|
|
6241
6395
|
}
|
|
6242
|
-
|
|
6243
|
-
|
|
6244
|
-
|
|
6396
|
+
};
|
|
6397
|
+
}
|
|
6398
|
+
function intersection(types) {
|
|
6399
|
+
return function validateIntersection(context) {
|
|
6400
|
+
for (const type of types) {
|
|
6401
|
+
context.validate(type);
|
|
6245
6402
|
}
|
|
6246
|
-
|
|
6247
|
-
|
|
6403
|
+
};
|
|
6404
|
+
}
|
|
6405
|
+
function literalType(literal) {
|
|
6406
|
+
return function validateLiteral(context) {
|
|
6407
|
+
if (context.value !== literal) {
|
|
6408
|
+
context.addIssue({
|
|
6409
|
+
message: `value is not equal to ${typeof literal === "string" ? `'${literal}'` : literal}`,
|
|
6410
|
+
});
|
|
6248
6411
|
}
|
|
6249
|
-
|
|
6250
|
-
|
|
6251
|
-
|
|
6252
|
-
|
|
6253
|
-
|
|
6254
|
-
|
|
6255
|
-
|
|
6256
|
-
|
|
6257
|
-
|
|
6258
|
-
return promise;
|
|
6259
|
-
},
|
|
6260
|
-
destroy: () => {
|
|
6261
|
-
this.roots.delete(root);
|
|
6262
|
-
node.destroy();
|
|
6263
|
-
this.scheduler.processTasks();
|
|
6264
|
-
},
|
|
6265
|
-
};
|
|
6266
|
-
this.roots.add(root);
|
|
6267
|
-
return root;
|
|
6412
|
+
};
|
|
6413
|
+
}
|
|
6414
|
+
function literalSelection(literals) {
|
|
6415
|
+
return union(literals.map(literalType));
|
|
6416
|
+
}
|
|
6417
|
+
function validateObject(context, schema, isStrict) {
|
|
6418
|
+
if (typeof context.value !== "object" || Array.isArray(context.value) || context.value === null) {
|
|
6419
|
+
context.addIssue({ message: "value is not an object" });
|
|
6420
|
+
return;
|
|
6268
6421
|
}
|
|
6269
|
-
|
|
6270
|
-
|
|
6271
|
-
let handlers = nodeErrorHandlers.get(node);
|
|
6272
|
-
if (!handlers) {
|
|
6273
|
-
handlers = [];
|
|
6274
|
-
nodeErrorHandlers.set(node, handlers);
|
|
6275
|
-
}
|
|
6276
|
-
handlers.unshift((e, finalize) => {
|
|
6277
|
-
const finalError = finalize();
|
|
6278
|
-
reject(finalError);
|
|
6279
|
-
});
|
|
6280
|
-
// manually set a onMounted callback.
|
|
6281
|
-
// that way, we are independant from the current node.
|
|
6282
|
-
node.mounted.push(() => {
|
|
6283
|
-
resolve(node.component);
|
|
6284
|
-
handlers.shift();
|
|
6285
|
-
});
|
|
6286
|
-
node.mountComponent(target, options);
|
|
6422
|
+
if (!schema) {
|
|
6423
|
+
return;
|
|
6287
6424
|
}
|
|
6288
|
-
|
|
6289
|
-
|
|
6290
|
-
|
|
6425
|
+
const isShape = !Array.isArray(schema);
|
|
6426
|
+
let shape = schema;
|
|
6427
|
+
if (Array.isArray(schema)) {
|
|
6428
|
+
shape = {};
|
|
6429
|
+
for (const key of schema) {
|
|
6430
|
+
shape[key] = null;
|
|
6291
6431
|
}
|
|
6292
|
-
this.pluginManager.destroy();
|
|
6293
|
-
this.scheduler.processTasks();
|
|
6294
|
-
apps.delete(this);
|
|
6295
|
-
}
|
|
6296
|
-
handleError(...args) {
|
|
6297
|
-
return handleError(...args);
|
|
6298
6432
|
}
|
|
6299
|
-
|
|
6300
|
-
|
|
6301
|
-
|
|
6302
|
-
|
|
6303
|
-
|
|
6304
|
-
|
|
6305
|
-
|
|
6306
|
-
const mainEventHandler = (data, ev, currentTarget) => {
|
|
6307
|
-
const { data: _data, modifiers } = filterOutModifiersFromData(data);
|
|
6308
|
-
data = _data;
|
|
6309
|
-
let stopped = false;
|
|
6310
|
-
if (modifiers.length) {
|
|
6311
|
-
let selfMode = false;
|
|
6312
|
-
const isSelf = ev.target === currentTarget;
|
|
6313
|
-
for (const mod of modifiers) {
|
|
6314
|
-
switch (mod) {
|
|
6315
|
-
case "self":
|
|
6316
|
-
selfMode = true;
|
|
6317
|
-
if (isSelf) {
|
|
6318
|
-
continue;
|
|
6319
|
-
}
|
|
6320
|
-
else {
|
|
6321
|
-
return stopped;
|
|
6322
|
-
}
|
|
6323
|
-
case "prevent":
|
|
6324
|
-
if ((selfMode && isSelf) || !selfMode)
|
|
6325
|
-
ev.preventDefault();
|
|
6326
|
-
continue;
|
|
6327
|
-
case "stop":
|
|
6328
|
-
if ((selfMode && isSelf) || !selfMode)
|
|
6329
|
-
ev.stopPropagation();
|
|
6330
|
-
stopped = true;
|
|
6331
|
-
continue;
|
|
6433
|
+
const missingKeys = [];
|
|
6434
|
+
for (const key in shape) {
|
|
6435
|
+
const property = key.endsWith("?") ? key.slice(0, -1) : key;
|
|
6436
|
+
if (context.value[property] === undefined) {
|
|
6437
|
+
if (!key.endsWith("?")) {
|
|
6438
|
+
missingKeys.push(property);
|
|
6332
6439
|
}
|
|
6440
|
+
continue;
|
|
6333
6441
|
}
|
|
6334
|
-
|
|
6335
|
-
|
|
6336
|
-
// We check this rather than data[0] being truthy (or typeof function) so that it crashes
|
|
6337
|
-
// as expected when there is a handler expression that evaluates to a falsy value
|
|
6338
|
-
if (Object.hasOwnProperty.call(data, 0)) {
|
|
6339
|
-
const handler = data[0];
|
|
6340
|
-
if (typeof handler !== "function") {
|
|
6341
|
-
throw new OwlError(`Invalid handler (expected a function, received: '${handler}')`);
|
|
6342
|
-
}
|
|
6343
|
-
let node = data[1] ? data[1].__owl__ : null;
|
|
6344
|
-
if (node ? node.status === 1 /* STATUS.MOUNTED */ : true) {
|
|
6345
|
-
handler(data[1], ev);
|
|
6442
|
+
if (isShape) {
|
|
6443
|
+
context.withKey(property).validate(shape[key]);
|
|
6346
6444
|
}
|
|
6347
6445
|
}
|
|
6348
|
-
|
|
6349
|
-
|
|
6350
|
-
|
|
6351
|
-
|
|
6352
|
-
|
|
6353
|
-
|
|
6354
|
-
|
|
6355
|
-
|
|
6356
|
-
|
|
6357
|
-
|
|
6358
|
-
|
|
6446
|
+
if (missingKeys.length) {
|
|
6447
|
+
context.addIssue({
|
|
6448
|
+
message: "object value has missing keys",
|
|
6449
|
+
missingKeys,
|
|
6450
|
+
});
|
|
6451
|
+
}
|
|
6452
|
+
if (isStrict) {
|
|
6453
|
+
const unknownKeys = [];
|
|
6454
|
+
for (const key in context.value) {
|
|
6455
|
+
if (!(key in shape) && !(`${key}?` in shape)) {
|
|
6456
|
+
unknownKeys.push(key);
|
|
6457
|
+
}
|
|
6458
|
+
}
|
|
6459
|
+
if (unknownKeys.length) {
|
|
6460
|
+
context.addIssue({
|
|
6461
|
+
message: "object value has unknown keys",
|
|
6462
|
+
unknownKeys,
|
|
6463
|
+
});
|
|
6359
6464
|
}
|
|
6360
|
-
onReadAtom(computation);
|
|
6361
|
-
return computation.value;
|
|
6362
6465
|
}
|
|
6363
|
-
readComputed[atomSymbol] = computation;
|
|
6364
|
-
readComputed.set = options.set ?? (() => { });
|
|
6365
|
-
return readComputed;
|
|
6366
6466
|
}
|
|
6367
|
-
|
|
6368
|
-
|
|
6369
|
-
|
|
6370
|
-
type: "signal",
|
|
6371
|
-
value,
|
|
6372
|
-
observers: new Set(),
|
|
6467
|
+
function objectType(schema = {}) {
|
|
6468
|
+
return function validateLooseObject(context) {
|
|
6469
|
+
validateObject(context, schema, false);
|
|
6373
6470
|
};
|
|
6374
|
-
|
|
6375
|
-
|
|
6376
|
-
|
|
6377
|
-
|
|
6471
|
+
}
|
|
6472
|
+
function strictObjectType(schema) {
|
|
6473
|
+
return function validateStrictObject(context) {
|
|
6474
|
+
validateObject(context, schema, true);
|
|
6378
6475
|
};
|
|
6379
|
-
|
|
6380
|
-
|
|
6381
|
-
|
|
6382
|
-
|
|
6476
|
+
}
|
|
6477
|
+
function promiseType(type) {
|
|
6478
|
+
return function validatePromise(context) {
|
|
6479
|
+
if (!(context.value instanceof Promise)) {
|
|
6480
|
+
context.addIssue({ message: "value is not a promise" });
|
|
6383
6481
|
}
|
|
6384
|
-
atom.value = newValue;
|
|
6385
|
-
readValue = set(atom);
|
|
6386
|
-
onWriteAtom(atom);
|
|
6387
6482
|
};
|
|
6388
|
-
return readSignal;
|
|
6389
|
-
}
|
|
6390
|
-
function invalidateSignal(signal) {
|
|
6391
|
-
if (typeof signal !== "function" || signal[atomSymbol]?.type !== "signal") {
|
|
6392
|
-
throw new OwlError(`Value is not a signal (${signal})`);
|
|
6393
|
-
}
|
|
6394
|
-
onWriteAtom(signal[atomSymbol]);
|
|
6395
6483
|
}
|
|
6396
|
-
function
|
|
6397
|
-
return
|
|
6484
|
+
function recordType(valueType) {
|
|
6485
|
+
return function validateRecord(context) {
|
|
6486
|
+
if (typeof context.value !== "object" ||
|
|
6487
|
+
Array.isArray(context.value) ||
|
|
6488
|
+
context.value === null) {
|
|
6489
|
+
context.addIssue({ message: "value is not an object" });
|
|
6490
|
+
return;
|
|
6491
|
+
}
|
|
6492
|
+
if (!valueType) {
|
|
6493
|
+
return;
|
|
6494
|
+
}
|
|
6495
|
+
for (const key in context.value) {
|
|
6496
|
+
context.withKey(key).validate(valueType);
|
|
6497
|
+
}
|
|
6498
|
+
};
|
|
6398
6499
|
}
|
|
6399
|
-
function
|
|
6400
|
-
return
|
|
6500
|
+
function tuple(types) {
|
|
6501
|
+
return function validateTuple(context) {
|
|
6502
|
+
if (!Array.isArray(context.value)) {
|
|
6503
|
+
context.addIssue({ message: "value is not an array" });
|
|
6504
|
+
return;
|
|
6505
|
+
}
|
|
6506
|
+
if (context.value.length !== types.length) {
|
|
6507
|
+
context.addIssue({ message: "tuple value does not have the correct length" });
|
|
6508
|
+
return;
|
|
6509
|
+
}
|
|
6510
|
+
for (let index = 0; index < types.length; index++) {
|
|
6511
|
+
context.withKey(index).validate(types[index]);
|
|
6512
|
+
}
|
|
6513
|
+
};
|
|
6401
6514
|
}
|
|
6402
|
-
function
|
|
6403
|
-
return
|
|
6515
|
+
function union(types) {
|
|
6516
|
+
return function validateUnion(context) {
|
|
6517
|
+
let firstIssueIndex = 0;
|
|
6518
|
+
const subIssues = [];
|
|
6519
|
+
for (const type of types) {
|
|
6520
|
+
const subContext = context.withIssues(subIssues);
|
|
6521
|
+
subContext.validate(type);
|
|
6522
|
+
if (subIssues.length === firstIssueIndex || subContext.issueDepth > 0) {
|
|
6523
|
+
context.mergeIssues(subIssues.slice(firstIssueIndex));
|
|
6524
|
+
return;
|
|
6525
|
+
}
|
|
6526
|
+
firstIssueIndex = subIssues.length;
|
|
6527
|
+
}
|
|
6528
|
+
context.addIssue({
|
|
6529
|
+
message: "value does not match union type",
|
|
6530
|
+
subIssues,
|
|
6531
|
+
});
|
|
6532
|
+
};
|
|
6404
6533
|
}
|
|
6405
|
-
function
|
|
6406
|
-
return
|
|
6534
|
+
function reactiveValueType(type) {
|
|
6535
|
+
return function validateReactiveValue(context) {
|
|
6536
|
+
if (typeof context.value !== "function" || !context.value[atomSymbol]) {
|
|
6537
|
+
context.addIssue({ message: "value is not a reactive value" });
|
|
6538
|
+
}
|
|
6539
|
+
};
|
|
6407
6540
|
}
|
|
6408
|
-
function
|
|
6409
|
-
return
|
|
6541
|
+
function ref(type) {
|
|
6542
|
+
return union([literalType(null), instanceType(type)]);
|
|
6410
6543
|
}
|
|
6411
|
-
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
|
|
6544
|
+
const types = {
|
|
6545
|
+
and: intersection,
|
|
6546
|
+
any: anyType,
|
|
6547
|
+
array: arrayType,
|
|
6548
|
+
boolean: booleanType,
|
|
6549
|
+
constructor: constructorType,
|
|
6550
|
+
customValidator: customValidator,
|
|
6551
|
+
function: functionType,
|
|
6552
|
+
instanceOf: instanceType,
|
|
6553
|
+
literal: literalType,
|
|
6554
|
+
number: numberType,
|
|
6555
|
+
object: objectType,
|
|
6556
|
+
or: union,
|
|
6557
|
+
promise: promiseType,
|
|
6558
|
+
record: recordType,
|
|
6559
|
+
ref,
|
|
6560
|
+
selection: literalSelection,
|
|
6561
|
+
signal: reactiveValueType,
|
|
6562
|
+
strictObject: strictObjectType,
|
|
6563
|
+
string: stringType,
|
|
6564
|
+
tuple: tuple,
|
|
6565
|
+
};
|
|
6416
6566
|
|
|
6417
|
-
|
|
6418
|
-
|
|
6419
|
-
|
|
6420
|
-
|
|
6421
|
-
|
|
6422
|
-
|
|
6423
|
-
|
|
6424
|
-
|
|
6425
|
-
|
|
6426
|
-
return this._items()
|
|
6427
|
-
.sort((el1, el2) => el1[0] - el2[0])
|
|
6428
|
-
.map((elem) => elem[1]);
|
|
6429
|
-
});
|
|
6430
|
-
add(item, options = {}) {
|
|
6431
|
-
if (this._validation) {
|
|
6432
|
-
const info = this._name ? ` (resource '${this._name}')` : "";
|
|
6433
|
-
assertType(item, this._validation, `Resource item does not match the type${info}`);
|
|
6434
|
-
}
|
|
6435
|
-
this._items().push([options.sequence ?? 50, item]);
|
|
6436
|
-
return this;
|
|
6437
|
-
}
|
|
6438
|
-
delete(item) {
|
|
6439
|
-
const items = this._items().filter(([seq, val]) => val !== item);
|
|
6440
|
-
this._items.set(items);
|
|
6441
|
-
return this;
|
|
6442
|
-
}
|
|
6443
|
-
has(item) {
|
|
6444
|
-
return this._items().some(([s, value]) => value === item);
|
|
6445
|
-
}
|
|
6446
|
-
}
|
|
6447
|
-
function useResource(r, elements) {
|
|
6448
|
-
for (let elem of elements) {
|
|
6449
|
-
r.add(elem);
|
|
6450
|
-
}
|
|
6451
|
-
onWillDestroy(() => {
|
|
6452
|
-
for (let elem of elements) {
|
|
6453
|
-
r.delete(elem);
|
|
6567
|
+
function validateObjectWithDefaults(schema, defaultValues) {
|
|
6568
|
+
const keys = Array.isArray(schema) ? schema : Object.keys(schema);
|
|
6569
|
+
const mandatoryDefaultedKeys = keys.filter((key) => !key.endsWith("?") && key in defaultValues);
|
|
6570
|
+
return (context) => {
|
|
6571
|
+
if (mandatoryDefaultedKeys.length) {
|
|
6572
|
+
context.addIssue({
|
|
6573
|
+
message: "props have default values on mandatory keys",
|
|
6574
|
+
keys: mandatoryDefaultedKeys,
|
|
6575
|
+
});
|
|
6454
6576
|
}
|
|
6455
|
-
|
|
6577
|
+
context.validate(types.object(schema));
|
|
6578
|
+
};
|
|
6456
6579
|
}
|
|
6457
|
-
|
|
6458
|
-
|
|
6459
|
-
|
|
6460
|
-
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
this._name = options.name || "registry";
|
|
6464
|
-
this._validation = options.validation;
|
|
6465
|
-
}
|
|
6466
|
-
entries = computed(() => {
|
|
6467
|
-
const entries = Object.entries(this._map())
|
|
6468
|
-
.sort((el1, el2) => el1[1][0] - el2[1][0])
|
|
6469
|
-
.map(([str, elem]) => [str, elem[1]]);
|
|
6470
|
-
return entries;
|
|
6471
|
-
});
|
|
6472
|
-
items = computed(() => this.entries().map((e) => e[1]));
|
|
6473
|
-
addById(item, options = {}) {
|
|
6474
|
-
if (!item.id) {
|
|
6475
|
-
throw new OwlError(`Item should have an id key (registry '${this._name}')`);
|
|
6580
|
+
function props(type, defaults) {
|
|
6581
|
+
const { node, app, componentName } = getContext("component");
|
|
6582
|
+
Object.assign(node.defaultProps, defaults);
|
|
6583
|
+
function getProp(key) {
|
|
6584
|
+
if (node.props[key] === undefined && defaults) {
|
|
6585
|
+
return defaults[key];
|
|
6476
6586
|
}
|
|
6477
|
-
return
|
|
6587
|
+
return node.props[key];
|
|
6478
6588
|
}
|
|
6479
|
-
|
|
6480
|
-
|
|
6481
|
-
|
|
6482
|
-
|
|
6589
|
+
const result = Object.create(null);
|
|
6590
|
+
function applyPropGetters(keys) {
|
|
6591
|
+
for (const key of keys) {
|
|
6592
|
+
Reflect.defineProperty(result, key, {
|
|
6593
|
+
enumerable: true,
|
|
6594
|
+
get: getProp.bind(null, key),
|
|
6595
|
+
});
|
|
6483
6596
|
}
|
|
6484
|
-
this._map()[key] = [options.sequence ?? 50, value];
|
|
6485
|
-
return this;
|
|
6486
6597
|
}
|
|
6487
|
-
|
|
6488
|
-
const
|
|
6489
|
-
|
|
6490
|
-
|
|
6598
|
+
if (type) {
|
|
6599
|
+
const keys = (Array.isArray(type) ? type : Object.keys(type)).map((key) => key.endsWith("?") ? key.slice(0, -1) : key);
|
|
6600
|
+
applyPropGetters(keys);
|
|
6601
|
+
if (app.dev) {
|
|
6602
|
+
const validation = defaults ? validateObjectWithDefaults(type, defaults) : types.object(type);
|
|
6603
|
+
assertType(node.props, validation, `Invalid component props (${componentName})`);
|
|
6604
|
+
node.willUpdateProps.push((np) => {
|
|
6605
|
+
assertType(np, validation, `Invalid component props (${componentName})`);
|
|
6606
|
+
});
|
|
6491
6607
|
}
|
|
6492
|
-
return hasKey ? this._map()[key][1] : defaultValue;
|
|
6493
|
-
}
|
|
6494
|
-
delete(key) {
|
|
6495
|
-
delete this._map()[key];
|
|
6496
6608
|
}
|
|
6497
|
-
|
|
6498
|
-
|
|
6609
|
+
else {
|
|
6610
|
+
const getKeys = (props) => {
|
|
6611
|
+
const keys = [];
|
|
6612
|
+
for (const k in props) {
|
|
6613
|
+
if (k.charCodeAt(0) !== 1) {
|
|
6614
|
+
keys.push(k);
|
|
6615
|
+
}
|
|
6616
|
+
}
|
|
6617
|
+
if (defaults) {
|
|
6618
|
+
for (const k in defaults) {
|
|
6619
|
+
if (!(k in props)) {
|
|
6620
|
+
keys.push(k);
|
|
6621
|
+
}
|
|
6622
|
+
}
|
|
6623
|
+
}
|
|
6624
|
+
return keys;
|
|
6625
|
+
};
|
|
6626
|
+
let keys = getKeys(node.props);
|
|
6627
|
+
applyPropGetters(keys);
|
|
6628
|
+
node.willUpdateProps.push((np) => {
|
|
6629
|
+
for (const key of keys) {
|
|
6630
|
+
Reflect.deleteProperty(result, key);
|
|
6631
|
+
}
|
|
6632
|
+
keys = getKeys(np);
|
|
6633
|
+
applyPropGetters(keys);
|
|
6634
|
+
});
|
|
6499
6635
|
}
|
|
6636
|
+
return result;
|
|
6500
6637
|
}
|
|
6501
6638
|
|
|
6502
6639
|
function status(entity) {
|
|
@@ -6677,8 +6814,8 @@
|
|
|
6677
6814
|
exports.xml = xml;
|
|
6678
6815
|
|
|
6679
6816
|
|
|
6680
|
-
__info__.date = '2026-
|
|
6681
|
-
__info__.hash = '
|
|
6817
|
+
__info__.date = '2026-04-02T11:14:58.432Z';
|
|
6818
|
+
__info__.hash = '9ca35d8';
|
|
6682
6819
|
__info__.url = 'https://github.com/odoo/owl';
|
|
6683
6820
|
|
|
6684
6821
|
|