@odoo/owl 2.8.1 → 3.0.0-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/compile_templates.mjs +92 -189
- package/dist/compiler.js +2378 -0
- package/dist/owl-devtools.zip +0 -0
- package/dist/owl.cjs.js +1371 -1261
- package/dist/owl.cjs.runtime.js +4094 -0
- package/dist/owl.es.js +1358 -1252
- package/dist/owl.es.runtime.js +4050 -0
- package/dist/owl.iife.js +1371 -1261
- package/dist/owl.iife.min.js +1 -1
- package/dist/owl.iife.runtime.js +4098 -0
- package/dist/owl.iife.runtime.min.js +1 -0
- package/dist/types/compiler/code_generator.d.ts +3 -5
- package/dist/types/compiler/index.d.ts +4 -4
- package/dist/types/compiler/inline_expressions.d.ts +1 -1
- package/dist/types/compiler/parser.d.ts +21 -28
- package/dist/types/owl.d.ts +299 -205
- package/dist/types/runtime/app.d.ts +29 -31
- package/dist/types/runtime/blockdom/block_compiler.d.ts +3 -3
- package/dist/types/runtime/blockdom/config.d.ts +1 -1
- package/dist/types/runtime/blockdom/event_catcher.d.ts +2 -2
- package/dist/types/runtime/blockdom/events.d.ts +1 -1
- package/dist/types/runtime/blockdom/index.d.ts +1 -1
- package/dist/types/runtime/cancellableContext.d.ts +15 -0
- package/dist/types/runtime/cancellablePromise.d.ts +15 -0
- package/dist/types/runtime/component.d.ts +5 -13
- package/dist/types/runtime/component_node.d.ts +15 -35
- package/dist/types/runtime/event_handling.d.ts +1 -1
- package/dist/types/runtime/executionContext.d.ts +0 -0
- package/dist/types/runtime/hooks.d.ts +7 -33
- package/dist/types/runtime/index.d.ts +15 -5
- package/dist/types/runtime/lifecycle_hooks.d.ts +1 -3
- package/dist/types/runtime/listOperation.d.ts +1 -0
- package/dist/types/runtime/plugins.d.ts +23 -0
- package/dist/types/runtime/portal.d.ts +4 -6
- package/dist/types/runtime/props.d.ts +65 -0
- package/dist/types/runtime/reactivity/computations.d.ts +31 -0
- package/dist/types/runtime/reactivity/computed.d.ts +7 -0
- package/dist/types/runtime/reactivity/derived.d.ts +7 -0
- package/dist/types/runtime/reactivity/effect.d.ts +2 -0
- package/dist/types/runtime/reactivity/proxy.d.ts +46 -0
- package/dist/types/runtime/reactivity/reactivity.d.ts +46 -0
- package/dist/types/runtime/reactivity/signal.d.ts +17 -0
- package/dist/types/runtime/reactivity/signals.d.ts +30 -0
- package/dist/types/runtime/registry.d.ts +19 -0
- package/dist/types/runtime/relationalModel/discussModel.d.ts +19 -0
- package/dist/types/runtime/relationalModel/discussModelTypes.d.ts +22 -0
- package/dist/types/runtime/relationalModel/field.d.ts +20 -0
- package/dist/types/runtime/relationalModel/model.d.ts +59 -0
- package/dist/types/runtime/relationalModel/modelData.d.ts +18 -0
- package/dist/types/runtime/relationalModel/modelRegistry.d.ts +3 -0
- package/dist/types/runtime/relationalModel/modelUtils.d.ts +4 -0
- package/dist/types/runtime/relationalModel/store.d.ts +16 -0
- package/dist/types/runtime/relationalModel/types.d.ts +83 -0
- package/dist/types/runtime/relationalModel/util.d.ts +1 -0
- package/dist/types/runtime/relationalModel/web/WebDataPoint.d.ts +25 -0
- package/dist/types/runtime/relationalModel/web/WebRecord.d.ts +131 -0
- package/dist/types/runtime/relationalModel/web/WebStaticList.d.ts +63 -0
- package/dist/types/runtime/relationalModel/web/webModel.d.ts +5 -0
- package/dist/types/runtime/relationalModel/web/webModelTypes.d.ts +139 -0
- package/dist/types/runtime/rendering/error_handling.d.ts +13 -0
- package/dist/types/runtime/rendering/fibers.d.ts +37 -0
- package/dist/types/runtime/rendering/scheduler.d.ts +21 -0
- package/dist/types/runtime/rendering/template_helpers.d.ts +50 -0
- package/dist/types/runtime/resource.d.ts +12 -0
- package/dist/types/runtime/signals.d.ts +19 -0
- package/dist/types/runtime/status.d.ts +2 -3
- package/dist/types/runtime/task.d.ts +12 -0
- package/dist/types/runtime/template_set.d.ts +3 -4
- package/dist/types/runtime/utils.d.ts +1 -2
- package/dist/types/runtime/validation.d.ts +6 -6
- package/dist/types/utils/registry.d.ts +15 -0
- package/dist/types/version.d.ts +1 -1
- package/package.json +9 -9
|
@@ -0,0 +1,4050 @@
|
|
|
1
|
+
// Custom error class that wraps error that happen in the owl lifecycle
|
|
2
|
+
class OwlError extends Error {
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
// do not modify manually. This file is generated by the release script.
|
|
6
|
+
const version = "3.0.0-alpha";
|
|
7
|
+
|
|
8
|
+
class Component {
|
|
9
|
+
constructor(node) {
|
|
10
|
+
this.__owl__ = node;
|
|
11
|
+
}
|
|
12
|
+
setup() { }
|
|
13
|
+
render(deep = false) {
|
|
14
|
+
this.__owl__.render(deep === true);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
Component.template = "";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Creates a batched version of a callback so that all calls to it in the same
|
|
21
|
+
* microtick will only call the original callback once.
|
|
22
|
+
*
|
|
23
|
+
* @param callback the callback to batch
|
|
24
|
+
* @returns a batched version of the original callback
|
|
25
|
+
*/
|
|
26
|
+
function batched(callback) {
|
|
27
|
+
let scheduled = false;
|
|
28
|
+
return async (...args) => {
|
|
29
|
+
if (!scheduled) {
|
|
30
|
+
scheduled = true;
|
|
31
|
+
await Promise.resolve();
|
|
32
|
+
scheduled = false;
|
|
33
|
+
callback(...args);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Determine whether the given element is contained in its ownerDocument:
|
|
39
|
+
* either directly or with a shadow root in between.
|
|
40
|
+
*/
|
|
41
|
+
function inOwnerDocument(el) {
|
|
42
|
+
if (!el) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
if (el.ownerDocument.contains(el)) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
const rootNode = el.getRootNode();
|
|
49
|
+
return rootNode instanceof ShadowRoot && el.ownerDocument.contains(rootNode.host);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Determine whether the given element is contained in a specific root documnet:
|
|
53
|
+
* either directly or with a shadow root in between or in an iframe.
|
|
54
|
+
*/
|
|
55
|
+
function isAttachedToDocument(element, documentElement) {
|
|
56
|
+
let current = element;
|
|
57
|
+
const shadowRoot = documentElement.defaultView.ShadowRoot;
|
|
58
|
+
while (current) {
|
|
59
|
+
if (current === documentElement) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
if (current.parentNode) {
|
|
63
|
+
current = current.parentNode;
|
|
64
|
+
}
|
|
65
|
+
else if (current instanceof shadowRoot && current.host) {
|
|
66
|
+
current = current.host;
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
function validateTarget(target) {
|
|
75
|
+
// Get the document and HTMLElement corresponding to the target to allow mounting in iframes
|
|
76
|
+
const document = target && target.ownerDocument;
|
|
77
|
+
if (document) {
|
|
78
|
+
if (!document.defaultView) {
|
|
79
|
+
throw new OwlError("Cannot mount a component: the target document is not attached to a window (defaultView is missing)");
|
|
80
|
+
}
|
|
81
|
+
const HTMLElement = document.defaultView.HTMLElement;
|
|
82
|
+
if (target instanceof HTMLElement || target instanceof ShadowRoot) {
|
|
83
|
+
if (!isAttachedToDocument(target, document)) {
|
|
84
|
+
throw new OwlError("Cannot mount a component on a detached dom node");
|
|
85
|
+
}
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
throw new OwlError("Cannot mount component: the target is not a valid DOM element");
|
|
90
|
+
}
|
|
91
|
+
class EventBus extends EventTarget {
|
|
92
|
+
trigger(name, payload) {
|
|
93
|
+
this.dispatchEvent(new CustomEvent(name, { detail: payload }));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function whenReady(fn) {
|
|
97
|
+
return new Promise(function (resolve) {
|
|
98
|
+
if (document.readyState !== "loading") {
|
|
99
|
+
resolve(true);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
document.addEventListener("DOMContentLoaded", resolve, false);
|
|
103
|
+
}
|
|
104
|
+
}).then(fn || function () { });
|
|
105
|
+
}
|
|
106
|
+
/*
|
|
107
|
+
* This class just transports the fact that a string is safe
|
|
108
|
+
* to be injected as HTML. Overriding a JS primitive is quite painful though
|
|
109
|
+
* so we need to redfine toString and valueOf.
|
|
110
|
+
*/
|
|
111
|
+
class Markup extends String {
|
|
112
|
+
}
|
|
113
|
+
function htmlEscape(str) {
|
|
114
|
+
if (str instanceof Markup) {
|
|
115
|
+
return str;
|
|
116
|
+
}
|
|
117
|
+
if (str === undefined) {
|
|
118
|
+
return markup("");
|
|
119
|
+
}
|
|
120
|
+
if (typeof str === "number") {
|
|
121
|
+
return markup(String(str));
|
|
122
|
+
}
|
|
123
|
+
[
|
|
124
|
+
["&", "&"],
|
|
125
|
+
["<", "<"],
|
|
126
|
+
[">", ">"],
|
|
127
|
+
["'", "'"],
|
|
128
|
+
['"', """],
|
|
129
|
+
["`", "`"],
|
|
130
|
+
].forEach((pairs) => {
|
|
131
|
+
str = String(str).replace(new RegExp(pairs[0], "g"), pairs[1]);
|
|
132
|
+
});
|
|
133
|
+
return markup(str);
|
|
134
|
+
}
|
|
135
|
+
function markup(valueOrStrings, ...placeholders) {
|
|
136
|
+
if (!Array.isArray(valueOrStrings)) {
|
|
137
|
+
return new Markup(valueOrStrings);
|
|
138
|
+
}
|
|
139
|
+
const strings = valueOrStrings;
|
|
140
|
+
let acc = "";
|
|
141
|
+
let i = 0;
|
|
142
|
+
for (; i < placeholders.length; ++i) {
|
|
143
|
+
acc += strings[i] + htmlEscape(placeholders[i]);
|
|
144
|
+
}
|
|
145
|
+
acc += strings[i];
|
|
146
|
+
return new Markup(acc);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
var ComputationState;
|
|
150
|
+
(function (ComputationState) {
|
|
151
|
+
ComputationState[ComputationState["EXECUTED"] = 0] = "EXECUTED";
|
|
152
|
+
ComputationState[ComputationState["STALE"] = 1] = "STALE";
|
|
153
|
+
ComputationState[ComputationState["PENDING"] = 2] = "PENDING";
|
|
154
|
+
})(ComputationState || (ComputationState = {}));
|
|
155
|
+
let Effects;
|
|
156
|
+
let CurrentComputation;
|
|
157
|
+
// export function computed<T>(fn: () => T, opts?: Opts) {
|
|
158
|
+
// // todo: handle cleanup
|
|
159
|
+
// let computedComputation: Computation = {
|
|
160
|
+
// state: ComputationState.STALE,
|
|
161
|
+
// sources: new Set(),
|
|
162
|
+
// isEager: true,
|
|
163
|
+
// compute: () => {
|
|
164
|
+
// return fn();
|
|
165
|
+
// },
|
|
166
|
+
// value: undefined,
|
|
167
|
+
// name: opts?.name,
|
|
168
|
+
// };
|
|
169
|
+
// updateComputation(computedComputation);
|
|
170
|
+
// }
|
|
171
|
+
function onReadAtom(atom) {
|
|
172
|
+
if (!CurrentComputation)
|
|
173
|
+
return;
|
|
174
|
+
CurrentComputation.sources.add(atom);
|
|
175
|
+
atom.observers.add(CurrentComputation);
|
|
176
|
+
}
|
|
177
|
+
function onWriteAtom(atom) {
|
|
178
|
+
collectEffects(() => {
|
|
179
|
+
for (const ctx of atom.observers) {
|
|
180
|
+
if (ctx.state === ComputationState.EXECUTED) {
|
|
181
|
+
if (ctx.isDerived)
|
|
182
|
+
markDownstream(ctx);
|
|
183
|
+
else
|
|
184
|
+
Effects.push(ctx);
|
|
185
|
+
}
|
|
186
|
+
ctx.state = ComputationState.STALE;
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
batchProcessEffects();
|
|
190
|
+
}
|
|
191
|
+
function collectEffects(fn) {
|
|
192
|
+
if (Effects)
|
|
193
|
+
return fn();
|
|
194
|
+
Effects = [];
|
|
195
|
+
try {
|
|
196
|
+
return fn();
|
|
197
|
+
}
|
|
198
|
+
finally {
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const batchProcessEffects = batched(processEffects);
|
|
202
|
+
function processEffects() {
|
|
203
|
+
if (!Effects)
|
|
204
|
+
return;
|
|
205
|
+
for (const computation of Effects) {
|
|
206
|
+
updateComputation(computation);
|
|
207
|
+
}
|
|
208
|
+
Effects = undefined;
|
|
209
|
+
}
|
|
210
|
+
function untrack(fn) {
|
|
211
|
+
return runWithComputation(undefined, fn);
|
|
212
|
+
}
|
|
213
|
+
function getCurrentComputation() {
|
|
214
|
+
return CurrentComputation;
|
|
215
|
+
}
|
|
216
|
+
function setComputation(computation) {
|
|
217
|
+
CurrentComputation = computation;
|
|
218
|
+
}
|
|
219
|
+
// todo: should probably use updateComputation instead.
|
|
220
|
+
function runWithComputation(computation, fn) {
|
|
221
|
+
const previousComputation = CurrentComputation;
|
|
222
|
+
CurrentComputation = computation;
|
|
223
|
+
let result;
|
|
224
|
+
try {
|
|
225
|
+
result = fn();
|
|
226
|
+
}
|
|
227
|
+
finally {
|
|
228
|
+
CurrentComputation = previousComputation;
|
|
229
|
+
}
|
|
230
|
+
return result;
|
|
231
|
+
}
|
|
232
|
+
function updateComputation(computation) {
|
|
233
|
+
var _a;
|
|
234
|
+
const state = computation.state;
|
|
235
|
+
if (computation.isDerived)
|
|
236
|
+
onReadAtom(computation);
|
|
237
|
+
if (state === ComputationState.EXECUTED)
|
|
238
|
+
return;
|
|
239
|
+
if (state === ComputationState.PENDING) {
|
|
240
|
+
computeSources(computation);
|
|
241
|
+
// If the state is still not stale after processing the sources, it means
|
|
242
|
+
// none of the dependencies have changed.
|
|
243
|
+
// todo: test it
|
|
244
|
+
if (computation.state !== ComputationState.STALE) {
|
|
245
|
+
computation.state = ComputationState.EXECUTED;
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
// todo: test performance. We might want to avoid removing the atoms to
|
|
250
|
+
// directly re-add them at compute. Especially as we are making them stale.
|
|
251
|
+
removeSources(computation);
|
|
252
|
+
const previousComputation = CurrentComputation;
|
|
253
|
+
CurrentComputation = computation;
|
|
254
|
+
computation.value = (_a = computation.compute) === null || _a === void 0 ? void 0 : _a.call(computation);
|
|
255
|
+
computation.state = ComputationState.EXECUTED;
|
|
256
|
+
CurrentComputation = previousComputation;
|
|
257
|
+
}
|
|
258
|
+
function removeSources(computation) {
|
|
259
|
+
const sources = computation.sources;
|
|
260
|
+
for (const source of sources) {
|
|
261
|
+
const observers = source.observers;
|
|
262
|
+
observers.delete(computation);
|
|
263
|
+
// todo: if source has no effect observer anymore, remove its sources too
|
|
264
|
+
// todo: test it
|
|
265
|
+
}
|
|
266
|
+
sources.clear();
|
|
267
|
+
}
|
|
268
|
+
function markDownstream(derived) {
|
|
269
|
+
for (const observer of derived.observers) {
|
|
270
|
+
// if the state has already been marked, skip it
|
|
271
|
+
if (observer.state)
|
|
272
|
+
continue;
|
|
273
|
+
observer.state = ComputationState.PENDING;
|
|
274
|
+
if (observer.isDerived)
|
|
275
|
+
markDownstream(observer);
|
|
276
|
+
else
|
|
277
|
+
Effects.push(observer);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
function computeSources(derived) {
|
|
281
|
+
for (const source of derived.sources) {
|
|
282
|
+
if (!("compute" in source))
|
|
283
|
+
continue;
|
|
284
|
+
updateComputation(source);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Maps fibers to thrown errors
|
|
289
|
+
const fibersInError = new WeakMap();
|
|
290
|
+
const nodeErrorHandlers = new WeakMap();
|
|
291
|
+
function destroyApp(app, error) {
|
|
292
|
+
try {
|
|
293
|
+
app.destroy();
|
|
294
|
+
}
|
|
295
|
+
catch (e) {
|
|
296
|
+
// mute all errors here because we are in a corrupted state anyway
|
|
297
|
+
}
|
|
298
|
+
const e = Object.assign(new OwlError(`[Owl] Unhandled error. Destroying the root component`), {
|
|
299
|
+
cause: error,
|
|
300
|
+
});
|
|
301
|
+
return e;
|
|
302
|
+
}
|
|
303
|
+
function _handleError(node, error) {
|
|
304
|
+
if (!node) {
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
const fiber = node.fiber;
|
|
308
|
+
if (fiber) {
|
|
309
|
+
fibersInError.set(fiber, error);
|
|
310
|
+
}
|
|
311
|
+
const errorHandlers = nodeErrorHandlers.get(node);
|
|
312
|
+
if (errorHandlers) {
|
|
313
|
+
let handled = false;
|
|
314
|
+
// execute in the opposite order
|
|
315
|
+
const finalize = () => destroyApp(node.app, error);
|
|
316
|
+
for (let i = errorHandlers.length - 1; i >= 0; i--) {
|
|
317
|
+
try {
|
|
318
|
+
errorHandlers[i](error, finalize);
|
|
319
|
+
handled = true;
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
catch (e) {
|
|
323
|
+
error = e;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (handled) {
|
|
327
|
+
return true;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return _handleError(node.parent, error);
|
|
331
|
+
}
|
|
332
|
+
function handleError(params) {
|
|
333
|
+
let { error } = params;
|
|
334
|
+
const node = "node" in params ? params.node : params.fiber.node;
|
|
335
|
+
const fiber = "fiber" in params ? params.fiber : node.fiber;
|
|
336
|
+
if (fiber) {
|
|
337
|
+
// resets the fibers on components if possible. This is important so that
|
|
338
|
+
// new renderings can be properly included in the initial one, if any.
|
|
339
|
+
let current = fiber;
|
|
340
|
+
do {
|
|
341
|
+
current.node.fiber = current;
|
|
342
|
+
current = current.parent;
|
|
343
|
+
} while (current);
|
|
344
|
+
fibersInError.set(fiber.root, error);
|
|
345
|
+
}
|
|
346
|
+
const handled = _handleError(node, error);
|
|
347
|
+
if (!handled) {
|
|
348
|
+
throw destroyApp(node.app, error);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function filterOutModifiersFromData(dataList) {
|
|
353
|
+
dataList = dataList.slice();
|
|
354
|
+
const modifiers = [];
|
|
355
|
+
let elm;
|
|
356
|
+
while ((elm = dataList[0]) && typeof elm === "string") {
|
|
357
|
+
modifiers.push(dataList.shift());
|
|
358
|
+
}
|
|
359
|
+
return { modifiers, data: dataList };
|
|
360
|
+
}
|
|
361
|
+
const config = {
|
|
362
|
+
// whether or not blockdom should normalize DOM whenever a block is created.
|
|
363
|
+
// Normalizing dom mean removing empty text nodes (or containing only spaces)
|
|
364
|
+
shouldNormalizeDom: true,
|
|
365
|
+
// this is the main event handler. Every event handler registered with blockdom
|
|
366
|
+
// will go through this function, giving it the data registered in the block
|
|
367
|
+
// and the event
|
|
368
|
+
mainEventHandler: (data, ev, currentTarget) => {
|
|
369
|
+
if (typeof data === "function") {
|
|
370
|
+
data(ev);
|
|
371
|
+
}
|
|
372
|
+
else if (Array.isArray(data)) {
|
|
373
|
+
data = filterOutModifiersFromData(data).data;
|
|
374
|
+
data[0](data[1], ev);
|
|
375
|
+
}
|
|
376
|
+
return false;
|
|
377
|
+
},
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
// -----------------------------------------------------------------------------
|
|
381
|
+
// Toggler node
|
|
382
|
+
// -----------------------------------------------------------------------------
|
|
383
|
+
const txt = document.createTextNode("");
|
|
384
|
+
class VToggler {
|
|
385
|
+
constructor(key, child) {
|
|
386
|
+
this.key = key;
|
|
387
|
+
this.child = child;
|
|
388
|
+
}
|
|
389
|
+
mount(parent, afterNode) {
|
|
390
|
+
this.parentEl = parent;
|
|
391
|
+
this.child.mount(parent, afterNode);
|
|
392
|
+
}
|
|
393
|
+
moveBeforeDOMNode(node, parent) {
|
|
394
|
+
this.child.moveBeforeDOMNode(node, parent);
|
|
395
|
+
}
|
|
396
|
+
moveBeforeVNode(other, afterNode) {
|
|
397
|
+
this.moveBeforeDOMNode((other && other.firstNode()) || afterNode);
|
|
398
|
+
}
|
|
399
|
+
patch(other, withBeforeRemove) {
|
|
400
|
+
if (this === other) {
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
let child1 = this.child;
|
|
404
|
+
let child2 = other.child;
|
|
405
|
+
if (this.key === other.key) {
|
|
406
|
+
child1.patch(child2, withBeforeRemove);
|
|
407
|
+
}
|
|
408
|
+
else {
|
|
409
|
+
const firstNode = child1.firstNode();
|
|
410
|
+
firstNode.parentElement.insertBefore(txt, firstNode);
|
|
411
|
+
if (withBeforeRemove) {
|
|
412
|
+
child1.beforeRemove();
|
|
413
|
+
}
|
|
414
|
+
child1.remove();
|
|
415
|
+
child2.mount(this.parentEl, txt);
|
|
416
|
+
this.child = child2;
|
|
417
|
+
this.key = other.key;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
beforeRemove() {
|
|
421
|
+
this.child.beforeRemove();
|
|
422
|
+
}
|
|
423
|
+
remove() {
|
|
424
|
+
this.child.remove();
|
|
425
|
+
}
|
|
426
|
+
firstNode() {
|
|
427
|
+
return this.child.firstNode();
|
|
428
|
+
}
|
|
429
|
+
toString() {
|
|
430
|
+
return this.child.toString();
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
function toggler(key, child) {
|
|
434
|
+
return new VToggler(key, child);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const { setAttribute: elemSetAttribute, removeAttribute } = Element.prototype;
|
|
438
|
+
const tokenList = DOMTokenList.prototype;
|
|
439
|
+
const tokenListAdd = tokenList.add;
|
|
440
|
+
const tokenListRemove = tokenList.remove;
|
|
441
|
+
const isArray = Array.isArray;
|
|
442
|
+
const { split, trim } = String.prototype;
|
|
443
|
+
const wordRegexp = /\s+/;
|
|
444
|
+
/**
|
|
445
|
+
* We regroup here all code related to updating attributes in a very loose sense:
|
|
446
|
+
* attributes, properties and classs are all managed by the functions in this
|
|
447
|
+
* file.
|
|
448
|
+
*/
|
|
449
|
+
function setAttribute(key, value) {
|
|
450
|
+
switch (value) {
|
|
451
|
+
case false:
|
|
452
|
+
case undefined:
|
|
453
|
+
removeAttribute.call(this, key);
|
|
454
|
+
break;
|
|
455
|
+
case true:
|
|
456
|
+
elemSetAttribute.call(this, key, "");
|
|
457
|
+
break;
|
|
458
|
+
default:
|
|
459
|
+
elemSetAttribute.call(this, key, value);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
function createAttrUpdater(attr) {
|
|
463
|
+
return function (value) {
|
|
464
|
+
setAttribute.call(this, attr, value);
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
function attrsSetter(attrs) {
|
|
468
|
+
if (isArray(attrs)) {
|
|
469
|
+
if (attrs[0] === "class") {
|
|
470
|
+
setClass.call(this, attrs[1]);
|
|
471
|
+
}
|
|
472
|
+
else {
|
|
473
|
+
setAttribute.call(this, attrs[0], attrs[1]);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
for (let k in attrs) {
|
|
478
|
+
if (k === "class") {
|
|
479
|
+
setClass.call(this, attrs[k]);
|
|
480
|
+
}
|
|
481
|
+
else {
|
|
482
|
+
setAttribute.call(this, k, attrs[k]);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
function attrsUpdater(attrs, oldAttrs) {
|
|
488
|
+
if (isArray(attrs)) {
|
|
489
|
+
const name = attrs[0];
|
|
490
|
+
const val = attrs[1];
|
|
491
|
+
if (name === oldAttrs[0]) {
|
|
492
|
+
if (val === oldAttrs[1]) {
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
if (name === "class") {
|
|
496
|
+
updateClass.call(this, val, oldAttrs[1]);
|
|
497
|
+
}
|
|
498
|
+
else {
|
|
499
|
+
setAttribute.call(this, name, val);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
else {
|
|
503
|
+
removeAttribute.call(this, oldAttrs[0]);
|
|
504
|
+
setAttribute.call(this, name, val);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
else {
|
|
508
|
+
for (let k in oldAttrs) {
|
|
509
|
+
if (!(k in attrs)) {
|
|
510
|
+
if (k === "class") {
|
|
511
|
+
updateClass.call(this, "", oldAttrs[k]);
|
|
512
|
+
}
|
|
513
|
+
else {
|
|
514
|
+
removeAttribute.call(this, k);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
for (let k in attrs) {
|
|
519
|
+
const val = attrs[k];
|
|
520
|
+
if (val !== oldAttrs[k]) {
|
|
521
|
+
if (k === "class") {
|
|
522
|
+
updateClass.call(this, val, oldAttrs[k]);
|
|
523
|
+
}
|
|
524
|
+
else {
|
|
525
|
+
setAttribute.call(this, k, val);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
function toClassObj(expr) {
|
|
532
|
+
const result = {};
|
|
533
|
+
switch (typeof expr) {
|
|
534
|
+
case "string":
|
|
535
|
+
// we transform here a list of classes into an object:
|
|
536
|
+
// 'hey you' becomes {hey: true, you: true}
|
|
537
|
+
const str = trim.call(expr);
|
|
538
|
+
if (!str) {
|
|
539
|
+
return {};
|
|
540
|
+
}
|
|
541
|
+
let words = split.call(str, wordRegexp);
|
|
542
|
+
for (let i = 0, l = words.length; i < l; i++) {
|
|
543
|
+
result[words[i]] = true;
|
|
544
|
+
}
|
|
545
|
+
return result;
|
|
546
|
+
case "object":
|
|
547
|
+
// this is already an object but we may need to split keys:
|
|
548
|
+
// {'a': true, 'b c': true} should become {a: true, b: true, c: true}
|
|
549
|
+
for (let key in expr) {
|
|
550
|
+
const value = expr[key];
|
|
551
|
+
if (value) {
|
|
552
|
+
key = trim.call(key);
|
|
553
|
+
if (!key) {
|
|
554
|
+
continue;
|
|
555
|
+
}
|
|
556
|
+
const words = split.call(key, wordRegexp);
|
|
557
|
+
for (let word of words) {
|
|
558
|
+
result[word] = value;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
return result;
|
|
563
|
+
case "undefined":
|
|
564
|
+
return {};
|
|
565
|
+
case "number":
|
|
566
|
+
return { [expr]: true };
|
|
567
|
+
default:
|
|
568
|
+
return { [expr]: true };
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
function setClass(val) {
|
|
572
|
+
val = val === "" ? {} : toClassObj(val);
|
|
573
|
+
// add classes
|
|
574
|
+
const cl = this.classList;
|
|
575
|
+
for (let c in val) {
|
|
576
|
+
tokenListAdd.call(cl, c);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
function updateClass(val, oldVal) {
|
|
580
|
+
oldVal = oldVal === "" ? {} : toClassObj(oldVal);
|
|
581
|
+
val = val === "" ? {} : toClassObj(val);
|
|
582
|
+
const cl = this.classList;
|
|
583
|
+
// remove classes
|
|
584
|
+
for (let c in oldVal) {
|
|
585
|
+
if (!(c in val)) {
|
|
586
|
+
tokenListRemove.call(cl, c);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
// add classes
|
|
590
|
+
for (let c in val) {
|
|
591
|
+
if (!(c in oldVal)) {
|
|
592
|
+
tokenListAdd.call(cl, c);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
function createEventHandler(rawEvent) {
|
|
598
|
+
const eventName = rawEvent.split(".")[0];
|
|
599
|
+
const capture = rawEvent.includes(".capture");
|
|
600
|
+
if (rawEvent.includes(".synthetic")) {
|
|
601
|
+
return createSyntheticHandler(eventName, capture);
|
|
602
|
+
}
|
|
603
|
+
else {
|
|
604
|
+
return createElementHandler(eventName, capture);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
// Native listener
|
|
608
|
+
let nextNativeEventId = 1;
|
|
609
|
+
function createElementHandler(evName, capture = false) {
|
|
610
|
+
let eventKey = `__event__${evName}_${nextNativeEventId++}`;
|
|
611
|
+
if (capture) {
|
|
612
|
+
eventKey = `${eventKey}_capture`;
|
|
613
|
+
}
|
|
614
|
+
function listener(ev) {
|
|
615
|
+
const currentTarget = ev.currentTarget;
|
|
616
|
+
if (!currentTarget || !inOwnerDocument(currentTarget))
|
|
617
|
+
return;
|
|
618
|
+
const data = currentTarget[eventKey];
|
|
619
|
+
if (!data)
|
|
620
|
+
return;
|
|
621
|
+
config.mainEventHandler(data, ev, currentTarget);
|
|
622
|
+
}
|
|
623
|
+
function setup(data) {
|
|
624
|
+
this[eventKey] = data;
|
|
625
|
+
this.addEventListener(evName, listener, { capture });
|
|
626
|
+
}
|
|
627
|
+
function remove() {
|
|
628
|
+
delete this[eventKey];
|
|
629
|
+
this.removeEventListener(evName, listener, { capture });
|
|
630
|
+
}
|
|
631
|
+
function update(data) {
|
|
632
|
+
this[eventKey] = data;
|
|
633
|
+
}
|
|
634
|
+
return { setup, update, remove };
|
|
635
|
+
}
|
|
636
|
+
// Synthetic handler: a form of event delegation that allows placing only one
|
|
637
|
+
// listener per event type.
|
|
638
|
+
let nextSyntheticEventId = 1;
|
|
639
|
+
function createSyntheticHandler(evName, capture = false) {
|
|
640
|
+
let eventKey = `__event__synthetic_${evName}`;
|
|
641
|
+
if (capture) {
|
|
642
|
+
eventKey = `${eventKey}_capture`;
|
|
643
|
+
}
|
|
644
|
+
setupSyntheticEvent(evName, eventKey, capture);
|
|
645
|
+
const currentId = nextSyntheticEventId++;
|
|
646
|
+
function setup(data) {
|
|
647
|
+
const _data = this[eventKey] || {};
|
|
648
|
+
_data[currentId] = data;
|
|
649
|
+
this[eventKey] = _data;
|
|
650
|
+
}
|
|
651
|
+
function remove() {
|
|
652
|
+
delete this[eventKey];
|
|
653
|
+
}
|
|
654
|
+
return { setup, update: setup, remove };
|
|
655
|
+
}
|
|
656
|
+
function nativeToSyntheticEvent(eventKey, event) {
|
|
657
|
+
let dom = event.target;
|
|
658
|
+
while (dom !== null) {
|
|
659
|
+
const _data = dom[eventKey];
|
|
660
|
+
if (_data) {
|
|
661
|
+
for (const data of Object.values(_data)) {
|
|
662
|
+
const stopped = config.mainEventHandler(data, event, dom);
|
|
663
|
+
if (stopped)
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
dom = dom.parentNode;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
const CONFIGURED_SYNTHETIC_EVENTS = {};
|
|
671
|
+
function setupSyntheticEvent(evName, eventKey, capture = false) {
|
|
672
|
+
if (CONFIGURED_SYNTHETIC_EVENTS[eventKey]) {
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
document.addEventListener(evName, (event) => nativeToSyntheticEvent(eventKey, event), {
|
|
676
|
+
capture,
|
|
677
|
+
});
|
|
678
|
+
CONFIGURED_SYNTHETIC_EVENTS[eventKey] = true;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
const getDescriptor$3 = (o, p) => Object.getOwnPropertyDescriptor(o, p);
|
|
682
|
+
const nodeProto$4 = Node.prototype;
|
|
683
|
+
const nodeInsertBefore$3 = nodeProto$4.insertBefore;
|
|
684
|
+
const nodeSetTextContent$1 = getDescriptor$3(nodeProto$4, "textContent").set;
|
|
685
|
+
const nodeRemoveChild$3 = nodeProto$4.removeChild;
|
|
686
|
+
// -----------------------------------------------------------------------------
|
|
687
|
+
// Multi NODE
|
|
688
|
+
// -----------------------------------------------------------------------------
|
|
689
|
+
class VMulti {
|
|
690
|
+
constructor(children) {
|
|
691
|
+
this.children = children;
|
|
692
|
+
}
|
|
693
|
+
mount(parent, afterNode) {
|
|
694
|
+
const children = this.children;
|
|
695
|
+
const l = children.length;
|
|
696
|
+
const anchors = new Array(l);
|
|
697
|
+
for (let i = 0; i < l; i++) {
|
|
698
|
+
let child = children[i];
|
|
699
|
+
if (child) {
|
|
700
|
+
child.mount(parent, afterNode);
|
|
701
|
+
}
|
|
702
|
+
else {
|
|
703
|
+
const childAnchor = document.createTextNode("");
|
|
704
|
+
anchors[i] = childAnchor;
|
|
705
|
+
nodeInsertBefore$3.call(parent, childAnchor, afterNode);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
this.anchors = anchors;
|
|
709
|
+
this.parentEl = parent;
|
|
710
|
+
}
|
|
711
|
+
moveBeforeDOMNode(node, parent = this.parentEl) {
|
|
712
|
+
this.parentEl = parent;
|
|
713
|
+
const children = this.children;
|
|
714
|
+
const anchors = this.anchors;
|
|
715
|
+
for (let i = 0, l = children.length; i < l; i++) {
|
|
716
|
+
let child = children[i];
|
|
717
|
+
if (child) {
|
|
718
|
+
child.moveBeforeDOMNode(node, parent);
|
|
719
|
+
}
|
|
720
|
+
else {
|
|
721
|
+
const anchor = anchors[i];
|
|
722
|
+
nodeInsertBefore$3.call(parent, anchor, node);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
moveBeforeVNode(other, afterNode) {
|
|
727
|
+
if (other) {
|
|
728
|
+
const next = other.children[0];
|
|
729
|
+
afterNode = (next ? next.firstNode() : other.anchors[0]) || null;
|
|
730
|
+
}
|
|
731
|
+
const children = this.children;
|
|
732
|
+
const parent = this.parentEl;
|
|
733
|
+
const anchors = this.anchors;
|
|
734
|
+
for (let i = 0, l = children.length; i < l; i++) {
|
|
735
|
+
let child = children[i];
|
|
736
|
+
if (child) {
|
|
737
|
+
child.moveBeforeVNode(null, afterNode);
|
|
738
|
+
}
|
|
739
|
+
else {
|
|
740
|
+
const anchor = anchors[i];
|
|
741
|
+
nodeInsertBefore$3.call(parent, anchor, afterNode);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
patch(other, withBeforeRemove) {
|
|
746
|
+
if (this === other) {
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
const children1 = this.children;
|
|
750
|
+
const children2 = other.children;
|
|
751
|
+
const anchors = this.anchors;
|
|
752
|
+
const parentEl = this.parentEl;
|
|
753
|
+
for (let i = 0, l = children1.length; i < l; i++) {
|
|
754
|
+
const vn1 = children1[i];
|
|
755
|
+
const vn2 = children2[i];
|
|
756
|
+
if (vn1) {
|
|
757
|
+
if (vn2) {
|
|
758
|
+
vn1.patch(vn2, withBeforeRemove);
|
|
759
|
+
}
|
|
760
|
+
else {
|
|
761
|
+
const afterNode = vn1.firstNode();
|
|
762
|
+
const anchor = document.createTextNode("");
|
|
763
|
+
anchors[i] = anchor;
|
|
764
|
+
nodeInsertBefore$3.call(parentEl, anchor, afterNode);
|
|
765
|
+
if (withBeforeRemove) {
|
|
766
|
+
vn1.beforeRemove();
|
|
767
|
+
}
|
|
768
|
+
vn1.remove();
|
|
769
|
+
children1[i] = undefined;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
else if (vn2) {
|
|
773
|
+
children1[i] = vn2;
|
|
774
|
+
const anchor = anchors[i];
|
|
775
|
+
vn2.mount(parentEl, anchor);
|
|
776
|
+
nodeRemoveChild$3.call(parentEl, anchor);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
beforeRemove() {
|
|
781
|
+
const children = this.children;
|
|
782
|
+
for (let i = 0, l = children.length; i < l; i++) {
|
|
783
|
+
const child = children[i];
|
|
784
|
+
if (child) {
|
|
785
|
+
child.beforeRemove();
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
remove() {
|
|
790
|
+
const parentEl = this.parentEl;
|
|
791
|
+
if (this.isOnlyChild) {
|
|
792
|
+
nodeSetTextContent$1.call(parentEl, "");
|
|
793
|
+
}
|
|
794
|
+
else {
|
|
795
|
+
const children = this.children;
|
|
796
|
+
const anchors = this.anchors;
|
|
797
|
+
for (let i = 0, l = children.length; i < l; i++) {
|
|
798
|
+
const child = children[i];
|
|
799
|
+
if (child) {
|
|
800
|
+
child.remove();
|
|
801
|
+
}
|
|
802
|
+
else {
|
|
803
|
+
nodeRemoveChild$3.call(parentEl, anchors[i]);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
firstNode() {
|
|
809
|
+
const child = this.children[0];
|
|
810
|
+
return child ? child.firstNode() : this.anchors[0];
|
|
811
|
+
}
|
|
812
|
+
toString() {
|
|
813
|
+
return this.children.map((c) => (c ? c.toString() : "")).join("");
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
function multi(children) {
|
|
817
|
+
return new VMulti(children);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
const getDescriptor$2 = (o, p) => Object.getOwnPropertyDescriptor(o, p);
|
|
821
|
+
const nodeProto$3 = Node.prototype;
|
|
822
|
+
const characterDataProto$1 = CharacterData.prototype;
|
|
823
|
+
const nodeInsertBefore$2 = nodeProto$3.insertBefore;
|
|
824
|
+
const characterDataSetData$1 = getDescriptor$2(characterDataProto$1, "data").set;
|
|
825
|
+
const nodeRemoveChild$2 = nodeProto$3.removeChild;
|
|
826
|
+
class VSimpleNode {
|
|
827
|
+
constructor(text) {
|
|
828
|
+
this.text = text;
|
|
829
|
+
}
|
|
830
|
+
mountNode(node, parent, afterNode) {
|
|
831
|
+
this.parentEl = parent;
|
|
832
|
+
nodeInsertBefore$2.call(parent, node, afterNode);
|
|
833
|
+
this.el = node;
|
|
834
|
+
}
|
|
835
|
+
moveBeforeDOMNode(node, parent = this.parentEl) {
|
|
836
|
+
this.parentEl = parent;
|
|
837
|
+
nodeInsertBefore$2.call(parent, this.el, node);
|
|
838
|
+
}
|
|
839
|
+
moveBeforeVNode(other, afterNode) {
|
|
840
|
+
nodeInsertBefore$2.call(this.parentEl, this.el, other ? other.el : afterNode);
|
|
841
|
+
}
|
|
842
|
+
beforeRemove() { }
|
|
843
|
+
remove() {
|
|
844
|
+
nodeRemoveChild$2.call(this.parentEl, this.el);
|
|
845
|
+
}
|
|
846
|
+
firstNode() {
|
|
847
|
+
return this.el;
|
|
848
|
+
}
|
|
849
|
+
toString() {
|
|
850
|
+
return this.text;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
class VText$1 extends VSimpleNode {
|
|
854
|
+
mount(parent, afterNode) {
|
|
855
|
+
this.mountNode(document.createTextNode(toText(this.text)), parent, afterNode);
|
|
856
|
+
}
|
|
857
|
+
patch(other) {
|
|
858
|
+
const text2 = other.text;
|
|
859
|
+
if (this.text !== text2) {
|
|
860
|
+
characterDataSetData$1.call(this.el, toText(text2));
|
|
861
|
+
this.text = text2;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
class VComment extends VSimpleNode {
|
|
866
|
+
mount(parent, afterNode) {
|
|
867
|
+
this.mountNode(document.createComment(toText(this.text)), parent, afterNode);
|
|
868
|
+
}
|
|
869
|
+
patch() { }
|
|
870
|
+
}
|
|
871
|
+
function text(str) {
|
|
872
|
+
return new VText$1(str);
|
|
873
|
+
}
|
|
874
|
+
function comment(str) {
|
|
875
|
+
return new VComment(str);
|
|
876
|
+
}
|
|
877
|
+
function toText(value) {
|
|
878
|
+
switch (typeof value) {
|
|
879
|
+
case "string":
|
|
880
|
+
return value;
|
|
881
|
+
case "number":
|
|
882
|
+
return String(value);
|
|
883
|
+
case "boolean":
|
|
884
|
+
return value ? "true" : "false";
|
|
885
|
+
default:
|
|
886
|
+
return value || "";
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
const getDescriptor$1 = (o, p) => Object.getOwnPropertyDescriptor(o, p);
|
|
891
|
+
const nodeProto$2 = Node.prototype;
|
|
892
|
+
const elementProto = Element.prototype;
|
|
893
|
+
const characterDataProto = CharacterData.prototype;
|
|
894
|
+
const characterDataSetData = getDescriptor$1(characterDataProto, "data").set;
|
|
895
|
+
const nodeGetFirstChild = getDescriptor$1(nodeProto$2, "firstChild").get;
|
|
896
|
+
const nodeGetNextSibling = getDescriptor$1(nodeProto$2, "nextSibling").get;
|
|
897
|
+
const NO_OP = () => { };
|
|
898
|
+
function makePropSetter(name) {
|
|
899
|
+
return function setProp(value) {
|
|
900
|
+
// support 0, fallback to empty string for other falsy values
|
|
901
|
+
this[name] = value === 0 ? 0 : value ? value.valueOf() : "";
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
const cache = {};
|
|
905
|
+
/**
|
|
906
|
+
* Compiling blocks is a multi-step process:
|
|
907
|
+
*
|
|
908
|
+
* 1. build an IntermediateTree from the HTML element. This intermediate tree
|
|
909
|
+
* is a binary tree structure that encode dynamic info sub nodes, and the
|
|
910
|
+
* path required to reach them
|
|
911
|
+
* 2. process the tree to build a block context, which is an object that aggregate
|
|
912
|
+
* all dynamic info in a list, and also, all ref indexes.
|
|
913
|
+
* 3. process the context to build appropriate builder/setter functions
|
|
914
|
+
* 4. make a dynamic block class, which will efficiently collect references and
|
|
915
|
+
* create/update dynamic locations/children
|
|
916
|
+
*
|
|
917
|
+
* @param str
|
|
918
|
+
* @returns a new block type, that can build concrete blocks
|
|
919
|
+
*/
|
|
920
|
+
function createBlock(str) {
|
|
921
|
+
if (str in cache) {
|
|
922
|
+
return cache[str];
|
|
923
|
+
}
|
|
924
|
+
// step 0: prepare html base element
|
|
925
|
+
const doc = new DOMParser().parseFromString(`<t>${str}</t>`, "text/xml");
|
|
926
|
+
const node = doc.firstChild.firstChild;
|
|
927
|
+
if (config.shouldNormalizeDom) {
|
|
928
|
+
normalizeNode(node);
|
|
929
|
+
}
|
|
930
|
+
// step 1: prepare intermediate tree
|
|
931
|
+
const tree = buildTree(node);
|
|
932
|
+
// step 2: prepare block context
|
|
933
|
+
const context = buildContext(tree);
|
|
934
|
+
// step 3: build the final block class
|
|
935
|
+
const template = tree.el;
|
|
936
|
+
const Block = buildBlock(template, context);
|
|
937
|
+
cache[str] = Block;
|
|
938
|
+
return Block;
|
|
939
|
+
}
|
|
940
|
+
// -----------------------------------------------------------------------------
|
|
941
|
+
// Helper
|
|
942
|
+
// -----------------------------------------------------------------------------
|
|
943
|
+
function normalizeNode(node) {
|
|
944
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
945
|
+
if (!/\S/.test(node.textContent)) {
|
|
946
|
+
node.remove();
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
951
|
+
if (node.tagName === "pre") {
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
for (let i = node.childNodes.length - 1; i >= 0; --i) {
|
|
956
|
+
normalizeNode(node.childNodes.item(i));
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
function buildTree(node, parent = null, domParentTree = null) {
|
|
960
|
+
switch (node.nodeType) {
|
|
961
|
+
case Node.ELEMENT_NODE: {
|
|
962
|
+
// HTMLElement
|
|
963
|
+
let currentNS = domParentTree && domParentTree.currentNS;
|
|
964
|
+
const tagName = node.tagName;
|
|
965
|
+
let el = undefined;
|
|
966
|
+
const info = [];
|
|
967
|
+
if (tagName.startsWith("block-text-")) {
|
|
968
|
+
const index = parseInt(tagName.slice(11), 10);
|
|
969
|
+
info.push({ type: "text", idx: index });
|
|
970
|
+
el = document.createTextNode("");
|
|
971
|
+
}
|
|
972
|
+
if (tagName.startsWith("block-child-")) {
|
|
973
|
+
if (!domParentTree.isRef) {
|
|
974
|
+
addRef(domParentTree);
|
|
975
|
+
}
|
|
976
|
+
const index = parseInt(tagName.slice(12), 10);
|
|
977
|
+
info.push({ type: "child", idx: index });
|
|
978
|
+
el = document.createTextNode("");
|
|
979
|
+
}
|
|
980
|
+
currentNS || (currentNS = node.namespaceURI);
|
|
981
|
+
if (!el) {
|
|
982
|
+
el = currentNS
|
|
983
|
+
? document.createElementNS(currentNS, tagName)
|
|
984
|
+
: document.createElement(tagName);
|
|
985
|
+
}
|
|
986
|
+
if (el instanceof Element) {
|
|
987
|
+
if (!domParentTree) {
|
|
988
|
+
// some html elements may have side effects when setting their attributes.
|
|
989
|
+
// For example, setting the src attribute of an <img/> will trigger a
|
|
990
|
+
// request to get the corresponding image. This is something that we
|
|
991
|
+
// don't want at compile time. We avoid that by putting the content of
|
|
992
|
+
// the block in a <template/> element
|
|
993
|
+
const fragment = document.createElement("template").content;
|
|
994
|
+
fragment.appendChild(el);
|
|
995
|
+
}
|
|
996
|
+
const attrs = node.attributes;
|
|
997
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
998
|
+
const attrName = attrs[i].name;
|
|
999
|
+
const attrValue = attrs[i].value;
|
|
1000
|
+
if (attrName.startsWith("block-handler-")) {
|
|
1001
|
+
const idx = parseInt(attrName.slice(14), 10);
|
|
1002
|
+
info.push({
|
|
1003
|
+
type: "handler",
|
|
1004
|
+
idx,
|
|
1005
|
+
event: attrValue,
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
else if (attrName.startsWith("block-attribute-")) {
|
|
1009
|
+
const idx = parseInt(attrName.slice(16), 10);
|
|
1010
|
+
info.push({
|
|
1011
|
+
type: "attribute",
|
|
1012
|
+
idx,
|
|
1013
|
+
name: attrValue,
|
|
1014
|
+
tag: tagName,
|
|
1015
|
+
});
|
|
1016
|
+
}
|
|
1017
|
+
else if (attrName.startsWith("block-property-")) {
|
|
1018
|
+
const idx = parseInt(attrName.slice(15), 10);
|
|
1019
|
+
info.push({
|
|
1020
|
+
type: "property",
|
|
1021
|
+
idx,
|
|
1022
|
+
name: attrValue,
|
|
1023
|
+
tag: tagName,
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
else if (attrName === "block-attributes") {
|
|
1027
|
+
info.push({
|
|
1028
|
+
type: "attributes",
|
|
1029
|
+
idx: parseInt(attrValue, 10),
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
1032
|
+
else if (attrName === "block-ref") {
|
|
1033
|
+
info.push({
|
|
1034
|
+
type: "ref",
|
|
1035
|
+
idx: parseInt(attrValue, 10),
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
else {
|
|
1039
|
+
el.setAttribute(attrs[i].name, attrValue);
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
const tree = {
|
|
1044
|
+
parent,
|
|
1045
|
+
firstChild: null,
|
|
1046
|
+
nextSibling: null,
|
|
1047
|
+
el,
|
|
1048
|
+
info,
|
|
1049
|
+
refN: 0,
|
|
1050
|
+
currentNS,
|
|
1051
|
+
};
|
|
1052
|
+
if (node.firstChild) {
|
|
1053
|
+
const childNode = node.childNodes[0];
|
|
1054
|
+
if (node.childNodes.length === 1 &&
|
|
1055
|
+
childNode.nodeType === Node.ELEMENT_NODE &&
|
|
1056
|
+
childNode.tagName.startsWith("block-child-")) {
|
|
1057
|
+
const tagName = childNode.tagName;
|
|
1058
|
+
const index = parseInt(tagName.slice(12), 10);
|
|
1059
|
+
info.push({ idx: index, type: "child", isOnlyChild: true });
|
|
1060
|
+
}
|
|
1061
|
+
else {
|
|
1062
|
+
tree.firstChild = buildTree(node.firstChild, tree, tree);
|
|
1063
|
+
el.appendChild(tree.firstChild.el);
|
|
1064
|
+
let curNode = node.firstChild;
|
|
1065
|
+
let curTree = tree.firstChild;
|
|
1066
|
+
while ((curNode = curNode.nextSibling)) {
|
|
1067
|
+
curTree.nextSibling = buildTree(curNode, curTree, tree);
|
|
1068
|
+
el.appendChild(curTree.nextSibling.el);
|
|
1069
|
+
curTree = curTree.nextSibling;
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
if (tree.info.length) {
|
|
1074
|
+
addRef(tree);
|
|
1075
|
+
}
|
|
1076
|
+
return tree;
|
|
1077
|
+
}
|
|
1078
|
+
case Node.TEXT_NODE:
|
|
1079
|
+
case Node.COMMENT_NODE: {
|
|
1080
|
+
// text node or comment node
|
|
1081
|
+
const el = node.nodeType === Node.TEXT_NODE
|
|
1082
|
+
? document.createTextNode(node.textContent)
|
|
1083
|
+
: document.createComment(node.textContent);
|
|
1084
|
+
return {
|
|
1085
|
+
parent: parent,
|
|
1086
|
+
firstChild: null,
|
|
1087
|
+
nextSibling: null,
|
|
1088
|
+
el,
|
|
1089
|
+
info: [],
|
|
1090
|
+
refN: 0,
|
|
1091
|
+
currentNS: null,
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
throw new OwlError("boom");
|
|
1096
|
+
}
|
|
1097
|
+
function addRef(tree) {
|
|
1098
|
+
tree.isRef = true;
|
|
1099
|
+
do {
|
|
1100
|
+
tree.refN++;
|
|
1101
|
+
} while ((tree = tree.parent));
|
|
1102
|
+
}
|
|
1103
|
+
function parentTree(tree) {
|
|
1104
|
+
let parent = tree.parent;
|
|
1105
|
+
while (parent && parent.nextSibling === tree) {
|
|
1106
|
+
tree = parent;
|
|
1107
|
+
parent = parent.parent;
|
|
1108
|
+
}
|
|
1109
|
+
return parent;
|
|
1110
|
+
}
|
|
1111
|
+
function buildContext(tree, ctx, fromIdx) {
|
|
1112
|
+
if (!ctx) {
|
|
1113
|
+
const children = new Array(tree.info.filter((v) => v.type === "child").length);
|
|
1114
|
+
ctx = { collectors: [], locations: [], children, cbRefs: [], refN: tree.refN };
|
|
1115
|
+
fromIdx = 0;
|
|
1116
|
+
}
|
|
1117
|
+
if (tree.refN) {
|
|
1118
|
+
const initialIdx = fromIdx;
|
|
1119
|
+
const isRef = tree.isRef;
|
|
1120
|
+
const firstChild = tree.firstChild ? tree.firstChild.refN : 0;
|
|
1121
|
+
const nextSibling = tree.nextSibling ? tree.nextSibling.refN : 0;
|
|
1122
|
+
//node
|
|
1123
|
+
if (isRef) {
|
|
1124
|
+
for (let info of tree.info) {
|
|
1125
|
+
info.refIdx = initialIdx;
|
|
1126
|
+
}
|
|
1127
|
+
tree.refIdx = initialIdx;
|
|
1128
|
+
updateCtx(ctx, tree);
|
|
1129
|
+
fromIdx++;
|
|
1130
|
+
}
|
|
1131
|
+
// right
|
|
1132
|
+
if (nextSibling) {
|
|
1133
|
+
const idx = fromIdx + firstChild;
|
|
1134
|
+
ctx.collectors.push({ idx, prevIdx: initialIdx, getVal: nodeGetNextSibling });
|
|
1135
|
+
buildContext(tree.nextSibling, ctx, idx);
|
|
1136
|
+
}
|
|
1137
|
+
// left
|
|
1138
|
+
if (firstChild) {
|
|
1139
|
+
ctx.collectors.push({ idx: fromIdx, prevIdx: initialIdx, getVal: nodeGetFirstChild });
|
|
1140
|
+
buildContext(tree.firstChild, ctx, fromIdx);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
return ctx;
|
|
1144
|
+
}
|
|
1145
|
+
function updateCtx(ctx, tree) {
|
|
1146
|
+
for (let info of tree.info) {
|
|
1147
|
+
switch (info.type) {
|
|
1148
|
+
case "text":
|
|
1149
|
+
ctx.locations.push({
|
|
1150
|
+
idx: info.idx,
|
|
1151
|
+
refIdx: info.refIdx,
|
|
1152
|
+
setData: setText,
|
|
1153
|
+
updateData: setText,
|
|
1154
|
+
});
|
|
1155
|
+
break;
|
|
1156
|
+
case "child":
|
|
1157
|
+
if (info.isOnlyChild) {
|
|
1158
|
+
// tree is the parentnode here
|
|
1159
|
+
ctx.children[info.idx] = {
|
|
1160
|
+
parentRefIdx: info.refIdx,
|
|
1161
|
+
isOnlyChild: true,
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1164
|
+
else {
|
|
1165
|
+
// tree is the anchor text node
|
|
1166
|
+
ctx.children[info.idx] = {
|
|
1167
|
+
parentRefIdx: parentTree(tree).refIdx,
|
|
1168
|
+
afterRefIdx: info.refIdx,
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1171
|
+
break;
|
|
1172
|
+
case "property": {
|
|
1173
|
+
const refIdx = info.refIdx;
|
|
1174
|
+
const setProp = makePropSetter(info.name);
|
|
1175
|
+
ctx.locations.push({
|
|
1176
|
+
idx: info.idx,
|
|
1177
|
+
refIdx,
|
|
1178
|
+
setData: setProp,
|
|
1179
|
+
updateData: setProp,
|
|
1180
|
+
});
|
|
1181
|
+
break;
|
|
1182
|
+
}
|
|
1183
|
+
case "attribute": {
|
|
1184
|
+
const refIdx = info.refIdx;
|
|
1185
|
+
let updater;
|
|
1186
|
+
let setter;
|
|
1187
|
+
if (info.name === "class") {
|
|
1188
|
+
setter = setClass;
|
|
1189
|
+
updater = updateClass;
|
|
1190
|
+
}
|
|
1191
|
+
else {
|
|
1192
|
+
setter = createAttrUpdater(info.name);
|
|
1193
|
+
updater = setter;
|
|
1194
|
+
}
|
|
1195
|
+
ctx.locations.push({
|
|
1196
|
+
idx: info.idx,
|
|
1197
|
+
refIdx,
|
|
1198
|
+
setData: setter,
|
|
1199
|
+
updateData: updater,
|
|
1200
|
+
});
|
|
1201
|
+
break;
|
|
1202
|
+
}
|
|
1203
|
+
case "attributes":
|
|
1204
|
+
ctx.locations.push({
|
|
1205
|
+
idx: info.idx,
|
|
1206
|
+
refIdx: info.refIdx,
|
|
1207
|
+
setData: attrsSetter,
|
|
1208
|
+
updateData: attrsUpdater,
|
|
1209
|
+
});
|
|
1210
|
+
break;
|
|
1211
|
+
case "handler": {
|
|
1212
|
+
const { setup, update } = createEventHandler(info.event);
|
|
1213
|
+
ctx.locations.push({
|
|
1214
|
+
idx: info.idx,
|
|
1215
|
+
refIdx: info.refIdx,
|
|
1216
|
+
setData: setup,
|
|
1217
|
+
updateData: update,
|
|
1218
|
+
});
|
|
1219
|
+
break;
|
|
1220
|
+
}
|
|
1221
|
+
case "ref": {
|
|
1222
|
+
ctx.locations.push({
|
|
1223
|
+
idx: info.idx,
|
|
1224
|
+
refIdx: info.refIdx,
|
|
1225
|
+
setData: NO_OP,
|
|
1226
|
+
updateData: NO_OP,
|
|
1227
|
+
});
|
|
1228
|
+
ctx.cbRefs.push(info.idx);
|
|
1229
|
+
break;
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
// -----------------------------------------------------------------------------
|
|
1235
|
+
// building the concrete block class
|
|
1236
|
+
// -----------------------------------------------------------------------------
|
|
1237
|
+
function buildBlock(template, ctx) {
|
|
1238
|
+
let B = createBlockClass(template, ctx);
|
|
1239
|
+
if (ctx.children.length) {
|
|
1240
|
+
B = class extends B {
|
|
1241
|
+
constructor(data, children) {
|
|
1242
|
+
super(data);
|
|
1243
|
+
this.children = children;
|
|
1244
|
+
}
|
|
1245
|
+
};
|
|
1246
|
+
B.prototype.beforeRemove = VMulti.prototype.beforeRemove;
|
|
1247
|
+
return (data, children = []) => new B(data, children);
|
|
1248
|
+
}
|
|
1249
|
+
return (data) => new B(data);
|
|
1250
|
+
}
|
|
1251
|
+
function createBlockClass(template, ctx) {
|
|
1252
|
+
const { refN, collectors, children, locations, cbRefs } = ctx;
|
|
1253
|
+
const colN = collectors.length;
|
|
1254
|
+
locations.sort((a, b) => a.idx - b.idx);
|
|
1255
|
+
const locN = locations.length;
|
|
1256
|
+
const childN = children.length;
|
|
1257
|
+
const childrenLocs = children;
|
|
1258
|
+
const isDynamic = refN > 0;
|
|
1259
|
+
// these values are defined here to make them faster to lookup in the class
|
|
1260
|
+
// block scope
|
|
1261
|
+
const nodeCloneNode = nodeProto$2.cloneNode;
|
|
1262
|
+
const nodeInsertBefore = nodeProto$2.insertBefore;
|
|
1263
|
+
const elementRemove = elementProto.remove;
|
|
1264
|
+
class Block {
|
|
1265
|
+
constructor(data) {
|
|
1266
|
+
this.data = data;
|
|
1267
|
+
}
|
|
1268
|
+
beforeRemove() { }
|
|
1269
|
+
remove() {
|
|
1270
|
+
elementRemove.call(this.el);
|
|
1271
|
+
}
|
|
1272
|
+
firstNode() {
|
|
1273
|
+
return this.el;
|
|
1274
|
+
}
|
|
1275
|
+
moveBeforeDOMNode(node, parent = this.parentEl) {
|
|
1276
|
+
this.parentEl = parent;
|
|
1277
|
+
nodeInsertBefore.call(parent, this.el, node);
|
|
1278
|
+
}
|
|
1279
|
+
moveBeforeVNode(other, afterNode) {
|
|
1280
|
+
nodeInsertBefore.call(this.parentEl, this.el, other ? other.el : afterNode);
|
|
1281
|
+
}
|
|
1282
|
+
toString() {
|
|
1283
|
+
const div = document.createElement("div");
|
|
1284
|
+
this.mount(div, null);
|
|
1285
|
+
return div.innerHTML;
|
|
1286
|
+
}
|
|
1287
|
+
mount(parent, afterNode) {
|
|
1288
|
+
const el = nodeCloneNode.call(template, true);
|
|
1289
|
+
nodeInsertBefore.call(parent, el, afterNode);
|
|
1290
|
+
this.el = el;
|
|
1291
|
+
this.parentEl = parent;
|
|
1292
|
+
}
|
|
1293
|
+
patch(other, withBeforeRemove) { }
|
|
1294
|
+
}
|
|
1295
|
+
if (isDynamic) {
|
|
1296
|
+
Block.prototype.mount = function mount(parent, afterNode) {
|
|
1297
|
+
const el = nodeCloneNode.call(template, true);
|
|
1298
|
+
// collecting references
|
|
1299
|
+
const refs = new Array(refN);
|
|
1300
|
+
this.refs = refs;
|
|
1301
|
+
refs[0] = el;
|
|
1302
|
+
for (let i = 0; i < colN; i++) {
|
|
1303
|
+
const w = collectors[i];
|
|
1304
|
+
refs[w.idx] = w.getVal.call(refs[w.prevIdx]);
|
|
1305
|
+
}
|
|
1306
|
+
// applying data to all update points
|
|
1307
|
+
if (locN) {
|
|
1308
|
+
const data = this.data;
|
|
1309
|
+
for (let i = 0; i < locN; i++) {
|
|
1310
|
+
const loc = locations[i];
|
|
1311
|
+
loc.setData.call(refs[loc.refIdx], data[i]);
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
nodeInsertBefore.call(parent, el, afterNode);
|
|
1315
|
+
// preparing all children
|
|
1316
|
+
if (childN) {
|
|
1317
|
+
const children = this.children;
|
|
1318
|
+
for (let i = 0; i < childN; i++) {
|
|
1319
|
+
const child = children[i];
|
|
1320
|
+
if (child) {
|
|
1321
|
+
const loc = childrenLocs[i];
|
|
1322
|
+
const afterNode = loc.afterRefIdx ? refs[loc.afterRefIdx] : null;
|
|
1323
|
+
child.isOnlyChild = loc.isOnlyChild;
|
|
1324
|
+
child.mount(refs[loc.parentRefIdx], afterNode);
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
this.el = el;
|
|
1329
|
+
this.parentEl = parent;
|
|
1330
|
+
if (cbRefs.length) {
|
|
1331
|
+
const data = this.data;
|
|
1332
|
+
const refs = this.refs;
|
|
1333
|
+
for (let cbRef of cbRefs) {
|
|
1334
|
+
const { idx, refIdx } = locations[cbRef];
|
|
1335
|
+
const fn = data[idx];
|
|
1336
|
+
fn(refs[refIdx], null);
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
};
|
|
1340
|
+
Block.prototype.patch = function patch(other, withBeforeRemove) {
|
|
1341
|
+
if (this === other) {
|
|
1342
|
+
return;
|
|
1343
|
+
}
|
|
1344
|
+
const refs = this.refs;
|
|
1345
|
+
// update texts/attributes/
|
|
1346
|
+
if (locN) {
|
|
1347
|
+
const data1 = this.data;
|
|
1348
|
+
const data2 = other.data;
|
|
1349
|
+
for (let i = 0; i < locN; i++) {
|
|
1350
|
+
const val1 = data1[i];
|
|
1351
|
+
const val2 = data2[i];
|
|
1352
|
+
if (val1 !== val2) {
|
|
1353
|
+
const loc = locations[i];
|
|
1354
|
+
loc.updateData.call(refs[loc.refIdx], val2, val1);
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
this.data = data2;
|
|
1358
|
+
}
|
|
1359
|
+
// update children
|
|
1360
|
+
if (childN) {
|
|
1361
|
+
let children1 = this.children;
|
|
1362
|
+
const children2 = other.children;
|
|
1363
|
+
for (let i = 0; i < childN; i++) {
|
|
1364
|
+
const child1 = children1[i];
|
|
1365
|
+
const child2 = children2[i];
|
|
1366
|
+
if (child1) {
|
|
1367
|
+
if (child2) {
|
|
1368
|
+
child1.patch(child2, withBeforeRemove);
|
|
1369
|
+
}
|
|
1370
|
+
else {
|
|
1371
|
+
if (withBeforeRemove) {
|
|
1372
|
+
child1.beforeRemove();
|
|
1373
|
+
}
|
|
1374
|
+
child1.remove();
|
|
1375
|
+
children1[i] = undefined;
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
else if (child2) {
|
|
1379
|
+
const loc = childrenLocs[i];
|
|
1380
|
+
const afterNode = loc.afterRefIdx ? refs[loc.afterRefIdx] : null;
|
|
1381
|
+
child2.mount(refs[loc.parentRefIdx], afterNode);
|
|
1382
|
+
children1[i] = child2;
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
};
|
|
1387
|
+
Block.prototype.remove = function remove() {
|
|
1388
|
+
if (cbRefs.length) {
|
|
1389
|
+
const data = this.data;
|
|
1390
|
+
const refs = this.refs;
|
|
1391
|
+
for (let cbRef of cbRefs) {
|
|
1392
|
+
const { idx, refIdx } = locations[cbRef];
|
|
1393
|
+
const fn = data[idx];
|
|
1394
|
+
fn(null, refs[refIdx]);
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
elementRemove.call(this.el);
|
|
1398
|
+
};
|
|
1399
|
+
}
|
|
1400
|
+
return Block;
|
|
1401
|
+
}
|
|
1402
|
+
function setText(value) {
|
|
1403
|
+
characterDataSetData.call(this, toText(value));
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
const getDescriptor = (o, p) => Object.getOwnPropertyDescriptor(o, p);
|
|
1407
|
+
const nodeProto$1 = Node.prototype;
|
|
1408
|
+
const nodeInsertBefore$1 = nodeProto$1.insertBefore;
|
|
1409
|
+
const nodeAppendChild = nodeProto$1.appendChild;
|
|
1410
|
+
const nodeRemoveChild$1 = nodeProto$1.removeChild;
|
|
1411
|
+
const nodeSetTextContent = getDescriptor(nodeProto$1, "textContent").set;
|
|
1412
|
+
// -----------------------------------------------------------------------------
|
|
1413
|
+
// List Node
|
|
1414
|
+
// -----------------------------------------------------------------------------
|
|
1415
|
+
class VList {
|
|
1416
|
+
constructor(children) {
|
|
1417
|
+
this.children = children;
|
|
1418
|
+
}
|
|
1419
|
+
mount(parent, afterNode) {
|
|
1420
|
+
const children = this.children;
|
|
1421
|
+
const _anchor = document.createTextNode("");
|
|
1422
|
+
this.anchor = _anchor;
|
|
1423
|
+
nodeInsertBefore$1.call(parent, _anchor, afterNode);
|
|
1424
|
+
const l = children.length;
|
|
1425
|
+
if (l) {
|
|
1426
|
+
const mount = children[0].mount;
|
|
1427
|
+
for (let i = 0; i < l; i++) {
|
|
1428
|
+
mount.call(children[i], parent, _anchor);
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
this.parentEl = parent;
|
|
1432
|
+
}
|
|
1433
|
+
moveBeforeDOMNode(node, parent = this.parentEl) {
|
|
1434
|
+
this.parentEl = parent;
|
|
1435
|
+
const children = this.children;
|
|
1436
|
+
for (let i = 0, l = children.length; i < l; i++) {
|
|
1437
|
+
children[i].moveBeforeDOMNode(node, parent);
|
|
1438
|
+
}
|
|
1439
|
+
parent.insertBefore(this.anchor, node);
|
|
1440
|
+
}
|
|
1441
|
+
moveBeforeVNode(other, afterNode) {
|
|
1442
|
+
if (other) {
|
|
1443
|
+
const next = other.children[0];
|
|
1444
|
+
afterNode = (next ? next.firstNode() : other.anchor) || null;
|
|
1445
|
+
}
|
|
1446
|
+
const children = this.children;
|
|
1447
|
+
for (let i = 0, l = children.length; i < l; i++) {
|
|
1448
|
+
children[i].moveBeforeVNode(null, afterNode);
|
|
1449
|
+
}
|
|
1450
|
+
this.parentEl.insertBefore(this.anchor, afterNode);
|
|
1451
|
+
}
|
|
1452
|
+
patch(other, withBeforeRemove) {
|
|
1453
|
+
if (this === other) {
|
|
1454
|
+
return;
|
|
1455
|
+
}
|
|
1456
|
+
const ch1 = this.children;
|
|
1457
|
+
const ch2 = other.children;
|
|
1458
|
+
if (ch2.length === 0 && ch1.length === 0) {
|
|
1459
|
+
return;
|
|
1460
|
+
}
|
|
1461
|
+
this.children = ch2;
|
|
1462
|
+
const proto = ch2[0] || ch1[0];
|
|
1463
|
+
const { mount: cMount, patch: cPatch, remove: cRemove, beforeRemove, moveBeforeVNode: cMoveBefore, firstNode: cFirstNode, } = proto;
|
|
1464
|
+
const _anchor = this.anchor;
|
|
1465
|
+
const isOnlyChild = this.isOnlyChild;
|
|
1466
|
+
const parent = this.parentEl;
|
|
1467
|
+
// fast path: no new child => only remove
|
|
1468
|
+
if (ch2.length === 0 && isOnlyChild) {
|
|
1469
|
+
if (withBeforeRemove) {
|
|
1470
|
+
for (let i = 0, l = ch1.length; i < l; i++) {
|
|
1471
|
+
beforeRemove.call(ch1[i]);
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
nodeSetTextContent.call(parent, "");
|
|
1475
|
+
nodeAppendChild.call(parent, _anchor);
|
|
1476
|
+
return;
|
|
1477
|
+
}
|
|
1478
|
+
let startIdx1 = 0;
|
|
1479
|
+
let startIdx2 = 0;
|
|
1480
|
+
let startVn1 = ch1[0];
|
|
1481
|
+
let startVn2 = ch2[0];
|
|
1482
|
+
let endIdx1 = ch1.length - 1;
|
|
1483
|
+
let endIdx2 = ch2.length - 1;
|
|
1484
|
+
let endVn1 = ch1[endIdx1];
|
|
1485
|
+
let endVn2 = ch2[endIdx2];
|
|
1486
|
+
let mapping = undefined;
|
|
1487
|
+
while (startIdx1 <= endIdx1 && startIdx2 <= endIdx2) {
|
|
1488
|
+
// -------------------------------------------------------------------
|
|
1489
|
+
if (startVn1 === null) {
|
|
1490
|
+
startVn1 = ch1[++startIdx1];
|
|
1491
|
+
continue;
|
|
1492
|
+
}
|
|
1493
|
+
// -------------------------------------------------------------------
|
|
1494
|
+
if (endVn1 === null) {
|
|
1495
|
+
endVn1 = ch1[--endIdx1];
|
|
1496
|
+
continue;
|
|
1497
|
+
}
|
|
1498
|
+
// -------------------------------------------------------------------
|
|
1499
|
+
let startKey1 = startVn1.key;
|
|
1500
|
+
let startKey2 = startVn2.key;
|
|
1501
|
+
if (startKey1 === startKey2) {
|
|
1502
|
+
cPatch.call(startVn1, startVn2, withBeforeRemove);
|
|
1503
|
+
ch2[startIdx2] = startVn1;
|
|
1504
|
+
startVn1 = ch1[++startIdx1];
|
|
1505
|
+
startVn2 = ch2[++startIdx2];
|
|
1506
|
+
continue;
|
|
1507
|
+
}
|
|
1508
|
+
// -------------------------------------------------------------------
|
|
1509
|
+
let endKey1 = endVn1.key;
|
|
1510
|
+
let endKey2 = endVn2.key;
|
|
1511
|
+
if (endKey1 === endKey2) {
|
|
1512
|
+
cPatch.call(endVn1, endVn2, withBeforeRemove);
|
|
1513
|
+
ch2[endIdx2] = endVn1;
|
|
1514
|
+
endVn1 = ch1[--endIdx1];
|
|
1515
|
+
endVn2 = ch2[--endIdx2];
|
|
1516
|
+
continue;
|
|
1517
|
+
}
|
|
1518
|
+
// -------------------------------------------------------------------
|
|
1519
|
+
if (startKey1 === endKey2) {
|
|
1520
|
+
// bnode moved right
|
|
1521
|
+
cPatch.call(startVn1, endVn2, withBeforeRemove);
|
|
1522
|
+
ch2[endIdx2] = startVn1;
|
|
1523
|
+
const nextChild = ch2[endIdx2 + 1];
|
|
1524
|
+
cMoveBefore.call(startVn1, nextChild, _anchor);
|
|
1525
|
+
startVn1 = ch1[++startIdx1];
|
|
1526
|
+
endVn2 = ch2[--endIdx2];
|
|
1527
|
+
continue;
|
|
1528
|
+
}
|
|
1529
|
+
// -------------------------------------------------------------------
|
|
1530
|
+
if (endKey1 === startKey2) {
|
|
1531
|
+
// bnode moved left
|
|
1532
|
+
cPatch.call(endVn1, startVn2, withBeforeRemove);
|
|
1533
|
+
ch2[startIdx2] = endVn1;
|
|
1534
|
+
const nextChild = ch1[startIdx1];
|
|
1535
|
+
cMoveBefore.call(endVn1, nextChild, _anchor);
|
|
1536
|
+
endVn1 = ch1[--endIdx1];
|
|
1537
|
+
startVn2 = ch2[++startIdx2];
|
|
1538
|
+
continue;
|
|
1539
|
+
}
|
|
1540
|
+
// -------------------------------------------------------------------
|
|
1541
|
+
mapping = mapping || createMapping(ch1, startIdx1, endIdx1);
|
|
1542
|
+
let idxInOld = mapping[startKey2];
|
|
1543
|
+
if (idxInOld === undefined) {
|
|
1544
|
+
cMount.call(startVn2, parent, cFirstNode.call(startVn1) || null);
|
|
1545
|
+
}
|
|
1546
|
+
else {
|
|
1547
|
+
const elmToMove = ch1[idxInOld];
|
|
1548
|
+
cMoveBefore.call(elmToMove, startVn1, null);
|
|
1549
|
+
cPatch.call(elmToMove, startVn2, withBeforeRemove);
|
|
1550
|
+
ch2[startIdx2] = elmToMove;
|
|
1551
|
+
ch1[idxInOld] = null;
|
|
1552
|
+
}
|
|
1553
|
+
startVn2 = ch2[++startIdx2];
|
|
1554
|
+
}
|
|
1555
|
+
// ---------------------------------------------------------------------
|
|
1556
|
+
if (startIdx1 <= endIdx1 || startIdx2 <= endIdx2) {
|
|
1557
|
+
if (startIdx1 > endIdx1) {
|
|
1558
|
+
const nextChild = ch2[endIdx2 + 1];
|
|
1559
|
+
const anchor = nextChild ? cFirstNode.call(nextChild) || null : _anchor;
|
|
1560
|
+
for (let i = startIdx2; i <= endIdx2; i++) {
|
|
1561
|
+
cMount.call(ch2[i], parent, anchor);
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
else {
|
|
1565
|
+
for (let i = startIdx1; i <= endIdx1; i++) {
|
|
1566
|
+
let ch = ch1[i];
|
|
1567
|
+
if (ch) {
|
|
1568
|
+
if (withBeforeRemove) {
|
|
1569
|
+
beforeRemove.call(ch);
|
|
1570
|
+
}
|
|
1571
|
+
cRemove.call(ch);
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
beforeRemove() {
|
|
1578
|
+
const children = this.children;
|
|
1579
|
+
const l = children.length;
|
|
1580
|
+
if (l) {
|
|
1581
|
+
const beforeRemove = children[0].beforeRemove;
|
|
1582
|
+
for (let i = 0; i < l; i++) {
|
|
1583
|
+
beforeRemove.call(children[i]);
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
remove() {
|
|
1588
|
+
const { parentEl, anchor } = this;
|
|
1589
|
+
if (this.isOnlyChild) {
|
|
1590
|
+
nodeSetTextContent.call(parentEl, "");
|
|
1591
|
+
}
|
|
1592
|
+
else {
|
|
1593
|
+
const children = this.children;
|
|
1594
|
+
const l = children.length;
|
|
1595
|
+
if (l) {
|
|
1596
|
+
const remove = children[0].remove;
|
|
1597
|
+
for (let i = 0; i < l; i++) {
|
|
1598
|
+
remove.call(children[i]);
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
nodeRemoveChild$1.call(parentEl, anchor);
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
firstNode() {
|
|
1605
|
+
const child = this.children[0];
|
|
1606
|
+
return child ? child.firstNode() : undefined;
|
|
1607
|
+
}
|
|
1608
|
+
toString() {
|
|
1609
|
+
return this.children.map((c) => c.toString()).join("");
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
function list(children) {
|
|
1613
|
+
return new VList(children);
|
|
1614
|
+
}
|
|
1615
|
+
function createMapping(ch1, startIdx1, endIdx2) {
|
|
1616
|
+
let mapping = {};
|
|
1617
|
+
for (let i = startIdx1; i <= endIdx2; i++) {
|
|
1618
|
+
mapping[ch1[i].key] = i;
|
|
1619
|
+
}
|
|
1620
|
+
return mapping;
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
const nodeProto = Node.prototype;
|
|
1624
|
+
const nodeInsertBefore = nodeProto.insertBefore;
|
|
1625
|
+
const nodeRemoveChild = nodeProto.removeChild;
|
|
1626
|
+
class VHtml {
|
|
1627
|
+
constructor(html) {
|
|
1628
|
+
this.content = [];
|
|
1629
|
+
this.html = html;
|
|
1630
|
+
}
|
|
1631
|
+
mount(parent, afterNode) {
|
|
1632
|
+
this.parentEl = parent;
|
|
1633
|
+
const template = document.createElement("template");
|
|
1634
|
+
template.innerHTML = this.html;
|
|
1635
|
+
this.content = [...template.content.childNodes];
|
|
1636
|
+
for (let elem of this.content) {
|
|
1637
|
+
nodeInsertBefore.call(parent, elem, afterNode);
|
|
1638
|
+
}
|
|
1639
|
+
if (!this.content.length) {
|
|
1640
|
+
const textNode = document.createTextNode("");
|
|
1641
|
+
this.content.push(textNode);
|
|
1642
|
+
nodeInsertBefore.call(parent, textNode, afterNode);
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
moveBeforeDOMNode(node, parent = this.parentEl) {
|
|
1646
|
+
this.parentEl = parent;
|
|
1647
|
+
for (let elem of this.content) {
|
|
1648
|
+
nodeInsertBefore.call(parent, elem, node);
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
moveBeforeVNode(other, afterNode) {
|
|
1652
|
+
const target = other ? other.content[0] : afterNode;
|
|
1653
|
+
this.moveBeforeDOMNode(target);
|
|
1654
|
+
}
|
|
1655
|
+
patch(other) {
|
|
1656
|
+
if (this === other) {
|
|
1657
|
+
return;
|
|
1658
|
+
}
|
|
1659
|
+
const html2 = other.html;
|
|
1660
|
+
if (this.html !== html2) {
|
|
1661
|
+
const parent = this.parentEl;
|
|
1662
|
+
// insert new html in front of current
|
|
1663
|
+
const afterNode = this.content[0];
|
|
1664
|
+
const template = document.createElement("template");
|
|
1665
|
+
template.innerHTML = html2;
|
|
1666
|
+
const content = [...template.content.childNodes];
|
|
1667
|
+
for (let elem of content) {
|
|
1668
|
+
nodeInsertBefore.call(parent, elem, afterNode);
|
|
1669
|
+
}
|
|
1670
|
+
if (!content.length) {
|
|
1671
|
+
const textNode = document.createTextNode("");
|
|
1672
|
+
content.push(textNode);
|
|
1673
|
+
nodeInsertBefore.call(parent, textNode, afterNode);
|
|
1674
|
+
}
|
|
1675
|
+
// remove current content
|
|
1676
|
+
this.remove();
|
|
1677
|
+
this.content = content;
|
|
1678
|
+
this.html = other.html;
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
beforeRemove() { }
|
|
1682
|
+
remove() {
|
|
1683
|
+
const parent = this.parentEl;
|
|
1684
|
+
for (let elem of this.content) {
|
|
1685
|
+
nodeRemoveChild.call(parent, elem);
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
firstNode() {
|
|
1689
|
+
return this.content[0];
|
|
1690
|
+
}
|
|
1691
|
+
toString() {
|
|
1692
|
+
return this.html;
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
function html(str) {
|
|
1696
|
+
return new VHtml(str);
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
function createCatcher(eventsSpec) {
|
|
1700
|
+
const n = Object.keys(eventsSpec).length;
|
|
1701
|
+
class VCatcher {
|
|
1702
|
+
constructor(child, handlers) {
|
|
1703
|
+
this.handlerFns = [];
|
|
1704
|
+
this.afterNode = null;
|
|
1705
|
+
this.child = child;
|
|
1706
|
+
this.handlerData = handlers;
|
|
1707
|
+
}
|
|
1708
|
+
mount(parent, afterNode) {
|
|
1709
|
+
this.parentEl = parent;
|
|
1710
|
+
this.child.mount(parent, afterNode);
|
|
1711
|
+
this.afterNode = document.createTextNode("");
|
|
1712
|
+
parent.insertBefore(this.afterNode, afterNode);
|
|
1713
|
+
this.wrapHandlerData();
|
|
1714
|
+
for (let name in eventsSpec) {
|
|
1715
|
+
const index = eventsSpec[name];
|
|
1716
|
+
const handler = createEventHandler(name);
|
|
1717
|
+
this.handlerFns[index] = handler;
|
|
1718
|
+
handler.setup.call(parent, this.handlerData[index]);
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
wrapHandlerData() {
|
|
1722
|
+
for (let i = 0; i < n; i++) {
|
|
1723
|
+
let handler = this.handlerData[i];
|
|
1724
|
+
// handler = [...mods, fn, comp], so we need to replace second to last elem
|
|
1725
|
+
let idx = handler.length - 2;
|
|
1726
|
+
let origFn = handler[idx];
|
|
1727
|
+
const self = this;
|
|
1728
|
+
handler[idx] = function (ev) {
|
|
1729
|
+
const target = ev.target;
|
|
1730
|
+
let currentNode = self.child.firstNode();
|
|
1731
|
+
const afterNode = self.afterNode;
|
|
1732
|
+
while (currentNode && currentNode !== afterNode) {
|
|
1733
|
+
if (currentNode.contains(target)) {
|
|
1734
|
+
return origFn.call(this, ev);
|
|
1735
|
+
}
|
|
1736
|
+
currentNode = currentNode.nextSibling;
|
|
1737
|
+
}
|
|
1738
|
+
};
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
moveBeforeDOMNode(node, parent = this.parentEl) {
|
|
1742
|
+
this.parentEl = parent;
|
|
1743
|
+
this.child.moveBeforeDOMNode(node, parent);
|
|
1744
|
+
parent.insertBefore(this.afterNode, node);
|
|
1745
|
+
}
|
|
1746
|
+
moveBeforeVNode(other, afterNode) {
|
|
1747
|
+
if (other) {
|
|
1748
|
+
// check this with @ged-odoo for use in foreach
|
|
1749
|
+
afterNode = other.firstNode() || afterNode;
|
|
1750
|
+
}
|
|
1751
|
+
this.child.moveBeforeVNode(other ? other.child : null, afterNode);
|
|
1752
|
+
this.parentEl.insertBefore(this.afterNode, afterNode);
|
|
1753
|
+
}
|
|
1754
|
+
patch(other, withBeforeRemove) {
|
|
1755
|
+
if (this === other) {
|
|
1756
|
+
return;
|
|
1757
|
+
}
|
|
1758
|
+
this.handlerData = other.handlerData;
|
|
1759
|
+
this.wrapHandlerData();
|
|
1760
|
+
for (let i = 0; i < n; i++) {
|
|
1761
|
+
this.handlerFns[i].update.call(this.parentEl, this.handlerData[i]);
|
|
1762
|
+
}
|
|
1763
|
+
this.child.patch(other.child, withBeforeRemove);
|
|
1764
|
+
}
|
|
1765
|
+
beforeRemove() {
|
|
1766
|
+
this.child.beforeRemove();
|
|
1767
|
+
}
|
|
1768
|
+
remove() {
|
|
1769
|
+
for (let i = 0; i < n; i++) {
|
|
1770
|
+
this.handlerFns[i].remove.call(this.parentEl);
|
|
1771
|
+
}
|
|
1772
|
+
this.child.remove();
|
|
1773
|
+
this.afterNode.remove();
|
|
1774
|
+
}
|
|
1775
|
+
firstNode() {
|
|
1776
|
+
return this.child.firstNode();
|
|
1777
|
+
}
|
|
1778
|
+
toString() {
|
|
1779
|
+
return this.child.toString();
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
return function (child, handlers) {
|
|
1783
|
+
return new VCatcher(child, handlers);
|
|
1784
|
+
};
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
function mount$1(vnode, fixture, afterNode = null) {
|
|
1788
|
+
vnode.mount(fixture, afterNode);
|
|
1789
|
+
}
|
|
1790
|
+
function patch(vnode1, vnode2, withBeforeRemove = false) {
|
|
1791
|
+
vnode1.patch(vnode2, withBeforeRemove);
|
|
1792
|
+
}
|
|
1793
|
+
function remove(vnode, withBeforeRemove = false) {
|
|
1794
|
+
if (withBeforeRemove) {
|
|
1795
|
+
vnode.beforeRemove();
|
|
1796
|
+
}
|
|
1797
|
+
vnode.remove();
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
function makeChildFiber(node, parent) {
|
|
1801
|
+
let current = node.fiber;
|
|
1802
|
+
if (current) {
|
|
1803
|
+
cancelFibers(current.children);
|
|
1804
|
+
current.root = null;
|
|
1805
|
+
}
|
|
1806
|
+
return new Fiber(node, parent);
|
|
1807
|
+
}
|
|
1808
|
+
function makeRootFiber(node) {
|
|
1809
|
+
let current = node.fiber;
|
|
1810
|
+
if (current) {
|
|
1811
|
+
let root = current.root;
|
|
1812
|
+
// lock root fiber because canceling children fibers may destroy components,
|
|
1813
|
+
// which means any arbitrary code can be run in onWillDestroy, which may
|
|
1814
|
+
// trigger new renderings
|
|
1815
|
+
root.locked = true;
|
|
1816
|
+
root.setCounter(root.counter + 1 - cancelFibers(current.children));
|
|
1817
|
+
root.locked = false;
|
|
1818
|
+
current.children = [];
|
|
1819
|
+
current.childrenMap = {};
|
|
1820
|
+
current.bdom = null;
|
|
1821
|
+
if (fibersInError.has(current)) {
|
|
1822
|
+
fibersInError.delete(current);
|
|
1823
|
+
fibersInError.delete(root);
|
|
1824
|
+
current.appliedToDom = false;
|
|
1825
|
+
if (current instanceof RootFiber) {
|
|
1826
|
+
// it is possible that this fiber is a fiber that crashed while being
|
|
1827
|
+
// mounted, so the mounted list is possibly corrupted. We restore it to
|
|
1828
|
+
// its normal initial state (which is empty list or a list with a mount
|
|
1829
|
+
// fiber.
|
|
1830
|
+
current.mounted = current instanceof MountFiber ? [current] : [];
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
return current;
|
|
1834
|
+
}
|
|
1835
|
+
const fiber = new RootFiber(node, null);
|
|
1836
|
+
if (node.willPatch.length) {
|
|
1837
|
+
fiber.willPatch.push(fiber);
|
|
1838
|
+
}
|
|
1839
|
+
if (node.patched.length) {
|
|
1840
|
+
fiber.patched.push(fiber);
|
|
1841
|
+
}
|
|
1842
|
+
return fiber;
|
|
1843
|
+
}
|
|
1844
|
+
function throwOnRender() {
|
|
1845
|
+
throw new OwlError("Attempted to render cancelled fiber");
|
|
1846
|
+
}
|
|
1847
|
+
/**
|
|
1848
|
+
* @returns number of not-yet rendered fibers cancelled
|
|
1849
|
+
*/
|
|
1850
|
+
function cancelFibers(fibers) {
|
|
1851
|
+
let result = 0;
|
|
1852
|
+
for (let fiber of fibers) {
|
|
1853
|
+
let node = fiber.node;
|
|
1854
|
+
fiber.render = throwOnRender;
|
|
1855
|
+
if (node.status === 0 /* STATUS.NEW */) {
|
|
1856
|
+
node.cancel();
|
|
1857
|
+
}
|
|
1858
|
+
node.fiber = null;
|
|
1859
|
+
if (fiber.bdom) {
|
|
1860
|
+
// if fiber has been rendered, this means that the component props have
|
|
1861
|
+
// been updated. however, this fiber will not be patched to the dom, so
|
|
1862
|
+
// it could happen that the next render compare the current props with
|
|
1863
|
+
// the same props, and skip the render completely. With the next line,
|
|
1864
|
+
// we kindly request the component code to force a render, so it works as
|
|
1865
|
+
// expected.
|
|
1866
|
+
node.forceNextRender = true;
|
|
1867
|
+
}
|
|
1868
|
+
else {
|
|
1869
|
+
result++;
|
|
1870
|
+
}
|
|
1871
|
+
result += cancelFibers(fiber.children);
|
|
1872
|
+
}
|
|
1873
|
+
return result;
|
|
1874
|
+
}
|
|
1875
|
+
class Fiber {
|
|
1876
|
+
constructor(node, parent) {
|
|
1877
|
+
this.bdom = null;
|
|
1878
|
+
this.children = [];
|
|
1879
|
+
this.appliedToDom = false;
|
|
1880
|
+
this.deep = false;
|
|
1881
|
+
this.childrenMap = {};
|
|
1882
|
+
this.node = node;
|
|
1883
|
+
this.parent = parent;
|
|
1884
|
+
if (parent) {
|
|
1885
|
+
this.deep = parent.deep;
|
|
1886
|
+
const root = parent.root;
|
|
1887
|
+
root.setCounter(root.counter + 1);
|
|
1888
|
+
this.root = root;
|
|
1889
|
+
parent.children.push(this);
|
|
1890
|
+
}
|
|
1891
|
+
else {
|
|
1892
|
+
this.root = this;
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
render() {
|
|
1896
|
+
// if some parent has a fiber => register in followup
|
|
1897
|
+
let prev = this.root.node;
|
|
1898
|
+
let scheduler = prev.app.scheduler;
|
|
1899
|
+
let current = prev.parent;
|
|
1900
|
+
while (current) {
|
|
1901
|
+
if (current.fiber) {
|
|
1902
|
+
let root = current.fiber.root;
|
|
1903
|
+
if (root.counter === 0 && prev.parentKey in current.fiber.childrenMap) {
|
|
1904
|
+
current = root.node;
|
|
1905
|
+
}
|
|
1906
|
+
else {
|
|
1907
|
+
scheduler.delayedRenders.push(this);
|
|
1908
|
+
return;
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
prev = current;
|
|
1912
|
+
current = current.parent;
|
|
1913
|
+
}
|
|
1914
|
+
// there are no current rendering from above => we can render
|
|
1915
|
+
this._render();
|
|
1916
|
+
}
|
|
1917
|
+
_render() {
|
|
1918
|
+
const node = this.node;
|
|
1919
|
+
const root = this.root;
|
|
1920
|
+
if (root) {
|
|
1921
|
+
// todo: should use updateComputation somewhere else.
|
|
1922
|
+
runWithComputation(node.signalComputation, () => {
|
|
1923
|
+
try {
|
|
1924
|
+
this.bdom = true;
|
|
1925
|
+
this.bdom = node.renderFn();
|
|
1926
|
+
}
|
|
1927
|
+
catch (e) {
|
|
1928
|
+
node.app.handleError({ node, error: e });
|
|
1929
|
+
}
|
|
1930
|
+
});
|
|
1931
|
+
root.setCounter(root.counter - 1);
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
class RootFiber extends Fiber {
|
|
1936
|
+
constructor() {
|
|
1937
|
+
super(...arguments);
|
|
1938
|
+
this.counter = 1;
|
|
1939
|
+
// only add stuff in this if they have registered some hooks
|
|
1940
|
+
this.willPatch = [];
|
|
1941
|
+
this.patched = [];
|
|
1942
|
+
this.mounted = [];
|
|
1943
|
+
// A fiber is typically locked when it is completing and the patch has not, or is being applied.
|
|
1944
|
+
// i.e.: render triggered in onWillUnmount or in willPatch will be delayed
|
|
1945
|
+
this.locked = false;
|
|
1946
|
+
}
|
|
1947
|
+
complete() {
|
|
1948
|
+
const node = this.node;
|
|
1949
|
+
this.locked = true;
|
|
1950
|
+
let current = undefined;
|
|
1951
|
+
let mountedFibers = this.mounted;
|
|
1952
|
+
try {
|
|
1953
|
+
// Step 1: calling all willPatch lifecycle hooks
|
|
1954
|
+
for (current of this.willPatch) {
|
|
1955
|
+
// because of the asynchronous nature of the rendering, some parts of the
|
|
1956
|
+
// UI may have been rendered, then deleted in a followup rendering, and we
|
|
1957
|
+
// do not want to call onWillPatch in that case.
|
|
1958
|
+
let node = current.node;
|
|
1959
|
+
if (node.fiber === current) {
|
|
1960
|
+
const component = node.component;
|
|
1961
|
+
for (let cb of node.willPatch) {
|
|
1962
|
+
cb.call(component);
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
current = undefined;
|
|
1967
|
+
// Step 2: patching the dom
|
|
1968
|
+
node._patch();
|
|
1969
|
+
this.locked = false;
|
|
1970
|
+
// Step 4: calling all mounted lifecycle hooks
|
|
1971
|
+
while ((current = mountedFibers.pop())) {
|
|
1972
|
+
current = current;
|
|
1973
|
+
if (current.appliedToDom) {
|
|
1974
|
+
for (let cb of current.node.mounted) {
|
|
1975
|
+
cb();
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
// Step 5: calling all patched hooks
|
|
1980
|
+
let patchedFibers = this.patched;
|
|
1981
|
+
while ((current = patchedFibers.pop())) {
|
|
1982
|
+
current = current;
|
|
1983
|
+
if (current.appliedToDom) {
|
|
1984
|
+
for (let cb of current.node.patched) {
|
|
1985
|
+
cb();
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
catch (e) {
|
|
1991
|
+
// if mountedFibers is not empty, this means that a crash occured while
|
|
1992
|
+
// calling the mounted hooks of some component. So, there may still be
|
|
1993
|
+
// some component that have been mounted, but for which the mounted hooks
|
|
1994
|
+
// have not been called. Here, we remove the willUnmount hooks for these
|
|
1995
|
+
// specific component to prevent a worse situation (willUnmount being
|
|
1996
|
+
// called even though mounted has not been called)
|
|
1997
|
+
for (let fiber of mountedFibers) {
|
|
1998
|
+
fiber.node.willUnmount = [];
|
|
1999
|
+
}
|
|
2000
|
+
this.locked = false;
|
|
2001
|
+
node.app.handleError({ fiber: current || this, error: e });
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
setCounter(newValue) {
|
|
2005
|
+
this.counter = newValue;
|
|
2006
|
+
if (newValue === 0) {
|
|
2007
|
+
this.node.app.scheduler.flush();
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
class MountFiber extends RootFiber {
|
|
2012
|
+
constructor(node, target, options = {}) {
|
|
2013
|
+
super(node, null);
|
|
2014
|
+
this.target = target;
|
|
2015
|
+
this.position = options.position || "last-child";
|
|
2016
|
+
}
|
|
2017
|
+
complete() {
|
|
2018
|
+
let current = this;
|
|
2019
|
+
try {
|
|
2020
|
+
const node = this.node;
|
|
2021
|
+
node.children = this.childrenMap;
|
|
2022
|
+
node.app.constructor.validateTarget(this.target);
|
|
2023
|
+
if (node.bdom) {
|
|
2024
|
+
// this is a complicated situation: if we mount a fiber with an existing
|
|
2025
|
+
// bdom, this means that this same fiber was already completed, mounted,
|
|
2026
|
+
// but a crash occurred in some mounted hook. Then, it was handled and
|
|
2027
|
+
// the new rendering is being applied.
|
|
2028
|
+
node.updateDom();
|
|
2029
|
+
}
|
|
2030
|
+
else {
|
|
2031
|
+
node.bdom = this.bdom;
|
|
2032
|
+
if (this.position === "last-child" || this.target.childNodes.length === 0) {
|
|
2033
|
+
mount$1(node.bdom, this.target);
|
|
2034
|
+
}
|
|
2035
|
+
else {
|
|
2036
|
+
const firstChild = this.target.childNodes[0];
|
|
2037
|
+
mount$1(node.bdom, this.target, firstChild);
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
// unregistering the fiber before mounted since it can do another render
|
|
2041
|
+
// and that the current rendering is obviously completed
|
|
2042
|
+
node.fiber = null;
|
|
2043
|
+
node.status = 1 /* STATUS.MOUNTED */;
|
|
2044
|
+
this.appliedToDom = true;
|
|
2045
|
+
let mountedFibers = this.mounted;
|
|
2046
|
+
while ((current = mountedFibers.pop())) {
|
|
2047
|
+
if (current.appliedToDom) {
|
|
2048
|
+
for (let cb of current.node.mounted) {
|
|
2049
|
+
cb();
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
catch (e) {
|
|
2055
|
+
this.node.app.handleError({ fiber: current, error: e });
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
|
|
2060
|
+
let currentNode = null;
|
|
2061
|
+
function saveCurrent() {
|
|
2062
|
+
let n = currentNode;
|
|
2063
|
+
return () => {
|
|
2064
|
+
currentNode = n;
|
|
2065
|
+
};
|
|
2066
|
+
}
|
|
2067
|
+
function getCurrent() {
|
|
2068
|
+
if (!currentNode) {
|
|
2069
|
+
throw new OwlError("No active component (a hook function should only be called in 'setup')");
|
|
2070
|
+
}
|
|
2071
|
+
return currentNode;
|
|
2072
|
+
}
|
|
2073
|
+
function useComponent() {
|
|
2074
|
+
return currentNode.component;
|
|
2075
|
+
}
|
|
2076
|
+
class ComponentNode {
|
|
2077
|
+
constructor(C, props, app, parent, parentKey) {
|
|
2078
|
+
this.fiber = null;
|
|
2079
|
+
this.bdom = null;
|
|
2080
|
+
this.status = 0 /* STATUS.NEW */;
|
|
2081
|
+
this.forceNextRender = false;
|
|
2082
|
+
this.children = Object.create(null);
|
|
2083
|
+
this.willStart = [];
|
|
2084
|
+
this.willUpdateProps = [];
|
|
2085
|
+
this.willUnmount = [];
|
|
2086
|
+
this.mounted = [];
|
|
2087
|
+
this.willPatch = [];
|
|
2088
|
+
this.patched = [];
|
|
2089
|
+
this.willDestroy = [];
|
|
2090
|
+
this.name = C.name;
|
|
2091
|
+
currentNode = this;
|
|
2092
|
+
this.app = app;
|
|
2093
|
+
this.parent = parent;
|
|
2094
|
+
this.parentKey = parentKey;
|
|
2095
|
+
this.pluginManager = parent ? parent.pluginManager : app.pluginManager;
|
|
2096
|
+
this.signalComputation = {
|
|
2097
|
+
value: undefined,
|
|
2098
|
+
compute: () => this.render(false),
|
|
2099
|
+
sources: new Set(),
|
|
2100
|
+
state: ComputationState.EXECUTED,
|
|
2101
|
+
};
|
|
2102
|
+
this.props = Object.assign({}, props);
|
|
2103
|
+
const previousComputation = getCurrentComputation();
|
|
2104
|
+
setComputation(this.signalComputation);
|
|
2105
|
+
this.component = new C(this);
|
|
2106
|
+
const ctx = { this: this.component, __owl__: this };
|
|
2107
|
+
this.renderFn = app.getTemplate(C.template).bind(this.component, ctx, this);
|
|
2108
|
+
this.component.setup();
|
|
2109
|
+
setComputation(previousComputation);
|
|
2110
|
+
currentNode = null;
|
|
2111
|
+
}
|
|
2112
|
+
mountComponent(target, options) {
|
|
2113
|
+
const fiber = new MountFiber(this, target, options);
|
|
2114
|
+
this.app.scheduler.addFiber(fiber);
|
|
2115
|
+
this.initiateRender(fiber);
|
|
2116
|
+
}
|
|
2117
|
+
async initiateRender(fiber) {
|
|
2118
|
+
this.fiber = fiber;
|
|
2119
|
+
if (this.mounted.length) {
|
|
2120
|
+
fiber.root.mounted.push(fiber);
|
|
2121
|
+
}
|
|
2122
|
+
const component = this.component;
|
|
2123
|
+
try {
|
|
2124
|
+
let promises;
|
|
2125
|
+
runWithComputation(undefined, () => {
|
|
2126
|
+
promises = this.willStart.map((f) => f.call(component));
|
|
2127
|
+
});
|
|
2128
|
+
await Promise.all(promises);
|
|
2129
|
+
}
|
|
2130
|
+
catch (e) {
|
|
2131
|
+
this.app.handleError({ node: this, error: e });
|
|
2132
|
+
return;
|
|
2133
|
+
}
|
|
2134
|
+
if (this.status === 0 /* STATUS.NEW */ && this.fiber === fiber) {
|
|
2135
|
+
fiber.render();
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
async render(deep) {
|
|
2139
|
+
if (this.status >= 2 /* STATUS.CANCELLED */) {
|
|
2140
|
+
return;
|
|
2141
|
+
}
|
|
2142
|
+
let current = this.fiber;
|
|
2143
|
+
if (current && (current.root.locked || current.bdom === true)) {
|
|
2144
|
+
await Promise.resolve();
|
|
2145
|
+
// situation may have changed after the microtask tick
|
|
2146
|
+
current = this.fiber;
|
|
2147
|
+
}
|
|
2148
|
+
if (current) {
|
|
2149
|
+
if (!current.bdom && !fibersInError.has(current)) {
|
|
2150
|
+
if (deep) {
|
|
2151
|
+
// we want the render from this point on to be with deep=true
|
|
2152
|
+
current.deep = deep;
|
|
2153
|
+
}
|
|
2154
|
+
return;
|
|
2155
|
+
}
|
|
2156
|
+
// if current rendering was with deep=true, we want this one to be the same
|
|
2157
|
+
deep = deep || current.deep;
|
|
2158
|
+
}
|
|
2159
|
+
else if (!this.bdom) {
|
|
2160
|
+
return;
|
|
2161
|
+
}
|
|
2162
|
+
const fiber = makeRootFiber(this);
|
|
2163
|
+
fiber.deep = deep;
|
|
2164
|
+
this.fiber = fiber;
|
|
2165
|
+
this.app.scheduler.addFiber(fiber);
|
|
2166
|
+
await Promise.resolve();
|
|
2167
|
+
if (this.status >= 2 /* STATUS.CANCELLED */) {
|
|
2168
|
+
return;
|
|
2169
|
+
}
|
|
2170
|
+
// We only want to actually render the component if the following two
|
|
2171
|
+
// conditions are true:
|
|
2172
|
+
// * this.fiber: it could be null, in which case the render has been cancelled
|
|
2173
|
+
// * (current || !fiber.parent): if current is not null, this means that the
|
|
2174
|
+
// render function was called when a render was already occurring. In this
|
|
2175
|
+
// case, the pending rendering was cancelled, and the fiber needs to be
|
|
2176
|
+
// rendered to complete the work. If current is null, we check that the
|
|
2177
|
+
// fiber has no parent. If that is the case, the fiber was downgraded from
|
|
2178
|
+
// a root fiber to a child fiber in the previous microtick, because it was
|
|
2179
|
+
// embedded in a rendering coming from above, so the fiber will be rendered
|
|
2180
|
+
// in the next microtick anyway, so we should not render it again.
|
|
2181
|
+
if (this.fiber === fiber && (current || !fiber.parent)) {
|
|
2182
|
+
fiber.render();
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
cancel() {
|
|
2186
|
+
this._cancel();
|
|
2187
|
+
delete this.parent.children[this.parentKey];
|
|
2188
|
+
this.app.scheduler.scheduleDestroy(this);
|
|
2189
|
+
}
|
|
2190
|
+
_cancel() {
|
|
2191
|
+
this.status = 2 /* STATUS.CANCELLED */;
|
|
2192
|
+
const children = this.children;
|
|
2193
|
+
for (let childKey in children) {
|
|
2194
|
+
children[childKey]._cancel();
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
destroy() {
|
|
2198
|
+
let shouldRemove = this.status === 1 /* STATUS.MOUNTED */;
|
|
2199
|
+
this._destroy();
|
|
2200
|
+
if (shouldRemove) {
|
|
2201
|
+
this.bdom.remove();
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
_destroy() {
|
|
2205
|
+
const component = this.component;
|
|
2206
|
+
if (this.status === 1 /* STATUS.MOUNTED */) {
|
|
2207
|
+
for (let cb of this.willUnmount) {
|
|
2208
|
+
cb.call(component);
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
for (let child of Object.values(this.children)) {
|
|
2212
|
+
child._destroy();
|
|
2213
|
+
}
|
|
2214
|
+
if (this.willDestroy.length) {
|
|
2215
|
+
try {
|
|
2216
|
+
for (let cb of this.willDestroy) {
|
|
2217
|
+
cb.call(component);
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2220
|
+
catch (e) {
|
|
2221
|
+
this.app.handleError({ error: e, node: this });
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
2224
|
+
this.status = 3 /* STATUS.DESTROYED */;
|
|
2225
|
+
}
|
|
2226
|
+
async updateAndRender(props, parentFiber) {
|
|
2227
|
+
props = Object.assign({}, props);
|
|
2228
|
+
// update
|
|
2229
|
+
const fiber = makeChildFiber(this, parentFiber);
|
|
2230
|
+
this.fiber = fiber;
|
|
2231
|
+
const component = this.component;
|
|
2232
|
+
let prom;
|
|
2233
|
+
untrack(() => {
|
|
2234
|
+
prom = Promise.all(this.willUpdateProps.map((f) => f.call(component, props)));
|
|
2235
|
+
});
|
|
2236
|
+
await prom;
|
|
2237
|
+
if (fiber !== this.fiber) {
|
|
2238
|
+
return;
|
|
2239
|
+
}
|
|
2240
|
+
this.props = props;
|
|
2241
|
+
fiber.render();
|
|
2242
|
+
const parentRoot = parentFiber.root;
|
|
2243
|
+
if (this.willPatch.length) {
|
|
2244
|
+
parentRoot.willPatch.push(fiber);
|
|
2245
|
+
}
|
|
2246
|
+
if (this.patched.length) {
|
|
2247
|
+
parentRoot.patched.push(fiber);
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2250
|
+
/**
|
|
2251
|
+
* Finds a child that has dom that is not yet updated, and update it. This
|
|
2252
|
+
* method is meant to be used only in the context of repatching the dom after
|
|
2253
|
+
* a mounted hook failed and was handled.
|
|
2254
|
+
*/
|
|
2255
|
+
updateDom() {
|
|
2256
|
+
if (!this.fiber) {
|
|
2257
|
+
return;
|
|
2258
|
+
}
|
|
2259
|
+
if (this.bdom === this.fiber.bdom) {
|
|
2260
|
+
// If the error was handled by some child component, we need to find it to
|
|
2261
|
+
// apply its change
|
|
2262
|
+
for (let k in this.children) {
|
|
2263
|
+
const child = this.children[k];
|
|
2264
|
+
child.updateDom();
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
else {
|
|
2268
|
+
// if we get here, this is the component that handled the error and rerendered
|
|
2269
|
+
// itself, so we can simply patch the dom
|
|
2270
|
+
this.bdom.patch(this.fiber.bdom, false);
|
|
2271
|
+
this.fiber.appliedToDom = true;
|
|
2272
|
+
this.fiber = null;
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
// ---------------------------------------------------------------------------
|
|
2276
|
+
// Block DOM methods
|
|
2277
|
+
// ---------------------------------------------------------------------------
|
|
2278
|
+
firstNode() {
|
|
2279
|
+
const bdom = this.bdom;
|
|
2280
|
+
return bdom ? bdom.firstNode() : undefined;
|
|
2281
|
+
}
|
|
2282
|
+
mount(parent, anchor) {
|
|
2283
|
+
const bdom = this.fiber.bdom;
|
|
2284
|
+
this.bdom = bdom;
|
|
2285
|
+
bdom.mount(parent, anchor);
|
|
2286
|
+
this.status = 1 /* STATUS.MOUNTED */;
|
|
2287
|
+
this.fiber.appliedToDom = true;
|
|
2288
|
+
this.children = this.fiber.childrenMap;
|
|
2289
|
+
this.fiber = null;
|
|
2290
|
+
}
|
|
2291
|
+
moveBeforeDOMNode(node, parent) {
|
|
2292
|
+
this.bdom.moveBeforeDOMNode(node, parent);
|
|
2293
|
+
}
|
|
2294
|
+
moveBeforeVNode(other, afterNode) {
|
|
2295
|
+
this.bdom.moveBeforeVNode(other ? other.bdom : null, afterNode);
|
|
2296
|
+
}
|
|
2297
|
+
patch() {
|
|
2298
|
+
if (this.fiber && this.fiber.parent) {
|
|
2299
|
+
// we only patch here renderings coming from above. renderings initiated
|
|
2300
|
+
// by the component will be patched independently in the appropriate
|
|
2301
|
+
// fiber.complete
|
|
2302
|
+
this._patch();
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2305
|
+
_patch() {
|
|
2306
|
+
let hasChildren = false;
|
|
2307
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2308
|
+
for (let _k in this.children) {
|
|
2309
|
+
hasChildren = true;
|
|
2310
|
+
break;
|
|
2311
|
+
}
|
|
2312
|
+
const fiber = this.fiber;
|
|
2313
|
+
this.children = fiber.childrenMap;
|
|
2314
|
+
this.bdom.patch(fiber.bdom, hasChildren);
|
|
2315
|
+
fiber.appliedToDom = true;
|
|
2316
|
+
this.fiber = null;
|
|
2317
|
+
}
|
|
2318
|
+
beforeRemove() {
|
|
2319
|
+
this._destroy();
|
|
2320
|
+
}
|
|
2321
|
+
remove() {
|
|
2322
|
+
this.bdom.remove();
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
|
|
2326
|
+
let currentPluginManager = null;
|
|
2327
|
+
const _getCurrentPluginManager = () => currentPluginManager;
|
|
2328
|
+
class Plugin {
|
|
2329
|
+
setup() { }
|
|
2330
|
+
}
|
|
2331
|
+
Plugin.id = "";
|
|
2332
|
+
class PluginManager {
|
|
2333
|
+
constructor(parent) {
|
|
2334
|
+
var _a;
|
|
2335
|
+
this.children = [];
|
|
2336
|
+
this.onDestroyCb = [];
|
|
2337
|
+
this.status = 0 /* STATUS.NEW */;
|
|
2338
|
+
this.parent = parent;
|
|
2339
|
+
(_a = this.parent) === null || _a === void 0 ? void 0 : _a.children.push(this);
|
|
2340
|
+
this.plugins = this.parent ? Object.create(this.parent.plugins) : {};
|
|
2341
|
+
}
|
|
2342
|
+
destroy() {
|
|
2343
|
+
for (let children of this.children) {
|
|
2344
|
+
children.destroy();
|
|
2345
|
+
}
|
|
2346
|
+
const cbs = this.onDestroyCb;
|
|
2347
|
+
while (cbs.length) {
|
|
2348
|
+
cbs.pop()();
|
|
2349
|
+
}
|
|
2350
|
+
this.status = 3 /* STATUS.DESTROYED */;
|
|
2351
|
+
}
|
|
2352
|
+
getPluginById(id) {
|
|
2353
|
+
return this.plugins[id] || null;
|
|
2354
|
+
}
|
|
2355
|
+
getPlugin(pluginType) {
|
|
2356
|
+
return this.getPluginById(pluginType.id);
|
|
2357
|
+
}
|
|
2358
|
+
startPlugins(pluginTypes) {
|
|
2359
|
+
const previousManager = currentPluginManager;
|
|
2360
|
+
currentPluginManager = this;
|
|
2361
|
+
const plugins = [];
|
|
2362
|
+
// instantiate plugins
|
|
2363
|
+
for (const pluginType of pluginTypes) {
|
|
2364
|
+
if (!pluginType.id) {
|
|
2365
|
+
currentPluginManager = previousManager;
|
|
2366
|
+
throw new OwlError(`Plugin "${pluginType.name}" has no id`);
|
|
2367
|
+
}
|
|
2368
|
+
if (this.plugins.hasOwnProperty(pluginType.id)) {
|
|
2369
|
+
continue;
|
|
2370
|
+
}
|
|
2371
|
+
const plugin = new pluginType();
|
|
2372
|
+
this.plugins[pluginType.id] = plugin;
|
|
2373
|
+
plugins.push(plugin);
|
|
2374
|
+
}
|
|
2375
|
+
// setup phase
|
|
2376
|
+
for (let p of plugins) {
|
|
2377
|
+
p.setup();
|
|
2378
|
+
}
|
|
2379
|
+
currentPluginManager = previousManager;
|
|
2380
|
+
if (!currentPluginManager) {
|
|
2381
|
+
this.status = 1 /* STATUS.MOUNTED */;
|
|
2382
|
+
}
|
|
2383
|
+
return plugins;
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
function plugin(pluginType) {
|
|
2387
|
+
// getCurrent will throw if we're not in a component
|
|
2388
|
+
const manager = currentPluginManager || getCurrent().pluginManager;
|
|
2389
|
+
let plugin = manager.getPluginById(pluginType.id);
|
|
2390
|
+
if (!plugin) {
|
|
2391
|
+
if (manager === currentPluginManager) {
|
|
2392
|
+
manager.startPlugins([pluginType]);
|
|
2393
|
+
plugin = manager.getPluginById(pluginType.id);
|
|
2394
|
+
}
|
|
2395
|
+
else {
|
|
2396
|
+
throw new OwlError(`Unknown plugin "${pluginType.id}"`);
|
|
2397
|
+
}
|
|
2398
|
+
}
|
|
2399
|
+
return plugin;
|
|
2400
|
+
}
|
|
2401
|
+
|
|
2402
|
+
// Special key to subscribe to, to be notified of key creation/deletion
|
|
2403
|
+
const KEYCHANGES = Symbol("Key changes");
|
|
2404
|
+
const objectToString = Object.prototype.toString;
|
|
2405
|
+
const objectHasOwnProperty = Object.prototype.hasOwnProperty;
|
|
2406
|
+
// Use arrays because Array.includes is faster than Set.has for small arrays
|
|
2407
|
+
const SUPPORTED_RAW_TYPES = ["Object", "Array", "Set", "Map", "WeakMap"];
|
|
2408
|
+
const COLLECTION_RAW_TYPES = ["Set", "Map", "WeakMap"];
|
|
2409
|
+
/**
|
|
2410
|
+
* extract "RawType" from strings like "[object RawType]" => this lets us ignore
|
|
2411
|
+
* many native objects such as Promise (whose toString is [object Promise])
|
|
2412
|
+
* or Date ([object Date]), while also supporting collections without using
|
|
2413
|
+
* instanceof in a loop
|
|
2414
|
+
*
|
|
2415
|
+
* @param obj the object to check
|
|
2416
|
+
* @returns the raw type of the object
|
|
2417
|
+
*/
|
|
2418
|
+
function rawType(obj) {
|
|
2419
|
+
return objectToString.call(toRaw(obj)).slice(8, -1);
|
|
2420
|
+
}
|
|
2421
|
+
/**
|
|
2422
|
+
* Checks whether a given value can be made into a proxy object.
|
|
2423
|
+
*
|
|
2424
|
+
* @param value the value to check
|
|
2425
|
+
* @returns whether the value can be made proxy
|
|
2426
|
+
*/
|
|
2427
|
+
function canBeMadeReactive(value) {
|
|
2428
|
+
if (typeof value !== "object") {
|
|
2429
|
+
return false;
|
|
2430
|
+
}
|
|
2431
|
+
return SUPPORTED_RAW_TYPES.includes(rawType(value));
|
|
2432
|
+
}
|
|
2433
|
+
/**
|
|
2434
|
+
* Creates a proxy from the given object/callback if possible and returns it,
|
|
2435
|
+
* returns the original object otherwise.
|
|
2436
|
+
*
|
|
2437
|
+
* @param value the value make proxy
|
|
2438
|
+
* @returns a proxy for the given object when possible, the original otherwise
|
|
2439
|
+
*/
|
|
2440
|
+
function possiblyReactive(val) {
|
|
2441
|
+
return canBeMadeReactive(val) ? proxy(val) : val;
|
|
2442
|
+
}
|
|
2443
|
+
const skipped = new WeakSet();
|
|
2444
|
+
/**
|
|
2445
|
+
* Mark an object or array so that it is ignored by the reactivity system
|
|
2446
|
+
*
|
|
2447
|
+
* @param value the value to mark
|
|
2448
|
+
* @returns the object itself
|
|
2449
|
+
*/
|
|
2450
|
+
function markRaw(value) {
|
|
2451
|
+
skipped.add(value);
|
|
2452
|
+
return value;
|
|
2453
|
+
}
|
|
2454
|
+
/**
|
|
2455
|
+
* Given a proxy objet, return the raw (non proxy) underlying object
|
|
2456
|
+
*
|
|
2457
|
+
* @param value a proxy value
|
|
2458
|
+
* @returns the underlying value
|
|
2459
|
+
*/
|
|
2460
|
+
function toRaw(value) {
|
|
2461
|
+
return targets.has(value) ? targets.get(value) : value;
|
|
2462
|
+
}
|
|
2463
|
+
const targetToKeysToAtomItem = new WeakMap();
|
|
2464
|
+
function getTargetKeyAtom(target, key) {
|
|
2465
|
+
let keyToAtomItem = targetToKeysToAtomItem.get(target);
|
|
2466
|
+
if (!keyToAtomItem) {
|
|
2467
|
+
keyToAtomItem = new Map();
|
|
2468
|
+
targetToKeysToAtomItem.set(target, keyToAtomItem);
|
|
2469
|
+
}
|
|
2470
|
+
let atom = keyToAtomItem.get(key);
|
|
2471
|
+
if (!atom) {
|
|
2472
|
+
atom = {
|
|
2473
|
+
value: undefined,
|
|
2474
|
+
observers: new Set(),
|
|
2475
|
+
};
|
|
2476
|
+
keyToAtomItem.set(key, atom);
|
|
2477
|
+
}
|
|
2478
|
+
return atom;
|
|
2479
|
+
}
|
|
2480
|
+
/**
|
|
2481
|
+
* Observes a given key on a target with an callback. The callback will be
|
|
2482
|
+
* called when the given key changes on the target.
|
|
2483
|
+
*
|
|
2484
|
+
* @param target the target whose key should be observed
|
|
2485
|
+
* @param key the key to observe (or Symbol(KEYCHANGES) for key creation
|
|
2486
|
+
* or deletion)
|
|
2487
|
+
* @param callback the function to call when the key changes
|
|
2488
|
+
*/
|
|
2489
|
+
function onReadTargetKey(target, key) {
|
|
2490
|
+
onReadAtom(getTargetKeyAtom(target, key));
|
|
2491
|
+
}
|
|
2492
|
+
/**
|
|
2493
|
+
* Notify Reactives that are observing a given target that a key has changed on
|
|
2494
|
+
* the target.
|
|
2495
|
+
*
|
|
2496
|
+
* @param target target whose Reactives should be notified that the target was
|
|
2497
|
+
* changed.
|
|
2498
|
+
* @param key the key that changed (or Symbol `KEYCHANGES` if a key was created
|
|
2499
|
+
* or deleted)
|
|
2500
|
+
*/
|
|
2501
|
+
function onWriteTargetKey(target, key) {
|
|
2502
|
+
const keyToAtomItem = targetToKeysToAtomItem.get(target);
|
|
2503
|
+
if (!keyToAtomItem) {
|
|
2504
|
+
return;
|
|
2505
|
+
}
|
|
2506
|
+
const atom = keyToAtomItem.get(key);
|
|
2507
|
+
if (!atom) {
|
|
2508
|
+
return;
|
|
2509
|
+
}
|
|
2510
|
+
onWriteAtom(atom);
|
|
2511
|
+
}
|
|
2512
|
+
// Maps proxy objects to the underlying target
|
|
2513
|
+
const targets = new WeakMap();
|
|
2514
|
+
const proxyCache = new WeakMap();
|
|
2515
|
+
/**
|
|
2516
|
+
* Creates a reactive proxy for an object. Reading data on the proxy object
|
|
2517
|
+
* subscribes to changes to the data. Writing data on the object will cause the
|
|
2518
|
+
* notify callback to be called if there are suscriptions to that data. Nested
|
|
2519
|
+
* objects and arrays are automatically made reactive as well.
|
|
2520
|
+
*
|
|
2521
|
+
* Whenever you are notified of a change, all subscriptions are cleared, and if
|
|
2522
|
+
* you would like to be notified of any further changes, you should go read
|
|
2523
|
+
* the underlying data again. We assume that if you don't go read it again after
|
|
2524
|
+
* being notified, it means that you are no longer interested in that data.
|
|
2525
|
+
*
|
|
2526
|
+
* Subscriptions:
|
|
2527
|
+
* + Reading a property on an object will subscribe you to changes in the value
|
|
2528
|
+
* of that property.
|
|
2529
|
+
* + Accessing an object's keys (eg with Object.keys or with `for..in`) will
|
|
2530
|
+
* subscribe you to the creation/deletion of keys. Checking the presence of a
|
|
2531
|
+
* key on the object with 'in' has the same effect.
|
|
2532
|
+
* - getOwnPropertyDescriptor does not currently subscribe you to the property.
|
|
2533
|
+
* This is a choice that was made because changing a key's value will trigger
|
|
2534
|
+
* this trap and we do not want to subscribe by writes. This also means that
|
|
2535
|
+
* Object.hasOwnProperty doesn't subscribe as it goes through this trap.
|
|
2536
|
+
*
|
|
2537
|
+
* @param target the object for which to create a proxy proxy
|
|
2538
|
+
* @param callback the function to call when an observed property of the
|
|
2539
|
+
* proxy has changed
|
|
2540
|
+
* @returns a proxy that tracks changes to it
|
|
2541
|
+
*/
|
|
2542
|
+
function proxy(target) {
|
|
2543
|
+
if (!canBeMadeReactive(target)) {
|
|
2544
|
+
throw new OwlError(`Cannot make the given value reactive`);
|
|
2545
|
+
}
|
|
2546
|
+
if (skipped.has(target)) {
|
|
2547
|
+
return target;
|
|
2548
|
+
}
|
|
2549
|
+
if (targets.has(target)) {
|
|
2550
|
+
// target is reactive, create a reactive on the underlying object instead
|
|
2551
|
+
return target;
|
|
2552
|
+
}
|
|
2553
|
+
const reactive = proxyCache.get(target);
|
|
2554
|
+
if (reactive)
|
|
2555
|
+
return reactive;
|
|
2556
|
+
const targetRawType = rawType(target);
|
|
2557
|
+
const handler = COLLECTION_RAW_TYPES.includes(targetRawType)
|
|
2558
|
+
? collectionsProxyHandler(target, targetRawType)
|
|
2559
|
+
: basicProxyHandler();
|
|
2560
|
+
const proxy = new Proxy(target, handler);
|
|
2561
|
+
proxyCache.set(target, proxy);
|
|
2562
|
+
targets.set(proxy, target);
|
|
2563
|
+
return proxy;
|
|
2564
|
+
}
|
|
2565
|
+
/**
|
|
2566
|
+
* Creates a basic proxy handler for regular objects and arrays.
|
|
2567
|
+
*
|
|
2568
|
+
* @param callback @see proxy
|
|
2569
|
+
* @returns a proxy handler object
|
|
2570
|
+
*/
|
|
2571
|
+
function basicProxyHandler() {
|
|
2572
|
+
return {
|
|
2573
|
+
get(target, key, receiver) {
|
|
2574
|
+
// non-writable non-configurable properties cannot be made proxy
|
|
2575
|
+
const desc = Object.getOwnPropertyDescriptor(target, key);
|
|
2576
|
+
if (desc && !desc.writable && !desc.configurable) {
|
|
2577
|
+
return Reflect.get(target, key, receiver);
|
|
2578
|
+
}
|
|
2579
|
+
onReadTargetKey(target, key);
|
|
2580
|
+
return possiblyReactive(Reflect.get(target, key, receiver));
|
|
2581
|
+
},
|
|
2582
|
+
set(target, key, value, receiver) {
|
|
2583
|
+
const hadKey = objectHasOwnProperty.call(target, key);
|
|
2584
|
+
const originalValue = Reflect.get(target, key, receiver);
|
|
2585
|
+
const ret = Reflect.set(target, key, toRaw(value), receiver);
|
|
2586
|
+
if (!hadKey && objectHasOwnProperty.call(target, key)) {
|
|
2587
|
+
onWriteTargetKey(target, KEYCHANGES);
|
|
2588
|
+
}
|
|
2589
|
+
// While Array length may trigger the set trap, it's not actually set by this
|
|
2590
|
+
// method but is updated behind the scenes, and the trap is not called with the
|
|
2591
|
+
// new value. We disable the "same-value-optimization" for it because of that.
|
|
2592
|
+
if (originalValue !== Reflect.get(target, key, receiver) ||
|
|
2593
|
+
(key === "length" && Array.isArray(target))) {
|
|
2594
|
+
onWriteTargetKey(target, key);
|
|
2595
|
+
}
|
|
2596
|
+
return ret;
|
|
2597
|
+
},
|
|
2598
|
+
deleteProperty(target, key) {
|
|
2599
|
+
const ret = Reflect.deleteProperty(target, key);
|
|
2600
|
+
// TODO: only notify when something was actually deleted
|
|
2601
|
+
onWriteTargetKey(target, KEYCHANGES);
|
|
2602
|
+
onWriteTargetKey(target, key);
|
|
2603
|
+
return ret;
|
|
2604
|
+
},
|
|
2605
|
+
ownKeys(target) {
|
|
2606
|
+
onReadTargetKey(target, KEYCHANGES);
|
|
2607
|
+
return Reflect.ownKeys(target);
|
|
2608
|
+
},
|
|
2609
|
+
has(target, key) {
|
|
2610
|
+
// TODO: this observes all key changes instead of only the presence of the argument key
|
|
2611
|
+
// observing the key itself would observe value changes instead of presence changes
|
|
2612
|
+
// so we may need a finer grained system to distinguish observing value vs presence.
|
|
2613
|
+
onReadTargetKey(target, KEYCHANGES);
|
|
2614
|
+
return Reflect.has(target, key);
|
|
2615
|
+
},
|
|
2616
|
+
};
|
|
2617
|
+
}
|
|
2618
|
+
/**
|
|
2619
|
+
* Creates a function that will observe the key that is passed to it when called
|
|
2620
|
+
* and delegates to the underlying method.
|
|
2621
|
+
*
|
|
2622
|
+
* @param methodName name of the method to delegate to
|
|
2623
|
+
* @param target @see proxy
|
|
2624
|
+
* @param callback @see proxy
|
|
2625
|
+
*/
|
|
2626
|
+
function makeKeyObserver(methodName, target) {
|
|
2627
|
+
return (key) => {
|
|
2628
|
+
key = toRaw(key);
|
|
2629
|
+
onReadTargetKey(target, key);
|
|
2630
|
+
return possiblyReactive(target[methodName](key));
|
|
2631
|
+
};
|
|
2632
|
+
}
|
|
2633
|
+
/**
|
|
2634
|
+
* Creates an iterable that will delegate to the underlying iteration method and
|
|
2635
|
+
* observe keys as necessary.
|
|
2636
|
+
*
|
|
2637
|
+
* @param methodName name of the method to delegate to
|
|
2638
|
+
* @param target @see proxy
|
|
2639
|
+
* @param callback @see proxy
|
|
2640
|
+
*/
|
|
2641
|
+
function makeIteratorObserver(methodName, target) {
|
|
2642
|
+
return function* () {
|
|
2643
|
+
onReadTargetKey(target, KEYCHANGES);
|
|
2644
|
+
const keys = target.keys();
|
|
2645
|
+
for (const item of target[methodName]()) {
|
|
2646
|
+
const key = keys.next().value;
|
|
2647
|
+
onReadTargetKey(target, key);
|
|
2648
|
+
yield possiblyReactive(item);
|
|
2649
|
+
}
|
|
2650
|
+
};
|
|
2651
|
+
}
|
|
2652
|
+
/**
|
|
2653
|
+
* Creates a forEach function that will delegate to forEach on the underlying
|
|
2654
|
+
* collection while observing key changes, and keys as they're iterated over,
|
|
2655
|
+
* and making the passed keys/values proxy.
|
|
2656
|
+
*
|
|
2657
|
+
* @param target @see proxy
|
|
2658
|
+
* @param callback @see proxy
|
|
2659
|
+
*/
|
|
2660
|
+
function makeForEachObserver(target) {
|
|
2661
|
+
return function forEach(forEachCb, thisArg) {
|
|
2662
|
+
onReadTargetKey(target, KEYCHANGES);
|
|
2663
|
+
target.forEach(function (val, key, targetObj) {
|
|
2664
|
+
onReadTargetKey(target, key);
|
|
2665
|
+
forEachCb.call(thisArg, possiblyReactive(val), possiblyReactive(key), possiblyReactive(targetObj));
|
|
2666
|
+
}, thisArg);
|
|
2667
|
+
};
|
|
2668
|
+
}
|
|
2669
|
+
/**
|
|
2670
|
+
* Creates a function that will delegate to an underlying method, and check if
|
|
2671
|
+
* that method has modified the presence or value of a key, and notify the
|
|
2672
|
+
* proxys appropriately.
|
|
2673
|
+
*
|
|
2674
|
+
* @param setterName name of the method to delegate to
|
|
2675
|
+
* @param getterName name of the method which should be used to retrieve the
|
|
2676
|
+
* value before calling the delegate method for comparison purposes
|
|
2677
|
+
* @param target @see proxy
|
|
2678
|
+
*/
|
|
2679
|
+
function delegateAndNotify(setterName, getterName, target) {
|
|
2680
|
+
return (key, value) => {
|
|
2681
|
+
key = toRaw(key);
|
|
2682
|
+
const hadKey = target.has(key);
|
|
2683
|
+
const originalValue = target[getterName](key);
|
|
2684
|
+
const ret = target[setterName](key, value);
|
|
2685
|
+
const hasKey = target.has(key);
|
|
2686
|
+
if (hadKey !== hasKey) {
|
|
2687
|
+
onWriteTargetKey(target, KEYCHANGES);
|
|
2688
|
+
}
|
|
2689
|
+
if (originalValue !== target[getterName](key)) {
|
|
2690
|
+
onWriteTargetKey(target, key);
|
|
2691
|
+
}
|
|
2692
|
+
return ret;
|
|
2693
|
+
};
|
|
2694
|
+
}
|
|
2695
|
+
/**
|
|
2696
|
+
* Creates a function that will clear the underlying collection and notify that
|
|
2697
|
+
* the keys of the collection have changed.
|
|
2698
|
+
*
|
|
2699
|
+
* @param target @see proxy
|
|
2700
|
+
*/
|
|
2701
|
+
function makeClearNotifier(target) {
|
|
2702
|
+
return () => {
|
|
2703
|
+
const allKeys = [...target.keys()];
|
|
2704
|
+
target.clear();
|
|
2705
|
+
onWriteTargetKey(target, KEYCHANGES);
|
|
2706
|
+
for (const key of allKeys) {
|
|
2707
|
+
onWriteTargetKey(target, key);
|
|
2708
|
+
}
|
|
2709
|
+
};
|
|
2710
|
+
}
|
|
2711
|
+
/**
|
|
2712
|
+
* Maps raw type of an object to an object containing functions that can be used
|
|
2713
|
+
* to build an appropritate proxy handler for that raw type. Eg: when making a
|
|
2714
|
+
* proxy set, calling the has method should mark the key that is being
|
|
2715
|
+
* retrieved as observed, and calling the add or delete method should notify the
|
|
2716
|
+
* proxys that the key which is being added or deleted has been modified.
|
|
2717
|
+
*/
|
|
2718
|
+
const rawTypeToFuncHandlers = {
|
|
2719
|
+
Set: (target) => ({
|
|
2720
|
+
has: makeKeyObserver("has", target),
|
|
2721
|
+
add: delegateAndNotify("add", "has", target),
|
|
2722
|
+
delete: delegateAndNotify("delete", "has", target),
|
|
2723
|
+
keys: makeIteratorObserver("keys", target),
|
|
2724
|
+
values: makeIteratorObserver("values", target),
|
|
2725
|
+
entries: makeIteratorObserver("entries", target),
|
|
2726
|
+
[Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target),
|
|
2727
|
+
forEach: makeForEachObserver(target),
|
|
2728
|
+
clear: makeClearNotifier(target),
|
|
2729
|
+
get size() {
|
|
2730
|
+
onReadTargetKey(target, KEYCHANGES);
|
|
2731
|
+
return target.size;
|
|
2732
|
+
},
|
|
2733
|
+
}),
|
|
2734
|
+
Map: (target) => ({
|
|
2735
|
+
has: makeKeyObserver("has", target),
|
|
2736
|
+
get: makeKeyObserver("get", target),
|
|
2737
|
+
set: delegateAndNotify("set", "get", target),
|
|
2738
|
+
delete: delegateAndNotify("delete", "has", target),
|
|
2739
|
+
keys: makeIteratorObserver("keys", target),
|
|
2740
|
+
values: makeIteratorObserver("values", target),
|
|
2741
|
+
entries: makeIteratorObserver("entries", target),
|
|
2742
|
+
[Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target),
|
|
2743
|
+
forEach: makeForEachObserver(target),
|
|
2744
|
+
clear: makeClearNotifier(target),
|
|
2745
|
+
get size() {
|
|
2746
|
+
onReadTargetKey(target, KEYCHANGES);
|
|
2747
|
+
return target.size;
|
|
2748
|
+
},
|
|
2749
|
+
}),
|
|
2750
|
+
WeakMap: (target) => ({
|
|
2751
|
+
has: makeKeyObserver("has", target),
|
|
2752
|
+
get: makeKeyObserver("get", target),
|
|
2753
|
+
set: delegateAndNotify("set", "get", target),
|
|
2754
|
+
delete: delegateAndNotify("delete", "has", target),
|
|
2755
|
+
}),
|
|
2756
|
+
};
|
|
2757
|
+
/**
|
|
2758
|
+
* Creates a proxy handler for collections (Set/Map/WeakMap)
|
|
2759
|
+
*
|
|
2760
|
+
* @param callback @see proxy
|
|
2761
|
+
* @param target @see proxy
|
|
2762
|
+
* @returns a proxy handler object
|
|
2763
|
+
*/
|
|
2764
|
+
function collectionsProxyHandler(target, targetRawType) {
|
|
2765
|
+
// TODO: if performance is an issue we can create the special handlers lazily when each
|
|
2766
|
+
// property is read.
|
|
2767
|
+
const specialHandlers = rawTypeToFuncHandlers[targetRawType](target);
|
|
2768
|
+
return Object.assign(basicProxyHandler(), {
|
|
2769
|
+
// FIXME: probably broken when part of prototype chain since we ignore the receiver
|
|
2770
|
+
get(target, key) {
|
|
2771
|
+
if (objectHasOwnProperty.call(specialHandlers, key)) {
|
|
2772
|
+
return specialHandlers[key];
|
|
2773
|
+
}
|
|
2774
|
+
onReadTargetKey(target, key);
|
|
2775
|
+
return possiblyReactive(target[key]);
|
|
2776
|
+
},
|
|
2777
|
+
});
|
|
2778
|
+
}
|
|
2779
|
+
|
|
2780
|
+
// -----------------------------------------------------------------------------
|
|
2781
|
+
// Scheduler
|
|
2782
|
+
// -----------------------------------------------------------------------------
|
|
2783
|
+
class Scheduler {
|
|
2784
|
+
constructor() {
|
|
2785
|
+
this.tasks = new Set();
|
|
2786
|
+
this.frame = 0;
|
|
2787
|
+
this.delayedRenders = [];
|
|
2788
|
+
this.cancelledNodes = new Set();
|
|
2789
|
+
this.processing = false;
|
|
2790
|
+
this.requestAnimationFrame = Scheduler.requestAnimationFrame;
|
|
2791
|
+
}
|
|
2792
|
+
addFiber(fiber) {
|
|
2793
|
+
this.tasks.add(fiber.root);
|
|
2794
|
+
}
|
|
2795
|
+
scheduleDestroy(node) {
|
|
2796
|
+
this.cancelledNodes.add(node);
|
|
2797
|
+
if (this.frame === 0) {
|
|
2798
|
+
this.frame = this.requestAnimationFrame(() => this.processTasks());
|
|
2799
|
+
}
|
|
2800
|
+
}
|
|
2801
|
+
/**
|
|
2802
|
+
* Process all current tasks. This only applies to the fibers that are ready.
|
|
2803
|
+
* Other tasks are left unchanged.
|
|
2804
|
+
*/
|
|
2805
|
+
flush() {
|
|
2806
|
+
if (this.delayedRenders.length) {
|
|
2807
|
+
let renders = this.delayedRenders;
|
|
2808
|
+
this.delayedRenders = [];
|
|
2809
|
+
for (let f of renders) {
|
|
2810
|
+
if (f.root && f.node.status !== 3 /* STATUS.DESTROYED */ && f.node.fiber === f) {
|
|
2811
|
+
f.render();
|
|
2812
|
+
}
|
|
2813
|
+
}
|
|
2814
|
+
}
|
|
2815
|
+
if (this.frame === 0) {
|
|
2816
|
+
this.frame = this.requestAnimationFrame(() => this.processTasks());
|
|
2817
|
+
}
|
|
2818
|
+
}
|
|
2819
|
+
processTasks() {
|
|
2820
|
+
if (this.processing) {
|
|
2821
|
+
return;
|
|
2822
|
+
}
|
|
2823
|
+
this.processing = true;
|
|
2824
|
+
this.frame = 0;
|
|
2825
|
+
for (let node of this.cancelledNodes) {
|
|
2826
|
+
node._destroy();
|
|
2827
|
+
}
|
|
2828
|
+
this.cancelledNodes.clear();
|
|
2829
|
+
for (let task of this.tasks) {
|
|
2830
|
+
this.processFiber(task);
|
|
2831
|
+
}
|
|
2832
|
+
for (let task of this.tasks) {
|
|
2833
|
+
if (task.node.status === 3 /* STATUS.DESTROYED */) {
|
|
2834
|
+
this.tasks.delete(task);
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2837
|
+
this.processing = false;
|
|
2838
|
+
}
|
|
2839
|
+
processFiber(fiber) {
|
|
2840
|
+
if (fiber.root !== fiber) {
|
|
2841
|
+
this.tasks.delete(fiber);
|
|
2842
|
+
return;
|
|
2843
|
+
}
|
|
2844
|
+
const hasError = fibersInError.has(fiber);
|
|
2845
|
+
if (hasError && fiber.counter !== 0) {
|
|
2846
|
+
this.tasks.delete(fiber);
|
|
2847
|
+
return;
|
|
2848
|
+
}
|
|
2849
|
+
if (fiber.node.status === 3 /* STATUS.DESTROYED */) {
|
|
2850
|
+
this.tasks.delete(fiber);
|
|
2851
|
+
return;
|
|
2852
|
+
}
|
|
2853
|
+
if (fiber.counter === 0) {
|
|
2854
|
+
if (!hasError) {
|
|
2855
|
+
fiber.complete();
|
|
2856
|
+
}
|
|
2857
|
+
// at this point, the fiber should have been applied to the DOM, so we can
|
|
2858
|
+
// remove it from the task list. If it is not the case, it means that there
|
|
2859
|
+
// was an error and an error handler triggered a new rendering that recycled
|
|
2860
|
+
// the fiber, so in that case, we actually want to keep the fiber around,
|
|
2861
|
+
// otherwise it will just be ignored.
|
|
2862
|
+
if (fiber.appliedToDom) {
|
|
2863
|
+
this.tasks.delete(fiber);
|
|
2864
|
+
}
|
|
2865
|
+
}
|
|
2866
|
+
}
|
|
2867
|
+
}
|
|
2868
|
+
// capture the value of requestAnimationFrame as soon as possible, to avoid
|
|
2869
|
+
// interactions with other code, such as test frameworks that override them
|
|
2870
|
+
Scheduler.requestAnimationFrame = window.requestAnimationFrame.bind(window);
|
|
2871
|
+
|
|
2872
|
+
// -----------------------------------------------------------------------------
|
|
2873
|
+
// hooks
|
|
2874
|
+
// -----------------------------------------------------------------------------
|
|
2875
|
+
function decorate(node, f, hookName) {
|
|
2876
|
+
const result = f.bind(node.component);
|
|
2877
|
+
if (node.app.dev) {
|
|
2878
|
+
const suffix = f.name ? ` <${f.name}>` : "";
|
|
2879
|
+
Reflect.defineProperty(result, "name", {
|
|
2880
|
+
value: hookName + suffix,
|
|
2881
|
+
});
|
|
2882
|
+
}
|
|
2883
|
+
return result;
|
|
2884
|
+
}
|
|
2885
|
+
function onWillStart(fn) {
|
|
2886
|
+
const node = getCurrent();
|
|
2887
|
+
node.willStart.push(decorate(node, fn, "onWillStart"));
|
|
2888
|
+
}
|
|
2889
|
+
function onWillUpdateProps(fn) {
|
|
2890
|
+
const node = getCurrent();
|
|
2891
|
+
node.willUpdateProps.push(decorate(node, fn, "onWillUpdateProps"));
|
|
2892
|
+
}
|
|
2893
|
+
function onMounted(fn) {
|
|
2894
|
+
const node = getCurrent();
|
|
2895
|
+
node.mounted.push(decorate(node, fn, "onMounted"));
|
|
2896
|
+
}
|
|
2897
|
+
function onWillPatch(fn) {
|
|
2898
|
+
const node = getCurrent();
|
|
2899
|
+
node.willPatch.unshift(decorate(node, fn, "onWillPatch"));
|
|
2900
|
+
}
|
|
2901
|
+
function onPatched(fn) {
|
|
2902
|
+
const node = getCurrent();
|
|
2903
|
+
node.patched.push(decorate(node, fn, "onPatched"));
|
|
2904
|
+
}
|
|
2905
|
+
function onWillUnmount(fn) {
|
|
2906
|
+
const node = getCurrent();
|
|
2907
|
+
node.willUnmount.unshift(decorate(node, fn, "onWillUnmount"));
|
|
2908
|
+
}
|
|
2909
|
+
function onWillDestroy(fn) {
|
|
2910
|
+
const pm = _getCurrentPluginManager();
|
|
2911
|
+
if (pm) {
|
|
2912
|
+
pm.onDestroyCb.push(fn);
|
|
2913
|
+
}
|
|
2914
|
+
else {
|
|
2915
|
+
const node = getCurrent();
|
|
2916
|
+
node.willDestroy.unshift(decorate(node, fn, "onWillDestroy"));
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2919
|
+
function onError(callback) {
|
|
2920
|
+
const node = getCurrent();
|
|
2921
|
+
let handlers = nodeErrorHandlers.get(node);
|
|
2922
|
+
if (!handlers) {
|
|
2923
|
+
handlers = [];
|
|
2924
|
+
nodeErrorHandlers.set(node, handlers);
|
|
2925
|
+
}
|
|
2926
|
+
handlers.push(callback.bind(node.component));
|
|
2927
|
+
}
|
|
2928
|
+
|
|
2929
|
+
// -----------------------------------------------------------------------------
|
|
2930
|
+
// helpers
|
|
2931
|
+
// -----------------------------------------------------------------------------
|
|
2932
|
+
const isUnionType = (t) => Array.isArray(t);
|
|
2933
|
+
const isBaseType = (t) => typeof t !== "object";
|
|
2934
|
+
const isValueType = (t) => typeof t === "object" && t && "value" in t;
|
|
2935
|
+
function isOptional(t) {
|
|
2936
|
+
return typeof t === "object" && "optional" in t ? t.optional || false : false;
|
|
2937
|
+
}
|
|
2938
|
+
function describeType(type) {
|
|
2939
|
+
return type === "*" || type === true ? "value" : type.name.toLowerCase();
|
|
2940
|
+
}
|
|
2941
|
+
function describe(info) {
|
|
2942
|
+
if (isBaseType(info)) {
|
|
2943
|
+
return describeType(info);
|
|
2944
|
+
}
|
|
2945
|
+
else if (isUnionType(info)) {
|
|
2946
|
+
return info.map(describe).join(" or ");
|
|
2947
|
+
}
|
|
2948
|
+
else if (isValueType(info)) {
|
|
2949
|
+
return String(info.value);
|
|
2950
|
+
}
|
|
2951
|
+
if ("element" in info) {
|
|
2952
|
+
return `list of ${describe({ type: info.element, optional: false })}s`;
|
|
2953
|
+
}
|
|
2954
|
+
if ("shape" in info) {
|
|
2955
|
+
return `object`;
|
|
2956
|
+
}
|
|
2957
|
+
return describe(info.type || "*");
|
|
2958
|
+
}
|
|
2959
|
+
function toSchema(spec) {
|
|
2960
|
+
return Object.fromEntries(spec.map((e) => e.endsWith("?") ? [e.slice(0, -1), { optional: true }] : [e, { type: "*", optional: false }]));
|
|
2961
|
+
}
|
|
2962
|
+
/**
|
|
2963
|
+
* Main validate function
|
|
2964
|
+
*/
|
|
2965
|
+
function validate(obj, spec) {
|
|
2966
|
+
let errors = validateSchema(obj, spec);
|
|
2967
|
+
if (errors.length) {
|
|
2968
|
+
throw new OwlError("Invalid object: " + errors.join(", "));
|
|
2969
|
+
}
|
|
2970
|
+
}
|
|
2971
|
+
/**
|
|
2972
|
+
* Helper validate function, to get the list of errors. useful if one want to
|
|
2973
|
+
* manipulate the errors without parsing an error object
|
|
2974
|
+
*/
|
|
2975
|
+
function validateSchema(obj, schema) {
|
|
2976
|
+
if (Array.isArray(schema)) {
|
|
2977
|
+
schema = toSchema(schema);
|
|
2978
|
+
}
|
|
2979
|
+
// obj = toRaw(obj);
|
|
2980
|
+
let errors = [];
|
|
2981
|
+
// check if each value in obj has correct shape
|
|
2982
|
+
for (let key in obj) {
|
|
2983
|
+
if (key in schema) {
|
|
2984
|
+
let result = validateType(key, obj[key], schema[key]);
|
|
2985
|
+
if (result) {
|
|
2986
|
+
errors.push(result);
|
|
2987
|
+
}
|
|
2988
|
+
}
|
|
2989
|
+
else if (!("*" in schema)) {
|
|
2990
|
+
errors.push(`unknown key '${key}'`);
|
|
2991
|
+
}
|
|
2992
|
+
}
|
|
2993
|
+
// check that all specified keys are defined in obj
|
|
2994
|
+
for (let key in schema) {
|
|
2995
|
+
const spec = schema[key];
|
|
2996
|
+
if (key !== "*" && !isOptional(spec) && !(key in obj)) {
|
|
2997
|
+
const isObj = typeof spec === "object" && !Array.isArray(spec);
|
|
2998
|
+
const isAny = spec === "*" || (isObj && "type" in spec ? spec.type === "*" : isObj);
|
|
2999
|
+
let detail = isAny ? "" : ` (should be a ${describe(spec)})`;
|
|
3000
|
+
errors.push(`'${key}' is missing${detail}`);
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
return errors;
|
|
3004
|
+
}
|
|
3005
|
+
function validateBaseType(key, value, type) {
|
|
3006
|
+
if (typeof type === "function") {
|
|
3007
|
+
if (typeof value === "object") {
|
|
3008
|
+
if (!(value instanceof type)) {
|
|
3009
|
+
return `'${key}' is not a ${describeType(type)}`;
|
|
3010
|
+
}
|
|
3011
|
+
}
|
|
3012
|
+
else if (typeof value !== type.name.toLowerCase()) {
|
|
3013
|
+
return `'${key}' is not a ${describeType(type)}`;
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
return null;
|
|
3017
|
+
}
|
|
3018
|
+
function validateArrayType(key, value, descr) {
|
|
3019
|
+
if (!Array.isArray(value)) {
|
|
3020
|
+
return `'${key}' is not a list of ${describe(descr)}s`;
|
|
3021
|
+
}
|
|
3022
|
+
for (let i = 0; i < value.length; i++) {
|
|
3023
|
+
const error = validateType(`${key}[${i}]`, value[i], descr);
|
|
3024
|
+
if (error) {
|
|
3025
|
+
return error;
|
|
3026
|
+
}
|
|
3027
|
+
}
|
|
3028
|
+
return null;
|
|
3029
|
+
}
|
|
3030
|
+
function validateType(key, value, descr) {
|
|
3031
|
+
if (value === undefined) {
|
|
3032
|
+
return isOptional(descr) ? null : `'${key}' is undefined (should be a ${describe(descr)})`;
|
|
3033
|
+
}
|
|
3034
|
+
else if (isBaseType(descr)) {
|
|
3035
|
+
return validateBaseType(key, value, descr);
|
|
3036
|
+
}
|
|
3037
|
+
else if (isValueType(descr)) {
|
|
3038
|
+
return value === descr.value ? null : `'${key}' is not equal to '${descr.value}'`;
|
|
3039
|
+
}
|
|
3040
|
+
else if (isUnionType(descr)) {
|
|
3041
|
+
let validDescr = descr.find((p) => !validateType(key, value, p));
|
|
3042
|
+
return validDescr ? null : `'${key}' is not a ${describe(descr)}`;
|
|
3043
|
+
}
|
|
3044
|
+
let result = null;
|
|
3045
|
+
if ("element" in descr) {
|
|
3046
|
+
result = validateArrayType(key, value, descr.element);
|
|
3047
|
+
}
|
|
3048
|
+
else if ("shape" in descr) {
|
|
3049
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
3050
|
+
result = `'${key}' is not an object`;
|
|
3051
|
+
}
|
|
3052
|
+
else {
|
|
3053
|
+
const errors = validateSchema(value, descr.shape);
|
|
3054
|
+
if (errors.length) {
|
|
3055
|
+
result = `'${key}' doesn't have the correct shape (${errors.join(", ")})`;
|
|
3056
|
+
}
|
|
3057
|
+
}
|
|
3058
|
+
}
|
|
3059
|
+
else if ("values" in descr) {
|
|
3060
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
3061
|
+
result = `'${key}' is not an object`;
|
|
3062
|
+
}
|
|
3063
|
+
else {
|
|
3064
|
+
const errors = Object.entries(value)
|
|
3065
|
+
.map(([key, value]) => validateType(key, value, descr.values))
|
|
3066
|
+
.filter(Boolean);
|
|
3067
|
+
if (errors.length) {
|
|
3068
|
+
result = `some of the values in '${key}' are invalid (${errors.join(", ")})`;
|
|
3069
|
+
}
|
|
3070
|
+
}
|
|
3071
|
+
}
|
|
3072
|
+
if ("type" in descr && !result) {
|
|
3073
|
+
result = validateType(key, value, descr.type);
|
|
3074
|
+
}
|
|
3075
|
+
if ("validate" in descr && !result) {
|
|
3076
|
+
result = !descr.validate(value) ? `'${key}' is not valid` : null;
|
|
3077
|
+
}
|
|
3078
|
+
return result;
|
|
3079
|
+
}
|
|
3080
|
+
|
|
3081
|
+
function validateProps(componentName, props, validation, keys) {
|
|
3082
|
+
const propsToValidate = Object.create(null);
|
|
3083
|
+
const errors = [];
|
|
3084
|
+
for (const key of keys) {
|
|
3085
|
+
if (key in props) {
|
|
3086
|
+
propsToValidate[key] = props[key];
|
|
3087
|
+
}
|
|
3088
|
+
if (propsToValidate[key] === undefined &&
|
|
3089
|
+
!Array.isArray(validation) &&
|
|
3090
|
+
validation[key].defaultValue !== undefined) {
|
|
3091
|
+
if (!validation[key].optional) {
|
|
3092
|
+
errors.push(`A default value cannot be defined for the mandatory prop '${key}'`);
|
|
3093
|
+
continue;
|
|
3094
|
+
}
|
|
3095
|
+
propsToValidate[key] = validation[key].defaultValue;
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
3098
|
+
errors.push(...validateSchema(propsToValidate, validation));
|
|
3099
|
+
if (errors.length) {
|
|
3100
|
+
throw new OwlError(`Invalid props for component '${componentName}': ` + errors.join(", "));
|
|
3101
|
+
// node.app.handleError({
|
|
3102
|
+
// error: new OwlError(`Invalid props for component '${componentName}': ` + errors.join(", ")),
|
|
3103
|
+
// node,
|
|
3104
|
+
// });
|
|
3105
|
+
}
|
|
3106
|
+
}
|
|
3107
|
+
function props(validation) {
|
|
3108
|
+
const node = getCurrent();
|
|
3109
|
+
const isSchemaValidated = validation && !Array.isArray(validation);
|
|
3110
|
+
function getProp(key) {
|
|
3111
|
+
if (isSchemaValidated && node.props[key] === undefined) {
|
|
3112
|
+
return validation[key].defaultValue;
|
|
3113
|
+
}
|
|
3114
|
+
return node.props[key];
|
|
3115
|
+
}
|
|
3116
|
+
const result = Object.create(null);
|
|
3117
|
+
function applyPropGetters(keys) {
|
|
3118
|
+
for (const key of keys) {
|
|
3119
|
+
Reflect.defineProperty(result, key, {
|
|
3120
|
+
enumerable: true,
|
|
3121
|
+
get: getProp.bind(null, key),
|
|
3122
|
+
});
|
|
3123
|
+
}
|
|
3124
|
+
}
|
|
3125
|
+
if (validation) {
|
|
3126
|
+
const keys = (isSchemaValidated ? Object.keys(validation) : validation).map((key) => key.endsWith("?") ? key.slice(0, -1) : key);
|
|
3127
|
+
applyPropGetters(keys);
|
|
3128
|
+
if (node.app.dev) {
|
|
3129
|
+
validateProps(node.name, node.props, validation, keys);
|
|
3130
|
+
node.willUpdateProps.push((np) => {
|
|
3131
|
+
validateProps(node.name, np, validation, keys);
|
|
3132
|
+
});
|
|
3133
|
+
}
|
|
3134
|
+
}
|
|
3135
|
+
else {
|
|
3136
|
+
applyPropGetters(Object.keys(node.props));
|
|
3137
|
+
node.willUpdateProps.push((np) => {
|
|
3138
|
+
for (let key in result) {
|
|
3139
|
+
Reflect.deleteProperty(result, key);
|
|
3140
|
+
}
|
|
3141
|
+
applyPropGetters(Object.keys(np));
|
|
3142
|
+
});
|
|
3143
|
+
}
|
|
3144
|
+
return result;
|
|
3145
|
+
}
|
|
3146
|
+
|
|
3147
|
+
const VText = text("").constructor;
|
|
3148
|
+
class VPortal extends VText {
|
|
3149
|
+
constructor(selector, content) {
|
|
3150
|
+
super("");
|
|
3151
|
+
this.target = null;
|
|
3152
|
+
this.selector = selector;
|
|
3153
|
+
this.content = content;
|
|
3154
|
+
}
|
|
3155
|
+
mount(parent, anchor) {
|
|
3156
|
+
super.mount(parent, anchor);
|
|
3157
|
+
this.target = document.querySelector(this.selector);
|
|
3158
|
+
if (this.target) {
|
|
3159
|
+
this.content.mount(this.target, null);
|
|
3160
|
+
}
|
|
3161
|
+
else {
|
|
3162
|
+
this.content.mount(parent, anchor);
|
|
3163
|
+
}
|
|
3164
|
+
}
|
|
3165
|
+
beforeRemove() {
|
|
3166
|
+
this.content.beforeRemove();
|
|
3167
|
+
}
|
|
3168
|
+
remove() {
|
|
3169
|
+
if (this.content) {
|
|
3170
|
+
super.remove();
|
|
3171
|
+
this.content.remove();
|
|
3172
|
+
this.content = null;
|
|
3173
|
+
}
|
|
3174
|
+
}
|
|
3175
|
+
patch(other) {
|
|
3176
|
+
super.patch(other);
|
|
3177
|
+
if (this.content) {
|
|
3178
|
+
this.content.patch(other.content, true);
|
|
3179
|
+
}
|
|
3180
|
+
else {
|
|
3181
|
+
this.content = other.content;
|
|
3182
|
+
this.content.mount(this.target, null);
|
|
3183
|
+
}
|
|
3184
|
+
}
|
|
3185
|
+
}
|
|
3186
|
+
/**
|
|
3187
|
+
* kind of similar to <t t-slot="default"/>, but it wraps it around a VPortal
|
|
3188
|
+
*/
|
|
3189
|
+
function portalTemplate(app, bdom, helpers) {
|
|
3190
|
+
let { callSlot } = helpers;
|
|
3191
|
+
return function template(ctx, node, key = "") {
|
|
3192
|
+
return new VPortal(ctx.this.props.target, callSlot(ctx, node, key, "default", false, null));
|
|
3193
|
+
};
|
|
3194
|
+
}
|
|
3195
|
+
class Portal extends Component {
|
|
3196
|
+
constructor() {
|
|
3197
|
+
super(...arguments);
|
|
3198
|
+
this.props = props({
|
|
3199
|
+
target: String,
|
|
3200
|
+
slots: true,
|
|
3201
|
+
});
|
|
3202
|
+
}
|
|
3203
|
+
setup() {
|
|
3204
|
+
const node = this.__owl__;
|
|
3205
|
+
onMounted(() => {
|
|
3206
|
+
const portal = node.bdom;
|
|
3207
|
+
if (!portal.target) {
|
|
3208
|
+
const target = document.querySelector(node.props.target);
|
|
3209
|
+
if (target) {
|
|
3210
|
+
portal.content.moveBeforeDOMNode(target.firstChild, target);
|
|
3211
|
+
}
|
|
3212
|
+
else {
|
|
3213
|
+
throw new OwlError("invalid portal target");
|
|
3214
|
+
}
|
|
3215
|
+
}
|
|
3216
|
+
});
|
|
3217
|
+
onWillUnmount(() => {
|
|
3218
|
+
const portal = node.bdom;
|
|
3219
|
+
portal.remove();
|
|
3220
|
+
});
|
|
3221
|
+
}
|
|
3222
|
+
}
|
|
3223
|
+
Portal.template = "__portal__";
|
|
3224
|
+
|
|
3225
|
+
const ObjectCreate = Object.create;
|
|
3226
|
+
/**
|
|
3227
|
+
* This file contains utility functions that will be injected in each template,
|
|
3228
|
+
* to perform various useful tasks in the compiled code.
|
|
3229
|
+
*/
|
|
3230
|
+
function withDefault(value, defaultValue) {
|
|
3231
|
+
return value === undefined || value === null || value === false ? defaultValue : value;
|
|
3232
|
+
}
|
|
3233
|
+
function callSlot(ctx, parent, key, name, dynamic, extra, defaultContent) {
|
|
3234
|
+
key = key + "__slot_" + name;
|
|
3235
|
+
const slots = ctx.__owl__.props.slots || {};
|
|
3236
|
+
const { __render, __ctx, __scope } = slots[name] || {};
|
|
3237
|
+
const slotScope = ObjectCreate(__ctx || {});
|
|
3238
|
+
if (__scope) {
|
|
3239
|
+
slotScope[__scope] = extra;
|
|
3240
|
+
}
|
|
3241
|
+
const slotBDom = __render ? __render(slotScope, parent, key) : null;
|
|
3242
|
+
if (defaultContent) {
|
|
3243
|
+
let child1 = undefined;
|
|
3244
|
+
let child2 = undefined;
|
|
3245
|
+
if (slotBDom) {
|
|
3246
|
+
child1 = dynamic ? toggler(name, slotBDom) : slotBDom;
|
|
3247
|
+
}
|
|
3248
|
+
else {
|
|
3249
|
+
child2 = defaultContent(ctx, parent, key);
|
|
3250
|
+
}
|
|
3251
|
+
return multi([child1, child2]);
|
|
3252
|
+
}
|
|
3253
|
+
return slotBDom || text("");
|
|
3254
|
+
}
|
|
3255
|
+
function capture(ctx) {
|
|
3256
|
+
const result = ObjectCreate(ctx);
|
|
3257
|
+
for (let k in ctx) {
|
|
3258
|
+
result[k] = ctx[k];
|
|
3259
|
+
}
|
|
3260
|
+
return result;
|
|
3261
|
+
}
|
|
3262
|
+
function withKey(elem, k) {
|
|
3263
|
+
elem.key = k;
|
|
3264
|
+
return elem;
|
|
3265
|
+
}
|
|
3266
|
+
function prepareList(collection) {
|
|
3267
|
+
let keys;
|
|
3268
|
+
let values;
|
|
3269
|
+
if (Array.isArray(collection)) {
|
|
3270
|
+
keys = collection;
|
|
3271
|
+
values = collection;
|
|
3272
|
+
}
|
|
3273
|
+
else if (collection instanceof Map) {
|
|
3274
|
+
keys = [...collection.keys()];
|
|
3275
|
+
values = [...collection.values()];
|
|
3276
|
+
}
|
|
3277
|
+
else if (Symbol.iterator in Object(collection)) {
|
|
3278
|
+
keys = [...collection];
|
|
3279
|
+
values = keys;
|
|
3280
|
+
}
|
|
3281
|
+
else if (collection && typeof collection === "object") {
|
|
3282
|
+
values = Object.values(collection);
|
|
3283
|
+
keys = Object.keys(collection);
|
|
3284
|
+
}
|
|
3285
|
+
else {
|
|
3286
|
+
throw new OwlError(`Invalid loop expression: "${collection}" is not iterable`);
|
|
3287
|
+
}
|
|
3288
|
+
const n = values.length;
|
|
3289
|
+
return [keys, values, n, new Array(n)];
|
|
3290
|
+
}
|
|
3291
|
+
const isBoundary = Symbol("isBoundary");
|
|
3292
|
+
function setContextValue(ctx, key, value) {
|
|
3293
|
+
const ctx0 = ctx;
|
|
3294
|
+
while (!ctx.hasOwnProperty(key) && !ctx.hasOwnProperty(isBoundary)) {
|
|
3295
|
+
const newCtx = ctx.__proto__;
|
|
3296
|
+
if (!newCtx) {
|
|
3297
|
+
ctx = ctx0;
|
|
3298
|
+
break;
|
|
3299
|
+
}
|
|
3300
|
+
ctx = newCtx;
|
|
3301
|
+
}
|
|
3302
|
+
ctx[key] = value;
|
|
3303
|
+
}
|
|
3304
|
+
function toNumber(val) {
|
|
3305
|
+
const n = parseFloat(val);
|
|
3306
|
+
return isNaN(n) ? val : n;
|
|
3307
|
+
}
|
|
3308
|
+
function shallowEqual(l1, l2) {
|
|
3309
|
+
for (let i = 0, l = l1.length; i < l; i++) {
|
|
3310
|
+
if (l1[i] !== l2[i]) {
|
|
3311
|
+
return false;
|
|
3312
|
+
}
|
|
3313
|
+
}
|
|
3314
|
+
return true;
|
|
3315
|
+
}
|
|
3316
|
+
class LazyValue {
|
|
3317
|
+
constructor(fn, ctx, component, node, key) {
|
|
3318
|
+
this.fn = fn;
|
|
3319
|
+
this.ctx = capture(ctx);
|
|
3320
|
+
this.component = component;
|
|
3321
|
+
this.node = node;
|
|
3322
|
+
this.key = key;
|
|
3323
|
+
}
|
|
3324
|
+
evaluate() {
|
|
3325
|
+
return this.fn.call(this.component, this.ctx, this.node, this.key);
|
|
3326
|
+
}
|
|
3327
|
+
toString() {
|
|
3328
|
+
return this.evaluate().toString();
|
|
3329
|
+
}
|
|
3330
|
+
}
|
|
3331
|
+
/*
|
|
3332
|
+
* Safely outputs `value` as a block depending on the nature of `value`
|
|
3333
|
+
*/
|
|
3334
|
+
function safeOutput(value, defaultValue) {
|
|
3335
|
+
if (value === undefined || value === null) {
|
|
3336
|
+
return defaultValue ? toggler("default", defaultValue) : toggler("undefined", text(""));
|
|
3337
|
+
}
|
|
3338
|
+
let safeKey;
|
|
3339
|
+
let block;
|
|
3340
|
+
if (value instanceof Markup) {
|
|
3341
|
+
safeKey = `string_safe`;
|
|
3342
|
+
block = html(value);
|
|
3343
|
+
}
|
|
3344
|
+
else if (value instanceof LazyValue) {
|
|
3345
|
+
safeKey = `lazy_value`;
|
|
3346
|
+
block = value.evaluate();
|
|
3347
|
+
}
|
|
3348
|
+
else {
|
|
3349
|
+
safeKey = "string_unsafe";
|
|
3350
|
+
block = text(value);
|
|
3351
|
+
}
|
|
3352
|
+
return toggler(safeKey, block);
|
|
3353
|
+
}
|
|
3354
|
+
function createRef(ref) {
|
|
3355
|
+
if (!ref) {
|
|
3356
|
+
throw new OwlError(`Ref is undefined or null`);
|
|
3357
|
+
}
|
|
3358
|
+
let add;
|
|
3359
|
+
let remove;
|
|
3360
|
+
if (ref.add && ref.remove) {
|
|
3361
|
+
add = ref.add.bind(ref);
|
|
3362
|
+
remove = ref.remove.bind(ref);
|
|
3363
|
+
}
|
|
3364
|
+
else if (ref.set) {
|
|
3365
|
+
add = ref.set.bind(ref);
|
|
3366
|
+
remove = () => ref.set(null);
|
|
3367
|
+
}
|
|
3368
|
+
else {
|
|
3369
|
+
throw new OwlError(`Ref should implement either a 'set' function or 'add' and 'remove' functions`);
|
|
3370
|
+
}
|
|
3371
|
+
return (el, previousEl) => {
|
|
3372
|
+
if (previousEl) {
|
|
3373
|
+
remove(previousEl);
|
|
3374
|
+
}
|
|
3375
|
+
if (el) {
|
|
3376
|
+
add(el);
|
|
3377
|
+
}
|
|
3378
|
+
};
|
|
3379
|
+
}
|
|
3380
|
+
function modelExpr(value) {
|
|
3381
|
+
if (!value.set || typeof value !== "function") {
|
|
3382
|
+
throw new OwlError(`Invalid t-model expression: expression should evaluate to a function with a 'set' method defined on it`);
|
|
3383
|
+
}
|
|
3384
|
+
return value;
|
|
3385
|
+
}
|
|
3386
|
+
const helpers = {
|
|
3387
|
+
withDefault,
|
|
3388
|
+
zero: Symbol("zero"),
|
|
3389
|
+
isBoundary,
|
|
3390
|
+
callSlot,
|
|
3391
|
+
capture,
|
|
3392
|
+
withKey,
|
|
3393
|
+
prepareList,
|
|
3394
|
+
setContextValue,
|
|
3395
|
+
shallowEqual,
|
|
3396
|
+
toNumber,
|
|
3397
|
+
LazyValue,
|
|
3398
|
+
safeOutput,
|
|
3399
|
+
createCatcher,
|
|
3400
|
+
markRaw,
|
|
3401
|
+
OwlError,
|
|
3402
|
+
createRef,
|
|
3403
|
+
modelExpr,
|
|
3404
|
+
};
|
|
3405
|
+
|
|
3406
|
+
/**
|
|
3407
|
+
* Parses an XML string into an XML document, throwing errors on parser errors
|
|
3408
|
+
* instead of returning an XML document containing the parseerror.
|
|
3409
|
+
*
|
|
3410
|
+
* @param xml the string to parse
|
|
3411
|
+
* @returns an XML document corresponding to the content of the string
|
|
3412
|
+
*/
|
|
3413
|
+
function parseXML(xml) {
|
|
3414
|
+
const parser = new DOMParser();
|
|
3415
|
+
const doc = parser.parseFromString(xml, "text/xml");
|
|
3416
|
+
if (doc.getElementsByTagName("parsererror").length) {
|
|
3417
|
+
let msg = "Invalid XML in template.";
|
|
3418
|
+
const parsererrorText = doc.getElementsByTagName("parsererror")[0].textContent;
|
|
3419
|
+
if (parsererrorText) {
|
|
3420
|
+
msg += "\nThe parser has produced the following error message:\n" + parsererrorText;
|
|
3421
|
+
const re = /\d+/g;
|
|
3422
|
+
const firstMatch = re.exec(parsererrorText);
|
|
3423
|
+
if (firstMatch) {
|
|
3424
|
+
const lineNumber = Number(firstMatch[0]);
|
|
3425
|
+
const line = xml.split("\n")[lineNumber - 1];
|
|
3426
|
+
const secondMatch = re.exec(parsererrorText);
|
|
3427
|
+
if (line && secondMatch) {
|
|
3428
|
+
const columnIndex = Number(secondMatch[0]) - 1;
|
|
3429
|
+
if (line[columnIndex]) {
|
|
3430
|
+
msg +=
|
|
3431
|
+
`\nThe error might be located at xml line ${lineNumber} column ${columnIndex}\n` +
|
|
3432
|
+
`${line}\n${"-".repeat(columnIndex - 1)}^`;
|
|
3433
|
+
}
|
|
3434
|
+
}
|
|
3435
|
+
}
|
|
3436
|
+
}
|
|
3437
|
+
throw new OwlError(msg);
|
|
3438
|
+
}
|
|
3439
|
+
return doc;
|
|
3440
|
+
}
|
|
3441
|
+
|
|
3442
|
+
const bdom = { text, createBlock, list, multi, html, toggler, comment };
|
|
3443
|
+
class TemplateSet {
|
|
3444
|
+
static registerTemplate(name, fn) {
|
|
3445
|
+
globalTemplates[name] = fn;
|
|
3446
|
+
}
|
|
3447
|
+
constructor(config = {}) {
|
|
3448
|
+
this.rawTemplates = Object.create(globalTemplates);
|
|
3449
|
+
this.templates = {};
|
|
3450
|
+
this.Portal = Portal;
|
|
3451
|
+
this.dev = config.dev || false;
|
|
3452
|
+
this.translateFn = config.translateFn;
|
|
3453
|
+
this.translatableAttributes = config.translatableAttributes;
|
|
3454
|
+
if (config.templates) {
|
|
3455
|
+
if (config.templates instanceof Document || typeof config.templates === "string") {
|
|
3456
|
+
this.addTemplates(config.templates);
|
|
3457
|
+
}
|
|
3458
|
+
else {
|
|
3459
|
+
for (const name in config.templates) {
|
|
3460
|
+
this.addTemplate(name, config.templates[name]);
|
|
3461
|
+
}
|
|
3462
|
+
}
|
|
3463
|
+
}
|
|
3464
|
+
this.getRawTemplate = config.getTemplate;
|
|
3465
|
+
this.customDirectives = config.customDirectives || {};
|
|
3466
|
+
this.runtimeUtils = { ...helpers, __globals__: config.globalValues || {} };
|
|
3467
|
+
this.hasGlobalValues = Boolean(config.globalValues && Object.keys(config.globalValues).length);
|
|
3468
|
+
}
|
|
3469
|
+
addTemplate(name, template) {
|
|
3470
|
+
if (name in this.rawTemplates) {
|
|
3471
|
+
// this check can be expensive, just silently ignore double definitions outside dev mode
|
|
3472
|
+
if (!this.dev) {
|
|
3473
|
+
return;
|
|
3474
|
+
}
|
|
3475
|
+
const rawTemplate = this.rawTemplates[name];
|
|
3476
|
+
const currentAsString = typeof rawTemplate === "string"
|
|
3477
|
+
? rawTemplate
|
|
3478
|
+
: rawTemplate instanceof Element
|
|
3479
|
+
? rawTemplate.outerHTML
|
|
3480
|
+
: rawTemplate.toString();
|
|
3481
|
+
const newAsString = typeof template === "string" ? template : template.outerHTML;
|
|
3482
|
+
if (currentAsString === newAsString) {
|
|
3483
|
+
return;
|
|
3484
|
+
}
|
|
3485
|
+
throw new OwlError(`Template ${name} already defined with different content`);
|
|
3486
|
+
}
|
|
3487
|
+
this.rawTemplates[name] = template;
|
|
3488
|
+
}
|
|
3489
|
+
addTemplates(xml) {
|
|
3490
|
+
if (!xml) {
|
|
3491
|
+
// empty string
|
|
3492
|
+
return;
|
|
3493
|
+
}
|
|
3494
|
+
xml = xml instanceof Document ? xml : parseXML(xml);
|
|
3495
|
+
for (const template of xml.querySelectorAll("[t-name]")) {
|
|
3496
|
+
const name = template.getAttribute("t-name");
|
|
3497
|
+
this.addTemplate(name, template);
|
|
3498
|
+
}
|
|
3499
|
+
}
|
|
3500
|
+
getTemplate(name) {
|
|
3501
|
+
var _a;
|
|
3502
|
+
if (!(name in this.templates)) {
|
|
3503
|
+
const rawTemplate = ((_a = this.getRawTemplate) === null || _a === void 0 ? void 0 : _a.call(this, name)) || this.rawTemplates[name];
|
|
3504
|
+
if (rawTemplate === undefined) {
|
|
3505
|
+
let extraInfo = "";
|
|
3506
|
+
try {
|
|
3507
|
+
const componentName = getCurrent().component.constructor.name;
|
|
3508
|
+
extraInfo = ` (for component "${componentName}")`;
|
|
3509
|
+
}
|
|
3510
|
+
catch { }
|
|
3511
|
+
throw new OwlError(`Missing template: "${name}"${extraInfo}`);
|
|
3512
|
+
}
|
|
3513
|
+
const isFn = typeof rawTemplate === "function" && !(rawTemplate instanceof Element);
|
|
3514
|
+
const templateFn = isFn ? rawTemplate : this._compileTemplate(name, rawTemplate);
|
|
3515
|
+
// first add a function to lazily get the template, in case there is a
|
|
3516
|
+
// recursive call to the template name
|
|
3517
|
+
const templates = this.templates;
|
|
3518
|
+
this.templates[name] = function (context, parent) {
|
|
3519
|
+
return templates[name].call(this, context, parent);
|
|
3520
|
+
};
|
|
3521
|
+
const template = templateFn(this, bdom, this.runtimeUtils);
|
|
3522
|
+
this.templates[name] = template;
|
|
3523
|
+
}
|
|
3524
|
+
return this.templates[name];
|
|
3525
|
+
}
|
|
3526
|
+
_compileTemplate(name, template) {
|
|
3527
|
+
throw new OwlError(`Unable to compile a template. Please use owl full build instead`);
|
|
3528
|
+
}
|
|
3529
|
+
callTemplate(owner, subTemplate, ctx, parent, key) {
|
|
3530
|
+
const template = this.getTemplate(subTemplate);
|
|
3531
|
+
return toggler(subTemplate, template.call(owner, ctx, parent, key + subTemplate));
|
|
3532
|
+
}
|
|
3533
|
+
}
|
|
3534
|
+
// -----------------------------------------------------------------------------
|
|
3535
|
+
// xml tag helper
|
|
3536
|
+
// -----------------------------------------------------------------------------
|
|
3537
|
+
const globalTemplates = {};
|
|
3538
|
+
function xml(...args) {
|
|
3539
|
+
const name = `__template__${xml.nextId++}`;
|
|
3540
|
+
const value = String.raw(...args);
|
|
3541
|
+
globalTemplates[name] = value;
|
|
3542
|
+
return name;
|
|
3543
|
+
}
|
|
3544
|
+
xml.nextId = 1;
|
|
3545
|
+
TemplateSet.registerTemplate("__portal__", portalTemplate);
|
|
3546
|
+
|
|
3547
|
+
let hasBeenLogged = false;
|
|
3548
|
+
const apps = new Set();
|
|
3549
|
+
window.__OWL_DEVTOOLS__ || (window.__OWL_DEVTOOLS__ = { apps, Fiber, RootFiber, toRaw, proxy });
|
|
3550
|
+
class App extends TemplateSet {
|
|
3551
|
+
constructor(config = {}) {
|
|
3552
|
+
super(config);
|
|
3553
|
+
this.scheduler = new Scheduler();
|
|
3554
|
+
this.roots = new Set();
|
|
3555
|
+
this.name = config.name || "";
|
|
3556
|
+
apps.add(this);
|
|
3557
|
+
this.pluginManager = config.pluginManager || new PluginManager(null);
|
|
3558
|
+
if (config.test) {
|
|
3559
|
+
this.dev = true;
|
|
3560
|
+
}
|
|
3561
|
+
if (this.dev && !config.test && !hasBeenLogged) {
|
|
3562
|
+
console.info(`Owl is running in 'dev' mode.`);
|
|
3563
|
+
hasBeenLogged = true;
|
|
3564
|
+
}
|
|
3565
|
+
}
|
|
3566
|
+
createRoot(Root, config = {}) {
|
|
3567
|
+
const props = config.props || {};
|
|
3568
|
+
let resolve;
|
|
3569
|
+
let reject;
|
|
3570
|
+
const promise = new Promise((res, rej) => {
|
|
3571
|
+
resolve = res;
|
|
3572
|
+
reject = rej;
|
|
3573
|
+
});
|
|
3574
|
+
const restore = saveCurrent();
|
|
3575
|
+
let node;
|
|
3576
|
+
let error = null;
|
|
3577
|
+
try {
|
|
3578
|
+
node = this.makeNode(Root, props);
|
|
3579
|
+
}
|
|
3580
|
+
catch (e) {
|
|
3581
|
+
error = e;
|
|
3582
|
+
reject(e);
|
|
3583
|
+
}
|
|
3584
|
+
finally {
|
|
3585
|
+
restore();
|
|
3586
|
+
}
|
|
3587
|
+
const root = {
|
|
3588
|
+
node: node,
|
|
3589
|
+
promise,
|
|
3590
|
+
mount: (target, options) => {
|
|
3591
|
+
if (error) {
|
|
3592
|
+
return promise;
|
|
3593
|
+
}
|
|
3594
|
+
App.validateTarget(target);
|
|
3595
|
+
this.mountNode(node, target, resolve, reject, options);
|
|
3596
|
+
return promise;
|
|
3597
|
+
},
|
|
3598
|
+
destroy: () => {
|
|
3599
|
+
this.roots.delete(root);
|
|
3600
|
+
node.destroy();
|
|
3601
|
+
this.scheduler.processTasks();
|
|
3602
|
+
},
|
|
3603
|
+
};
|
|
3604
|
+
this.roots.add(root);
|
|
3605
|
+
return root;
|
|
3606
|
+
}
|
|
3607
|
+
makeNode(Component, props) {
|
|
3608
|
+
return new ComponentNode(Component, props, this, null, null);
|
|
3609
|
+
}
|
|
3610
|
+
mountNode(node, target, resolve, reject, options) {
|
|
3611
|
+
// Manually add the last resort error handler on the node
|
|
3612
|
+
let handlers = nodeErrorHandlers.get(node);
|
|
3613
|
+
if (!handlers) {
|
|
3614
|
+
handlers = [];
|
|
3615
|
+
nodeErrorHandlers.set(node, handlers);
|
|
3616
|
+
}
|
|
3617
|
+
handlers.unshift((e, finalize) => {
|
|
3618
|
+
const finalError = finalize();
|
|
3619
|
+
reject(finalError);
|
|
3620
|
+
});
|
|
3621
|
+
// manually set a onMounted callback.
|
|
3622
|
+
// that way, we are independant from the current node.
|
|
3623
|
+
node.mounted.push(() => {
|
|
3624
|
+
resolve(node.component);
|
|
3625
|
+
handlers.shift();
|
|
3626
|
+
});
|
|
3627
|
+
node.mountComponent(target, options);
|
|
3628
|
+
}
|
|
3629
|
+
destroy() {
|
|
3630
|
+
for (let root of this.roots) {
|
|
3631
|
+
root.destroy();
|
|
3632
|
+
}
|
|
3633
|
+
this.scheduler.processTasks();
|
|
3634
|
+
apps.delete(this);
|
|
3635
|
+
}
|
|
3636
|
+
createComponent(name, isStatic, hasSlotsProp, hasDynamicPropList, propList) {
|
|
3637
|
+
const isDynamic = !isStatic;
|
|
3638
|
+
let arePropsDifferent;
|
|
3639
|
+
const hasNoProp = propList.length === 0;
|
|
3640
|
+
if (hasSlotsProp) {
|
|
3641
|
+
arePropsDifferent = (_1, _2) => true;
|
|
3642
|
+
}
|
|
3643
|
+
else if (hasDynamicPropList) {
|
|
3644
|
+
arePropsDifferent = function (props1, props2) {
|
|
3645
|
+
for (let k in props1) {
|
|
3646
|
+
if (props1[k] !== props2[k]) {
|
|
3647
|
+
return true;
|
|
3648
|
+
}
|
|
3649
|
+
}
|
|
3650
|
+
return Object.keys(props1).length !== Object.keys(props2).length;
|
|
3651
|
+
};
|
|
3652
|
+
}
|
|
3653
|
+
else if (hasNoProp) {
|
|
3654
|
+
arePropsDifferent = (_1, _2) => false;
|
|
3655
|
+
}
|
|
3656
|
+
else {
|
|
3657
|
+
arePropsDifferent = function (props1, props2) {
|
|
3658
|
+
for (let p of propList) {
|
|
3659
|
+
if (props1[p] !== props2[p]) {
|
|
3660
|
+
return true;
|
|
3661
|
+
}
|
|
3662
|
+
}
|
|
3663
|
+
return false;
|
|
3664
|
+
};
|
|
3665
|
+
}
|
|
3666
|
+
const updateAndRender = ComponentNode.prototype.updateAndRender;
|
|
3667
|
+
const initiateRender = ComponentNode.prototype.initiateRender;
|
|
3668
|
+
return (props, key, ctx, parent, C) => {
|
|
3669
|
+
let children = ctx.children;
|
|
3670
|
+
let node = children[key];
|
|
3671
|
+
if (isDynamic && node && node.component.constructor !== C) {
|
|
3672
|
+
node = undefined;
|
|
3673
|
+
}
|
|
3674
|
+
const parentFiber = ctx.fiber;
|
|
3675
|
+
if (node) {
|
|
3676
|
+
if (arePropsDifferent(node.props, props) || parentFiber.deep || node.forceNextRender) {
|
|
3677
|
+
node.forceNextRender = false;
|
|
3678
|
+
updateAndRender.call(node, props, parentFiber);
|
|
3679
|
+
}
|
|
3680
|
+
}
|
|
3681
|
+
else {
|
|
3682
|
+
// new component
|
|
3683
|
+
if (isStatic) {
|
|
3684
|
+
const components = parent.constructor.components;
|
|
3685
|
+
if (!components) {
|
|
3686
|
+
throw new OwlError(`Cannot find the definition of component "${name}", missing static components key in parent`);
|
|
3687
|
+
}
|
|
3688
|
+
C = components[name];
|
|
3689
|
+
if (!C) {
|
|
3690
|
+
throw new OwlError(`Cannot find the definition of component "${name}"`);
|
|
3691
|
+
}
|
|
3692
|
+
else if (!(C.prototype instanceof Component)) {
|
|
3693
|
+
throw new OwlError(`"${name}" is not a Component. It must inherit from the Component class`);
|
|
3694
|
+
}
|
|
3695
|
+
}
|
|
3696
|
+
node = new ComponentNode(C, props, this, ctx, key);
|
|
3697
|
+
children[key] = node;
|
|
3698
|
+
initiateRender.call(node, new Fiber(node, parentFiber));
|
|
3699
|
+
}
|
|
3700
|
+
parentFiber.childrenMap[key] = node;
|
|
3701
|
+
return node;
|
|
3702
|
+
};
|
|
3703
|
+
}
|
|
3704
|
+
handleError(...args) {
|
|
3705
|
+
return handleError(...args);
|
|
3706
|
+
}
|
|
3707
|
+
}
|
|
3708
|
+
App.validateTarget = validateTarget;
|
|
3709
|
+
App.apps = apps;
|
|
3710
|
+
App.version = version;
|
|
3711
|
+
async function mount(C, target, config = {}) {
|
|
3712
|
+
const app = new App(config);
|
|
3713
|
+
const root = app.createRoot(C, config);
|
|
3714
|
+
return root.mount(target, config);
|
|
3715
|
+
}
|
|
3716
|
+
|
|
3717
|
+
const mainEventHandler = (data, ev, currentTarget) => {
|
|
3718
|
+
const { data: _data, modifiers } = filterOutModifiersFromData(data);
|
|
3719
|
+
data = _data;
|
|
3720
|
+
let stopped = false;
|
|
3721
|
+
if (modifiers.length) {
|
|
3722
|
+
let selfMode = false;
|
|
3723
|
+
const isSelf = ev.target === currentTarget;
|
|
3724
|
+
for (const mod of modifiers) {
|
|
3725
|
+
switch (mod) {
|
|
3726
|
+
case "self":
|
|
3727
|
+
selfMode = true;
|
|
3728
|
+
if (isSelf) {
|
|
3729
|
+
continue;
|
|
3730
|
+
}
|
|
3731
|
+
else {
|
|
3732
|
+
return stopped;
|
|
3733
|
+
}
|
|
3734
|
+
case "prevent":
|
|
3735
|
+
if ((selfMode && isSelf) || !selfMode)
|
|
3736
|
+
ev.preventDefault();
|
|
3737
|
+
continue;
|
|
3738
|
+
case "stop":
|
|
3739
|
+
if ((selfMode && isSelf) || !selfMode)
|
|
3740
|
+
ev.stopPropagation();
|
|
3741
|
+
stopped = true;
|
|
3742
|
+
continue;
|
|
3743
|
+
}
|
|
3744
|
+
}
|
|
3745
|
+
}
|
|
3746
|
+
// If handler is empty, the array slot 0 will also be empty, and data will not have the property 0
|
|
3747
|
+
// We check this rather than data[0] being truthy (or typeof function) so that it crashes
|
|
3748
|
+
// as expected when there is a handler expression that evaluates to a falsy value
|
|
3749
|
+
if (Object.hasOwnProperty.call(data, 0)) {
|
|
3750
|
+
const handler = data[0];
|
|
3751
|
+
if (typeof handler !== "function") {
|
|
3752
|
+
throw new OwlError(`Invalid handler (expected a function, received: '${handler}')`);
|
|
3753
|
+
}
|
|
3754
|
+
let node = data[1] ? data[1].__owl__ : null;
|
|
3755
|
+
if (node ? node.status === 1 /* STATUS.MOUNTED */ : true) {
|
|
3756
|
+
handler.call(node ? node.component : null, ev);
|
|
3757
|
+
}
|
|
3758
|
+
}
|
|
3759
|
+
return stopped;
|
|
3760
|
+
};
|
|
3761
|
+
|
|
3762
|
+
function computed(fn, opts) {
|
|
3763
|
+
// todo: handle cleanup
|
|
3764
|
+
let derivedComputation;
|
|
3765
|
+
return () => {
|
|
3766
|
+
derivedComputation !== null && derivedComputation !== void 0 ? derivedComputation : (derivedComputation = {
|
|
3767
|
+
state: ComputationState.STALE,
|
|
3768
|
+
sources: new Set(),
|
|
3769
|
+
compute: () => {
|
|
3770
|
+
onWriteAtom(derivedComputation);
|
|
3771
|
+
return fn();
|
|
3772
|
+
},
|
|
3773
|
+
isDerived: true,
|
|
3774
|
+
value: undefined,
|
|
3775
|
+
observers: new Set(),
|
|
3776
|
+
name: opts === null || opts === void 0 ? void 0 : opts.name,
|
|
3777
|
+
});
|
|
3778
|
+
updateComputation(derivedComputation);
|
|
3779
|
+
return derivedComputation.value;
|
|
3780
|
+
};
|
|
3781
|
+
}
|
|
3782
|
+
|
|
3783
|
+
function signal(value, opts) {
|
|
3784
|
+
const atom = {
|
|
3785
|
+
value,
|
|
3786
|
+
observers: new Set(),
|
|
3787
|
+
name: opts === null || opts === void 0 ? void 0 : opts.name,
|
|
3788
|
+
};
|
|
3789
|
+
const read = () => {
|
|
3790
|
+
onReadAtom(atom);
|
|
3791
|
+
return atom.value;
|
|
3792
|
+
};
|
|
3793
|
+
const write = (newValue) => {
|
|
3794
|
+
if (typeof newValue === "function") {
|
|
3795
|
+
newValue = newValue(atom.value);
|
|
3796
|
+
}
|
|
3797
|
+
if (Object.is(atom.value, newValue))
|
|
3798
|
+
return;
|
|
3799
|
+
atom.value = newValue;
|
|
3800
|
+
onWriteAtom(atom);
|
|
3801
|
+
};
|
|
3802
|
+
read.set = write;
|
|
3803
|
+
read.update = (updater) => {
|
|
3804
|
+
if (updater) {
|
|
3805
|
+
write(updater(atom.value));
|
|
3806
|
+
}
|
|
3807
|
+
else {
|
|
3808
|
+
onWriteAtom(atom);
|
|
3809
|
+
}
|
|
3810
|
+
};
|
|
3811
|
+
return read;
|
|
3812
|
+
}
|
|
3813
|
+
|
|
3814
|
+
class Resource {
|
|
3815
|
+
constructor(name, type) {
|
|
3816
|
+
this._items = signal([]);
|
|
3817
|
+
this.items = computed(() => {
|
|
3818
|
+
return this._items()
|
|
3819
|
+
.sort((el1, el2) => el1[0] - el2[0])
|
|
3820
|
+
.map((elem) => elem[1]);
|
|
3821
|
+
});
|
|
3822
|
+
this._name = name || "resource";
|
|
3823
|
+
this._type = type;
|
|
3824
|
+
}
|
|
3825
|
+
add(item, sequence = 50) {
|
|
3826
|
+
if (this._type) {
|
|
3827
|
+
const error = validateType("item", item, this._type);
|
|
3828
|
+
if (error) {
|
|
3829
|
+
throw new Error(`Invalid type: ${error} (resource '${this._name}')`);
|
|
3830
|
+
}
|
|
3831
|
+
}
|
|
3832
|
+
this._items().push([sequence, item]);
|
|
3833
|
+
this._items.update();
|
|
3834
|
+
return this;
|
|
3835
|
+
}
|
|
3836
|
+
remove(item) {
|
|
3837
|
+
const items = this._items().filter(([seq, val]) => val !== item);
|
|
3838
|
+
this._items.set(items);
|
|
3839
|
+
return this;
|
|
3840
|
+
}
|
|
3841
|
+
}
|
|
3842
|
+
function useResource(r, elements) {
|
|
3843
|
+
for (let elem of elements) {
|
|
3844
|
+
r.add(elem);
|
|
3845
|
+
}
|
|
3846
|
+
onWillDestroy(() => {
|
|
3847
|
+
for (let elem of elements) {
|
|
3848
|
+
r.remove(elem);
|
|
3849
|
+
}
|
|
3850
|
+
});
|
|
3851
|
+
}
|
|
3852
|
+
|
|
3853
|
+
class Registry {
|
|
3854
|
+
constructor(name, type) {
|
|
3855
|
+
this._map = signal(Object.create(null));
|
|
3856
|
+
this.entries = computed(() => {
|
|
3857
|
+
const entries = Object.entries(this._map())
|
|
3858
|
+
.sort((el1, el2) => el1[1][0] - el2[1][0])
|
|
3859
|
+
.map(([str, elem]) => [str, elem[1]]);
|
|
3860
|
+
return entries;
|
|
3861
|
+
});
|
|
3862
|
+
this.items = computed(() => this.entries().map((e) => e[1]));
|
|
3863
|
+
this._name = name || "registry";
|
|
3864
|
+
this._type = type;
|
|
3865
|
+
}
|
|
3866
|
+
addById(item, sequence = 50) {
|
|
3867
|
+
if (!item.id) {
|
|
3868
|
+
throw new Error(`Item should have an id key`);
|
|
3869
|
+
}
|
|
3870
|
+
return this.add(item.id, item, sequence);
|
|
3871
|
+
}
|
|
3872
|
+
add(key, value, sequence = 50) {
|
|
3873
|
+
if (this._type) {
|
|
3874
|
+
const error = validateType(key, value, this._type);
|
|
3875
|
+
// todo: move error handling in validation.js
|
|
3876
|
+
if (error) {
|
|
3877
|
+
throw new Error("Invalid type: " + error);
|
|
3878
|
+
}
|
|
3879
|
+
}
|
|
3880
|
+
this._map()[key] = [sequence, value];
|
|
3881
|
+
this._map.update();
|
|
3882
|
+
return this;
|
|
3883
|
+
}
|
|
3884
|
+
get(key, defaultValue) {
|
|
3885
|
+
const hasKey = key in this._map();
|
|
3886
|
+
if (!hasKey && arguments.length < 2) {
|
|
3887
|
+
throw new Error(`KeyNotFoundError: Cannot find key "${key}" in this registry`);
|
|
3888
|
+
}
|
|
3889
|
+
return hasKey ? this._map()[key][1] : defaultValue;
|
|
3890
|
+
}
|
|
3891
|
+
remove(key) {
|
|
3892
|
+
delete this._map()[key];
|
|
3893
|
+
this._map.update();
|
|
3894
|
+
}
|
|
3895
|
+
}
|
|
3896
|
+
|
|
3897
|
+
function status() {
|
|
3898
|
+
const pm = _getCurrentPluginManager();
|
|
3899
|
+
const node = pm || getCurrent();
|
|
3900
|
+
return () => {
|
|
3901
|
+
switch (node.status) {
|
|
3902
|
+
case 0 /* STATUS.NEW */:
|
|
3903
|
+
return "new";
|
|
3904
|
+
case 2 /* STATUS.CANCELLED */:
|
|
3905
|
+
return "cancelled";
|
|
3906
|
+
case 1 /* STATUS.MOUNTED */:
|
|
3907
|
+
return pm ? "started" : "mounted";
|
|
3908
|
+
case 3 /* STATUS.DESTROYED */:
|
|
3909
|
+
return "destroyed";
|
|
3910
|
+
}
|
|
3911
|
+
};
|
|
3912
|
+
}
|
|
3913
|
+
|
|
3914
|
+
function effect(fn, opts) {
|
|
3915
|
+
var _a, _b, _c;
|
|
3916
|
+
const effectComputation = {
|
|
3917
|
+
state: ComputationState.STALE,
|
|
3918
|
+
value: undefined,
|
|
3919
|
+
compute() {
|
|
3920
|
+
// In case the cleanup read an atom.
|
|
3921
|
+
// todo: test it
|
|
3922
|
+
setComputation(undefined);
|
|
3923
|
+
// CurrentComputation = undefined!;
|
|
3924
|
+
// `removeSources` is made by `runComputation`.
|
|
3925
|
+
unsubscribeEffect(effectComputation);
|
|
3926
|
+
setComputation(effectComputation);
|
|
3927
|
+
// CurrentComputation = effectComputation;
|
|
3928
|
+
return fn();
|
|
3929
|
+
},
|
|
3930
|
+
sources: new Set(),
|
|
3931
|
+
childrenEffect: [],
|
|
3932
|
+
name: opts === null || opts === void 0 ? void 0 : opts.name,
|
|
3933
|
+
};
|
|
3934
|
+
(_c = (_b = (_a = getCurrentComputation()) === null || _a === void 0 ? void 0 : _a.childrenEffect) === null || _b === void 0 ? void 0 : _b.push) === null || _c === void 0 ? void 0 : _c.call(_b, effectComputation);
|
|
3935
|
+
updateComputation(effectComputation);
|
|
3936
|
+
// Remove sources and unsubscribe
|
|
3937
|
+
return () => {
|
|
3938
|
+
// In case the cleanup read an atom.
|
|
3939
|
+
// todo: test it
|
|
3940
|
+
const previousComputation = getCurrentComputation();
|
|
3941
|
+
setComputation(undefined);
|
|
3942
|
+
unsubscribeEffect(effectComputation);
|
|
3943
|
+
setComputation(previousComputation);
|
|
3944
|
+
};
|
|
3945
|
+
}
|
|
3946
|
+
function unsubscribeEffect(effectComputation) {
|
|
3947
|
+
removeSources(effectComputation);
|
|
3948
|
+
cleanupEffect(effectComputation);
|
|
3949
|
+
for (const children of effectComputation.childrenEffect) {
|
|
3950
|
+
// Consider it executed to avoid it's re-execution
|
|
3951
|
+
// todo: make a test for it
|
|
3952
|
+
children.state = ComputationState.EXECUTED;
|
|
3953
|
+
removeSources(children);
|
|
3954
|
+
unsubscribeEffect(children);
|
|
3955
|
+
}
|
|
3956
|
+
effectComputation.childrenEffect.length = 0;
|
|
3957
|
+
}
|
|
3958
|
+
function cleanupEffect(computation) {
|
|
3959
|
+
// the computation.value of an effect is a cleanup function
|
|
3960
|
+
const cleanupFn = computation.value;
|
|
3961
|
+
if (cleanupFn && typeof cleanupFn === "function") {
|
|
3962
|
+
cleanupFn();
|
|
3963
|
+
computation.value = undefined;
|
|
3964
|
+
}
|
|
3965
|
+
}
|
|
3966
|
+
|
|
3967
|
+
// -----------------------------------------------------------------------------
|
|
3968
|
+
// useEffect
|
|
3969
|
+
// -----------------------------------------------------------------------------
|
|
3970
|
+
/**
|
|
3971
|
+
* This hook will run a callback when a component is mounted and patched, and
|
|
3972
|
+
* will run a cleanup function before patching and before unmounting the
|
|
3973
|
+
* the component.
|
|
3974
|
+
*
|
|
3975
|
+
* @template T
|
|
3976
|
+
* @param {Effect<T>} effect the effect to run on component mount and/or patch
|
|
3977
|
+
* @param {()=>[...T]} [computeDependencies=()=>[NaN]] a callback to compute
|
|
3978
|
+
* dependencies that will decide if the effect needs to be cleaned up and
|
|
3979
|
+
* run again. If the dependencies did not change, the effect will not run
|
|
3980
|
+
* again. The default value returns an array containing only NaN because
|
|
3981
|
+
* NaN !== NaN, which will cause the effect to rerun on every patch.
|
|
3982
|
+
*/
|
|
3983
|
+
function useEffect(fn) {
|
|
3984
|
+
let cleanup = null;
|
|
3985
|
+
let effectCleanup = effect(() => {
|
|
3986
|
+
cleanup === null || cleanup === void 0 ? void 0 : cleanup();
|
|
3987
|
+
cleanup = fn();
|
|
3988
|
+
});
|
|
3989
|
+
onWillDestroy(() => {
|
|
3990
|
+
cleanup === null || cleanup === void 0 ? void 0 : cleanup();
|
|
3991
|
+
effectCleanup();
|
|
3992
|
+
});
|
|
3993
|
+
}
|
|
3994
|
+
// -----------------------------------------------------------------------------
|
|
3995
|
+
// useListener
|
|
3996
|
+
// -----------------------------------------------------------------------------
|
|
3997
|
+
/**
|
|
3998
|
+
* When a component needs to listen to DOM Events on element(s) that are not
|
|
3999
|
+
* part of his hierarchy, we can use the `useListener` hook.
|
|
4000
|
+
* It will correctly add and remove the event listener, whenever the
|
|
4001
|
+
* component is mounted and unmounted.
|
|
4002
|
+
*
|
|
4003
|
+
* Example:
|
|
4004
|
+
* a menu needs to listen to the click on window to be closed automatically
|
|
4005
|
+
*
|
|
4006
|
+
* Usage:
|
|
4007
|
+
* in the constructor of the OWL component that needs to be notified,
|
|
4008
|
+
* `useListener(window, 'click', this._doSomething);`
|
|
4009
|
+
* */
|
|
4010
|
+
function useListener(target, eventName, handler, eventParams) {
|
|
4011
|
+
const node = getCurrent();
|
|
4012
|
+
const boundHandler = handler.bind(node.component);
|
|
4013
|
+
onMounted(() => target.addEventListener(eventName, boundHandler, eventParams));
|
|
4014
|
+
onWillUnmount(() => target.removeEventListener(eventName, boundHandler, eventParams));
|
|
4015
|
+
}
|
|
4016
|
+
function usePlugins(Plugins) {
|
|
4017
|
+
const node = getCurrent();
|
|
4018
|
+
const manager = new PluginManager(node.pluginManager);
|
|
4019
|
+
node.pluginManager = manager;
|
|
4020
|
+
onWillDestroy(() => manager.destroy());
|
|
4021
|
+
return manager.startPlugins(Plugins);
|
|
4022
|
+
}
|
|
4023
|
+
|
|
4024
|
+
config.shouldNormalizeDom = false;
|
|
4025
|
+
config.mainEventHandler = mainEventHandler;
|
|
4026
|
+
const blockDom = {
|
|
4027
|
+
config,
|
|
4028
|
+
// bdom entry points
|
|
4029
|
+
mount: mount$1,
|
|
4030
|
+
patch,
|
|
4031
|
+
remove,
|
|
4032
|
+
// bdom block types
|
|
4033
|
+
list,
|
|
4034
|
+
multi,
|
|
4035
|
+
text,
|
|
4036
|
+
toggler,
|
|
4037
|
+
createBlock,
|
|
4038
|
+
html,
|
|
4039
|
+
comment,
|
|
4040
|
+
};
|
|
4041
|
+
const __info__ = {
|
|
4042
|
+
version: App.version,
|
|
4043
|
+
};
|
|
4044
|
+
|
|
4045
|
+
export { App, Component, EventBus, OwlError, Plugin, PluginManager, Registry, Resource, __info__, batched, blockDom, computed, effect, htmlEscape, markRaw, markup, mount, onError, onMounted, onPatched, onWillDestroy, onWillPatch, onWillStart, onWillUnmount, onWillUpdateProps, plugin, props, proxy, signal, status, toRaw, untrack, useComponent, useEffect, useListener, usePlugins, useResource, validate, validateType, whenReady, xml };
|
|
4046
|
+
|
|
4047
|
+
|
|
4048
|
+
__info__.date = '2025-12-15T09:45:48.537Z';
|
|
4049
|
+
__info__.hash = '4af29e6';
|
|
4050
|
+
__info__.url = 'https://github.com/odoo/owl';
|