@odoo/owl 2.0.0-beta-5 → 2.0.0-beta-8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/owl.cjs.js +1984 -1893
- package/dist/owl.es.js +1984 -1894
- package/dist/owl.iife.js +1984 -1893
- package/dist/owl.iife.min.js +1 -1
- package/dist/types/compiler/code_generator.d.ts +0 -8
- package/dist/types/compiler/index.d.ts +3 -2
- package/dist/types/index.d.ts +1 -27
- package/dist/types/{app → runtime}/app.d.ts +7 -5
- package/dist/types/{blockdom → runtime/blockdom}/attributes.d.ts +0 -0
- package/dist/types/{blockdom → runtime/blockdom}/block_compiler.d.ts +0 -0
- package/dist/types/{blockdom → runtime/blockdom}/config.d.ts +0 -0
- package/dist/types/{blockdom → runtime/blockdom}/event_catcher.d.ts +0 -0
- package/dist/types/{blockdom → runtime/blockdom}/events.d.ts +0 -0
- package/dist/types/{blockdom → runtime/blockdom}/html.d.ts +0 -0
- package/dist/types/{blockdom → runtime/blockdom}/index.d.ts +0 -0
- package/dist/types/{blockdom → runtime/blockdom}/list.d.ts +0 -0
- package/dist/types/{blockdom → runtime/blockdom}/multi.d.ts +0 -0
- package/dist/types/{blockdom → runtime/blockdom}/text.d.ts +0 -0
- package/dist/types/{blockdom → runtime/blockdom}/toggler.d.ts +0 -0
- package/dist/types/{component → runtime}/component.d.ts +5 -1
- package/dist/types/{component → runtime}/component_node.d.ts +10 -7
- package/dist/types/{component → runtime}/error_handling.d.ts +0 -0
- package/dist/types/{component/handler.d.ts → runtime/event_handling.d.ts} +0 -0
- package/dist/types/{component → runtime}/fibers.d.ts +1 -1
- package/dist/types/{hooks.d.ts → runtime/hooks.d.ts} +1 -1
- package/dist/types/runtime/index.d.ts +29 -0
- package/dist/types/{component → runtime}/lifecycle_hooks.d.ts +0 -0
- package/dist/types/{portal.d.ts → runtime/portal.d.ts} +5 -1
- package/dist/types/{reactivity.d.ts → runtime/reactivity.d.ts} +0 -0
- package/dist/types/{component → runtime}/scheduler.d.ts +0 -0
- package/dist/types/{component → runtime}/status.d.ts +0 -0
- package/dist/types/{app → runtime}/template_helpers.d.ts +11 -2
- package/dist/types/runtime/template_set.d.ts +32 -0
- package/dist/types/{utils.d.ts → runtime/utils.d.ts} +0 -7
- package/dist/types/runtime/validation.d.ts +30 -0
- package/package.json +5 -2
- package/dist/owl.cjs.min.js +0 -1
- package/dist/owl.es.min.js +0 -1
- package/dist/types/app/template_set.d.ts +0 -27
- package/dist/types/component/props_validation.d.ts +0 -14
package/dist/owl.iife.js
CHANGED
|
@@ -1371,51 +1371,6 @@
|
|
|
1371
1371
|
vnode.remove();
|
|
1372
1372
|
}
|
|
1373
1373
|
|
|
1374
|
-
const mainEventHandler = (data, ev, currentTarget) => {
|
|
1375
|
-
const { data: _data, modifiers } = filterOutModifiersFromData(data);
|
|
1376
|
-
data = _data;
|
|
1377
|
-
let stopped = false;
|
|
1378
|
-
if (modifiers.length) {
|
|
1379
|
-
let selfMode = false;
|
|
1380
|
-
const isSelf = ev.target === currentTarget;
|
|
1381
|
-
for (const mod of modifiers) {
|
|
1382
|
-
switch (mod) {
|
|
1383
|
-
case "self":
|
|
1384
|
-
selfMode = true;
|
|
1385
|
-
if (isSelf) {
|
|
1386
|
-
continue;
|
|
1387
|
-
}
|
|
1388
|
-
else {
|
|
1389
|
-
return stopped;
|
|
1390
|
-
}
|
|
1391
|
-
case "prevent":
|
|
1392
|
-
if ((selfMode && isSelf) || !selfMode)
|
|
1393
|
-
ev.preventDefault();
|
|
1394
|
-
continue;
|
|
1395
|
-
case "stop":
|
|
1396
|
-
if ((selfMode && isSelf) || !selfMode)
|
|
1397
|
-
ev.stopPropagation();
|
|
1398
|
-
stopped = true;
|
|
1399
|
-
continue;
|
|
1400
|
-
}
|
|
1401
|
-
}
|
|
1402
|
-
}
|
|
1403
|
-
// If handler is empty, the array slot 0 will also be empty, and data will not have the property 0
|
|
1404
|
-
// We check this rather than data[0] being truthy (or typeof function) so that it crashes
|
|
1405
|
-
// as expected when there is a handler expression that evaluates to a falsy value
|
|
1406
|
-
if (Object.hasOwnProperty.call(data, 0)) {
|
|
1407
|
-
const handler = data[0];
|
|
1408
|
-
if (typeof handler !== "function") {
|
|
1409
|
-
throw new Error(`Invalid handler (expected a function, received: '${handler}')`);
|
|
1410
|
-
}
|
|
1411
|
-
let node = data[1] ? data[1].__owl__ : null;
|
|
1412
|
-
if (node ? node.status === 1 /* MOUNTED */ : true) {
|
|
1413
|
-
handler.call(node ? node.component : null, ev);
|
|
1414
|
-
}
|
|
1415
|
-
}
|
|
1416
|
-
return stopped;
|
|
1417
|
-
};
|
|
1418
|
-
|
|
1419
1374
|
// Allows to get the target of a Reactive (used for making a new Reactive from the underlying object)
|
|
1420
1375
|
const TARGET = Symbol("Target");
|
|
1421
1376
|
// Escape hatch to prevent reactivity system to turn something into a reactive
|
|
@@ -1599,7 +1554,7 @@
|
|
|
1599
1554
|
return reactive(originalTarget, callback);
|
|
1600
1555
|
}
|
|
1601
1556
|
if (!reactiveCache.has(target)) {
|
|
1602
|
-
reactiveCache.set(target, new
|
|
1557
|
+
reactiveCache.set(target, new WeakMap());
|
|
1603
1558
|
}
|
|
1604
1559
|
const reactivesForTarget = reactiveCache.get(target);
|
|
1605
1560
|
if (!reactivesForTarget.has(callback)) {
|
|
@@ -1899,18 +1854,20 @@
|
|
|
1899
1854
|
*/
|
|
1900
1855
|
function markup(value) {
|
|
1901
1856
|
return new Markup(value);
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
class Component {
|
|
1860
|
+
constructor(props, env, node) {
|
|
1861
|
+
this.props = props;
|
|
1862
|
+
this.env = env;
|
|
1863
|
+
this.__owl__ = node;
|
|
1864
|
+
}
|
|
1865
|
+
setup() { }
|
|
1866
|
+
render(deep = false) {
|
|
1867
|
+
this.__owl__.render(deep === true);
|
|
1868
|
+
}
|
|
1902
1869
|
}
|
|
1903
|
-
|
|
1904
|
-
// xml tag helper
|
|
1905
|
-
// -----------------------------------------------------------------------------
|
|
1906
|
-
const globalTemplates = {};
|
|
1907
|
-
function xml(...args) {
|
|
1908
|
-
const name = `__template__${xml.nextId++}`;
|
|
1909
|
-
const value = String.raw(...args);
|
|
1910
|
-
globalTemplates[name] = value;
|
|
1911
|
-
return name;
|
|
1912
|
-
}
|
|
1913
|
-
xml.nextId = 1;
|
|
1870
|
+
Component.template = "";
|
|
1914
1871
|
|
|
1915
1872
|
// Maps fibers to thrown errors
|
|
1916
1873
|
const fibersInError = new WeakMap();
|
|
@@ -2004,6 +1961,9 @@
|
|
|
2004
1961
|
}
|
|
2005
1962
|
return fiber;
|
|
2006
1963
|
}
|
|
1964
|
+
function throwOnRender() {
|
|
1965
|
+
throw new Error("Attempted to render cancelled fiber");
|
|
1966
|
+
}
|
|
2007
1967
|
/**
|
|
2008
1968
|
* @returns number of not-yet rendered fibers cancelled
|
|
2009
1969
|
*/
|
|
@@ -2011,6 +1971,7 @@
|
|
|
2011
1971
|
let result = 0;
|
|
2012
1972
|
for (let fiber of fibers) {
|
|
2013
1973
|
let node = fiber.node;
|
|
1974
|
+
fiber.render = throwOnRender;
|
|
2014
1975
|
if (node.status === 0 /* NEW */) {
|
|
2015
1976
|
node.destroy();
|
|
2016
1977
|
}
|
|
@@ -2204,145 +2165,6 @@
|
|
|
2204
2165
|
}
|
|
2205
2166
|
}
|
|
2206
2167
|
|
|
2207
|
-
/**
|
|
2208
|
-
* Apply default props (only top level).
|
|
2209
|
-
*
|
|
2210
|
-
* Note that this method does modify in place the props
|
|
2211
|
-
*/
|
|
2212
|
-
function applyDefaultProps(props, ComponentClass) {
|
|
2213
|
-
const defaultProps = ComponentClass.defaultProps;
|
|
2214
|
-
if (defaultProps) {
|
|
2215
|
-
for (let propName in defaultProps) {
|
|
2216
|
-
if (props[propName] === undefined) {
|
|
2217
|
-
props[propName] = defaultProps[propName];
|
|
2218
|
-
}
|
|
2219
|
-
}
|
|
2220
|
-
}
|
|
2221
|
-
}
|
|
2222
|
-
//------------------------------------------------------------------------------
|
|
2223
|
-
// Prop validation helper
|
|
2224
|
-
//------------------------------------------------------------------------------
|
|
2225
|
-
function getPropDescription(staticProps) {
|
|
2226
|
-
if (staticProps instanceof Array) {
|
|
2227
|
-
return Object.fromEntries(staticProps.map((p) => (p.endsWith("?") ? [p.slice(0, -1), false] : [p, true])));
|
|
2228
|
-
}
|
|
2229
|
-
return staticProps || { "*": true };
|
|
2230
|
-
}
|
|
2231
|
-
/**
|
|
2232
|
-
* Validate the component props (or next props) against the (static) props
|
|
2233
|
-
* description. This is potentially an expensive operation: it may needs to
|
|
2234
|
-
* visit recursively the props and all the children to check if they are valid.
|
|
2235
|
-
* This is why it is only done in 'dev' mode.
|
|
2236
|
-
*/
|
|
2237
|
-
function validateProps(name, props, parent) {
|
|
2238
|
-
const ComponentClass = typeof name !== "string"
|
|
2239
|
-
? name
|
|
2240
|
-
: parent.constructor.components[name];
|
|
2241
|
-
if (!ComponentClass) {
|
|
2242
|
-
// this is an error, wrong component. We silently return here instead so the
|
|
2243
|
-
// error is triggered by the usual path ('component' function)
|
|
2244
|
-
return;
|
|
2245
|
-
}
|
|
2246
|
-
applyDefaultProps(props, ComponentClass);
|
|
2247
|
-
const defaultProps = ComponentClass.defaultProps || {};
|
|
2248
|
-
let propsDef = getPropDescription(ComponentClass.props);
|
|
2249
|
-
const allowAdditionalProps = "*" in propsDef;
|
|
2250
|
-
for (let propName in propsDef) {
|
|
2251
|
-
if (propName === "*") {
|
|
2252
|
-
continue;
|
|
2253
|
-
}
|
|
2254
|
-
const propDef = propsDef[propName];
|
|
2255
|
-
let isMandatory = !!propDef;
|
|
2256
|
-
if (typeof propDef === "object" && "optional" in propDef) {
|
|
2257
|
-
isMandatory = !propDef.optional;
|
|
2258
|
-
}
|
|
2259
|
-
if (isMandatory && propName in defaultProps) {
|
|
2260
|
-
throw new Error(`A default value cannot be defined for a mandatory prop (name: '${propName}', component: ${ComponentClass.name})`);
|
|
2261
|
-
}
|
|
2262
|
-
if (props[propName] === undefined) {
|
|
2263
|
-
if (isMandatory) {
|
|
2264
|
-
throw new Error(`Missing props '${propName}' (component '${ComponentClass.name}')`);
|
|
2265
|
-
}
|
|
2266
|
-
else {
|
|
2267
|
-
continue;
|
|
2268
|
-
}
|
|
2269
|
-
}
|
|
2270
|
-
let isValid;
|
|
2271
|
-
try {
|
|
2272
|
-
isValid = isValidProp(props[propName], propDef);
|
|
2273
|
-
}
|
|
2274
|
-
catch (e) {
|
|
2275
|
-
e.message = `Invalid prop '${propName}' in component ${ComponentClass.name} (${e.message})`;
|
|
2276
|
-
throw e;
|
|
2277
|
-
}
|
|
2278
|
-
if (!isValid) {
|
|
2279
|
-
throw new Error(`Invalid Prop '${propName}' in component '${ComponentClass.name}'`);
|
|
2280
|
-
}
|
|
2281
|
-
}
|
|
2282
|
-
if (!allowAdditionalProps) {
|
|
2283
|
-
for (let propName in props) {
|
|
2284
|
-
if (!(propName in propsDef)) {
|
|
2285
|
-
throw new Error(`Unknown prop '${propName}' given to component '${ComponentClass.name}'`);
|
|
2286
|
-
}
|
|
2287
|
-
}
|
|
2288
|
-
}
|
|
2289
|
-
}
|
|
2290
|
-
/**
|
|
2291
|
-
* Check if an invidual prop value matches its (static) prop definition
|
|
2292
|
-
*/
|
|
2293
|
-
function isValidProp(prop, propDef) {
|
|
2294
|
-
if (propDef === true) {
|
|
2295
|
-
return true;
|
|
2296
|
-
}
|
|
2297
|
-
if (typeof propDef === "function") {
|
|
2298
|
-
// Check if a value is constructed by some Constructor. Note that there is a
|
|
2299
|
-
// slight abuse of language: we want to consider primitive values as well.
|
|
2300
|
-
//
|
|
2301
|
-
// So, even though 1 is not an instance of Number, we want to consider that
|
|
2302
|
-
// it is valid.
|
|
2303
|
-
if (typeof prop === "object") {
|
|
2304
|
-
return prop instanceof propDef;
|
|
2305
|
-
}
|
|
2306
|
-
return typeof prop === propDef.name.toLowerCase();
|
|
2307
|
-
}
|
|
2308
|
-
else if (propDef instanceof Array) {
|
|
2309
|
-
// If this code is executed, this means that we want to check if a prop
|
|
2310
|
-
// matches at least one of its descriptor.
|
|
2311
|
-
let result = false;
|
|
2312
|
-
for (let i = 0, iLen = propDef.length; i < iLen; i++) {
|
|
2313
|
-
result = result || isValidProp(prop, propDef[i]);
|
|
2314
|
-
}
|
|
2315
|
-
return result;
|
|
2316
|
-
}
|
|
2317
|
-
// propsDef is an object
|
|
2318
|
-
if (propDef.optional && prop === undefined) {
|
|
2319
|
-
return true;
|
|
2320
|
-
}
|
|
2321
|
-
let result = propDef.type ? isValidProp(prop, propDef.type) : true;
|
|
2322
|
-
if (propDef.validate) {
|
|
2323
|
-
result = result && propDef.validate(prop);
|
|
2324
|
-
}
|
|
2325
|
-
if (propDef.type === Array && propDef.element) {
|
|
2326
|
-
for (let i = 0, iLen = prop.length; i < iLen; i++) {
|
|
2327
|
-
result = result && isValidProp(prop[i], propDef.element);
|
|
2328
|
-
}
|
|
2329
|
-
}
|
|
2330
|
-
if (propDef.type === Object && propDef.shape) {
|
|
2331
|
-
const shape = propDef.shape;
|
|
2332
|
-
for (let key in shape) {
|
|
2333
|
-
result = result && isValidProp(prop[key], shape[key]);
|
|
2334
|
-
}
|
|
2335
|
-
if (result) {
|
|
2336
|
-
for (let propName in prop) {
|
|
2337
|
-
if (!(propName in shape)) {
|
|
2338
|
-
throw new Error(`unknown prop '${propName}'`);
|
|
2339
|
-
}
|
|
2340
|
-
}
|
|
2341
|
-
}
|
|
2342
|
-
}
|
|
2343
|
-
return result;
|
|
2344
|
-
}
|
|
2345
|
-
|
|
2346
2168
|
let currentNode = null;
|
|
2347
2169
|
function getCurrent() {
|
|
2348
2170
|
if (!currentNode) {
|
|
@@ -2353,6 +2175,18 @@
|
|
|
2353
2175
|
function useComponent() {
|
|
2354
2176
|
return currentNode.component;
|
|
2355
2177
|
}
|
|
2178
|
+
/**
|
|
2179
|
+
* Apply default props (only top level).
|
|
2180
|
+
*
|
|
2181
|
+
* Note that this method does modify in place the props
|
|
2182
|
+
*/
|
|
2183
|
+
function applyDefaultProps(props, defaultProps) {
|
|
2184
|
+
for (let propName in defaultProps) {
|
|
2185
|
+
if (props[propName] === undefined) {
|
|
2186
|
+
props[propName] = defaultProps[propName];
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2356
2190
|
// -----------------------------------------------------------------------------
|
|
2357
2191
|
// Integration with reactivity system (useState)
|
|
2358
2192
|
// -----------------------------------------------------------------------------
|
|
@@ -2371,7 +2205,7 @@
|
|
|
2371
2205
|
const node = getCurrent();
|
|
2372
2206
|
let render = batchedRenderFunctions.get(node);
|
|
2373
2207
|
if (!render) {
|
|
2374
|
-
render = batched(node.render.bind(node));
|
|
2208
|
+
render = batched(node.render.bind(node, false));
|
|
2375
2209
|
batchedRenderFunctions.set(node, render);
|
|
2376
2210
|
// manual implementation of onWillDestroy to break cyclic dependency
|
|
2377
2211
|
node.willDestroy.push(clearReactivesForCallback.bind(null, render));
|
|
@@ -2380,7 +2214,9 @@
|
|
|
2380
2214
|
}
|
|
2381
2215
|
function arePropsDifferent(props1, props2) {
|
|
2382
2216
|
for (let k in props1) {
|
|
2383
|
-
|
|
2217
|
+
const prop1 = props1[k] && typeof props1[k] === "object" ? toRaw(props1[k]) : props1[k];
|
|
2218
|
+
const prop2 = props2[k] && typeof props2[k] === "object" ? toRaw(props2[k]) : props2[k];
|
|
2219
|
+
if (prop1 !== prop2) {
|
|
2384
2220
|
return true;
|
|
2385
2221
|
}
|
|
2386
2222
|
}
|
|
@@ -2402,7 +2238,7 @@
|
|
|
2402
2238
|
node.forceNextRender = false;
|
|
2403
2239
|
}
|
|
2404
2240
|
else {
|
|
2405
|
-
const currentProps = node.component.props
|
|
2241
|
+
const currentProps = node.component.props;
|
|
2406
2242
|
shouldRender = parentFiber.deep || arePropsDifferent(currentProps, props);
|
|
2407
2243
|
}
|
|
2408
2244
|
if (shouldRender) {
|
|
@@ -2420,6 +2256,9 @@
|
|
|
2420
2256
|
if (!C) {
|
|
2421
2257
|
throw new Error(`Cannot find the definition of component "${name}"`);
|
|
2422
2258
|
}
|
|
2259
|
+
else if (!(C.prototype instanceof Component)) {
|
|
2260
|
+
throw new Error(`"${name}" is not a Component. It must inherit from the Component class`);
|
|
2261
|
+
}
|
|
2423
2262
|
}
|
|
2424
2263
|
node = new ComponentNode(C, props, ctx.app, ctx, key);
|
|
2425
2264
|
ctx.children[key] = node;
|
|
@@ -2448,10 +2287,18 @@
|
|
|
2448
2287
|
this.parent = parent;
|
|
2449
2288
|
this.parentKey = parentKey;
|
|
2450
2289
|
this.level = parent ? parent.level + 1 : 0;
|
|
2451
|
-
|
|
2290
|
+
const defaultProps = C.defaultProps;
|
|
2291
|
+
if (defaultProps) {
|
|
2292
|
+
applyDefaultProps(props, defaultProps);
|
|
2293
|
+
}
|
|
2452
2294
|
const env = (parent && parent.childEnv) || app.env;
|
|
2453
2295
|
this.childEnv = env;
|
|
2454
|
-
|
|
2296
|
+
for (const key in props) {
|
|
2297
|
+
const prop = props[key];
|
|
2298
|
+
if (prop && typeof prop === "object" && prop[TARGET]) {
|
|
2299
|
+
props[key] = useState(prop);
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2455
2302
|
this.component = new C(props, env, this);
|
|
2456
2303
|
this.renderFn = app.getTemplate(C.template).bind(this.component, this.component, this);
|
|
2457
2304
|
this.component.setup();
|
|
@@ -2479,7 +2326,7 @@
|
|
|
2479
2326
|
fiber.render();
|
|
2480
2327
|
}
|
|
2481
2328
|
}
|
|
2482
|
-
async render(deep
|
|
2329
|
+
async render(deep) {
|
|
2483
2330
|
let current = this.fiber;
|
|
2484
2331
|
if (current && (current.root.locked || current.bdom === true)) {
|
|
2485
2332
|
await Promise.resolve();
|
|
@@ -2557,9 +2404,17 @@
|
|
|
2557
2404
|
const fiber = makeChildFiber(this, parentFiber);
|
|
2558
2405
|
this.fiber = fiber;
|
|
2559
2406
|
const component = this.component;
|
|
2560
|
-
|
|
2407
|
+
const defaultProps = component.constructor.defaultProps;
|
|
2408
|
+
if (defaultProps) {
|
|
2409
|
+
applyDefaultProps(props, defaultProps);
|
|
2410
|
+
}
|
|
2561
2411
|
currentNode = this;
|
|
2562
|
-
|
|
2412
|
+
for (const key in props) {
|
|
2413
|
+
const prop = props[key];
|
|
2414
|
+
if (prop && typeof prop === "object" && prop[TARGET]) {
|
|
2415
|
+
props[key] = useState(prop);
|
|
2416
|
+
}
|
|
2417
|
+
}
|
|
2563
2418
|
currentNode = null;
|
|
2564
2419
|
const prom = Promise.all(this.willUpdateProps.map((f) => f.call(component, props)));
|
|
2565
2420
|
await prom;
|
|
@@ -2653,663 +2508,1234 @@
|
|
|
2653
2508
|
}
|
|
2654
2509
|
}
|
|
2655
2510
|
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
let renders = this.delayedRenders;
|
|
2676
|
-
this.delayedRenders = [];
|
|
2677
|
-
for (let f of renders) {
|
|
2678
|
-
if (f.root && f.node.status !== 2 /* DESTROYED */) {
|
|
2679
|
-
f.render();
|
|
2511
|
+
const TIMEOUT = Symbol("timeout");
|
|
2512
|
+
function wrapError(fn, hookName) {
|
|
2513
|
+
const error = new Error(`The following error occurred in ${hookName}: `);
|
|
2514
|
+
const timeoutError = new Error(`${hookName}'s promise hasn't resolved after 3 seconds`);
|
|
2515
|
+
const node = getCurrent();
|
|
2516
|
+
return (...args) => {
|
|
2517
|
+
try {
|
|
2518
|
+
const result = fn(...args);
|
|
2519
|
+
if (result instanceof Promise) {
|
|
2520
|
+
if (hookName === "onWillStart" || hookName === "onWillUpdateProps") {
|
|
2521
|
+
const fiber = node.fiber;
|
|
2522
|
+
Promise.race([
|
|
2523
|
+
result,
|
|
2524
|
+
new Promise((resolve) => setTimeout(() => resolve(TIMEOUT), 3000)),
|
|
2525
|
+
]).then((res) => {
|
|
2526
|
+
if (res === TIMEOUT && node.fiber === fiber) {
|
|
2527
|
+
console.warn(timeoutError);
|
|
2528
|
+
}
|
|
2529
|
+
});
|
|
2680
2530
|
}
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
this.frame = 0;
|
|
2686
|
-
this.tasks.forEach((fiber) => this.processFiber(fiber));
|
|
2687
|
-
for (let task of this.tasks) {
|
|
2688
|
-
if (task.node.status === 2 /* DESTROYED */) {
|
|
2689
|
-
this.tasks.delete(task);
|
|
2531
|
+
return result.catch((cause) => {
|
|
2532
|
+
error.cause = cause;
|
|
2533
|
+
if (cause instanceof Error) {
|
|
2534
|
+
error.message += `"${cause.message}"`;
|
|
2690
2535
|
}
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
processFiber(fiber) {
|
|
2696
|
-
if (fiber.root !== fiber) {
|
|
2697
|
-
this.tasks.delete(fiber);
|
|
2698
|
-
return;
|
|
2699
|
-
}
|
|
2700
|
-
const hasError = fibersInError.has(fiber);
|
|
2701
|
-
if (hasError && fiber.counter !== 0) {
|
|
2702
|
-
this.tasks.delete(fiber);
|
|
2703
|
-
return;
|
|
2704
|
-
}
|
|
2705
|
-
if (fiber.node.status === 2 /* DESTROYED */) {
|
|
2706
|
-
this.tasks.delete(fiber);
|
|
2707
|
-
return;
|
|
2536
|
+
throw error;
|
|
2537
|
+
});
|
|
2538
|
+
}
|
|
2539
|
+
return result;
|
|
2708
2540
|
}
|
|
2709
|
-
|
|
2710
|
-
if (
|
|
2711
|
-
|
|
2541
|
+
catch (cause) {
|
|
2542
|
+
if (cause instanceof Error) {
|
|
2543
|
+
error.message += `"${cause.message}"`;
|
|
2712
2544
|
}
|
|
2713
|
-
|
|
2545
|
+
throw error;
|
|
2714
2546
|
}
|
|
2715
|
-
}
|
|
2547
|
+
};
|
|
2716
2548
|
}
|
|
2717
|
-
//
|
|
2718
|
-
//
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
}
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
}
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2549
|
+
// -----------------------------------------------------------------------------
|
|
2550
|
+
// hooks
|
|
2551
|
+
// -----------------------------------------------------------------------------
|
|
2552
|
+
function onWillStart(fn) {
|
|
2553
|
+
const node = getCurrent();
|
|
2554
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
2555
|
+
node.willStart.push(decorate(fn.bind(node.component), "onWillStart"));
|
|
2556
|
+
}
|
|
2557
|
+
function onWillUpdateProps(fn) {
|
|
2558
|
+
const node = getCurrent();
|
|
2559
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
2560
|
+
node.willUpdateProps.push(decorate(fn.bind(node.component), "onWillUpdateProps"));
|
|
2561
|
+
}
|
|
2562
|
+
function onMounted(fn) {
|
|
2563
|
+
const node = getCurrent();
|
|
2564
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
2565
|
+
node.mounted.push(decorate(fn.bind(node.component), "onMounted"));
|
|
2566
|
+
}
|
|
2567
|
+
function onWillPatch(fn) {
|
|
2568
|
+
const node = getCurrent();
|
|
2569
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
2570
|
+
node.willPatch.unshift(decorate(fn.bind(node.component), "onWillPatch"));
|
|
2571
|
+
}
|
|
2572
|
+
function onPatched(fn) {
|
|
2573
|
+
const node = getCurrent();
|
|
2574
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
2575
|
+
node.patched.push(decorate(fn.bind(node.component), "onPatched"));
|
|
2576
|
+
}
|
|
2577
|
+
function onWillUnmount(fn) {
|
|
2578
|
+
const node = getCurrent();
|
|
2579
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
2580
|
+
node.willUnmount.unshift(decorate(fn.bind(node.component), "onWillUnmount"));
|
|
2581
|
+
}
|
|
2582
|
+
function onWillDestroy(fn) {
|
|
2583
|
+
const node = getCurrent();
|
|
2584
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
2585
|
+
node.willDestroy.push(decorate(fn.bind(node.component), "onWillDestroy"));
|
|
2586
|
+
}
|
|
2587
|
+
function onWillRender(fn) {
|
|
2588
|
+
const node = getCurrent();
|
|
2589
|
+
const renderFn = node.renderFn;
|
|
2590
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
2591
|
+
fn = decorate(fn.bind(node.component), "onWillRender");
|
|
2592
|
+
node.renderFn = () => {
|
|
2593
|
+
fn();
|
|
2594
|
+
return renderFn();
|
|
2595
|
+
};
|
|
2596
|
+
}
|
|
2597
|
+
function onRendered(fn) {
|
|
2598
|
+
const node = getCurrent();
|
|
2599
|
+
const renderFn = node.renderFn;
|
|
2600
|
+
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
2601
|
+
fn = decorate(fn.bind(node.component), "onRendered");
|
|
2602
|
+
node.renderFn = () => {
|
|
2603
|
+
const result = renderFn();
|
|
2604
|
+
fn();
|
|
2605
|
+
return result;
|
|
2606
|
+
};
|
|
2607
|
+
}
|
|
2608
|
+
function onError(callback) {
|
|
2609
|
+
const node = getCurrent();
|
|
2610
|
+
let handlers = nodeErrorHandlers.get(node);
|
|
2611
|
+
if (!handlers) {
|
|
2612
|
+
handlers = [];
|
|
2613
|
+
nodeErrorHandlers.set(node, handlers);
|
|
2773
2614
|
}
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2615
|
+
handlers.push(callback.bind(node.component));
|
|
2616
|
+
}
|
|
2617
|
+
|
|
2618
|
+
const VText = text("").constructor;
|
|
2619
|
+
class VPortal extends VText {
|
|
2620
|
+
constructor(selector, realBDom) {
|
|
2621
|
+
super("");
|
|
2622
|
+
this.target = null;
|
|
2623
|
+
this.selector = selector;
|
|
2624
|
+
this.realBDom = realBDom;
|
|
2625
|
+
}
|
|
2626
|
+
mount(parent, anchor) {
|
|
2627
|
+
super.mount(parent, anchor);
|
|
2628
|
+
this.target = document.querySelector(this.selector);
|
|
2629
|
+
if (!this.target) {
|
|
2630
|
+
let el = this.el;
|
|
2631
|
+
while (el && el.parentElement instanceof HTMLElement) {
|
|
2632
|
+
el = el.parentElement;
|
|
2633
|
+
}
|
|
2634
|
+
this.target = el && el.querySelector(this.selector);
|
|
2635
|
+
if (!this.target) {
|
|
2636
|
+
throw new Error("invalid portal target");
|
|
2784
2637
|
}
|
|
2785
|
-
s += cur;
|
|
2786
2638
|
}
|
|
2787
|
-
|
|
2639
|
+
this.realBDom.mount(this.target, null);
|
|
2788
2640
|
}
|
|
2789
|
-
|
|
2790
|
-
|
|
2641
|
+
beforeRemove() {
|
|
2642
|
+
this.realBDom.beforeRemove();
|
|
2791
2643
|
}
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
return s.replace(/\$\{(.*?)\}/g, (match, group) => {
|
|
2799
|
-
return "${" + replacer(group) + "}";
|
|
2800
|
-
});
|
|
2801
|
-
},
|
|
2802
|
-
};
|
|
2644
|
+
remove() {
|
|
2645
|
+
if (this.realBDom) {
|
|
2646
|
+
super.remove();
|
|
2647
|
+
this.realBDom.remove();
|
|
2648
|
+
this.realBDom = null;
|
|
2649
|
+
}
|
|
2803
2650
|
}
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
i++;
|
|
2651
|
+
patch(other) {
|
|
2652
|
+
super.patch(other);
|
|
2653
|
+
if (this.realBDom) {
|
|
2654
|
+
this.realBDom.patch(other.realBDom, true);
|
|
2655
|
+
}
|
|
2656
|
+
else {
|
|
2657
|
+
this.realBDom = other.realBDom;
|
|
2658
|
+
this.realBDom.mount(this.target, null);
|
|
2813
2659
|
}
|
|
2814
|
-
return { type: "VALUE", value: s };
|
|
2815
2660
|
}
|
|
2816
|
-
|
|
2817
|
-
|
|
2661
|
+
}
|
|
2662
|
+
/**
|
|
2663
|
+
* <t t-slot="default"/>
|
|
2664
|
+
*/
|
|
2665
|
+
function portalTemplate(app, bdom, helpers) {
|
|
2666
|
+
let { callSlot } = helpers;
|
|
2667
|
+
return function template(ctx, node, key = "") {
|
|
2668
|
+
return callSlot(ctx, node, key, "default", false, null);
|
|
2669
|
+
};
|
|
2670
|
+
}
|
|
2671
|
+
class Portal extends Component {
|
|
2672
|
+
setup() {
|
|
2673
|
+
const node = this.__owl__;
|
|
2674
|
+
const renderFn = node.renderFn;
|
|
2675
|
+
node.renderFn = () => new VPortal(this.props.target, renderFn());
|
|
2676
|
+
onWillUnmount(() => {
|
|
2677
|
+
if (node.bdom) {
|
|
2678
|
+
node.bdom.remove();
|
|
2679
|
+
}
|
|
2680
|
+
});
|
|
2818
2681
|
}
|
|
2819
|
-
}
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2682
|
+
}
|
|
2683
|
+
Portal.template = "__portal__";
|
|
2684
|
+
Portal.props = {
|
|
2685
|
+
target: {
|
|
2686
|
+
type: String,
|
|
2687
|
+
},
|
|
2688
|
+
slots: true,
|
|
2689
|
+
};
|
|
2690
|
+
|
|
2691
|
+
// -----------------------------------------------------------------------------
|
|
2692
|
+
// helpers
|
|
2693
|
+
// -----------------------------------------------------------------------------
|
|
2694
|
+
const isUnionType = (t) => Array.isArray(t);
|
|
2695
|
+
const isBaseType = (t) => typeof t !== "object";
|
|
2696
|
+
function isOptional(t) {
|
|
2697
|
+
return typeof t === "object" && "optional" in t ? t.optional || false : false;
|
|
2698
|
+
}
|
|
2699
|
+
function describeType(type) {
|
|
2700
|
+
return type === "*" || type === true ? "value" : type.name.toLowerCase();
|
|
2701
|
+
}
|
|
2702
|
+
function describe(info) {
|
|
2703
|
+
if (isBaseType(info)) {
|
|
2704
|
+
return describeType(info);
|
|
2705
|
+
}
|
|
2706
|
+
else if (isUnionType(info)) {
|
|
2707
|
+
return info.map(describe).join(" or ");
|
|
2708
|
+
}
|
|
2709
|
+
if ("element" in info) {
|
|
2710
|
+
return `list of ${describe({ type: info.element, optional: false })}s`;
|
|
2711
|
+
}
|
|
2712
|
+
if ("shape" in info) {
|
|
2713
|
+
return `object`;
|
|
2714
|
+
}
|
|
2715
|
+
return describe(info.type || "*");
|
|
2716
|
+
}
|
|
2717
|
+
function toSchema(spec) {
|
|
2718
|
+
return Object.fromEntries(spec.map((e) => e.endsWith("?") ? [e.slice(0, -1), { optional: true }] : [e, { type: "*", optional: false }]));
|
|
2719
|
+
}
|
|
2720
|
+
/**
|
|
2721
|
+
* Main validate function
|
|
2722
|
+
*/
|
|
2723
|
+
function validate(obj, spec) {
|
|
2724
|
+
let errors = validateSchema(obj, spec);
|
|
2725
|
+
if (errors.length) {
|
|
2726
|
+
throw new Error("Invalid object: " + errors.join(", "));
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
/**
|
|
2730
|
+
* Helper validate function, to get the list of errors. useful if one want to
|
|
2731
|
+
* manipulate the errors without parsing an error object
|
|
2732
|
+
*/
|
|
2733
|
+
function validateSchema(obj, schema) {
|
|
2734
|
+
if (Array.isArray(schema)) {
|
|
2735
|
+
schema = toSchema(schema);
|
|
2736
|
+
}
|
|
2737
|
+
let errors = [];
|
|
2738
|
+
// check if each value in obj has correct shape
|
|
2739
|
+
for (let key in obj) {
|
|
2740
|
+
if (key in schema) {
|
|
2741
|
+
let result = validateType(key, obj[key], schema[key]);
|
|
2742
|
+
if (result) {
|
|
2743
|
+
errors.push(result);
|
|
2744
|
+
}
|
|
2827
2745
|
}
|
|
2828
|
-
if (
|
|
2829
|
-
|
|
2746
|
+
else if (!("*" in schema)) {
|
|
2747
|
+
errors.push(`unknown key '${key}'`);
|
|
2830
2748
|
}
|
|
2831
|
-
return { type: "SYMBOL", value: s };
|
|
2832
2749
|
}
|
|
2833
|
-
|
|
2834
|
-
|
|
2750
|
+
// check that all specified keys are defined in obj
|
|
2751
|
+
for (let key in schema) {
|
|
2752
|
+
const spec = schema[key];
|
|
2753
|
+
if (key !== "*" && !isOptional(spec) && !(key in obj)) {
|
|
2754
|
+
const isObj = typeof spec === "object" && !Array.isArray(spec);
|
|
2755
|
+
const isAny = spec === "*" || (isObj && "type" in spec ? spec.type === "*" : isObj);
|
|
2756
|
+
let detail = isAny ? "" : ` (should be a ${describe(spec)})`;
|
|
2757
|
+
errors.push(`'${key}' is missing${detail}`);
|
|
2758
|
+
}
|
|
2835
2759
|
}
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
if (
|
|
2840
|
-
|
|
2760
|
+
return errors;
|
|
2761
|
+
}
|
|
2762
|
+
function validateBaseType(key, value, type) {
|
|
2763
|
+
if (typeof type === "function") {
|
|
2764
|
+
if (typeof value === "object") {
|
|
2765
|
+
if (!(value instanceof type)) {
|
|
2766
|
+
return `'${key}' is not a ${describeType(type)}`;
|
|
2767
|
+
}
|
|
2768
|
+
}
|
|
2769
|
+
else if (typeof value !== type.name.toLowerCase()) {
|
|
2770
|
+
return `'${key}' is not a ${describeType(type)}`;
|
|
2771
|
+
}
|
|
2841
2772
|
}
|
|
2842
|
-
return
|
|
2843
|
-
}
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2773
|
+
return null;
|
|
2774
|
+
}
|
|
2775
|
+
function validateArrayType(key, value, descr) {
|
|
2776
|
+
if (!Array.isArray(value)) {
|
|
2777
|
+
return `'${key}' is not a list of ${describe(descr)}s`;
|
|
2778
|
+
}
|
|
2779
|
+
for (let i = 0; i < value.length; i++) {
|
|
2780
|
+
const error = validateType(`${key}[${i}]`, value[i], descr);
|
|
2781
|
+
if (error) {
|
|
2782
|
+
return error;
|
|
2848
2783
|
}
|
|
2849
2784
|
}
|
|
2850
|
-
return
|
|
2851
|
-
}
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
try {
|
|
2876
|
-
while (token) {
|
|
2877
|
-
current = current.trim();
|
|
2878
|
-
if (current) {
|
|
2879
|
-
for (let tokenizer of TOKENIZERS) {
|
|
2880
|
-
token = tokenizer(current);
|
|
2881
|
-
if (token) {
|
|
2882
|
-
result.push(token);
|
|
2883
|
-
current = current.slice(token.size || token.value.length);
|
|
2884
|
-
break;
|
|
2885
|
-
}
|
|
2886
|
-
}
|
|
2887
|
-
}
|
|
2888
|
-
else {
|
|
2889
|
-
token = false;
|
|
2785
|
+
return null;
|
|
2786
|
+
}
|
|
2787
|
+
function validateType(key, value, descr) {
|
|
2788
|
+
if (value === undefined) {
|
|
2789
|
+
return isOptional(descr) ? null : `'${key}' is undefined (should be a ${describe(descr)})`;
|
|
2790
|
+
}
|
|
2791
|
+
else if (isBaseType(descr)) {
|
|
2792
|
+
return validateBaseType(key, value, descr);
|
|
2793
|
+
}
|
|
2794
|
+
else if (isUnionType(descr)) {
|
|
2795
|
+
let validDescr = descr.find((p) => !validateType(key, value, p));
|
|
2796
|
+
return validDescr ? null : `'${key}' is not a ${describe(descr)}`;
|
|
2797
|
+
}
|
|
2798
|
+
let result = null;
|
|
2799
|
+
if ("element" in descr) {
|
|
2800
|
+
result = validateArrayType(key, value, descr.element);
|
|
2801
|
+
}
|
|
2802
|
+
else if ("shape" in descr && !result) {
|
|
2803
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
2804
|
+
result = `'${key}' is not an object`;
|
|
2805
|
+
}
|
|
2806
|
+
else {
|
|
2807
|
+
const errors = validateSchema(value, descr.shape);
|
|
2808
|
+
if (errors.length) {
|
|
2809
|
+
result = `'${key}' has not the correct shape (${errors.join(", ")})`;
|
|
2890
2810
|
}
|
|
2891
2811
|
}
|
|
2892
2812
|
}
|
|
2893
|
-
|
|
2894
|
-
|
|
2813
|
+
if ("type" in descr && !result) {
|
|
2814
|
+
result = validateType(key, value, descr.type);
|
|
2895
2815
|
}
|
|
2896
|
-
if (
|
|
2897
|
-
|
|
2816
|
+
if ("validate" in descr && !result) {
|
|
2817
|
+
result = !descr.validate(value) ? `'${key}' is not valid` : null;
|
|
2898
2818
|
}
|
|
2899
2819
|
return result;
|
|
2900
|
-
}
|
|
2901
|
-
|
|
2902
|
-
// Expression "evaluator"
|
|
2903
|
-
//------------------------------------------------------------------------------
|
|
2904
|
-
const isLeftSeparator = (token) => token && (token.type === "LEFT_BRACE" || token.type === "COMMA");
|
|
2905
|
-
const isRightSeparator = (token) => token && (token.type === "RIGHT_BRACE" || token.type === "COMMA");
|
|
2820
|
+
}
|
|
2821
|
+
|
|
2906
2822
|
/**
|
|
2907
|
-
* This
|
|
2908
|
-
*
|
|
2909
|
-
* proper lookups in the context.
|
|
2910
|
-
*
|
|
2911
|
-
* Usually, this kind of code would be very simple to do if we had an AST (so,
|
|
2912
|
-
* if we had a javascript parser), since then, we would only need to find the
|
|
2913
|
-
* variables and replace them. However, a parser is more complicated, and there
|
|
2914
|
-
* are no standard builtin parser API.
|
|
2915
|
-
*
|
|
2916
|
-
* Since this method is applied to simple javasript expressions, and the work to
|
|
2917
|
-
* be done is actually quite simple, we actually can get away with not using a
|
|
2918
|
-
* parser, which helps with the code size.
|
|
2919
|
-
*
|
|
2920
|
-
* Here is the heuristic used by this method to determine if a token is a
|
|
2921
|
-
* variable:
|
|
2922
|
-
* - by default, all symbols are considered a variable
|
|
2923
|
-
* - unless the previous token is a dot (in that case, this is a property: `a.b`)
|
|
2924
|
-
* - or if the previous token is a left brace or a comma, and the next token is
|
|
2925
|
-
* a colon (in that case, this is an object key: `{a: b}`)
|
|
2926
|
-
*
|
|
2927
|
-
* Some specific code is also required to support arrow functions. If we detect
|
|
2928
|
-
* the arrow operator, then we add the current (or some previous tokens) token to
|
|
2929
|
-
* the list of variables so it does not get replaced by a lookup in the context
|
|
2823
|
+
* This file contains utility functions that will be injected in each template,
|
|
2824
|
+
* to perform various useful tasks in the compiled code.
|
|
2930
2825
|
*/
|
|
2931
|
-
function
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
stack.pop();
|
|
2949
|
-
}
|
|
2950
|
-
let isVar = token.type === "SYMBOL" && !RESERVED_WORDS.includes(token.value);
|
|
2951
|
-
if (token.type === "SYMBOL" && !RESERVED_WORDS.includes(token.value)) {
|
|
2952
|
-
if (prevToken) {
|
|
2953
|
-
// normalize missing tokens: {a} should be equivalent to {a:a}
|
|
2954
|
-
if (groupType === "LEFT_BRACE" &&
|
|
2955
|
-
isLeftSeparator(prevToken) &&
|
|
2956
|
-
isRightSeparator(nextToken)) {
|
|
2957
|
-
tokens.splice(i + 1, 0, { type: "COLON", value: ":" }, { ...token });
|
|
2958
|
-
nextToken = tokens[i + 1];
|
|
2959
|
-
}
|
|
2960
|
-
if (prevToken.type === "OPERATOR" && prevToken.value === ".") {
|
|
2961
|
-
isVar = false;
|
|
2962
|
-
}
|
|
2963
|
-
else if (prevToken.type === "LEFT_BRACE" || prevToken.type === "COMMA") {
|
|
2964
|
-
if (nextToken && nextToken.type === "COLON") {
|
|
2965
|
-
isVar = false;
|
|
2966
|
-
}
|
|
2967
|
-
}
|
|
2968
|
-
}
|
|
2969
|
-
}
|
|
2970
|
-
if (token.type === "TEMPLATE_STRING") {
|
|
2971
|
-
token.value = token.replace((expr) => compileExpr(expr));
|
|
2972
|
-
}
|
|
2973
|
-
if (nextToken && nextToken.type === "OPERATOR" && nextToken.value === "=>") {
|
|
2974
|
-
if (token.type === "RIGHT_PAREN") {
|
|
2975
|
-
let j = i - 1;
|
|
2976
|
-
while (j > 0 && tokens[j].type !== "LEFT_PAREN") {
|
|
2977
|
-
if (tokens[j].type === "SYMBOL" && tokens[j].originalValue) {
|
|
2978
|
-
tokens[j].value = tokens[j].originalValue;
|
|
2979
|
-
localVars.add(tokens[j].value); //] = { id: tokens[j].value, expr: tokens[j].value };
|
|
2980
|
-
}
|
|
2981
|
-
j--;
|
|
2982
|
-
}
|
|
2983
|
-
}
|
|
2984
|
-
else {
|
|
2985
|
-
localVars.add(token.value); //] = { id: token.value, expr: token.value };
|
|
2986
|
-
}
|
|
2826
|
+
function withDefault(value, defaultValue) {
|
|
2827
|
+
return value === undefined || value === null || value === false ? defaultValue : value;
|
|
2828
|
+
}
|
|
2829
|
+
function callSlot(ctx, parent, key, name, dynamic, extra, defaultContent) {
|
|
2830
|
+
key = key + "__slot_" + name;
|
|
2831
|
+
const slots = ctx.props.slots || {};
|
|
2832
|
+
const { __render, __ctx, __scope } = slots[name] || {};
|
|
2833
|
+
const slotScope = Object.create(__ctx || {});
|
|
2834
|
+
if (__scope) {
|
|
2835
|
+
slotScope[__scope] = extra;
|
|
2836
|
+
}
|
|
2837
|
+
const slotBDom = __render ? __render.call(__ctx.__owl__.component, slotScope, parent, key) : null;
|
|
2838
|
+
if (defaultContent) {
|
|
2839
|
+
let child1 = undefined;
|
|
2840
|
+
let child2 = undefined;
|
|
2841
|
+
if (slotBDom) {
|
|
2842
|
+
child1 = dynamic ? toggler(name, slotBDom) : slotBDom;
|
|
2987
2843
|
}
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
if (!localVars.has(token.value)) {
|
|
2991
|
-
token.originalValue = token.value;
|
|
2992
|
-
token.value = `ctx['${token.value}']`;
|
|
2993
|
-
}
|
|
2844
|
+
else {
|
|
2845
|
+
child2 = defaultContent.call(ctx.__owl__.component, ctx, parent, key);
|
|
2994
2846
|
}
|
|
2995
|
-
|
|
2847
|
+
return multi([child1, child2]);
|
|
2996
2848
|
}
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
}
|
|
2849
|
+
return slotBDom || text("");
|
|
2850
|
+
}
|
|
2851
|
+
function capture(ctx) {
|
|
2852
|
+
const component = ctx.__owl__.component;
|
|
2853
|
+
const result = Object.create(component);
|
|
2854
|
+
for (let k in ctx) {
|
|
2855
|
+
result[k] = ctx[k];
|
|
3005
2856
|
}
|
|
3006
|
-
return
|
|
2857
|
+
return result;
|
|
3007
2858
|
}
|
|
3008
|
-
function
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
.join("");
|
|
2859
|
+
function withKey(elem, k) {
|
|
2860
|
+
elem.key = k;
|
|
2861
|
+
return elem;
|
|
3012
2862
|
}
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
}
|
|
3020
|
-
let r = s.replace(INTERP_GROUP_REGEXP, (s) => "${" + compileExpr(s.slice(2, -2)) + "}");
|
|
3021
|
-
return "`" + r + "`";
|
|
3022
|
-
}
|
|
3023
|
-
|
|
3024
|
-
// using a non-html document so that <inner/outer>HTML serializes as XML instead
|
|
3025
|
-
// of HTML (as we will parse it as xml later)
|
|
3026
|
-
const xmlDoc = document.implementation.createDocument(null, null, null);
|
|
3027
|
-
const MODS = new Set(["stop", "capture", "prevent", "self", "synthetic"]);
|
|
3028
|
-
// -----------------------------------------------------------------------------
|
|
3029
|
-
// BlockDescription
|
|
3030
|
-
// -----------------------------------------------------------------------------
|
|
3031
|
-
class BlockDescription {
|
|
3032
|
-
constructor(target, type) {
|
|
3033
|
-
this.dynamicTagName = null;
|
|
3034
|
-
this.isRoot = false;
|
|
3035
|
-
this.hasDynamicChildren = false;
|
|
3036
|
-
this.children = [];
|
|
3037
|
-
this.data = [];
|
|
3038
|
-
this.childNumber = 0;
|
|
3039
|
-
this.parentVar = "";
|
|
3040
|
-
this.id = BlockDescription.nextBlockId++;
|
|
3041
|
-
this.varName = "b" + this.id;
|
|
3042
|
-
this.blockName = "block" + this.id;
|
|
3043
|
-
this.target = target;
|
|
3044
|
-
this.type = type;
|
|
2863
|
+
function prepareList(collection) {
|
|
2864
|
+
let keys;
|
|
2865
|
+
let values;
|
|
2866
|
+
if (Array.isArray(collection)) {
|
|
2867
|
+
keys = collection;
|
|
2868
|
+
values = collection;
|
|
3045
2869
|
}
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
2870
|
+
else if (collection) {
|
|
2871
|
+
values = Object.keys(collection);
|
|
2872
|
+
keys = Object.values(collection);
|
|
3049
2873
|
}
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
this.target.addLine(`let ${id} = ${str};`);
|
|
3053
|
-
return this.data.push(id) - 1;
|
|
2874
|
+
else {
|
|
2875
|
+
throw new Error("Invalid loop expression");
|
|
3054
2876
|
}
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
2877
|
+
const n = values.length;
|
|
2878
|
+
return [keys, values, n, new Array(n)];
|
|
2879
|
+
}
|
|
2880
|
+
const isBoundary = Symbol("isBoundary");
|
|
2881
|
+
function setContextValue(ctx, key, value) {
|
|
2882
|
+
const ctx0 = ctx;
|
|
2883
|
+
while (!ctx.hasOwnProperty(key) && !ctx.hasOwnProperty(isBoundary)) {
|
|
2884
|
+
const newCtx = ctx.__proto__;
|
|
2885
|
+
if (!newCtx) {
|
|
2886
|
+
ctx = ctx0;
|
|
2887
|
+
break;
|
|
3061
2888
|
}
|
|
2889
|
+
ctx = newCtx;
|
|
3062
2890
|
}
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
return `${this.blockName}(${params})`;
|
|
3074
|
-
}
|
|
3075
|
-
else if (this.type === "list") {
|
|
3076
|
-
return `list(c_block${this.id})`;
|
|
2891
|
+
ctx[key] = value;
|
|
2892
|
+
}
|
|
2893
|
+
function toNumber(val) {
|
|
2894
|
+
const n = parseFloat(val);
|
|
2895
|
+
return isNaN(n) ? val : n;
|
|
2896
|
+
}
|
|
2897
|
+
function shallowEqual(l1, l2) {
|
|
2898
|
+
for (let i = 0, l = l1.length; i < l; i++) {
|
|
2899
|
+
if (l1[i] !== l2[i]) {
|
|
2900
|
+
return false;
|
|
3077
2901
|
}
|
|
3078
|
-
return expr;
|
|
3079
2902
|
}
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
2903
|
+
return true;
|
|
2904
|
+
}
|
|
2905
|
+
class LazyValue {
|
|
2906
|
+
constructor(fn, ctx, node) {
|
|
2907
|
+
this.fn = fn;
|
|
2908
|
+
this.ctx = capture(ctx);
|
|
2909
|
+
this.node = node;
|
|
2910
|
+
}
|
|
2911
|
+
evaluate() {
|
|
2912
|
+
return this.fn(this.ctx, this.node);
|
|
2913
|
+
}
|
|
2914
|
+
toString() {
|
|
2915
|
+
return this.evaluate().toString();
|
|
3086
2916
|
}
|
|
3087
2917
|
}
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
2918
|
+
/*
|
|
2919
|
+
* Safely outputs `value` as a block depending on the nature of `value`
|
|
2920
|
+
*/
|
|
2921
|
+
function safeOutput(value) {
|
|
2922
|
+
if (!value) {
|
|
2923
|
+
return value;
|
|
2924
|
+
}
|
|
2925
|
+
let safeKey;
|
|
2926
|
+
let block;
|
|
2927
|
+
if (value instanceof Markup) {
|
|
2928
|
+
safeKey = `string_safe`;
|
|
2929
|
+
block = html(value);
|
|
2930
|
+
}
|
|
2931
|
+
else if (value instanceof LazyValue) {
|
|
2932
|
+
safeKey = `lazy_value`;
|
|
2933
|
+
block = value.evaluate();
|
|
2934
|
+
}
|
|
2935
|
+
else if (value instanceof String || typeof value === "string") {
|
|
2936
|
+
safeKey = "string_unsafe";
|
|
2937
|
+
block = text(value);
|
|
2938
|
+
}
|
|
2939
|
+
else {
|
|
2940
|
+
// Assuming it is a block
|
|
2941
|
+
safeKey = "block_safe";
|
|
2942
|
+
block = value;
|
|
2943
|
+
}
|
|
2944
|
+
return toggler(safeKey, block);
|
|
3100
2945
|
}
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
this.hasRef = false;
|
|
3109
|
-
// maps ref name to [id, expr]
|
|
3110
|
-
this.refInfo = {};
|
|
3111
|
-
this.shouldProtectScope = false;
|
|
3112
|
-
this.name = name;
|
|
3113
|
-
this.on = on || null;
|
|
2946
|
+
let boundFunctions = new WeakMap();
|
|
2947
|
+
function bind(ctx, fn) {
|
|
2948
|
+
let component = ctx.__owl__.component;
|
|
2949
|
+
let boundFnMap = boundFunctions.get(component);
|
|
2950
|
+
if (!boundFnMap) {
|
|
2951
|
+
boundFnMap = new WeakMap();
|
|
2952
|
+
boundFunctions.set(component, boundFnMap);
|
|
3114
2953
|
}
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
}
|
|
3120
|
-
else {
|
|
3121
|
-
this.code.splice(idx, 0, prefix + line);
|
|
3122
|
-
}
|
|
2954
|
+
let boundFn = boundFnMap.get(fn);
|
|
2955
|
+
if (!boundFn) {
|
|
2956
|
+
boundFn = fn.bind(component);
|
|
2957
|
+
boundFnMap.set(fn, boundFn);
|
|
3123
2958
|
}
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
2959
|
+
return boundFn;
|
|
2960
|
+
}
|
|
2961
|
+
function multiRefSetter(refs, name) {
|
|
2962
|
+
let count = 0;
|
|
2963
|
+
return (el) => {
|
|
2964
|
+
if (el) {
|
|
2965
|
+
count++;
|
|
2966
|
+
if (count > 1) {
|
|
2967
|
+
throw new Error("Cannot have 2 elements with same ref name at the same time");
|
|
3132
2968
|
}
|
|
3133
2969
|
}
|
|
3134
|
-
if (
|
|
3135
|
-
|
|
3136
|
-
result.push(` ctx[isBoundary] = 1`);
|
|
3137
|
-
}
|
|
3138
|
-
if (this.hasCache) {
|
|
3139
|
-
result.push(` let cache = ctx.cache || {};`);
|
|
3140
|
-
result.push(` let nextCache = ctx.cache = {};`);
|
|
2970
|
+
if (count === 0 || el) {
|
|
2971
|
+
refs[name] = el;
|
|
3141
2972
|
}
|
|
3142
|
-
|
|
3143
|
-
|
|
2973
|
+
};
|
|
2974
|
+
}
|
|
2975
|
+
/**
|
|
2976
|
+
* Validate the component props (or next props) against the (static) props
|
|
2977
|
+
* description. This is potentially an expensive operation: it may needs to
|
|
2978
|
+
* visit recursively the props and all the children to check if they are valid.
|
|
2979
|
+
* This is why it is only done in 'dev' mode.
|
|
2980
|
+
*/
|
|
2981
|
+
function validateProps(name, props, parent) {
|
|
2982
|
+
const ComponentClass = typeof name !== "string"
|
|
2983
|
+
? name
|
|
2984
|
+
: parent.constructor.components[name];
|
|
2985
|
+
if (!ComponentClass) {
|
|
2986
|
+
// this is an error, wrong component. We silently return here instead so the
|
|
2987
|
+
// error is triggered by the usual path ('component' function)
|
|
2988
|
+
return;
|
|
2989
|
+
}
|
|
2990
|
+
const schema = ComponentClass.props;
|
|
2991
|
+
if (!schema) {
|
|
2992
|
+
if (parent.__owl__.app.warnIfNoStaticProps) {
|
|
2993
|
+
console.warn(`Component '${ComponentClass.name}' does not have a static props description`);
|
|
3144
2994
|
}
|
|
3145
|
-
|
|
3146
|
-
|
|
2995
|
+
return;
|
|
2996
|
+
}
|
|
2997
|
+
const defaultProps = ComponentClass.defaultProps;
|
|
2998
|
+
if (defaultProps) {
|
|
2999
|
+
let isMandatory = (name) => Array.isArray(schema)
|
|
3000
|
+
? schema.includes(name)
|
|
3001
|
+
: name in schema && !("*" in schema) && !isOptional(schema[name]);
|
|
3002
|
+
for (let p in defaultProps) {
|
|
3003
|
+
if (isMandatory(p)) {
|
|
3004
|
+
throw new Error(`A default value cannot be defined for a mandatory prop (name: '${p}', component: ${ComponentClass.name})`);
|
|
3005
|
+
}
|
|
3147
3006
|
}
|
|
3148
|
-
|
|
3149
|
-
|
|
3007
|
+
}
|
|
3008
|
+
const errors = validateSchema(props, schema);
|
|
3009
|
+
if (errors.length) {
|
|
3010
|
+
throw new Error(`Invalid props for component '${ComponentClass.name}': ` + errors.join(", "));
|
|
3150
3011
|
}
|
|
3151
3012
|
}
|
|
3152
|
-
const
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3013
|
+
const helpers = {
|
|
3014
|
+
withDefault,
|
|
3015
|
+
zero: Symbol("zero"),
|
|
3016
|
+
isBoundary,
|
|
3017
|
+
callSlot,
|
|
3018
|
+
capture,
|
|
3019
|
+
withKey,
|
|
3020
|
+
prepareList,
|
|
3021
|
+
setContextValue,
|
|
3022
|
+
multiRefSetter,
|
|
3023
|
+
shallowEqual,
|
|
3024
|
+
toNumber,
|
|
3025
|
+
validateProps,
|
|
3026
|
+
LazyValue,
|
|
3027
|
+
safeOutput,
|
|
3028
|
+
bind,
|
|
3029
|
+
createCatcher,
|
|
3030
|
+
markRaw,
|
|
3031
|
+
};
|
|
3032
|
+
|
|
3033
|
+
const bdom = { text, createBlock, list, multi, html, toggler, component, comment };
|
|
3034
|
+
function parseXML$1(xml) {
|
|
3035
|
+
const parser = new DOMParser();
|
|
3036
|
+
const doc = parser.parseFromString(xml, "text/xml");
|
|
3037
|
+
if (doc.getElementsByTagName("parsererror").length) {
|
|
3038
|
+
let msg = "Invalid XML in template.";
|
|
3039
|
+
const parsererrorText = doc.getElementsByTagName("parsererror")[0].textContent;
|
|
3040
|
+
if (parsererrorText) {
|
|
3041
|
+
msg += "\nThe parser has produced the following error message:\n" + parsererrorText;
|
|
3042
|
+
const re = /\d+/g;
|
|
3043
|
+
const firstMatch = re.exec(parsererrorText);
|
|
3044
|
+
if (firstMatch) {
|
|
3045
|
+
const lineNumber = Number(firstMatch[0]);
|
|
3046
|
+
const line = xml.split("\n")[lineNumber - 1];
|
|
3047
|
+
const secondMatch = re.exec(parsererrorText);
|
|
3048
|
+
if (line && secondMatch) {
|
|
3049
|
+
const columnIndex = Number(secondMatch[0]) - 1;
|
|
3050
|
+
if (line[columnIndex]) {
|
|
3051
|
+
msg +=
|
|
3052
|
+
`\nThe error might be located at xml line ${lineNumber} column ${columnIndex}\n` +
|
|
3053
|
+
`${line}\n${"-".repeat(columnIndex - 1)}^`;
|
|
3054
|
+
}
|
|
3174
3055
|
}
|
|
3175
3056
|
}
|
|
3176
|
-
this.translatableAttributes = [...attrs];
|
|
3177
3057
|
}
|
|
3178
|
-
|
|
3179
|
-
this.dev = options.dev || false;
|
|
3180
|
-
this.ast = ast;
|
|
3181
|
-
this.templateName = options.name;
|
|
3058
|
+
throw new Error(msg);
|
|
3182
3059
|
}
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
this.
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
});
|
|
3196
|
-
// define blocks and utility functions
|
|
3197
|
-
let mainCode = [
|
|
3198
|
-
` let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;`,
|
|
3199
|
-
];
|
|
3200
|
-
if (this.helpers.size) {
|
|
3201
|
-
mainCode.push(`let { ${[...this.helpers].join(", ")} } = helpers;`);
|
|
3202
|
-
}
|
|
3203
|
-
if (this.templateName) {
|
|
3204
|
-
mainCode.push(`// Template name: "${this.templateName}"`);
|
|
3205
|
-
}
|
|
3206
|
-
for (let { id, expr } of this.staticDefs) {
|
|
3207
|
-
mainCode.push(`const ${id} = ${expr};`);
|
|
3208
|
-
}
|
|
3209
|
-
// define all blocks
|
|
3210
|
-
if (this.blocks.length) {
|
|
3211
|
-
mainCode.push(``);
|
|
3212
|
-
for (let block of this.blocks) {
|
|
3213
|
-
if (block.dom) {
|
|
3214
|
-
let xmlString = block.asXmlString();
|
|
3215
|
-
if (block.dynamicTagName) {
|
|
3216
|
-
xmlString = xmlString.replace(/^<\w+/, `<\${tag || '${block.dom.nodeName}'}`);
|
|
3217
|
-
xmlString = xmlString.replace(/\w+>$/, `\${tag || '${block.dom.nodeName}'}>`);
|
|
3218
|
-
mainCode.push(`let ${block.blockName} = tag => createBlock(\`${xmlString}\`);`);
|
|
3219
|
-
}
|
|
3220
|
-
else {
|
|
3221
|
-
mainCode.push(`let ${block.blockName} = createBlock(\`${xmlString}\`);`);
|
|
3222
|
-
}
|
|
3223
|
-
}
|
|
3224
|
-
}
|
|
3060
|
+
return doc;
|
|
3061
|
+
}
|
|
3062
|
+
class TemplateSet {
|
|
3063
|
+
constructor(config = {}) {
|
|
3064
|
+
this.rawTemplates = Object.create(globalTemplates);
|
|
3065
|
+
this.templates = {};
|
|
3066
|
+
this.Portal = Portal;
|
|
3067
|
+
this.dev = config.dev || false;
|
|
3068
|
+
this.translateFn = config.translateFn;
|
|
3069
|
+
this.translatableAttributes = config.translatableAttributes;
|
|
3070
|
+
if (config.templates) {
|
|
3071
|
+
this.addTemplates(config.templates);
|
|
3225
3072
|
}
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3073
|
+
}
|
|
3074
|
+
static registerTemplate(name, fn) {
|
|
3075
|
+
globalTemplates[name] = fn;
|
|
3076
|
+
}
|
|
3077
|
+
addTemplate(name, template) {
|
|
3078
|
+
if (name in this.rawTemplates) {
|
|
3079
|
+
const rawTemplate = this.rawTemplates[name];
|
|
3080
|
+
const currentAsString = typeof rawTemplate === "string"
|
|
3081
|
+
? rawTemplate
|
|
3082
|
+
: rawTemplate instanceof Element
|
|
3083
|
+
? rawTemplate.outerHTML
|
|
3084
|
+
: rawTemplate.toString();
|
|
3085
|
+
const newAsString = typeof template === "string" ? template : template.outerHTML;
|
|
3086
|
+
if (currentAsString === newAsString) {
|
|
3087
|
+
return;
|
|
3231
3088
|
}
|
|
3089
|
+
throw new Error(`Template ${name} already defined with different content`);
|
|
3232
3090
|
}
|
|
3233
|
-
|
|
3234
|
-
mainCode.push("");
|
|
3235
|
-
mainCode = mainCode.concat("return " + this.target.generateCode());
|
|
3236
|
-
const code = mainCode.join("\n ");
|
|
3237
|
-
if (this.isDebug) {
|
|
3238
|
-
const msg = `[Owl Debug]\n${code}`;
|
|
3239
|
-
console.log(msg);
|
|
3240
|
-
}
|
|
3241
|
-
return code;
|
|
3091
|
+
this.rawTemplates[name] = template;
|
|
3242
3092
|
}
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3093
|
+
addTemplates(xml) {
|
|
3094
|
+
if (!xml) {
|
|
3095
|
+
// empty string
|
|
3096
|
+
return;
|
|
3097
|
+
}
|
|
3098
|
+
xml = xml instanceof Document ? xml : parseXML$1(xml);
|
|
3099
|
+
for (const template of xml.querySelectorAll("[t-name]")) {
|
|
3100
|
+
const name = template.getAttribute("t-name");
|
|
3101
|
+
this.addTemplate(name, template);
|
|
3102
|
+
}
|
|
3252
3103
|
}
|
|
3253
|
-
|
|
3254
|
-
this.
|
|
3104
|
+
getTemplate(name) {
|
|
3105
|
+
if (!(name in this.templates)) {
|
|
3106
|
+
const rawTemplate = this.rawTemplates[name];
|
|
3107
|
+
if (rawTemplate === undefined) {
|
|
3108
|
+
let extraInfo = "";
|
|
3109
|
+
try {
|
|
3110
|
+
const componentName = getCurrent().component.constructor.name;
|
|
3111
|
+
extraInfo = ` (for component "${componentName}")`;
|
|
3112
|
+
}
|
|
3113
|
+
catch { }
|
|
3114
|
+
throw new Error(`Missing template: "${name}"${extraInfo}`);
|
|
3115
|
+
}
|
|
3116
|
+
const isFn = typeof rawTemplate === "function" && !(rawTemplate instanceof Element);
|
|
3117
|
+
const templateFn = isFn ? rawTemplate : this._compileTemplate(name, rawTemplate);
|
|
3118
|
+
// first add a function to lazily get the template, in case there is a
|
|
3119
|
+
// recursive call to the template name
|
|
3120
|
+
const templates = this.templates;
|
|
3121
|
+
this.templates[name] = function (context, parent) {
|
|
3122
|
+
return templates[name].call(this, context, parent);
|
|
3123
|
+
};
|
|
3124
|
+
const template = templateFn(this, bdom, helpers);
|
|
3125
|
+
this.templates[name] = template;
|
|
3126
|
+
}
|
|
3127
|
+
return this.templates[name];
|
|
3255
3128
|
}
|
|
3256
|
-
|
|
3257
|
-
|
|
3129
|
+
_compileTemplate(name, template) {
|
|
3130
|
+
throw new Error(`Unable to compile a template. Please use owl full build instead`);
|
|
3258
3131
|
}
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
return
|
|
3132
|
+
callTemplate(owner, subTemplate, ctx, parent, key) {
|
|
3133
|
+
const template = this.getTemplate(subTemplate);
|
|
3134
|
+
return toggler(subTemplate, template.call(owner, ctx, parent, key));
|
|
3262
3135
|
}
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3136
|
+
}
|
|
3137
|
+
// -----------------------------------------------------------------------------
|
|
3138
|
+
// xml tag helper
|
|
3139
|
+
// -----------------------------------------------------------------------------
|
|
3140
|
+
const globalTemplates = {};
|
|
3141
|
+
function xml(...args) {
|
|
3142
|
+
const name = `__template__${xml.nextId++}`;
|
|
3143
|
+
const value = String.raw(...args);
|
|
3144
|
+
globalTemplates[name] = value;
|
|
3145
|
+
return name;
|
|
3146
|
+
}
|
|
3147
|
+
xml.nextId = 1;
|
|
3148
|
+
TemplateSet.registerTemplate("__portal__", portalTemplate);
|
|
3149
|
+
|
|
3150
|
+
/**
|
|
3151
|
+
* Owl QWeb Expression Parser
|
|
3152
|
+
*
|
|
3153
|
+
* Owl needs in various contexts to be able to understand the structure of a
|
|
3154
|
+
* string representing a javascript expression. The usual goal is to be able
|
|
3155
|
+
* to rewrite some variables. For example, if a template has
|
|
3156
|
+
*
|
|
3157
|
+
* ```xml
|
|
3158
|
+
* <t t-if="computeSomething({val: state.val})">...</t>
|
|
3159
|
+
* ```
|
|
3160
|
+
*
|
|
3161
|
+
* this needs to be translated in something like this:
|
|
3162
|
+
*
|
|
3163
|
+
* ```js
|
|
3164
|
+
* if (context["computeSomething"]({val: context["state"].val})) { ... }
|
|
3165
|
+
* ```
|
|
3166
|
+
*
|
|
3167
|
+
* This file contains the implementation of an extremely naive tokenizer/parser
|
|
3168
|
+
* and evaluator for javascript expressions. The supported grammar is basically
|
|
3169
|
+
* only expressive enough to understand the shape of objects, of arrays, and
|
|
3170
|
+
* various operators.
|
|
3171
|
+
*/
|
|
3172
|
+
//------------------------------------------------------------------------------
|
|
3173
|
+
// Misc types, constants and helpers
|
|
3174
|
+
//------------------------------------------------------------------------------
|
|
3175
|
+
const RESERVED_WORDS = "true,false,NaN,null,undefined,debugger,console,window,in,instanceof,new,function,return,this,eval,void,Math,RegExp,Array,Object,Date".split(",");
|
|
3176
|
+
const WORD_REPLACEMENT = Object.assign(Object.create(null), {
|
|
3177
|
+
and: "&&",
|
|
3178
|
+
or: "||",
|
|
3179
|
+
gt: ">",
|
|
3180
|
+
gte: ">=",
|
|
3181
|
+
lt: "<",
|
|
3182
|
+
lte: "<=",
|
|
3183
|
+
});
|
|
3184
|
+
const STATIC_TOKEN_MAP = Object.assign(Object.create(null), {
|
|
3185
|
+
"{": "LEFT_BRACE",
|
|
3186
|
+
"}": "RIGHT_BRACE",
|
|
3187
|
+
"[": "LEFT_BRACKET",
|
|
3188
|
+
"]": "RIGHT_BRACKET",
|
|
3189
|
+
":": "COLON",
|
|
3190
|
+
",": "COMMA",
|
|
3191
|
+
"(": "LEFT_PAREN",
|
|
3192
|
+
")": "RIGHT_PAREN",
|
|
3193
|
+
});
|
|
3194
|
+
// note that the space after typeof is relevant. It makes sure that the formatted
|
|
3195
|
+
// expression has a space after typeof. Currently we don't support delete and void
|
|
3196
|
+
const OPERATORS = "...,.,===,==,+,!==,!=,!,||,&&,>=,>,<=,<,?,-,*,/,%,typeof ,=>,=,;,in ,new ".split(",");
|
|
3197
|
+
let tokenizeString = function (expr) {
|
|
3198
|
+
let s = expr[0];
|
|
3199
|
+
let start = s;
|
|
3200
|
+
if (s !== "'" && s !== '"' && s !== "`") {
|
|
3201
|
+
return false;
|
|
3267
3202
|
}
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
block.parentVar = `c_block${parentBlock.id}`;
|
|
3203
|
+
let i = 1;
|
|
3204
|
+
let cur;
|
|
3205
|
+
while (expr[i] && expr[i] !== start) {
|
|
3206
|
+
cur = expr[i];
|
|
3207
|
+
s += cur;
|
|
3208
|
+
if (cur === "\\") {
|
|
3209
|
+
i++;
|
|
3210
|
+
cur = expr[i];
|
|
3211
|
+
if (!cur) {
|
|
3212
|
+
throw new Error("Invalid expression");
|
|
3279
3213
|
}
|
|
3214
|
+
s += cur;
|
|
3280
3215
|
}
|
|
3281
|
-
|
|
3216
|
+
i++;
|
|
3282
3217
|
}
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3218
|
+
if (expr[i] !== start) {
|
|
3219
|
+
throw new Error("Invalid expression");
|
|
3220
|
+
}
|
|
3221
|
+
s += start;
|
|
3222
|
+
if (start === "`") {
|
|
3223
|
+
return {
|
|
3224
|
+
type: "TEMPLATE_STRING",
|
|
3225
|
+
value: s,
|
|
3226
|
+
replace(replacer) {
|
|
3227
|
+
return s.replace(/\$\{(.*?)\}/g, (match, group) => {
|
|
3228
|
+
return "${" + replacer(group) + "}";
|
|
3229
|
+
});
|
|
3230
|
+
},
|
|
3231
|
+
};
|
|
3232
|
+
}
|
|
3233
|
+
return { type: "VALUE", value: s };
|
|
3234
|
+
};
|
|
3235
|
+
let tokenizeNumber = function (expr) {
|
|
3236
|
+
let s = expr[0];
|
|
3237
|
+
if (s && s.match(/[0-9]/)) {
|
|
3238
|
+
let i = 1;
|
|
3239
|
+
while (expr[i] && expr[i].match(/[0-9]|\./)) {
|
|
3240
|
+
s += expr[i];
|
|
3241
|
+
i++;
|
|
3294
3242
|
}
|
|
3295
|
-
|
|
3296
|
-
|
|
3243
|
+
return { type: "VALUE", value: s };
|
|
3244
|
+
}
|
|
3245
|
+
else {
|
|
3246
|
+
return false;
|
|
3247
|
+
}
|
|
3248
|
+
};
|
|
3249
|
+
let tokenizeSymbol = function (expr) {
|
|
3250
|
+
let s = expr[0];
|
|
3251
|
+
if (s && s.match(/[a-zA-Z_\$]/)) {
|
|
3252
|
+
let i = 1;
|
|
3253
|
+
while (expr[i] && expr[i].match(/\w/)) {
|
|
3254
|
+
s += expr[i];
|
|
3255
|
+
i++;
|
|
3297
3256
|
}
|
|
3298
|
-
if (
|
|
3299
|
-
|
|
3300
|
-
blockExpr = this.wrapWithEventCatcher(blockExpr, this.target.on);
|
|
3301
|
-
}
|
|
3302
|
-
this.addLine(`return ${blockExpr};`);
|
|
3257
|
+
if (s in WORD_REPLACEMENT) {
|
|
3258
|
+
return { type: "OPERATOR", value: WORD_REPLACEMENT[s], size: s.length };
|
|
3303
3259
|
}
|
|
3304
|
-
|
|
3305
|
-
|
|
3260
|
+
return { type: "SYMBOL", value: s };
|
|
3261
|
+
}
|
|
3262
|
+
else {
|
|
3263
|
+
return false;
|
|
3264
|
+
}
|
|
3265
|
+
};
|
|
3266
|
+
const tokenizeStatic = function (expr) {
|
|
3267
|
+
const char = expr[0];
|
|
3268
|
+
if (char && char in STATIC_TOKEN_MAP) {
|
|
3269
|
+
return { type: STATIC_TOKEN_MAP[char], value: char };
|
|
3270
|
+
}
|
|
3271
|
+
return false;
|
|
3272
|
+
};
|
|
3273
|
+
const tokenizeOperator = function (expr) {
|
|
3274
|
+
for (let op of OPERATORS) {
|
|
3275
|
+
if (expr.startsWith(op)) {
|
|
3276
|
+
return { type: "OPERATOR", value: op };
|
|
3306
3277
|
}
|
|
3307
3278
|
}
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3279
|
+
return false;
|
|
3280
|
+
};
|
|
3281
|
+
const TOKENIZERS = [
|
|
3282
|
+
tokenizeString,
|
|
3283
|
+
tokenizeNumber,
|
|
3284
|
+
tokenizeOperator,
|
|
3285
|
+
tokenizeSymbol,
|
|
3286
|
+
tokenizeStatic,
|
|
3287
|
+
];
|
|
3288
|
+
/**
|
|
3289
|
+
* Convert a javascript expression (as a string) into a list of tokens. For
|
|
3290
|
+
* example: `tokenize("1 + b")` will return:
|
|
3291
|
+
* ```js
|
|
3292
|
+
* [
|
|
3293
|
+
* {type: "VALUE", value: "1"},
|
|
3294
|
+
* {type: "OPERATOR", value: "+"},
|
|
3295
|
+
* {type: "SYMBOL", value: "b"}
|
|
3296
|
+
* ]
|
|
3297
|
+
* ```
|
|
3298
|
+
*/
|
|
3299
|
+
function tokenize(expr) {
|
|
3300
|
+
const result = [];
|
|
3301
|
+
let token = true;
|
|
3302
|
+
let error;
|
|
3303
|
+
let current = expr;
|
|
3304
|
+
try {
|
|
3305
|
+
while (token) {
|
|
3306
|
+
current = current.trim();
|
|
3307
|
+
if (current) {
|
|
3308
|
+
for (let tokenizer of TOKENIZERS) {
|
|
3309
|
+
token = tokenizer(current);
|
|
3310
|
+
if (token) {
|
|
3311
|
+
result.push(token);
|
|
3312
|
+
current = current.slice(token.size || token.value.length);
|
|
3313
|
+
break;
|
|
3314
|
+
}
|
|
3315
|
+
}
|
|
3316
|
+
}
|
|
3317
|
+
else {
|
|
3318
|
+
token = false;
|
|
3319
|
+
}
|
|
3320
|
+
}
|
|
3321
|
+
}
|
|
3322
|
+
catch (e) {
|
|
3323
|
+
error = e; // Silence all errors and throw a generic error below
|
|
3324
|
+
}
|
|
3325
|
+
if (current.length || error) {
|
|
3326
|
+
throw new Error(`Tokenizer error: could not tokenize \`${expr}\``);
|
|
3327
|
+
}
|
|
3328
|
+
return result;
|
|
3329
|
+
}
|
|
3330
|
+
//------------------------------------------------------------------------------
|
|
3331
|
+
// Expression "evaluator"
|
|
3332
|
+
//------------------------------------------------------------------------------
|
|
3333
|
+
const isLeftSeparator = (token) => token && (token.type === "LEFT_BRACE" || token.type === "COMMA");
|
|
3334
|
+
const isRightSeparator = (token) => token && (token.type === "RIGHT_BRACE" || token.type === "COMMA");
|
|
3335
|
+
/**
|
|
3336
|
+
* This is the main function exported by this file. This is the code that will
|
|
3337
|
+
* process an expression (given as a string) and returns another expression with
|
|
3338
|
+
* proper lookups in the context.
|
|
3339
|
+
*
|
|
3340
|
+
* Usually, this kind of code would be very simple to do if we had an AST (so,
|
|
3341
|
+
* if we had a javascript parser), since then, we would only need to find the
|
|
3342
|
+
* variables and replace them. However, a parser is more complicated, and there
|
|
3343
|
+
* are no standard builtin parser API.
|
|
3344
|
+
*
|
|
3345
|
+
* Since this method is applied to simple javasript expressions, and the work to
|
|
3346
|
+
* be done is actually quite simple, we actually can get away with not using a
|
|
3347
|
+
* parser, which helps with the code size.
|
|
3348
|
+
*
|
|
3349
|
+
* Here is the heuristic used by this method to determine if a token is a
|
|
3350
|
+
* variable:
|
|
3351
|
+
* - by default, all symbols are considered a variable
|
|
3352
|
+
* - unless the previous token is a dot (in that case, this is a property: `a.b`)
|
|
3353
|
+
* - or if the previous token is a left brace or a comma, and the next token is
|
|
3354
|
+
* a colon (in that case, this is an object key: `{a: b}`)
|
|
3355
|
+
*
|
|
3356
|
+
* Some specific code is also required to support arrow functions. If we detect
|
|
3357
|
+
* the arrow operator, then we add the current (or some previous tokens) token to
|
|
3358
|
+
* the list of variables so it does not get replaced by a lookup in the context
|
|
3359
|
+
*/
|
|
3360
|
+
function compileExprToArray(expr) {
|
|
3361
|
+
const localVars = new Set();
|
|
3362
|
+
const tokens = tokenize(expr);
|
|
3363
|
+
let i = 0;
|
|
3364
|
+
let stack = []; // to track last opening [ or {
|
|
3365
|
+
while (i < tokens.length) {
|
|
3366
|
+
let token = tokens[i];
|
|
3367
|
+
let prevToken = tokens[i - 1];
|
|
3368
|
+
let nextToken = tokens[i + 1];
|
|
3369
|
+
let groupType = stack[stack.length - 1];
|
|
3370
|
+
switch (token.type) {
|
|
3371
|
+
case "LEFT_BRACE":
|
|
3372
|
+
case "LEFT_BRACKET":
|
|
3373
|
+
stack.push(token.type);
|
|
3374
|
+
break;
|
|
3375
|
+
case "RIGHT_BRACE":
|
|
3376
|
+
case "RIGHT_BRACKET":
|
|
3377
|
+
stack.pop();
|
|
3378
|
+
}
|
|
3379
|
+
let isVar = token.type === "SYMBOL" && !RESERVED_WORDS.includes(token.value);
|
|
3380
|
+
if (token.type === "SYMBOL" && !RESERVED_WORDS.includes(token.value)) {
|
|
3381
|
+
if (prevToken) {
|
|
3382
|
+
// normalize missing tokens: {a} should be equivalent to {a:a}
|
|
3383
|
+
if (groupType === "LEFT_BRACE" &&
|
|
3384
|
+
isLeftSeparator(prevToken) &&
|
|
3385
|
+
isRightSeparator(nextToken)) {
|
|
3386
|
+
tokens.splice(i + 1, 0, { type: "COLON", value: ":" }, { ...token });
|
|
3387
|
+
nextToken = tokens[i + 1];
|
|
3388
|
+
}
|
|
3389
|
+
if (prevToken.type === "OPERATOR" && prevToken.value === ".") {
|
|
3390
|
+
isVar = false;
|
|
3391
|
+
}
|
|
3392
|
+
else if (prevToken.type === "LEFT_BRACE" || prevToken.type === "COMMA") {
|
|
3393
|
+
if (nextToken && nextToken.type === "COLON") {
|
|
3394
|
+
isVar = false;
|
|
3395
|
+
}
|
|
3396
|
+
}
|
|
3397
|
+
}
|
|
3398
|
+
}
|
|
3399
|
+
if (token.type === "TEMPLATE_STRING") {
|
|
3400
|
+
token.value = token.replace((expr) => compileExpr(expr));
|
|
3401
|
+
}
|
|
3402
|
+
if (nextToken && nextToken.type === "OPERATOR" && nextToken.value === "=>") {
|
|
3403
|
+
if (token.type === "RIGHT_PAREN") {
|
|
3404
|
+
let j = i - 1;
|
|
3405
|
+
while (j > 0 && tokens[j].type !== "LEFT_PAREN") {
|
|
3406
|
+
if (tokens[j].type === "SYMBOL" && tokens[j].originalValue) {
|
|
3407
|
+
tokens[j].value = tokens[j].originalValue;
|
|
3408
|
+
localVars.add(tokens[j].value); //] = { id: tokens[j].value, expr: tokens[j].value };
|
|
3409
|
+
}
|
|
3410
|
+
j--;
|
|
3411
|
+
}
|
|
3412
|
+
}
|
|
3413
|
+
else {
|
|
3414
|
+
localVars.add(token.value); //] = { id: token.value, expr: token.value };
|
|
3415
|
+
}
|
|
3416
|
+
}
|
|
3417
|
+
if (isVar) {
|
|
3418
|
+
token.varName = token.value;
|
|
3419
|
+
if (!localVars.has(token.value)) {
|
|
3420
|
+
token.originalValue = token.value;
|
|
3421
|
+
token.value = `ctx['${token.value}']`;
|
|
3422
|
+
}
|
|
3423
|
+
}
|
|
3424
|
+
i++;
|
|
3425
|
+
}
|
|
3426
|
+
// Mark all variables that have been used locally.
|
|
3427
|
+
// This assumes the expression has only one scope (incorrect but "good enough for now")
|
|
3428
|
+
for (const token of tokens) {
|
|
3429
|
+
if (token.type === "SYMBOL" && token.varName && localVars.has(token.value)) {
|
|
3430
|
+
token.originalValue = token.value;
|
|
3431
|
+
token.value = `_${token.value}`;
|
|
3432
|
+
token.isLocal = true;
|
|
3433
|
+
}
|
|
3434
|
+
}
|
|
3435
|
+
return tokens;
|
|
3436
|
+
}
|
|
3437
|
+
// Leading spaces are trimmed during tokenization, so they need to be added back for some values
|
|
3438
|
+
const paddedValues = new Map([["in ", " in "]]);
|
|
3439
|
+
function compileExpr(expr) {
|
|
3440
|
+
return compileExprToArray(expr)
|
|
3441
|
+
.map((t) => paddedValues.get(t.value) || t.value)
|
|
3442
|
+
.join("");
|
|
3443
|
+
}
|
|
3444
|
+
const INTERP_REGEXP = /\{\{.*?\}\}/g;
|
|
3445
|
+
const INTERP_GROUP_REGEXP = /\{\{.*?\}\}/g;
|
|
3446
|
+
function interpolate(s) {
|
|
3447
|
+
let matches = s.match(INTERP_REGEXP);
|
|
3448
|
+
if (matches && matches[0].length === s.length) {
|
|
3449
|
+
return `(${compileExpr(s.slice(2, -2))})`;
|
|
3450
|
+
}
|
|
3451
|
+
let r = s.replace(INTERP_GROUP_REGEXP, (s) => "${" + compileExpr(s.slice(2, -2)) + "}");
|
|
3452
|
+
return "`" + r + "`";
|
|
3453
|
+
}
|
|
3454
|
+
|
|
3455
|
+
// using a non-html document so that <inner/outer>HTML serializes as XML instead
|
|
3456
|
+
// of HTML (as we will parse it as xml later)
|
|
3457
|
+
const xmlDoc = document.implementation.createDocument(null, null, null);
|
|
3458
|
+
const MODS = new Set(["stop", "capture", "prevent", "self", "synthetic"]);
|
|
3459
|
+
let nextDataIds = {};
|
|
3460
|
+
function generateId(prefix = "") {
|
|
3461
|
+
nextDataIds[prefix] = (nextDataIds[prefix] || 0) + 1;
|
|
3462
|
+
return prefix + nextDataIds[prefix];
|
|
3463
|
+
}
|
|
3464
|
+
// -----------------------------------------------------------------------------
|
|
3465
|
+
// BlockDescription
|
|
3466
|
+
// -----------------------------------------------------------------------------
|
|
3467
|
+
class BlockDescription {
|
|
3468
|
+
constructor(target, type) {
|
|
3469
|
+
this.dynamicTagName = null;
|
|
3470
|
+
this.isRoot = false;
|
|
3471
|
+
this.hasDynamicChildren = false;
|
|
3472
|
+
this.children = [];
|
|
3473
|
+
this.data = [];
|
|
3474
|
+
this.childNumber = 0;
|
|
3475
|
+
this.parentVar = "";
|
|
3476
|
+
this.id = BlockDescription.nextBlockId++;
|
|
3477
|
+
this.varName = "b" + this.id;
|
|
3478
|
+
this.blockName = "block" + this.id;
|
|
3479
|
+
this.target = target;
|
|
3480
|
+
this.type = type;
|
|
3481
|
+
}
|
|
3482
|
+
insertData(str, prefix = "d") {
|
|
3483
|
+
const id = generateId(prefix);
|
|
3484
|
+
this.target.addLine(`let ${id} = ${str};`);
|
|
3485
|
+
return this.data.push(id) - 1;
|
|
3486
|
+
}
|
|
3487
|
+
insert(dom) {
|
|
3488
|
+
if (this.currentDom) {
|
|
3489
|
+
this.currentDom.appendChild(dom);
|
|
3490
|
+
}
|
|
3491
|
+
else {
|
|
3492
|
+
this.dom = dom;
|
|
3493
|
+
}
|
|
3494
|
+
}
|
|
3495
|
+
generateExpr(expr) {
|
|
3496
|
+
if (this.type === "block") {
|
|
3497
|
+
const hasChildren = this.children.length;
|
|
3498
|
+
let params = this.data.length ? `[${this.data.join(", ")}]` : hasChildren ? "[]" : "";
|
|
3499
|
+
if (hasChildren) {
|
|
3500
|
+
params += ", [" + this.children.map((c) => c.varName).join(", ") + "]";
|
|
3501
|
+
}
|
|
3502
|
+
if (this.dynamicTagName) {
|
|
3503
|
+
return `toggler(${this.dynamicTagName}, ${this.blockName}(${this.dynamicTagName})(${params}))`;
|
|
3504
|
+
}
|
|
3505
|
+
return `${this.blockName}(${params})`;
|
|
3506
|
+
}
|
|
3507
|
+
else if (this.type === "list") {
|
|
3508
|
+
return `list(c_block${this.id})`;
|
|
3509
|
+
}
|
|
3510
|
+
return expr;
|
|
3511
|
+
}
|
|
3512
|
+
asXmlString() {
|
|
3513
|
+
// Can't use outerHTML on text/comment nodes
|
|
3514
|
+
// append dom to any element and use innerHTML instead
|
|
3515
|
+
const t = xmlDoc.createElement("t");
|
|
3516
|
+
t.appendChild(this.dom);
|
|
3517
|
+
return t.innerHTML;
|
|
3518
|
+
}
|
|
3519
|
+
}
|
|
3520
|
+
BlockDescription.nextBlockId = 1;
|
|
3521
|
+
function createContext(parentCtx, params) {
|
|
3522
|
+
return Object.assign({
|
|
3523
|
+
block: null,
|
|
3524
|
+
index: 0,
|
|
3525
|
+
forceNewBlock: true,
|
|
3526
|
+
translate: parentCtx.translate,
|
|
3527
|
+
tKeyExpr: null,
|
|
3528
|
+
nameSpace: parentCtx.nameSpace,
|
|
3529
|
+
tModelSelectedExpr: parentCtx.tModelSelectedExpr,
|
|
3530
|
+
}, params);
|
|
3531
|
+
}
|
|
3532
|
+
class CodeTarget {
|
|
3533
|
+
constructor(name, on) {
|
|
3534
|
+
this.indentLevel = 0;
|
|
3535
|
+
this.loopLevel = 0;
|
|
3536
|
+
this.code = [];
|
|
3537
|
+
this.hasRoot = false;
|
|
3538
|
+
this.hasCache = false;
|
|
3539
|
+
this.hasRef = false;
|
|
3540
|
+
// maps ref name to [id, expr]
|
|
3541
|
+
this.refInfo = {};
|
|
3542
|
+
this.shouldProtectScope = false;
|
|
3543
|
+
this.name = name;
|
|
3544
|
+
this.on = on || null;
|
|
3545
|
+
}
|
|
3546
|
+
addLine(line, idx) {
|
|
3547
|
+
const prefix = new Array(this.indentLevel + 2).join(" ");
|
|
3548
|
+
if (idx === undefined) {
|
|
3549
|
+
this.code.push(prefix + line);
|
|
3550
|
+
}
|
|
3551
|
+
else {
|
|
3552
|
+
this.code.splice(idx, 0, prefix + line);
|
|
3553
|
+
}
|
|
3554
|
+
}
|
|
3555
|
+
generateCode() {
|
|
3556
|
+
let result = [];
|
|
3557
|
+
result.push(`function ${this.name}(ctx, node, key = "") {`);
|
|
3558
|
+
if (this.hasRef) {
|
|
3559
|
+
result.push(` const refs = ctx.__owl__.refs;`);
|
|
3560
|
+
for (let name in this.refInfo) {
|
|
3561
|
+
const [id, expr] = this.refInfo[name];
|
|
3562
|
+
result.push(` const ${id} = ${expr};`);
|
|
3563
|
+
}
|
|
3564
|
+
}
|
|
3565
|
+
if (this.shouldProtectScope) {
|
|
3566
|
+
result.push(` ctx = Object.create(ctx);`);
|
|
3567
|
+
result.push(` ctx[isBoundary] = 1`);
|
|
3568
|
+
}
|
|
3569
|
+
if (this.hasCache) {
|
|
3570
|
+
result.push(` let cache = ctx.cache || {};`);
|
|
3571
|
+
result.push(` let nextCache = ctx.cache = {};`);
|
|
3572
|
+
}
|
|
3573
|
+
for (let line of this.code) {
|
|
3574
|
+
result.push(line);
|
|
3575
|
+
}
|
|
3576
|
+
if (!this.hasRoot) {
|
|
3577
|
+
result.push(`return text('');`);
|
|
3578
|
+
}
|
|
3579
|
+
result.push(`}`);
|
|
3580
|
+
return result.join("\n ");
|
|
3581
|
+
}
|
|
3582
|
+
}
|
|
3583
|
+
const TRANSLATABLE_ATTRS = ["label", "title", "placeholder", "alt"];
|
|
3584
|
+
const translationRE = /^(\s*)([\s\S]+?)(\s*)$/;
|
|
3585
|
+
class CodeGenerator {
|
|
3586
|
+
constructor(ast, options) {
|
|
3587
|
+
this.blocks = [];
|
|
3588
|
+
this.nextBlockId = 1;
|
|
3589
|
+
this.isDebug = false;
|
|
3590
|
+
this.targets = [];
|
|
3591
|
+
this.target = new CodeTarget("template");
|
|
3592
|
+
this.translatableAttributes = TRANSLATABLE_ATTRS;
|
|
3593
|
+
this.staticDefs = [];
|
|
3594
|
+
this.helpers = new Set();
|
|
3595
|
+
this.translateFn = options.translateFn || ((s) => s);
|
|
3596
|
+
if (options.translatableAttributes) {
|
|
3597
|
+
const attrs = new Set(TRANSLATABLE_ATTRS);
|
|
3598
|
+
for (let attr of options.translatableAttributes) {
|
|
3599
|
+
if (attr.startsWith("-")) {
|
|
3600
|
+
attrs.delete(attr.slice(1));
|
|
3601
|
+
}
|
|
3602
|
+
else {
|
|
3603
|
+
attrs.add(attr);
|
|
3604
|
+
}
|
|
3605
|
+
}
|
|
3606
|
+
this.translatableAttributes = [...attrs];
|
|
3607
|
+
}
|
|
3608
|
+
this.hasSafeContext = options.hasSafeContext || false;
|
|
3609
|
+
this.dev = options.dev || false;
|
|
3610
|
+
this.ast = ast;
|
|
3611
|
+
this.templateName = options.name;
|
|
3612
|
+
}
|
|
3613
|
+
generateCode() {
|
|
3614
|
+
const ast = this.ast;
|
|
3615
|
+
this.isDebug = ast.type === 12 /* TDebug */;
|
|
3616
|
+
BlockDescription.nextBlockId = 1;
|
|
3617
|
+
nextDataIds = {};
|
|
3618
|
+
this.compileAST(ast, {
|
|
3619
|
+
block: null,
|
|
3620
|
+
index: 0,
|
|
3621
|
+
forceNewBlock: false,
|
|
3622
|
+
isLast: true,
|
|
3623
|
+
translate: true,
|
|
3624
|
+
tKeyExpr: null,
|
|
3625
|
+
});
|
|
3626
|
+
// define blocks and utility functions
|
|
3627
|
+
let mainCode = [
|
|
3628
|
+
` let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;`,
|
|
3629
|
+
];
|
|
3630
|
+
if (this.helpers.size) {
|
|
3631
|
+
mainCode.push(`let { ${[...this.helpers].join(", ")} } = helpers;`);
|
|
3632
|
+
}
|
|
3633
|
+
if (this.templateName) {
|
|
3634
|
+
mainCode.push(`// Template name: "${this.templateName}"`);
|
|
3635
|
+
}
|
|
3636
|
+
for (let { id, expr } of this.staticDefs) {
|
|
3637
|
+
mainCode.push(`const ${id} = ${expr};`);
|
|
3638
|
+
}
|
|
3639
|
+
// define all blocks
|
|
3640
|
+
if (this.blocks.length) {
|
|
3641
|
+
mainCode.push(``);
|
|
3642
|
+
for (let block of this.blocks) {
|
|
3643
|
+
if (block.dom) {
|
|
3644
|
+
let xmlString = block.asXmlString();
|
|
3645
|
+
if (block.dynamicTagName) {
|
|
3646
|
+
xmlString = xmlString.replace(/^<\w+/, `<\${tag || '${block.dom.nodeName}'}`);
|
|
3647
|
+
xmlString = xmlString.replace(/\w+>$/, `\${tag || '${block.dom.nodeName}'}>`);
|
|
3648
|
+
mainCode.push(`let ${block.blockName} = tag => createBlock(\`${xmlString}\`);`);
|
|
3649
|
+
}
|
|
3650
|
+
else {
|
|
3651
|
+
mainCode.push(`let ${block.blockName} = createBlock(\`${xmlString}\`);`);
|
|
3652
|
+
}
|
|
3653
|
+
}
|
|
3654
|
+
}
|
|
3655
|
+
}
|
|
3656
|
+
// define all slots/defaultcontent function
|
|
3657
|
+
if (this.targets.length) {
|
|
3658
|
+
for (let fn of this.targets) {
|
|
3659
|
+
mainCode.push("");
|
|
3660
|
+
mainCode = mainCode.concat(fn.generateCode());
|
|
3661
|
+
}
|
|
3662
|
+
}
|
|
3663
|
+
// generate main code
|
|
3664
|
+
mainCode.push("");
|
|
3665
|
+
mainCode = mainCode.concat("return " + this.target.generateCode());
|
|
3666
|
+
const code = mainCode.join("\n ");
|
|
3667
|
+
if (this.isDebug) {
|
|
3668
|
+
const msg = `[Owl Debug]\n${code}`;
|
|
3669
|
+
console.log(msg);
|
|
3670
|
+
}
|
|
3671
|
+
return code;
|
|
3672
|
+
}
|
|
3673
|
+
compileInNewTarget(prefix, ast, ctx, on) {
|
|
3674
|
+
const name = generateId(prefix);
|
|
3675
|
+
const initialTarget = this.target;
|
|
3676
|
+
const target = new CodeTarget(name, on);
|
|
3677
|
+
this.targets.push(target);
|
|
3678
|
+
this.target = target;
|
|
3679
|
+
this.compileAST(ast, createContext(ctx));
|
|
3680
|
+
this.target = initialTarget;
|
|
3681
|
+
return name;
|
|
3682
|
+
}
|
|
3683
|
+
addLine(line, idx) {
|
|
3684
|
+
this.target.addLine(line, idx);
|
|
3685
|
+
}
|
|
3686
|
+
define(varName, expr) {
|
|
3687
|
+
this.addLine(`const ${varName} = ${expr};`);
|
|
3688
|
+
}
|
|
3689
|
+
insertAnchor(block) {
|
|
3690
|
+
const tag = `block-child-${block.children.length}`;
|
|
3691
|
+
const anchor = xmlDoc.createElement(tag);
|
|
3692
|
+
block.insert(anchor);
|
|
3693
|
+
}
|
|
3694
|
+
createBlock(parentBlock, type, ctx) {
|
|
3695
|
+
const hasRoot = this.target.hasRoot;
|
|
3696
|
+
const block = new BlockDescription(this.target, type);
|
|
3697
|
+
if (!hasRoot && !ctx.preventRoot) {
|
|
3698
|
+
this.target.hasRoot = true;
|
|
3699
|
+
block.isRoot = true;
|
|
3700
|
+
}
|
|
3701
|
+
if (parentBlock) {
|
|
3702
|
+
parentBlock.children.push(block);
|
|
3703
|
+
if (parentBlock.type === "list") {
|
|
3704
|
+
block.parentVar = `c_block${parentBlock.id}`;
|
|
3705
|
+
}
|
|
3706
|
+
}
|
|
3707
|
+
return block;
|
|
3708
|
+
}
|
|
3709
|
+
insertBlock(expression, block, ctx) {
|
|
3710
|
+
let blockExpr = block.generateExpr(expression);
|
|
3711
|
+
const tKeyExpr = ctx.tKeyExpr;
|
|
3712
|
+
if (block.parentVar) {
|
|
3713
|
+
let keyArg = `key${this.target.loopLevel}`;
|
|
3714
|
+
if (tKeyExpr) {
|
|
3715
|
+
keyArg = `${tKeyExpr} + ${keyArg}`;
|
|
3716
|
+
}
|
|
3717
|
+
this.helpers.add("withKey");
|
|
3718
|
+
this.addLine(`${block.parentVar}[${ctx.index}] = withKey(${blockExpr}, ${keyArg});`);
|
|
3719
|
+
return;
|
|
3720
|
+
}
|
|
3721
|
+
if (tKeyExpr) {
|
|
3722
|
+
blockExpr = `toggler(${tKeyExpr}, ${blockExpr})`;
|
|
3723
|
+
}
|
|
3724
|
+
if (block.isRoot && !ctx.preventRoot) {
|
|
3725
|
+
if (this.target.on) {
|
|
3726
|
+
blockExpr = this.wrapWithEventCatcher(blockExpr, this.target.on);
|
|
3727
|
+
}
|
|
3728
|
+
this.addLine(`return ${blockExpr};`);
|
|
3729
|
+
}
|
|
3730
|
+
else {
|
|
3731
|
+
this.define(block.varName, blockExpr);
|
|
3732
|
+
}
|
|
3733
|
+
}
|
|
3734
|
+
/**
|
|
3735
|
+
* Captures variables that are used inside of an expression. This is useful
|
|
3736
|
+
* because in compiled code, almost all variables are accessed through the ctx
|
|
3737
|
+
* object. In the case of functions, that lookup in the context can be delayed
|
|
3738
|
+
* which can cause issues if the value has changed since the function was
|
|
3313
3739
|
* defined.
|
|
3314
3740
|
*
|
|
3315
3741
|
* @param expr the expression to capture
|
|
@@ -3328,7 +3754,7 @@
|
|
|
3328
3754
|
.map((tok) => {
|
|
3329
3755
|
if (tok.varName && !tok.isLocal) {
|
|
3330
3756
|
if (!mapping.has(tok.varName)) {
|
|
3331
|
-
const varId =
|
|
3757
|
+
const varId = generateId("v");
|
|
3332
3758
|
mapping.set(tok.varName, varId);
|
|
3333
3759
|
this.define(varId, tok.value);
|
|
3334
3760
|
}
|
|
@@ -3468,7 +3894,7 @@
|
|
|
3468
3894
|
block = this.createBlock(block, "block", ctx);
|
|
3469
3895
|
this.blocks.push(block);
|
|
3470
3896
|
if (ast.dynamicTag) {
|
|
3471
|
-
const tagExpr =
|
|
3897
|
+
const tagExpr = generateId("tag");
|
|
3472
3898
|
this.define(tagExpr, compileExpr(ast.dynamicTag));
|
|
3473
3899
|
block.dynamicTagName = tagExpr;
|
|
3474
3900
|
}
|
|
@@ -3538,7 +3964,7 @@
|
|
|
3538
3964
|
info[1] = `multiRefSetter(refs, \`${name}\`)`;
|
|
3539
3965
|
}
|
|
3540
3966
|
else {
|
|
3541
|
-
let id =
|
|
3967
|
+
let id = generateId("ref");
|
|
3542
3968
|
this.target.refInfo[name] = [id, `(el) => refs[\`${name}\`] = el`];
|
|
3543
3969
|
const index = block.data.push(id) - 1;
|
|
3544
3970
|
attrs["block-ref"] = String(index);
|
|
@@ -3550,10 +3976,10 @@
|
|
|
3550
3976
|
if (ast.model) {
|
|
3551
3977
|
const { hasDynamicChildren, baseExpr, expr, eventType, shouldNumberize, shouldTrim, targetAttr, specialInitTargetAttr, } = ast.model;
|
|
3552
3978
|
const baseExpression = compileExpr(baseExpr);
|
|
3553
|
-
const bExprId =
|
|
3979
|
+
const bExprId = generateId("bExpr");
|
|
3554
3980
|
this.define(bExprId, baseExpression);
|
|
3555
3981
|
const expression = compileExpr(expr);
|
|
3556
|
-
const exprId =
|
|
3982
|
+
const exprId = generateId("expr");
|
|
3557
3983
|
this.define(exprId, expression);
|
|
3558
3984
|
const fullExpression = `${bExprId}[${exprId}]`;
|
|
3559
3985
|
let idx;
|
|
@@ -3562,7 +3988,7 @@
|
|
|
3562
3988
|
attrs[`block-attribute-${idx}`] = specialInitTargetAttr;
|
|
3563
3989
|
}
|
|
3564
3990
|
else if (hasDynamicChildren) {
|
|
3565
|
-
const bValueId =
|
|
3991
|
+
const bValueId = generateId("bValue");
|
|
3566
3992
|
tModelSelectedExpr = `${bValueId}`;
|
|
3567
3993
|
this.define(tModelSelectedExpr, fullExpression);
|
|
3568
3994
|
}
|
|
@@ -3764,7 +4190,7 @@
|
|
|
3764
4190
|
let id;
|
|
3765
4191
|
if (ast.memo) {
|
|
3766
4192
|
this.target.hasCache = true;
|
|
3767
|
-
id =
|
|
4193
|
+
id = generateId();
|
|
3768
4194
|
this.define(`memo${id}`, compileExpr(ast.memo));
|
|
3769
4195
|
this.define(`vnode${id}`, `cache[key${this.target.loopLevel}];`);
|
|
3770
4196
|
this.addLine(`if (vnode${id}) {`);
|
|
@@ -3793,7 +4219,7 @@
|
|
|
3793
4219
|
this.insertBlock("l", block, ctx);
|
|
3794
4220
|
}
|
|
3795
4221
|
compileTKey(ast, ctx) {
|
|
3796
|
-
const tKeyExpr =
|
|
4222
|
+
const tKeyExpr = generateId("tKey_");
|
|
3797
4223
|
this.define(tKeyExpr, compileExpr(ast.expr));
|
|
3798
4224
|
ctx = createContext(ctx, {
|
|
3799
4225
|
tKeyExpr,
|
|
@@ -3876,19 +4302,20 @@
|
|
|
3876
4302
|
}
|
|
3877
4303
|
const key = `key + \`${this.generateComponentKey()}\``;
|
|
3878
4304
|
if (isDynamic) {
|
|
3879
|
-
const templateVar =
|
|
4305
|
+
const templateVar = generateId("template");
|
|
4306
|
+
if (!this.staticDefs.find((d) => d.id === "call")) {
|
|
4307
|
+
this.staticDefs.push({ id: "call", expr: `app.callTemplate.bind(app)` });
|
|
4308
|
+
}
|
|
3880
4309
|
this.define(templateVar, subTemplate);
|
|
3881
4310
|
block = this.createBlock(block, "multi", ctx);
|
|
3882
|
-
this.helpers.add("call");
|
|
3883
4311
|
this.insertBlock(`call(this, ${templateVar}, ctx, node, ${key})`, block, {
|
|
3884
4312
|
...ctx,
|
|
3885
4313
|
forceNewBlock: !block,
|
|
3886
4314
|
});
|
|
3887
4315
|
}
|
|
3888
4316
|
else {
|
|
3889
|
-
const id =
|
|
3890
|
-
this.
|
|
3891
|
-
this.staticDefs.push({ id, expr: `getTemplate(${subTemplate})` });
|
|
4317
|
+
const id = generateId(`callTemplate_`);
|
|
4318
|
+
this.staticDefs.push({ id, expr: `app.getTemplate(${subTemplate})` });
|
|
3892
4319
|
block = this.createBlock(block, "multi", ctx);
|
|
3893
4320
|
this.insertBlock(`${id}.call(this, ctx, node, ${key})`, block, {
|
|
3894
4321
|
...ctx,
|
|
@@ -3939,7 +4366,7 @@
|
|
|
3939
4366
|
}
|
|
3940
4367
|
}
|
|
3941
4368
|
generateComponentKey() {
|
|
3942
|
-
const parts = [
|
|
4369
|
+
const parts = [generateId("__")];
|
|
3943
4370
|
for (let i = 0; i < this.target.loopLevel; i++) {
|
|
3944
4371
|
parts.push(`\${key${i + 1}}`);
|
|
3945
4372
|
}
|
|
@@ -3993,7 +4420,7 @@
|
|
|
3993
4420
|
if (ast.slots) {
|
|
3994
4421
|
let ctxStr = "ctx";
|
|
3995
4422
|
if (this.target.loopLevel || !this.hasSafeContext) {
|
|
3996
|
-
ctxStr =
|
|
4423
|
+
ctxStr = generateId("ctx");
|
|
3997
4424
|
this.helpers.add("capture");
|
|
3998
4425
|
this.define(ctxStr, `capture(ctx)`);
|
|
3999
4426
|
}
|
|
@@ -4028,7 +4455,7 @@
|
|
|
4028
4455
|
}
|
|
4029
4456
|
let propVar;
|
|
4030
4457
|
if ((slotDef && (ast.dynamicProps || hasSlotsProp)) || this.dev) {
|
|
4031
|
-
propVar =
|
|
4458
|
+
propVar = generateId("props");
|
|
4032
4459
|
this.define(propVar, propString);
|
|
4033
4460
|
propString = propVar;
|
|
4034
4461
|
}
|
|
@@ -4040,7 +4467,7 @@
|
|
|
4040
4467
|
const key = this.generateComponentKey();
|
|
4041
4468
|
let expr;
|
|
4042
4469
|
if (ast.isDynamic) {
|
|
4043
|
-
expr =
|
|
4470
|
+
expr = generateId("Comp");
|
|
4044
4471
|
this.define(expr, compileExpr(ast.name));
|
|
4045
4472
|
}
|
|
4046
4473
|
else {
|
|
@@ -4071,11 +4498,11 @@
|
|
|
4071
4498
|
}
|
|
4072
4499
|
wrapWithEventCatcher(expr, on) {
|
|
4073
4500
|
this.helpers.add("createCatcher");
|
|
4074
|
-
let name =
|
|
4501
|
+
let name = generateId("catcher");
|
|
4075
4502
|
let spec = {};
|
|
4076
4503
|
let handlers = [];
|
|
4077
4504
|
for (let ev in on) {
|
|
4078
|
-
let handlerId =
|
|
4505
|
+
let handlerId = generateId("hdlr");
|
|
4079
4506
|
let idx = handlers.push(handlerId) - 1;
|
|
4080
4507
|
spec[ev] = idx;
|
|
4081
4508
|
const handler = this.generateHandlerCode(ev, on[ev]);
|
|
@@ -4104,7 +4531,7 @@
|
|
|
4104
4531
|
}
|
|
4105
4532
|
else {
|
|
4106
4533
|
if (dynamic) {
|
|
4107
|
-
let name =
|
|
4534
|
+
let name = generateId("slot");
|
|
4108
4535
|
this.define(name, slotName);
|
|
4109
4536
|
blockString = `toggler(${name}, callSlot(ctx, node, key, ${name}, ${dynamic}, ${scope}))`;
|
|
4110
4537
|
}
|
|
@@ -4120,1140 +4547,743 @@
|
|
|
4120
4547
|
this.insertAnchor(block);
|
|
4121
4548
|
}
|
|
4122
4549
|
block = this.createBlock(block, "multi", ctx);
|
|
4123
|
-
this.insertBlock(blockString, block, { ...ctx, forceNewBlock: false });
|
|
4124
|
-
}
|
|
4125
|
-
compileTTranslation(ast, ctx) {
|
|
4126
|
-
if (ast.content) {
|
|
4127
|
-
this.compileAST(ast.content, Object.assign({}, ctx, { translate: false }));
|
|
4128
|
-
}
|
|
4129
|
-
}
|
|
4130
|
-
compileTPortal(ast, ctx) {
|
|
4131
|
-
this.helpers.add("Portal");
|
|
4132
|
-
let { block } = ctx;
|
|
4133
|
-
const name = this.compileInNewTarget("slot", ast.content, ctx);
|
|
4134
|
-
const key = this.generateComponentKey();
|
|
4135
|
-
let ctxStr = "ctx";
|
|
4136
|
-
if (this.target.loopLevel || !this.hasSafeContext) {
|
|
4137
|
-
ctxStr = this.generateId("ctx");
|
|
4138
|
-
this.helpers.add("capture");
|
|
4139
|
-
this.define(ctxStr, `capture(ctx);`);
|
|
4140
|
-
}
|
|
4141
|
-
const blockString = `component(Portal, {target: ${ast.target},slots: {'default': {__render: ${name}, __ctx: ${ctxStr}}}}, key + \`${key}\`, node, ctx)`;
|
|
4142
|
-
if (block) {
|
|
4143
|
-
this.insertAnchor(block);
|
|
4144
|
-
}
|
|
4145
|
-
block = this.createBlock(block, "multi", ctx);
|
|
4146
|
-
this.insertBlock(blockString, block, { ...ctx, forceNewBlock: false });
|
|
4147
|
-
}
|
|
4148
|
-
}
|
|
4149
|
-
|
|
4150
|
-
// -----------------------------------------------------------------------------
|
|
4151
|
-
// AST Type definition
|
|
4152
|
-
// -----------------------------------------------------------------------------
|
|
4153
|
-
// -----------------------------------------------------------------------------
|
|
4154
|
-
// Parser
|
|
4155
|
-
// -----------------------------------------------------------------------------
|
|
4156
|
-
const cache = new WeakMap();
|
|
4157
|
-
function parse(xml) {
|
|
4158
|
-
if (typeof xml === "string") {
|
|
4159
|
-
const elem = parseXML$1(`<t>${xml}</t>`).firstChild;
|
|
4160
|
-
return _parse(elem);
|
|
4161
|
-
}
|
|
4162
|
-
let ast = cache.get(xml);
|
|
4163
|
-
if (!ast) {
|
|
4164
|
-
// we clone here the xml to prevent modifying it in place
|
|
4165
|
-
ast = _parse(xml.cloneNode(true));
|
|
4166
|
-
cache.set(xml, ast);
|
|
4167
|
-
}
|
|
4168
|
-
return ast;
|
|
4169
|
-
}
|
|
4170
|
-
function _parse(xml) {
|
|
4171
|
-
normalizeXML(xml);
|
|
4172
|
-
const ctx = { inPreTag: false, inSVG: false };
|
|
4173
|
-
return parseNode(xml, ctx) || { type: 0 /* Text */, value: "" };
|
|
4174
|
-
}
|
|
4175
|
-
function parseNode(node, ctx) {
|
|
4176
|
-
if (!(node instanceof Element)) {
|
|
4177
|
-
return parseTextCommentNode(node, ctx);
|
|
4178
|
-
}
|
|
4179
|
-
return (parseTDebugLog(node, ctx) ||
|
|
4180
|
-
parseTForEach(node, ctx) ||
|
|
4181
|
-
parseTIf(node, ctx) ||
|
|
4182
|
-
parseTPortal(node, ctx) ||
|
|
4183
|
-
parseTCall(node, ctx) ||
|
|
4184
|
-
parseTCallBlock(node) ||
|
|
4185
|
-
parseTEscNode(node, ctx) ||
|
|
4186
|
-
parseTKey(node, ctx) ||
|
|
4187
|
-
parseTTranslation(node, ctx) ||
|
|
4188
|
-
parseTSlot(node, ctx) ||
|
|
4189
|
-
parseTOutNode(node, ctx) ||
|
|
4190
|
-
parseComponent(node, ctx) ||
|
|
4191
|
-
parseDOMNode(node, ctx) ||
|
|
4192
|
-
parseTSetNode(node, ctx) ||
|
|
4193
|
-
parseTNode(node, ctx));
|
|
4194
|
-
}
|
|
4195
|
-
// -----------------------------------------------------------------------------
|
|
4196
|
-
// <t /> tag
|
|
4197
|
-
// -----------------------------------------------------------------------------
|
|
4198
|
-
function parseTNode(node, ctx) {
|
|
4199
|
-
if (node.tagName !== "t") {
|
|
4200
|
-
return null;
|
|
4201
|
-
}
|
|
4202
|
-
return parseChildNodes(node, ctx);
|
|
4203
|
-
}
|
|
4204
|
-
// -----------------------------------------------------------------------------
|
|
4205
|
-
// Text and Comment Nodes
|
|
4206
|
-
// -----------------------------------------------------------------------------
|
|
4207
|
-
const lineBreakRE = /[\r\n]/;
|
|
4208
|
-
const whitespaceRE = /\s+/g;
|
|
4209
|
-
function parseTextCommentNode(node, ctx) {
|
|
4210
|
-
if (node.nodeType === Node.TEXT_NODE) {
|
|
4211
|
-
let value = node.textContent || "";
|
|
4212
|
-
if (!ctx.inPreTag) {
|
|
4213
|
-
if (lineBreakRE.test(value) && !value.trim()) {
|
|
4214
|
-
return null;
|
|
4215
|
-
}
|
|
4216
|
-
value = value.replace(whitespaceRE, " ");
|
|
4217
|
-
}
|
|
4218
|
-
return { type: 0 /* Text */, value };
|
|
4219
|
-
}
|
|
4220
|
-
else if (node.nodeType === Node.COMMENT_NODE) {
|
|
4221
|
-
return { type: 1 /* Comment */, value: node.textContent || "" };
|
|
4222
|
-
}
|
|
4223
|
-
return null;
|
|
4224
|
-
}
|
|
4225
|
-
// -----------------------------------------------------------------------------
|
|
4226
|
-
// debugging
|
|
4227
|
-
// -----------------------------------------------------------------------------
|
|
4228
|
-
function parseTDebugLog(node, ctx) {
|
|
4229
|
-
if (node.hasAttribute("t-debug")) {
|
|
4230
|
-
node.removeAttribute("t-debug");
|
|
4231
|
-
return {
|
|
4232
|
-
type: 12 /* TDebug */,
|
|
4233
|
-
content: parseNode(node, ctx),
|
|
4234
|
-
};
|
|
4235
|
-
}
|
|
4236
|
-
if (node.hasAttribute("t-log")) {
|
|
4237
|
-
const expr = node.getAttribute("t-log");
|
|
4238
|
-
node.removeAttribute("t-log");
|
|
4239
|
-
return {
|
|
4240
|
-
type: 13 /* TLog */,
|
|
4241
|
-
expr,
|
|
4242
|
-
content: parseNode(node, ctx),
|
|
4243
|
-
};
|
|
4244
|
-
}
|
|
4245
|
-
return null;
|
|
4246
|
-
}
|
|
4247
|
-
// -----------------------------------------------------------------------------
|
|
4248
|
-
// Regular dom node
|
|
4249
|
-
// -----------------------------------------------------------------------------
|
|
4250
|
-
const hasDotAtTheEnd = /\.[\w_]+\s*$/;
|
|
4251
|
-
const hasBracketsAtTheEnd = /\[[^\[]+\]\s*$/;
|
|
4252
|
-
const ROOT_SVG_TAGS = new Set(["svg", "g", "path"]);
|
|
4253
|
-
function parseDOMNode(node, ctx) {
|
|
4254
|
-
const { tagName } = node;
|
|
4255
|
-
const dynamicTag = node.getAttribute("t-tag");
|
|
4256
|
-
node.removeAttribute("t-tag");
|
|
4257
|
-
if (tagName === "t" && !dynamicTag) {
|
|
4258
|
-
return null;
|
|
4259
|
-
}
|
|
4260
|
-
if (tagName.startsWith("block-")) {
|
|
4261
|
-
throw new Error(`Invalid tag name: '${tagName}'`);
|
|
4262
|
-
}
|
|
4263
|
-
ctx = Object.assign({}, ctx);
|
|
4264
|
-
if (tagName === "pre") {
|
|
4265
|
-
ctx.inPreTag = true;
|
|
4266
|
-
}
|
|
4267
|
-
const shouldAddSVGNS = ROOT_SVG_TAGS.has(tagName) && !ctx.inSVG;
|
|
4268
|
-
ctx.inSVG = ctx.inSVG || shouldAddSVGNS;
|
|
4269
|
-
const ns = shouldAddSVGNS ? "http://www.w3.org/2000/svg" : null;
|
|
4270
|
-
const ref = node.getAttribute("t-ref");
|
|
4271
|
-
node.removeAttribute("t-ref");
|
|
4272
|
-
const nodeAttrsNames = node.getAttributeNames();
|
|
4273
|
-
let attrs = null;
|
|
4274
|
-
let on = null;
|
|
4275
|
-
let model = null;
|
|
4276
|
-
for (let attr of nodeAttrsNames) {
|
|
4277
|
-
const value = node.getAttribute(attr);
|
|
4278
|
-
if (attr.startsWith("t-on")) {
|
|
4279
|
-
if (attr === "t-on") {
|
|
4280
|
-
throw new Error("Missing event name with t-on directive");
|
|
4281
|
-
}
|
|
4282
|
-
on = on || {};
|
|
4283
|
-
on[attr.slice(5)] = value;
|
|
4284
|
-
}
|
|
4285
|
-
else if (attr.startsWith("t-model")) {
|
|
4286
|
-
if (!["input", "select", "textarea"].includes(tagName)) {
|
|
4287
|
-
throw new Error("The t-model directive only works with <input>, <textarea> and <select>");
|
|
4288
|
-
}
|
|
4289
|
-
let baseExpr, expr;
|
|
4290
|
-
if (hasDotAtTheEnd.test(value)) {
|
|
4291
|
-
const index = value.lastIndexOf(".");
|
|
4292
|
-
baseExpr = value.slice(0, index);
|
|
4293
|
-
expr = `'${value.slice(index + 1)}'`;
|
|
4294
|
-
}
|
|
4295
|
-
else if (hasBracketsAtTheEnd.test(value)) {
|
|
4296
|
-
const index = value.lastIndexOf("[");
|
|
4297
|
-
baseExpr = value.slice(0, index);
|
|
4298
|
-
expr = value.slice(index + 1, -1);
|
|
4299
|
-
}
|
|
4300
|
-
else {
|
|
4301
|
-
throw new Error(`Invalid t-model expression: "${value}" (it should be assignable)`);
|
|
4302
|
-
}
|
|
4303
|
-
const typeAttr = node.getAttribute("type");
|
|
4304
|
-
const isInput = tagName === "input";
|
|
4305
|
-
const isSelect = tagName === "select";
|
|
4306
|
-
const isTextarea = tagName === "textarea";
|
|
4307
|
-
const isCheckboxInput = isInput && typeAttr === "checkbox";
|
|
4308
|
-
const isRadioInput = isInput && typeAttr === "radio";
|
|
4309
|
-
const isOtherInput = isInput && !isCheckboxInput && !isRadioInput;
|
|
4310
|
-
const hasLazyMod = attr.includes(".lazy");
|
|
4311
|
-
const hasNumberMod = attr.includes(".number");
|
|
4312
|
-
const hasTrimMod = attr.includes(".trim");
|
|
4313
|
-
const eventType = isRadioInput ? "click" : isSelect || hasLazyMod ? "change" : "input";
|
|
4314
|
-
model = {
|
|
4315
|
-
baseExpr,
|
|
4316
|
-
expr,
|
|
4317
|
-
targetAttr: isCheckboxInput ? "checked" : "value",
|
|
4318
|
-
specialInitTargetAttr: isRadioInput ? "checked" : null,
|
|
4319
|
-
eventType,
|
|
4320
|
-
hasDynamicChildren: false,
|
|
4321
|
-
shouldTrim: hasTrimMod && (isOtherInput || isTextarea),
|
|
4322
|
-
shouldNumberize: hasNumberMod && (isOtherInput || isTextarea),
|
|
4323
|
-
};
|
|
4324
|
-
if (isSelect) {
|
|
4325
|
-
// don't pollute the original ctx
|
|
4326
|
-
ctx = Object.assign({}, ctx);
|
|
4327
|
-
ctx.tModelInfo = model;
|
|
4328
|
-
}
|
|
4329
|
-
}
|
|
4330
|
-
else if (attr.startsWith("block-")) {
|
|
4331
|
-
throw new Error(`Invalid attribute: '${attr}'`);
|
|
4332
|
-
}
|
|
4333
|
-
else if (attr !== "t-name") {
|
|
4334
|
-
if (attr.startsWith("t-") && !attr.startsWith("t-att")) {
|
|
4335
|
-
throw new Error(`Unknown QWeb directive: '${attr}'`);
|
|
4336
|
-
}
|
|
4337
|
-
const tModel = ctx.tModelInfo;
|
|
4338
|
-
if (tModel && ["t-att-value", "t-attf-value"].includes(attr)) {
|
|
4339
|
-
tModel.hasDynamicChildren = true;
|
|
4340
|
-
}
|
|
4341
|
-
attrs = attrs || {};
|
|
4342
|
-
attrs[attr] = value;
|
|
4343
|
-
}
|
|
4344
|
-
}
|
|
4345
|
-
const children = parseChildren(node, ctx);
|
|
4346
|
-
return {
|
|
4347
|
-
type: 2 /* DomNode */,
|
|
4348
|
-
tag: tagName,
|
|
4349
|
-
dynamicTag,
|
|
4350
|
-
attrs,
|
|
4351
|
-
on,
|
|
4352
|
-
ref,
|
|
4353
|
-
content: children,
|
|
4354
|
-
model,
|
|
4355
|
-
ns,
|
|
4356
|
-
};
|
|
4357
|
-
}
|
|
4358
|
-
// -----------------------------------------------------------------------------
|
|
4359
|
-
// t-esc
|
|
4360
|
-
// -----------------------------------------------------------------------------
|
|
4361
|
-
function parseTEscNode(node, ctx) {
|
|
4362
|
-
if (!node.hasAttribute("t-esc")) {
|
|
4363
|
-
return null;
|
|
4364
|
-
}
|
|
4365
|
-
const escValue = node.getAttribute("t-esc");
|
|
4366
|
-
node.removeAttribute("t-esc");
|
|
4367
|
-
const tesc = {
|
|
4368
|
-
type: 4 /* TEsc */,
|
|
4369
|
-
expr: escValue,
|
|
4370
|
-
defaultValue: node.textContent || "",
|
|
4371
|
-
};
|
|
4372
|
-
let ref = node.getAttribute("t-ref");
|
|
4373
|
-
node.removeAttribute("t-ref");
|
|
4374
|
-
const ast = parseNode(node, ctx);
|
|
4375
|
-
if (!ast) {
|
|
4376
|
-
return tesc;
|
|
4550
|
+
this.insertBlock(blockString, block, { ...ctx, forceNewBlock: false });
|
|
4377
4551
|
}
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
content: [tesc],
|
|
4383
|
-
};
|
|
4552
|
+
compileTTranslation(ast, ctx) {
|
|
4553
|
+
if (ast.content) {
|
|
4554
|
+
this.compileAST(ast.content, Object.assign({}, ctx, { translate: false }));
|
|
4555
|
+
}
|
|
4384
4556
|
}
|
|
4385
|
-
|
|
4386
|
-
|
|
4557
|
+
compileTPortal(ast, ctx) {
|
|
4558
|
+
if (!this.staticDefs.find((d) => d.id === "Portal")) {
|
|
4559
|
+
this.staticDefs.push({ id: "Portal", expr: `app.Portal` });
|
|
4560
|
+
}
|
|
4561
|
+
let { block } = ctx;
|
|
4562
|
+
const name = this.compileInNewTarget("slot", ast.content, ctx);
|
|
4563
|
+
const key = this.generateComponentKey();
|
|
4564
|
+
let ctxStr = "ctx";
|
|
4565
|
+
if (this.target.loopLevel || !this.hasSafeContext) {
|
|
4566
|
+
ctxStr = generateId("ctx");
|
|
4567
|
+
this.helpers.add("capture");
|
|
4568
|
+
this.define(ctxStr, `capture(ctx);`);
|
|
4569
|
+
}
|
|
4570
|
+
const target = compileExpr(ast.target);
|
|
4571
|
+
const blockString = `component(Portal, {target: ${target},slots: {'default': {__render: ${name}, __ctx: ${ctxStr}}}}, key + \`${key}\`, node, ctx)`;
|
|
4572
|
+
if (block) {
|
|
4573
|
+
this.insertAnchor(block);
|
|
4574
|
+
}
|
|
4575
|
+
block = this.createBlock(block, "multi", ctx);
|
|
4576
|
+
this.insertBlock(blockString, block, { ...ctx, forceNewBlock: false });
|
|
4387
4577
|
}
|
|
4388
|
-
|
|
4389
|
-
|
|
4578
|
+
}
|
|
4579
|
+
|
|
4390
4580
|
// -----------------------------------------------------------------------------
|
|
4391
|
-
//
|
|
4581
|
+
// AST Type definition
|
|
4392
4582
|
// -----------------------------------------------------------------------------
|
|
4393
|
-
function parseTOutNode(node, ctx) {
|
|
4394
|
-
if (!node.hasAttribute("t-out") && !node.hasAttribute("t-raw")) {
|
|
4395
|
-
return null;
|
|
4396
|
-
}
|
|
4397
|
-
if (node.hasAttribute("t-raw")) {
|
|
4398
|
-
console.warn(`t-raw has been deprecated in favor of t-out. If the value to render is not wrapped by the "markup" function, it will be escaped`);
|
|
4399
|
-
}
|
|
4400
|
-
const expr = (node.getAttribute("t-out") || node.getAttribute("t-raw"));
|
|
4401
|
-
node.removeAttribute("t-out");
|
|
4402
|
-
node.removeAttribute("t-raw");
|
|
4403
|
-
const tOut = { type: 8 /* TOut */, expr, body: null };
|
|
4404
|
-
const ref = node.getAttribute("t-ref");
|
|
4405
|
-
node.removeAttribute("t-ref");
|
|
4406
|
-
const ast = parseNode(node, ctx);
|
|
4407
|
-
if (!ast) {
|
|
4408
|
-
return tOut;
|
|
4409
|
-
}
|
|
4410
|
-
if (ast.type === 2 /* DomNode */) {
|
|
4411
|
-
tOut.body = ast.content.length ? ast.content : null;
|
|
4412
|
-
return {
|
|
4413
|
-
...ast,
|
|
4414
|
-
ref,
|
|
4415
|
-
content: [tOut],
|
|
4416
|
-
};
|
|
4417
|
-
}
|
|
4418
|
-
return tOut;
|
|
4419
|
-
}
|
|
4420
4583
|
// -----------------------------------------------------------------------------
|
|
4421
|
-
//
|
|
4584
|
+
// Parser
|
|
4422
4585
|
// -----------------------------------------------------------------------------
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
const collection = node.getAttribute("t-foreach");
|
|
4429
|
-
node.removeAttribute("t-foreach");
|
|
4430
|
-
const elem = node.getAttribute("t-as") || "";
|
|
4431
|
-
node.removeAttribute("t-as");
|
|
4432
|
-
const key = node.getAttribute("t-key");
|
|
4433
|
-
if (!key) {
|
|
4434
|
-
throw new Error(`"Directive t-foreach should always be used with a t-key!" (expression: t-foreach="${collection}" t-as="${elem}")`);
|
|
4586
|
+
const cache = new WeakMap();
|
|
4587
|
+
function parse(xml) {
|
|
4588
|
+
if (typeof xml === "string") {
|
|
4589
|
+
const elem = parseXML(`<t>${xml}</t>`).firstChild;
|
|
4590
|
+
return _parse(elem);
|
|
4435
4591
|
}
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
return null;
|
|
4592
|
+
let ast = cache.get(xml);
|
|
4593
|
+
if (!ast) {
|
|
4594
|
+
// we clone here the xml to prevent modifying it in place
|
|
4595
|
+
ast = _parse(xml.cloneNode(true));
|
|
4596
|
+
cache.set(xml, ast);
|
|
4442
4597
|
}
|
|
4443
|
-
|
|
4444
|
-
const hasNoFirst = hasNoTCall && !html.includes(`${elem}_first`);
|
|
4445
|
-
const hasNoLast = hasNoTCall && !html.includes(`${elem}_last`);
|
|
4446
|
-
const hasNoIndex = hasNoTCall && !html.includes(`${elem}_index`);
|
|
4447
|
-
const hasNoValue = hasNoTCall && !html.includes(`${elem}_value`);
|
|
4448
|
-
return {
|
|
4449
|
-
type: 9 /* TForEach */,
|
|
4450
|
-
collection,
|
|
4451
|
-
elem,
|
|
4452
|
-
body,
|
|
4453
|
-
memo,
|
|
4454
|
-
key,
|
|
4455
|
-
hasNoFirst,
|
|
4456
|
-
hasNoLast,
|
|
4457
|
-
hasNoIndex,
|
|
4458
|
-
hasNoValue,
|
|
4459
|
-
};
|
|
4598
|
+
return ast;
|
|
4460
4599
|
}
|
|
4461
|
-
function
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
}
|
|
4465
|
-
const key = node.getAttribute("t-key");
|
|
4466
|
-
node.removeAttribute("t-key");
|
|
4467
|
-
const body = parseNode(node, ctx);
|
|
4468
|
-
if (!body) {
|
|
4469
|
-
return null;
|
|
4470
|
-
}
|
|
4471
|
-
return { type: 10 /* TKey */, expr: key, content: body };
|
|
4600
|
+
function _parse(xml) {
|
|
4601
|
+
normalizeXML(xml);
|
|
4602
|
+
const ctx = { inPreTag: false, inSVG: false };
|
|
4603
|
+
return parseNode(xml, ctx) || { type: 0 /* Text */, value: "" };
|
|
4472
4604
|
}
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
function parseTCall(node, ctx) {
|
|
4477
|
-
if (!node.hasAttribute("t-call")) {
|
|
4478
|
-
return null;
|
|
4479
|
-
}
|
|
4480
|
-
const subTemplate = node.getAttribute("t-call");
|
|
4481
|
-
node.removeAttribute("t-call");
|
|
4482
|
-
if (node.tagName !== "t") {
|
|
4483
|
-
const ast = parseNode(node, ctx);
|
|
4484
|
-
const tcall = { type: 7 /* TCall */, name: subTemplate, body: null };
|
|
4485
|
-
if (ast && ast.type === 2 /* DomNode */) {
|
|
4486
|
-
ast.content = [tcall];
|
|
4487
|
-
return ast;
|
|
4488
|
-
}
|
|
4489
|
-
if (ast && ast.type === 11 /* TComponent */) {
|
|
4490
|
-
return {
|
|
4491
|
-
...ast,
|
|
4492
|
-
slots: { default: { content: tcall, scope: null, on: null, attrs: null } },
|
|
4493
|
-
};
|
|
4494
|
-
}
|
|
4605
|
+
function parseNode(node, ctx) {
|
|
4606
|
+
if (!(node instanceof Element)) {
|
|
4607
|
+
return parseTextCommentNode(node, ctx);
|
|
4495
4608
|
}
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4609
|
+
return (parseTDebugLog(node, ctx) ||
|
|
4610
|
+
parseTForEach(node, ctx) ||
|
|
4611
|
+
parseTIf(node, ctx) ||
|
|
4612
|
+
parseTPortal(node, ctx) ||
|
|
4613
|
+
parseTCall(node, ctx) ||
|
|
4614
|
+
parseTCallBlock(node) ||
|
|
4615
|
+
parseTEscNode(node, ctx) ||
|
|
4616
|
+
parseTKey(node, ctx) ||
|
|
4617
|
+
parseTTranslation(node, ctx) ||
|
|
4618
|
+
parseTSlot(node, ctx) ||
|
|
4619
|
+
parseTOutNode(node, ctx) ||
|
|
4620
|
+
parseComponent(node, ctx) ||
|
|
4621
|
+
parseDOMNode(node, ctx) ||
|
|
4622
|
+
parseTSetNode(node, ctx) ||
|
|
4623
|
+
parseTNode(node, ctx));
|
|
4502
4624
|
}
|
|
4503
4625
|
// -----------------------------------------------------------------------------
|
|
4504
|
-
// t
|
|
4626
|
+
// <t /> tag
|
|
4505
4627
|
// -----------------------------------------------------------------------------
|
|
4506
|
-
function
|
|
4507
|
-
if (
|
|
4628
|
+
function parseTNode(node, ctx) {
|
|
4629
|
+
if (node.tagName !== "t") {
|
|
4508
4630
|
return null;
|
|
4509
4631
|
}
|
|
4510
|
-
|
|
4511
|
-
return {
|
|
4512
|
-
type: 15 /* TCallBlock */,
|
|
4513
|
-
name,
|
|
4514
|
-
};
|
|
4632
|
+
return parseChildNodes(node, ctx);
|
|
4515
4633
|
}
|
|
4516
4634
|
// -----------------------------------------------------------------------------
|
|
4517
|
-
//
|
|
4635
|
+
// Text and Comment Nodes
|
|
4518
4636
|
// -----------------------------------------------------------------------------
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
while (nextElement && nextElement.hasAttribute("t-elif")) {
|
|
4530
|
-
const condition = nextElement.getAttribute("t-elif");
|
|
4531
|
-
nextElement.removeAttribute("t-elif");
|
|
4532
|
-
const tElif = parseNode(nextElement, ctx);
|
|
4533
|
-
const next = nextElement.nextElementSibling;
|
|
4534
|
-
nextElement.remove();
|
|
4535
|
-
nextElement = next;
|
|
4536
|
-
if (tElif) {
|
|
4537
|
-
tElifs.push({ condition, content: tElif });
|
|
4637
|
+
const lineBreakRE = /[\r\n]/;
|
|
4638
|
+
const whitespaceRE = /\s+/g;
|
|
4639
|
+
function parseTextCommentNode(node, ctx) {
|
|
4640
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
4641
|
+
let value = node.textContent || "";
|
|
4642
|
+
if (!ctx.inPreTag) {
|
|
4643
|
+
if (lineBreakRE.test(value) && !value.trim()) {
|
|
4644
|
+
return null;
|
|
4645
|
+
}
|
|
4646
|
+
value = value.replace(whitespaceRE, " ");
|
|
4538
4647
|
}
|
|
4648
|
+
return { type: 0 /* Text */, value };
|
|
4539
4649
|
}
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
if (nextElement && nextElement.hasAttribute("t-else")) {
|
|
4543
|
-
nextElement.removeAttribute("t-else");
|
|
4544
|
-
tElse = parseNode(nextElement, ctx);
|
|
4545
|
-
nextElement.remove();
|
|
4650
|
+
else if (node.nodeType === Node.COMMENT_NODE) {
|
|
4651
|
+
return { type: 1 /* Comment */, value: node.textContent || "" };
|
|
4546
4652
|
}
|
|
4547
|
-
return
|
|
4548
|
-
type: 5 /* TIf */,
|
|
4549
|
-
condition,
|
|
4550
|
-
content,
|
|
4551
|
-
tElif: tElifs.length ? tElifs : null,
|
|
4552
|
-
tElse,
|
|
4553
|
-
};
|
|
4653
|
+
return null;
|
|
4554
4654
|
}
|
|
4555
4655
|
// -----------------------------------------------------------------------------
|
|
4556
|
-
//
|
|
4656
|
+
// debugging
|
|
4557
4657
|
// -----------------------------------------------------------------------------
|
|
4558
|
-
function
|
|
4559
|
-
if (
|
|
4560
|
-
|
|
4658
|
+
function parseTDebugLog(node, ctx) {
|
|
4659
|
+
if (node.hasAttribute("t-debug")) {
|
|
4660
|
+
node.removeAttribute("t-debug");
|
|
4661
|
+
return {
|
|
4662
|
+
type: 12 /* TDebug */,
|
|
4663
|
+
content: parseNode(node, ctx),
|
|
4664
|
+
};
|
|
4561
4665
|
}
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4666
|
+
if (node.hasAttribute("t-log")) {
|
|
4667
|
+
const expr = node.getAttribute("t-log");
|
|
4668
|
+
node.removeAttribute("t-log");
|
|
4669
|
+
return {
|
|
4670
|
+
type: 13 /* TLog */,
|
|
4671
|
+
expr,
|
|
4672
|
+
content: parseNode(node, ctx),
|
|
4673
|
+
};
|
|
4568
4674
|
}
|
|
4569
|
-
return
|
|
4675
|
+
return null;
|
|
4570
4676
|
}
|
|
4571
4677
|
// -----------------------------------------------------------------------------
|
|
4572
|
-
//
|
|
4678
|
+
// Regular dom node
|
|
4573
4679
|
// -----------------------------------------------------------------------------
|
|
4574
|
-
|
|
4575
|
-
const
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
"t-attf",
|
|
4583
|
-
"t-attf is not supported on components: use template strings for string interpolation in props",
|
|
4584
|
-
],
|
|
4585
|
-
]);
|
|
4586
|
-
function parseComponent(node, ctx) {
|
|
4587
|
-
let name = node.tagName;
|
|
4588
|
-
const firstLetter = name[0];
|
|
4589
|
-
let isDynamic = node.hasAttribute("t-component");
|
|
4590
|
-
if (isDynamic && name !== "t") {
|
|
4591
|
-
throw new Error(`Directive 't-component' can only be used on <t> nodes (used on a <${name}>)`);
|
|
4592
|
-
}
|
|
4593
|
-
if (!(firstLetter === firstLetter.toUpperCase() || isDynamic)) {
|
|
4680
|
+
const hasDotAtTheEnd = /\.[\w_]+\s*$/;
|
|
4681
|
+
const hasBracketsAtTheEnd = /\[[^\[]+\]\s*$/;
|
|
4682
|
+
const ROOT_SVG_TAGS = new Set(["svg", "g", "path"]);
|
|
4683
|
+
function parseDOMNode(node, ctx) {
|
|
4684
|
+
const { tagName } = node;
|
|
4685
|
+
const dynamicTag = node.getAttribute("t-tag");
|
|
4686
|
+
node.removeAttribute("t-tag");
|
|
4687
|
+
if (tagName === "t" && !dynamicTag) {
|
|
4594
4688
|
return null;
|
|
4595
4689
|
}
|
|
4596
|
-
if (
|
|
4597
|
-
|
|
4598
|
-
node.removeAttribute("t-component");
|
|
4690
|
+
if (tagName.startsWith("block-")) {
|
|
4691
|
+
throw new Error(`Invalid tag name: '${tagName}'`);
|
|
4599
4692
|
}
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4693
|
+
ctx = Object.assign({}, ctx);
|
|
4694
|
+
if (tagName === "pre") {
|
|
4695
|
+
ctx.inPreTag = true;
|
|
4696
|
+
}
|
|
4697
|
+
const shouldAddSVGNS = ROOT_SVG_TAGS.has(tagName) && !ctx.inSVG;
|
|
4698
|
+
ctx.inSVG = ctx.inSVG || shouldAddSVGNS;
|
|
4699
|
+
const ns = shouldAddSVGNS ? "http://www.w3.org/2000/svg" : null;
|
|
4700
|
+
const ref = node.getAttribute("t-ref");
|
|
4701
|
+
node.removeAttribute("t-ref");
|
|
4702
|
+
const nodeAttrsNames = node.getAttributeNames();
|
|
4703
|
+
let attrs = null;
|
|
4604
4704
|
let on = null;
|
|
4605
|
-
let
|
|
4606
|
-
for (let
|
|
4607
|
-
const value = node.getAttribute(
|
|
4608
|
-
if (
|
|
4609
|
-
if (
|
|
4610
|
-
|
|
4611
|
-
on[name.slice(5)] = value;
|
|
4612
|
-
}
|
|
4613
|
-
else {
|
|
4614
|
-
const message = directiveErrorMap.get(name.split("-").slice(0, 2).join("-"));
|
|
4615
|
-
throw new Error(message || `unsupported directive on Component: ${name}`);
|
|
4705
|
+
let model = null;
|
|
4706
|
+
for (let attr of nodeAttrsNames) {
|
|
4707
|
+
const value = node.getAttribute(attr);
|
|
4708
|
+
if (attr.startsWith("t-on")) {
|
|
4709
|
+
if (attr === "t-on") {
|
|
4710
|
+
throw new Error("Missing event name with t-on directive");
|
|
4616
4711
|
}
|
|
4712
|
+
on = on || {};
|
|
4713
|
+
on[attr.slice(5)] = value;
|
|
4617
4714
|
}
|
|
4618
|
-
else {
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
}
|
|
4622
|
-
}
|
|
4623
|
-
let slots = null;
|
|
4624
|
-
if (node.hasChildNodes()) {
|
|
4625
|
-
const clone = node.cloneNode(true);
|
|
4626
|
-
// named slots
|
|
4627
|
-
const slotNodes = Array.from(clone.querySelectorAll("[t-set-slot]"));
|
|
4628
|
-
for (let slotNode of slotNodes) {
|
|
4629
|
-
if (slotNode.tagName !== "t") {
|
|
4630
|
-
throw new Error(`Directive 't-set-slot' can only be used on <t> nodes (used on a <${slotNode.tagName}>)`);
|
|
4715
|
+
else if (attr.startsWith("t-model")) {
|
|
4716
|
+
if (!["input", "select", "textarea"].includes(tagName)) {
|
|
4717
|
+
throw new Error("The t-model directive only works with <input>, <textarea> and <select>");
|
|
4631
4718
|
}
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
while (el !== clone) {
|
|
4638
|
-
if (el.hasAttribute("t-component") || el.tagName[0] === el.tagName[0].toUpperCase()) {
|
|
4639
|
-
isInSubComponent = true;
|
|
4640
|
-
break;
|
|
4641
|
-
}
|
|
4642
|
-
el = el.parentElement;
|
|
4719
|
+
let baseExpr, expr;
|
|
4720
|
+
if (hasDotAtTheEnd.test(value)) {
|
|
4721
|
+
const index = value.lastIndexOf(".");
|
|
4722
|
+
baseExpr = value.slice(0, index);
|
|
4723
|
+
expr = `'${value.slice(index + 1)}'`;
|
|
4643
4724
|
}
|
|
4644
|
-
if (
|
|
4645
|
-
|
|
4725
|
+
else if (hasBracketsAtTheEnd.test(value)) {
|
|
4726
|
+
const index = value.lastIndexOf("[");
|
|
4727
|
+
baseExpr = value.slice(0, index);
|
|
4728
|
+
expr = value.slice(index + 1, -1);
|
|
4646
4729
|
}
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4730
|
+
else {
|
|
4731
|
+
throw new Error(`Invalid t-model expression: "${value}" (it should be assignable)`);
|
|
4732
|
+
}
|
|
4733
|
+
const typeAttr = node.getAttribute("type");
|
|
4734
|
+
const isInput = tagName === "input";
|
|
4735
|
+
const isSelect = tagName === "select";
|
|
4736
|
+
const isTextarea = tagName === "textarea";
|
|
4737
|
+
const isCheckboxInput = isInput && typeAttr === "checkbox";
|
|
4738
|
+
const isRadioInput = isInput && typeAttr === "radio";
|
|
4739
|
+
const isOtherInput = isInput && !isCheckboxInput && !isRadioInput;
|
|
4740
|
+
const hasLazyMod = attr.includes(".lazy");
|
|
4741
|
+
const hasNumberMod = attr.includes(".number");
|
|
4742
|
+
const hasTrimMod = attr.includes(".trim");
|
|
4743
|
+
const eventType = isRadioInput ? "click" : isSelect || hasLazyMod ? "change" : "input";
|
|
4744
|
+
model = {
|
|
4745
|
+
baseExpr,
|
|
4746
|
+
expr,
|
|
4747
|
+
targetAttr: isCheckboxInput ? "checked" : "value",
|
|
4748
|
+
specialInitTargetAttr: isRadioInput ? "checked" : null,
|
|
4749
|
+
eventType,
|
|
4750
|
+
hasDynamicChildren: false,
|
|
4751
|
+
shouldTrim: hasTrimMod && (isOtherInput || isTextarea),
|
|
4752
|
+
shouldNumberize: hasNumberMod && (isOtherInput || isTextarea),
|
|
4753
|
+
};
|
|
4754
|
+
if (isSelect) {
|
|
4755
|
+
// don't pollute the original ctx
|
|
4756
|
+
ctx = Object.assign({}, ctx);
|
|
4757
|
+
ctx.tModelInfo = model;
|
|
4667
4758
|
}
|
|
4668
|
-
slots = slots || {};
|
|
4669
|
-
slots[name] = { content: slotAst, on, attrs, scope };
|
|
4670
|
-
}
|
|
4671
|
-
// default slot
|
|
4672
|
-
const defaultContent = parseChildNodes(clone, ctx);
|
|
4673
|
-
if (defaultContent) {
|
|
4674
|
-
slots = slots || {};
|
|
4675
|
-
slots.default = { content: defaultContent, on, attrs: null, scope: defaultSlotScope };
|
|
4676
4759
|
}
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
}
|
|
4680
|
-
// -----------------------------------------------------------------------------
|
|
4681
|
-
// Slots
|
|
4682
|
-
// -----------------------------------------------------------------------------
|
|
4683
|
-
function parseTSlot(node, ctx) {
|
|
4684
|
-
if (!node.hasAttribute("t-slot")) {
|
|
4685
|
-
return null;
|
|
4686
|
-
}
|
|
4687
|
-
const name = node.getAttribute("t-slot");
|
|
4688
|
-
node.removeAttribute("t-slot");
|
|
4689
|
-
let attrs = null;
|
|
4690
|
-
let on = null;
|
|
4691
|
-
for (let attributeName of node.getAttributeNames()) {
|
|
4692
|
-
const value = node.getAttribute(attributeName);
|
|
4693
|
-
if (attributeName.startsWith("t-on-")) {
|
|
4694
|
-
on = on || {};
|
|
4695
|
-
on[attributeName.slice(5)] = value;
|
|
4760
|
+
else if (attr.startsWith("block-")) {
|
|
4761
|
+
throw new Error(`Invalid attribute: '${attr}'`);
|
|
4696
4762
|
}
|
|
4697
|
-
else {
|
|
4763
|
+
else if (attr !== "t-name") {
|
|
4764
|
+
if (attr.startsWith("t-") && !attr.startsWith("t-att")) {
|
|
4765
|
+
throw new Error(`Unknown QWeb directive: '${attr}'`);
|
|
4766
|
+
}
|
|
4767
|
+
const tModel = ctx.tModelInfo;
|
|
4768
|
+
if (tModel && ["t-att-value", "t-attf-value"].includes(attr)) {
|
|
4769
|
+
tModel.hasDynamicChildren = true;
|
|
4770
|
+
}
|
|
4698
4771
|
attrs = attrs || {};
|
|
4699
|
-
attrs[
|
|
4772
|
+
attrs[attr] = value;
|
|
4700
4773
|
}
|
|
4701
4774
|
}
|
|
4775
|
+
const children = parseChildren(node, ctx);
|
|
4702
4776
|
return {
|
|
4703
|
-
type:
|
|
4704
|
-
|
|
4777
|
+
type: 2 /* DomNode */,
|
|
4778
|
+
tag: tagName,
|
|
4779
|
+
dynamicTag,
|
|
4705
4780
|
attrs,
|
|
4706
4781
|
on,
|
|
4707
|
-
|
|
4782
|
+
ref,
|
|
4783
|
+
content: children,
|
|
4784
|
+
model,
|
|
4785
|
+
ns,
|
|
4708
4786
|
};
|
|
4709
4787
|
}
|
|
4710
|
-
|
|
4711
|
-
|
|
4788
|
+
// -----------------------------------------------------------------------------
|
|
4789
|
+
// t-esc
|
|
4790
|
+
// -----------------------------------------------------------------------------
|
|
4791
|
+
function parseTEscNode(node, ctx) {
|
|
4792
|
+
if (!node.hasAttribute("t-esc")) {
|
|
4712
4793
|
return null;
|
|
4713
4794
|
}
|
|
4714
|
-
node.
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4795
|
+
const escValue = node.getAttribute("t-esc");
|
|
4796
|
+
node.removeAttribute("t-esc");
|
|
4797
|
+
const tesc = {
|
|
4798
|
+
type: 4 /* TEsc */,
|
|
4799
|
+
expr: escValue,
|
|
4800
|
+
defaultValue: node.textContent || "",
|
|
4718
4801
|
};
|
|
4802
|
+
let ref = node.getAttribute("t-ref");
|
|
4803
|
+
node.removeAttribute("t-ref");
|
|
4804
|
+
const ast = parseNode(node, ctx);
|
|
4805
|
+
if (!ast) {
|
|
4806
|
+
return tesc;
|
|
4807
|
+
}
|
|
4808
|
+
if (ast.type === 2 /* DomNode */) {
|
|
4809
|
+
return {
|
|
4810
|
+
...ast,
|
|
4811
|
+
ref,
|
|
4812
|
+
content: [tesc],
|
|
4813
|
+
};
|
|
4814
|
+
}
|
|
4815
|
+
if (ast.type === 11 /* TComponent */) {
|
|
4816
|
+
throw new Error("t-esc is not supported on Component nodes");
|
|
4817
|
+
}
|
|
4818
|
+
return tesc;
|
|
4719
4819
|
}
|
|
4720
4820
|
// -----------------------------------------------------------------------------
|
|
4721
|
-
//
|
|
4821
|
+
// t-out
|
|
4722
4822
|
// -----------------------------------------------------------------------------
|
|
4723
|
-
function
|
|
4724
|
-
if (!node.hasAttribute("t-
|
|
4823
|
+
function parseTOutNode(node, ctx) {
|
|
4824
|
+
if (!node.hasAttribute("t-out") && !node.hasAttribute("t-raw")) {
|
|
4725
4825
|
return null;
|
|
4726
4826
|
}
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4827
|
+
if (node.hasAttribute("t-raw")) {
|
|
4828
|
+
console.warn(`t-raw has been deprecated in favor of t-out. If the value to render is not wrapped by the "markup" function, it will be escaped`);
|
|
4829
|
+
}
|
|
4830
|
+
const expr = (node.getAttribute("t-out") || node.getAttribute("t-raw"));
|
|
4831
|
+
node.removeAttribute("t-out");
|
|
4832
|
+
node.removeAttribute("t-raw");
|
|
4833
|
+
const tOut = { type: 8 /* TOut */, expr, body: null };
|
|
4834
|
+
const ref = node.getAttribute("t-ref");
|
|
4835
|
+
node.removeAttribute("t-ref");
|
|
4836
|
+
const ast = parseNode(node, ctx);
|
|
4837
|
+
if (!ast) {
|
|
4838
|
+
return tOut;
|
|
4839
|
+
}
|
|
4840
|
+
if (ast.type === 2 /* DomNode */) {
|
|
4841
|
+
tOut.body = ast.content.length ? ast.content : null;
|
|
4731
4842
|
return {
|
|
4732
|
-
|
|
4733
|
-
|
|
4843
|
+
...ast,
|
|
4844
|
+
ref,
|
|
4845
|
+
content: [tOut],
|
|
4734
4846
|
};
|
|
4735
4847
|
}
|
|
4736
|
-
return
|
|
4737
|
-
type: 17 /* TPortal */,
|
|
4738
|
-
target,
|
|
4739
|
-
content,
|
|
4740
|
-
};
|
|
4848
|
+
return tOut;
|
|
4741
4849
|
}
|
|
4742
4850
|
// -----------------------------------------------------------------------------
|
|
4743
|
-
//
|
|
4851
|
+
// t-foreach and t-key
|
|
4744
4852
|
// -----------------------------------------------------------------------------
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
function parseChildren(node, ctx) {
|
|
4749
|
-
const children = [];
|
|
4750
|
-
for (let child of node.childNodes) {
|
|
4751
|
-
const childAst = parseNode(child, ctx);
|
|
4752
|
-
if (childAst) {
|
|
4753
|
-
if (childAst.type === 3 /* Multi */) {
|
|
4754
|
-
children.push(...childAst.content);
|
|
4755
|
-
}
|
|
4756
|
-
else {
|
|
4757
|
-
children.push(childAst);
|
|
4758
|
-
}
|
|
4759
|
-
}
|
|
4853
|
+
function parseTForEach(node, ctx) {
|
|
4854
|
+
if (!node.hasAttribute("t-foreach")) {
|
|
4855
|
+
return null;
|
|
4760
4856
|
}
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
switch (children.length) {
|
|
4770
|
-
case 0:
|
|
4771
|
-
return null;
|
|
4772
|
-
case 1:
|
|
4773
|
-
return children[0];
|
|
4774
|
-
default:
|
|
4775
|
-
return { type: 3 /* Multi */, content: children };
|
|
4857
|
+
const html = node.outerHTML;
|
|
4858
|
+
const collection = node.getAttribute("t-foreach");
|
|
4859
|
+
node.removeAttribute("t-foreach");
|
|
4860
|
+
const elem = node.getAttribute("t-as") || "";
|
|
4861
|
+
node.removeAttribute("t-as");
|
|
4862
|
+
const key = node.getAttribute("t-key");
|
|
4863
|
+
if (!key) {
|
|
4864
|
+
throw new Error(`"Directive t-foreach should always be used with a t-key!" (expression: t-foreach="${collection}" t-as="${elem}")`);
|
|
4776
4865
|
}
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
*
|
|
4784
|
-
* @param el the element containing the tree that should be normalized
|
|
4785
|
-
*/
|
|
4786
|
-
function normalizeTIf(el) {
|
|
4787
|
-
let tbranch = el.querySelectorAll("[t-elif], [t-else]");
|
|
4788
|
-
for (let i = 0, ilen = tbranch.length; i < ilen; i++) {
|
|
4789
|
-
let node = tbranch[i];
|
|
4790
|
-
let prevElem = node.previousElementSibling;
|
|
4791
|
-
let pattr = (name) => prevElem.getAttribute(name);
|
|
4792
|
-
let nattr = (name) => +!!node.getAttribute(name);
|
|
4793
|
-
if (prevElem && (pattr("t-if") || pattr("t-elif"))) {
|
|
4794
|
-
if (pattr("t-foreach")) {
|
|
4795
|
-
throw new Error("t-if cannot stay at the same level as t-foreach when using t-elif or t-else");
|
|
4796
|
-
}
|
|
4797
|
-
if (["t-if", "t-elif", "t-else"].map(nattr).reduce(function (a, b) {
|
|
4798
|
-
return a + b;
|
|
4799
|
-
}) > 1) {
|
|
4800
|
-
throw new Error("Only one conditional branching directive is allowed per node");
|
|
4801
|
-
}
|
|
4802
|
-
// All text (with only spaces) and comment nodes (nodeType 8) between
|
|
4803
|
-
// branch nodes are removed
|
|
4804
|
-
let textNode;
|
|
4805
|
-
while ((textNode = node.previousSibling) !== prevElem) {
|
|
4806
|
-
if (textNode.nodeValue.trim().length && textNode.nodeType !== 8) {
|
|
4807
|
-
throw new Error("text is not allowed between branching directives");
|
|
4808
|
-
}
|
|
4809
|
-
textNode.remove();
|
|
4810
|
-
}
|
|
4811
|
-
}
|
|
4812
|
-
else {
|
|
4813
|
-
throw new Error("t-elif and t-else directives must be preceded by a t-if or t-elif directive");
|
|
4814
|
-
}
|
|
4866
|
+
node.removeAttribute("t-key");
|
|
4867
|
+
const memo = node.getAttribute("t-memo") || "";
|
|
4868
|
+
node.removeAttribute("t-memo");
|
|
4869
|
+
const body = parseNode(node, ctx);
|
|
4870
|
+
if (!body) {
|
|
4871
|
+
return null;
|
|
4815
4872
|
}
|
|
4873
|
+
const hasNoTCall = !html.includes("t-call");
|
|
4874
|
+
const hasNoFirst = hasNoTCall && !html.includes(`${elem}_first`);
|
|
4875
|
+
const hasNoLast = hasNoTCall && !html.includes(`${elem}_last`);
|
|
4876
|
+
const hasNoIndex = hasNoTCall && !html.includes(`${elem}_index`);
|
|
4877
|
+
const hasNoValue = hasNoTCall && !html.includes(`${elem}_value`);
|
|
4878
|
+
return {
|
|
4879
|
+
type: 9 /* TForEach */,
|
|
4880
|
+
collection,
|
|
4881
|
+
elem,
|
|
4882
|
+
body,
|
|
4883
|
+
memo,
|
|
4884
|
+
key,
|
|
4885
|
+
hasNoFirst,
|
|
4886
|
+
hasNoLast,
|
|
4887
|
+
hasNoIndex,
|
|
4888
|
+
hasNoValue,
|
|
4889
|
+
};
|
|
4816
4890
|
}
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
* component. Also throws if the component already has content. This function
|
|
4821
|
-
* modifies the Element in place.
|
|
4822
|
-
*
|
|
4823
|
-
* @param el the element containing the tree that should be normalized
|
|
4824
|
-
*/
|
|
4825
|
-
function normalizeTEsc(el) {
|
|
4826
|
-
const elements = [...el.querySelectorAll("[t-esc]")].filter((el) => el.tagName[0] === el.tagName[0].toUpperCase() || el.hasAttribute("t-component"));
|
|
4827
|
-
for (const el of elements) {
|
|
4828
|
-
if (el.childNodes.length) {
|
|
4829
|
-
throw new Error("Cannot have t-esc on a component that already has content");
|
|
4830
|
-
}
|
|
4831
|
-
const value = el.getAttribute("t-esc");
|
|
4832
|
-
el.removeAttribute("t-esc");
|
|
4833
|
-
const t = el.ownerDocument.createElement("t");
|
|
4834
|
-
if (value != null) {
|
|
4835
|
-
t.setAttribute("t-esc", value);
|
|
4836
|
-
}
|
|
4837
|
-
el.appendChild(t);
|
|
4891
|
+
function parseTKey(node, ctx) {
|
|
4892
|
+
if (!node.hasAttribute("t-key")) {
|
|
4893
|
+
return null;
|
|
4838
4894
|
}
|
|
4895
|
+
const key = node.getAttribute("t-key");
|
|
4896
|
+
node.removeAttribute("t-key");
|
|
4897
|
+
const body = parseNode(node, ctx);
|
|
4898
|
+
if (!body) {
|
|
4899
|
+
return null;
|
|
4900
|
+
}
|
|
4901
|
+
return { type: 10 /* TKey */, expr: key, content: body };
|
|
4839
4902
|
}
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
function normalizeXML(el) {
|
|
4847
|
-
normalizeTIf(el);
|
|
4848
|
-
normalizeTEsc(el);
|
|
4849
|
-
}
|
|
4850
|
-
/**
|
|
4851
|
-
* Parses an XML string into an XML document, throwing errors on parser errors
|
|
4852
|
-
* instead of returning an XML document containing the parseerror.
|
|
4853
|
-
*
|
|
4854
|
-
* @param xml the string to parse
|
|
4855
|
-
* @returns an XML document corresponding to the content of the string
|
|
4856
|
-
*/
|
|
4857
|
-
function parseXML$1(xml) {
|
|
4858
|
-
const parser = new DOMParser();
|
|
4859
|
-
const doc = parser.parseFromString(xml, "text/xml");
|
|
4860
|
-
if (doc.getElementsByTagName("parsererror").length) {
|
|
4861
|
-
let msg = "Invalid XML in template.";
|
|
4862
|
-
const parsererrorText = doc.getElementsByTagName("parsererror")[0].textContent;
|
|
4863
|
-
if (parsererrorText) {
|
|
4864
|
-
msg += "\nThe parser has produced the following error message:\n" + parsererrorText;
|
|
4865
|
-
const re = /\d+/g;
|
|
4866
|
-
const firstMatch = re.exec(parsererrorText);
|
|
4867
|
-
if (firstMatch) {
|
|
4868
|
-
const lineNumber = Number(firstMatch[0]);
|
|
4869
|
-
const line = xml.split("\n")[lineNumber - 1];
|
|
4870
|
-
const secondMatch = re.exec(parsererrorText);
|
|
4871
|
-
if (line && secondMatch) {
|
|
4872
|
-
const columnIndex = Number(secondMatch[0]) - 1;
|
|
4873
|
-
if (line[columnIndex]) {
|
|
4874
|
-
msg +=
|
|
4875
|
-
`\nThe error might be located at xml line ${lineNumber} column ${columnIndex}\n` +
|
|
4876
|
-
`${line}\n${"-".repeat(columnIndex - 1)}^`;
|
|
4877
|
-
}
|
|
4878
|
-
}
|
|
4879
|
-
}
|
|
4880
|
-
}
|
|
4881
|
-
throw new Error(msg);
|
|
4903
|
+
// -----------------------------------------------------------------------------
|
|
4904
|
+
// t-call
|
|
4905
|
+
// -----------------------------------------------------------------------------
|
|
4906
|
+
function parseTCall(node, ctx) {
|
|
4907
|
+
if (!node.hasAttribute("t-call")) {
|
|
4908
|
+
return null;
|
|
4882
4909
|
}
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
? !(template instanceof Element) || template.querySelector("[t-set], [t-call]") === null
|
|
4892
|
-
: !template.includes("t-set") && !template.includes("t-call");
|
|
4893
|
-
// code generation
|
|
4894
|
-
const codeGenerator = new CodeGenerator(ast, { ...options, hasSafeContext });
|
|
4895
|
-
const code = codeGenerator.generateCode();
|
|
4896
|
-
// template function
|
|
4897
|
-
return new Function("bdom, helpers", code);
|
|
4898
|
-
}
|
|
4899
|
-
|
|
4900
|
-
const TIMEOUT = Symbol("timeout");
|
|
4901
|
-
function wrapError(fn, hookName) {
|
|
4902
|
-
const error = new Error(`The following error occurred in ${hookName}: `);
|
|
4903
|
-
const timeoutError = new Error(`${hookName}'s promise hasn't resolved after 3 seconds`);
|
|
4904
|
-
const node = getCurrent();
|
|
4905
|
-
return (...args) => {
|
|
4906
|
-
try {
|
|
4907
|
-
const result = fn(...args);
|
|
4908
|
-
if (result instanceof Promise) {
|
|
4909
|
-
if (hookName === "onWillStart" || hookName === "onWillUpdateProps") {
|
|
4910
|
-
const fiber = node.fiber;
|
|
4911
|
-
Promise.race([
|
|
4912
|
-
result,
|
|
4913
|
-
new Promise((resolve) => setTimeout(() => resolve(TIMEOUT), 3000)),
|
|
4914
|
-
]).then((res) => {
|
|
4915
|
-
if (res === TIMEOUT && node.fiber === fiber) {
|
|
4916
|
-
console.warn(timeoutError);
|
|
4917
|
-
}
|
|
4918
|
-
});
|
|
4919
|
-
}
|
|
4920
|
-
return result.catch((cause) => {
|
|
4921
|
-
error.cause = cause;
|
|
4922
|
-
if (cause instanceof Error) {
|
|
4923
|
-
error.message += `"${cause.message}"`;
|
|
4924
|
-
}
|
|
4925
|
-
throw error;
|
|
4926
|
-
});
|
|
4927
|
-
}
|
|
4928
|
-
return result;
|
|
4910
|
+
const subTemplate = node.getAttribute("t-call");
|
|
4911
|
+
node.removeAttribute("t-call");
|
|
4912
|
+
if (node.tagName !== "t") {
|
|
4913
|
+
const ast = parseNode(node, ctx);
|
|
4914
|
+
const tcall = { type: 7 /* TCall */, name: subTemplate, body: null };
|
|
4915
|
+
if (ast && ast.type === 2 /* DomNode */) {
|
|
4916
|
+
ast.content = [tcall];
|
|
4917
|
+
return ast;
|
|
4929
4918
|
}
|
|
4930
|
-
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
|
|
4934
|
-
|
|
4919
|
+
if (ast && ast.type === 11 /* TComponent */) {
|
|
4920
|
+
return {
|
|
4921
|
+
...ast,
|
|
4922
|
+
slots: { default: { content: tcall, scope: null, on: null, attrs: null } },
|
|
4923
|
+
};
|
|
4935
4924
|
}
|
|
4925
|
+
}
|
|
4926
|
+
const body = parseChildren(node, ctx);
|
|
4927
|
+
return {
|
|
4928
|
+
type: 7 /* TCall */,
|
|
4929
|
+
name: subTemplate,
|
|
4930
|
+
body: body.length ? body : null,
|
|
4936
4931
|
};
|
|
4937
4932
|
}
|
|
4938
4933
|
// -----------------------------------------------------------------------------
|
|
4939
|
-
//
|
|
4934
|
+
// t-call-block
|
|
4940
4935
|
// -----------------------------------------------------------------------------
|
|
4941
|
-
function
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
node.willUpdateProps.push(decorate(fn.bind(node.component), "onWillUpdateProps"));
|
|
4950
|
-
}
|
|
4951
|
-
function onMounted(fn) {
|
|
4952
|
-
const node = getCurrent();
|
|
4953
|
-
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4954
|
-
node.mounted.push(decorate(fn.bind(node.component), "onMounted"));
|
|
4955
|
-
}
|
|
4956
|
-
function onWillPatch(fn) {
|
|
4957
|
-
const node = getCurrent();
|
|
4958
|
-
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4959
|
-
node.willPatch.unshift(decorate(fn.bind(node.component), "onWillPatch"));
|
|
4960
|
-
}
|
|
4961
|
-
function onPatched(fn) {
|
|
4962
|
-
const node = getCurrent();
|
|
4963
|
-
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4964
|
-
node.patched.push(decorate(fn.bind(node.component), "onPatched"));
|
|
4965
|
-
}
|
|
4966
|
-
function onWillUnmount(fn) {
|
|
4967
|
-
const node = getCurrent();
|
|
4968
|
-
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4969
|
-
node.willUnmount.unshift(decorate(fn.bind(node.component), "onWillUnmount"));
|
|
4970
|
-
}
|
|
4971
|
-
function onWillDestroy(fn) {
|
|
4972
|
-
const node = getCurrent();
|
|
4973
|
-
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4974
|
-
node.willDestroy.push(decorate(fn.bind(node.component), "onWillDestroy"));
|
|
4975
|
-
}
|
|
4976
|
-
function onWillRender(fn) {
|
|
4977
|
-
const node = getCurrent();
|
|
4978
|
-
const renderFn = node.renderFn;
|
|
4979
|
-
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
|
4980
|
-
fn = decorate(fn.bind(node.component), "onWillRender");
|
|
4981
|
-
node.renderFn = () => {
|
|
4982
|
-
fn();
|
|
4983
|
-
return renderFn();
|
|
4936
|
+
function parseTCallBlock(node, ctx) {
|
|
4937
|
+
if (!node.hasAttribute("t-call-block")) {
|
|
4938
|
+
return null;
|
|
4939
|
+
}
|
|
4940
|
+
const name = node.getAttribute("t-call-block");
|
|
4941
|
+
return {
|
|
4942
|
+
type: 15 /* TCallBlock */,
|
|
4943
|
+
name,
|
|
4984
4944
|
};
|
|
4985
4945
|
}
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
4946
|
+
// -----------------------------------------------------------------------------
|
|
4947
|
+
// t-if
|
|
4948
|
+
// -----------------------------------------------------------------------------
|
|
4949
|
+
function parseTIf(node, ctx) {
|
|
4950
|
+
if (!node.hasAttribute("t-if")) {
|
|
4951
|
+
return null;
|
|
4952
|
+
}
|
|
4953
|
+
const condition = node.getAttribute("t-if");
|
|
4954
|
+
node.removeAttribute("t-if");
|
|
4955
|
+
const content = parseNode(node, ctx) || { type: 0 /* Text */, value: "" };
|
|
4956
|
+
let nextElement = node.nextElementSibling;
|
|
4957
|
+
// t-elifs
|
|
4958
|
+
const tElifs = [];
|
|
4959
|
+
while (nextElement && nextElement.hasAttribute("t-elif")) {
|
|
4960
|
+
const condition = nextElement.getAttribute("t-elif");
|
|
4961
|
+
nextElement.removeAttribute("t-elif");
|
|
4962
|
+
const tElif = parseNode(nextElement, ctx);
|
|
4963
|
+
const next = nextElement.nextElementSibling;
|
|
4964
|
+
nextElement.remove();
|
|
4965
|
+
nextElement = next;
|
|
4966
|
+
if (tElif) {
|
|
4967
|
+
tElifs.push({ condition, content: tElif });
|
|
4968
|
+
}
|
|
4969
|
+
}
|
|
4970
|
+
// t-else
|
|
4971
|
+
let tElse = null;
|
|
4972
|
+
if (nextElement && nextElement.hasAttribute("t-else")) {
|
|
4973
|
+
nextElement.removeAttribute("t-else");
|
|
4974
|
+
tElse = parseNode(nextElement, ctx);
|
|
4975
|
+
nextElement.remove();
|
|
4976
|
+
}
|
|
4977
|
+
return {
|
|
4978
|
+
type: 5 /* TIf */,
|
|
4979
|
+
condition,
|
|
4980
|
+
content,
|
|
4981
|
+
tElif: tElifs.length ? tElifs : null,
|
|
4982
|
+
tElse,
|
|
4995
4983
|
};
|
|
4996
4984
|
}
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
}
|
|
5004
|
-
handlers.push(callback.bind(node.component));
|
|
5005
|
-
}
|
|
5006
|
-
|
|
5007
|
-
class Component {
|
|
5008
|
-
constructor(props, env, node) {
|
|
5009
|
-
this.props = props;
|
|
5010
|
-
this.env = env;
|
|
5011
|
-
this.__owl__ = node;
|
|
4985
|
+
// -----------------------------------------------------------------------------
|
|
4986
|
+
// t-set directive
|
|
4987
|
+
// -----------------------------------------------------------------------------
|
|
4988
|
+
function parseTSetNode(node, ctx) {
|
|
4989
|
+
if (!node.hasAttribute("t-set")) {
|
|
4990
|
+
return null;
|
|
5012
4991
|
}
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
4992
|
+
const name = node.getAttribute("t-set");
|
|
4993
|
+
const value = node.getAttribute("t-value") || null;
|
|
4994
|
+
const defaultValue = node.innerHTML === node.textContent ? node.textContent || null : null;
|
|
4995
|
+
let body = null;
|
|
4996
|
+
if (node.textContent !== node.innerHTML) {
|
|
4997
|
+
body = parseChildren(node, ctx);
|
|
5016
4998
|
}
|
|
4999
|
+
return { type: 6 /* TSet */, name, value, defaultValue, body };
|
|
5017
5000
|
}
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5001
|
+
// -----------------------------------------------------------------------------
|
|
5002
|
+
// Components
|
|
5003
|
+
// -----------------------------------------------------------------------------
|
|
5004
|
+
// Error messages when trying to use an unsupported directive on a component
|
|
5005
|
+
const directiveErrorMap = new Map([
|
|
5006
|
+
[
|
|
5007
|
+
"t-ref",
|
|
5008
|
+
"t-ref is no longer supported on components. Consider exposing only the public part of the component's API through a callback prop.",
|
|
5009
|
+
],
|
|
5010
|
+
["t-att", "t-att makes no sense on component: props are already treated as expressions"],
|
|
5011
|
+
[
|
|
5012
|
+
"t-attf",
|
|
5013
|
+
"t-attf is not supported on components: use template strings for string interpolation in props",
|
|
5014
|
+
],
|
|
5015
|
+
]);
|
|
5016
|
+
function parseComponent(node, ctx) {
|
|
5017
|
+
let name = node.tagName;
|
|
5018
|
+
const firstLetter = name[0];
|
|
5019
|
+
let isDynamic = node.hasAttribute("t-component");
|
|
5020
|
+
if (isDynamic && name !== "t") {
|
|
5021
|
+
throw new Error(`Directive 't-component' can only be used on <t> nodes (used on a <${name}>)`);
|
|
5027
5022
|
}
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
5023
|
+
if (!(firstLetter === firstLetter.toUpperCase() || isDynamic)) {
|
|
5024
|
+
return null;
|
|
5025
|
+
}
|
|
5026
|
+
if (isDynamic) {
|
|
5027
|
+
name = node.getAttribute("t-component");
|
|
5028
|
+
node.removeAttribute("t-component");
|
|
5029
|
+
}
|
|
5030
|
+
const dynamicProps = node.getAttribute("t-props");
|
|
5031
|
+
node.removeAttribute("t-props");
|
|
5032
|
+
const defaultSlotScope = node.getAttribute("t-slot-scope");
|
|
5033
|
+
node.removeAttribute("t-slot-scope");
|
|
5034
|
+
let on = null;
|
|
5035
|
+
let props = null;
|
|
5036
|
+
for (let name of node.getAttributeNames()) {
|
|
5037
|
+
const value = node.getAttribute(name);
|
|
5038
|
+
if (name.startsWith("t-")) {
|
|
5039
|
+
if (name.startsWith("t-on-")) {
|
|
5040
|
+
on = on || {};
|
|
5041
|
+
on[name.slice(5)] = value;
|
|
5035
5042
|
}
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
throw new Error(
|
|
5043
|
+
else {
|
|
5044
|
+
const message = directiveErrorMap.get(name.split("-").slice(0, 2).join("-"));
|
|
5045
|
+
throw new Error(message || `unsupported directive on Component: ${name}`);
|
|
5039
5046
|
}
|
|
5040
5047
|
}
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
this.realBDom.beforeRemove();
|
|
5045
|
-
}
|
|
5046
|
-
remove() {
|
|
5047
|
-
if (this.realBDom) {
|
|
5048
|
-
super.remove();
|
|
5049
|
-
this.realBDom.remove();
|
|
5050
|
-
this.realBDom = null;
|
|
5048
|
+
else {
|
|
5049
|
+
props = props || {};
|
|
5050
|
+
props[name] = value;
|
|
5051
5051
|
}
|
|
5052
5052
|
}
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5053
|
+
let slots = null;
|
|
5054
|
+
if (node.hasChildNodes()) {
|
|
5055
|
+
const clone = node.cloneNode(true);
|
|
5056
|
+
// named slots
|
|
5057
|
+
const slotNodes = Array.from(clone.querySelectorAll("[t-set-slot]"));
|
|
5058
|
+
for (let slotNode of slotNodes) {
|
|
5059
|
+
if (slotNode.tagName !== "t") {
|
|
5060
|
+
throw new Error(`Directive 't-set-slot' can only be used on <t> nodes (used on a <${slotNode.tagName}>)`);
|
|
5061
|
+
}
|
|
5062
|
+
const name = slotNode.getAttribute("t-set-slot");
|
|
5063
|
+
// check if this is defined in a sub component (in which case it should
|
|
5064
|
+
// be ignored)
|
|
5065
|
+
let el = slotNode.parentElement;
|
|
5066
|
+
let isInSubComponent = false;
|
|
5067
|
+
while (el !== clone) {
|
|
5068
|
+
if (el.hasAttribute("t-component") || el.tagName[0] === el.tagName[0].toUpperCase()) {
|
|
5069
|
+
isInSubComponent = true;
|
|
5070
|
+
break;
|
|
5071
|
+
}
|
|
5072
|
+
el = el.parentElement;
|
|
5073
|
+
}
|
|
5074
|
+
if (isInSubComponent) {
|
|
5075
|
+
continue;
|
|
5076
|
+
}
|
|
5077
|
+
slotNode.removeAttribute("t-set-slot");
|
|
5078
|
+
slotNode.remove();
|
|
5079
|
+
const slotAst = parseNode(slotNode, ctx);
|
|
5080
|
+
let on = null;
|
|
5081
|
+
let attrs = null;
|
|
5082
|
+
let scope = null;
|
|
5083
|
+
for (let attributeName of slotNode.getAttributeNames()) {
|
|
5084
|
+
const value = slotNode.getAttribute(attributeName);
|
|
5085
|
+
if (attributeName === "t-slot-scope") {
|
|
5086
|
+
scope = value;
|
|
5087
|
+
continue;
|
|
5088
|
+
}
|
|
5089
|
+
else if (attributeName.startsWith("t-on-")) {
|
|
5090
|
+
on = on || {};
|
|
5091
|
+
on[attributeName.slice(5)] = value;
|
|
5092
|
+
}
|
|
5093
|
+
else {
|
|
5094
|
+
attrs = attrs || {};
|
|
5095
|
+
attrs[attributeName] = value;
|
|
5096
|
+
}
|
|
5097
|
+
}
|
|
5098
|
+
slots = slots || {};
|
|
5099
|
+
slots[name] = { content: slotAst, on, attrs, scope };
|
|
5057
5100
|
}
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
5101
|
+
// default slot
|
|
5102
|
+
const defaultContent = parseChildNodes(clone, ctx);
|
|
5103
|
+
if (defaultContent) {
|
|
5104
|
+
slots = slots || {};
|
|
5105
|
+
slots.default = { content: defaultContent, on, attrs: null, scope: defaultSlotScope };
|
|
5061
5106
|
}
|
|
5062
5107
|
}
|
|
5108
|
+
return { type: 11 /* TComponent */, name, isDynamic, dynamicProps, props, slots, on };
|
|
5063
5109
|
}
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
if (node.bdom) {
|
|
5071
|
-
node.bdom.remove();
|
|
5072
|
-
}
|
|
5073
|
-
});
|
|
5074
|
-
}
|
|
5075
|
-
}
|
|
5076
|
-
Portal.template = xml `<t t-slot="default"/>`;
|
|
5077
|
-
Portal.props = {
|
|
5078
|
-
target: {
|
|
5079
|
-
type: String,
|
|
5080
|
-
},
|
|
5081
|
-
slots: true,
|
|
5082
|
-
};
|
|
5083
|
-
|
|
5084
|
-
/**
|
|
5085
|
-
* This file contains utility functions that will be injected in each template,
|
|
5086
|
-
* to perform various useful tasks in the compiled code.
|
|
5087
|
-
*/
|
|
5088
|
-
function withDefault(value, defaultValue) {
|
|
5089
|
-
return value === undefined || value === null || value === false ? defaultValue : value;
|
|
5090
|
-
}
|
|
5091
|
-
function callSlot(ctx, parent, key, name, dynamic, extra, defaultContent) {
|
|
5092
|
-
key = key + "__slot_" + name;
|
|
5093
|
-
const slots = ctx.props[TARGET].slots || {};
|
|
5094
|
-
const { __render, __ctx, __scope } = slots[name] || {};
|
|
5095
|
-
const slotScope = Object.create(__ctx || {});
|
|
5096
|
-
if (__scope) {
|
|
5097
|
-
slotScope[__scope] = extra;
|
|
5110
|
+
// -----------------------------------------------------------------------------
|
|
5111
|
+
// Slots
|
|
5112
|
+
// -----------------------------------------------------------------------------
|
|
5113
|
+
function parseTSlot(node, ctx) {
|
|
5114
|
+
if (!node.hasAttribute("t-slot")) {
|
|
5115
|
+
return null;
|
|
5098
5116
|
}
|
|
5099
|
-
const
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5117
|
+
const name = node.getAttribute("t-slot");
|
|
5118
|
+
node.removeAttribute("t-slot");
|
|
5119
|
+
let attrs = null;
|
|
5120
|
+
let on = null;
|
|
5121
|
+
for (let attributeName of node.getAttributeNames()) {
|
|
5122
|
+
const value = node.getAttribute(attributeName);
|
|
5123
|
+
if (attributeName.startsWith("t-on-")) {
|
|
5124
|
+
on = on || {};
|
|
5125
|
+
on[attributeName.slice(5)] = value;
|
|
5105
5126
|
}
|
|
5106
5127
|
else {
|
|
5107
|
-
|
|
5128
|
+
attrs = attrs || {};
|
|
5129
|
+
attrs[attributeName] = value;
|
|
5108
5130
|
}
|
|
5109
|
-
return multi([child1, child2]);
|
|
5110
5131
|
}
|
|
5111
|
-
return
|
|
5132
|
+
return {
|
|
5133
|
+
type: 14 /* TSlot */,
|
|
5134
|
+
name,
|
|
5135
|
+
attrs,
|
|
5136
|
+
on,
|
|
5137
|
+
defaultContent: parseChildNodes(node, ctx),
|
|
5138
|
+
};
|
|
5112
5139
|
}
|
|
5113
|
-
function
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
for (let k in ctx) {
|
|
5117
|
-
result[k] = ctx[k];
|
|
5140
|
+
function parseTTranslation(node, ctx) {
|
|
5141
|
+
if (node.getAttribute("t-translation") !== "off") {
|
|
5142
|
+
return null;
|
|
5118
5143
|
}
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5144
|
+
node.removeAttribute("t-translation");
|
|
5145
|
+
return {
|
|
5146
|
+
type: 16 /* TTranslation */,
|
|
5147
|
+
content: parseNode(node, ctx),
|
|
5148
|
+
};
|
|
5124
5149
|
}
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
}
|
|
5132
|
-
else if (collection) {
|
|
5133
|
-
values = Object.keys(collection);
|
|
5134
|
-
keys = Object.values(collection);
|
|
5135
|
-
}
|
|
5136
|
-
else {
|
|
5137
|
-
throw new Error("Invalid loop expression");
|
|
5150
|
+
// -----------------------------------------------------------------------------
|
|
5151
|
+
// Portal
|
|
5152
|
+
// -----------------------------------------------------------------------------
|
|
5153
|
+
function parseTPortal(node, ctx) {
|
|
5154
|
+
if (!node.hasAttribute("t-portal")) {
|
|
5155
|
+
return null;
|
|
5138
5156
|
}
|
|
5139
|
-
const
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
if (!newCtx) {
|
|
5148
|
-
ctx = ctx0;
|
|
5149
|
-
break;
|
|
5150
|
-
}
|
|
5151
|
-
ctx = newCtx;
|
|
5157
|
+
const target = node.getAttribute("t-portal");
|
|
5158
|
+
node.removeAttribute("t-portal");
|
|
5159
|
+
const content = parseNode(node, ctx);
|
|
5160
|
+
if (!content) {
|
|
5161
|
+
return {
|
|
5162
|
+
type: 0 /* Text */,
|
|
5163
|
+
value: "",
|
|
5164
|
+
};
|
|
5152
5165
|
}
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5166
|
+
return {
|
|
5167
|
+
type: 17 /* TPortal */,
|
|
5168
|
+
target,
|
|
5169
|
+
content,
|
|
5170
|
+
};
|
|
5158
5171
|
}
|
|
5159
|
-
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5172
|
+
// -----------------------------------------------------------------------------
|
|
5173
|
+
// helpers
|
|
5174
|
+
// -----------------------------------------------------------------------------
|
|
5175
|
+
/**
|
|
5176
|
+
* Parse all the child nodes of a given node and return a list of ast elements
|
|
5177
|
+
*/
|
|
5178
|
+
function parseChildren(node, ctx) {
|
|
5179
|
+
const children = [];
|
|
5180
|
+
for (let child of node.childNodes) {
|
|
5181
|
+
const childAst = parseNode(child, ctx);
|
|
5182
|
+
if (childAst) {
|
|
5183
|
+
if (childAst.type === 3 /* Multi */) {
|
|
5184
|
+
children.push(...childAst.content);
|
|
5185
|
+
}
|
|
5186
|
+
else {
|
|
5187
|
+
children.push(childAst);
|
|
5188
|
+
}
|
|
5163
5189
|
}
|
|
5164
5190
|
}
|
|
5165
|
-
return
|
|
5166
|
-
}
|
|
5167
|
-
class LazyValue {
|
|
5168
|
-
constructor(fn, ctx, node) {
|
|
5169
|
-
this.fn = fn;
|
|
5170
|
-
this.ctx = capture(ctx);
|
|
5171
|
-
this.node = node;
|
|
5172
|
-
}
|
|
5173
|
-
evaluate() {
|
|
5174
|
-
return this.fn(this.ctx, this.node);
|
|
5175
|
-
}
|
|
5176
|
-
toString() {
|
|
5177
|
-
return this.evaluate().toString();
|
|
5178
|
-
}
|
|
5191
|
+
return children;
|
|
5179
5192
|
}
|
|
5180
|
-
|
|
5181
|
-
*
|
|
5193
|
+
/**
|
|
5194
|
+
* Parse all the child nodes of a given node and return an ast if possible.
|
|
5195
|
+
* In the case there are multiple children, they are wrapped in a astmulti.
|
|
5182
5196
|
*/
|
|
5183
|
-
function
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
}
|
|
5193
|
-
else if (value instanceof LazyValue) {
|
|
5194
|
-
safeKey = `lazy_value`;
|
|
5195
|
-
block = value.evaluate();
|
|
5196
|
-
}
|
|
5197
|
-
else if (value instanceof String || typeof value === "string") {
|
|
5198
|
-
safeKey = "string_unsafe";
|
|
5199
|
-
block = text(value);
|
|
5200
|
-
}
|
|
5201
|
-
else {
|
|
5202
|
-
// Assuming it is a block
|
|
5203
|
-
safeKey = "block_safe";
|
|
5204
|
-
block = value;
|
|
5197
|
+
function parseChildNodes(node, ctx) {
|
|
5198
|
+
const children = parseChildren(node, ctx);
|
|
5199
|
+
switch (children.length) {
|
|
5200
|
+
case 0:
|
|
5201
|
+
return null;
|
|
5202
|
+
case 1:
|
|
5203
|
+
return children[0];
|
|
5204
|
+
default:
|
|
5205
|
+
return { type: 3 /* Multi */, content: children };
|
|
5205
5206
|
}
|
|
5206
|
-
return toggler(safeKey, block);
|
|
5207
5207
|
}
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5208
|
+
/**
|
|
5209
|
+
* Normalizes the content of an Element so that t-if/t-elif/t-else directives
|
|
5210
|
+
* immediately follow one another (by removing empty text nodes or comments).
|
|
5211
|
+
* Throws an error when a conditional branching statement is malformed. This
|
|
5212
|
+
* function modifies the Element in place.
|
|
5213
|
+
*
|
|
5214
|
+
* @param el the element containing the tree that should be normalized
|
|
5215
|
+
*/
|
|
5216
|
+
function normalizeTIf(el) {
|
|
5217
|
+
let tbranch = el.querySelectorAll("[t-elif], [t-else]");
|
|
5218
|
+
for (let i = 0, ilen = tbranch.length; i < ilen; i++) {
|
|
5219
|
+
let node = tbranch[i];
|
|
5220
|
+
let prevElem = node.previousElementSibling;
|
|
5221
|
+
let pattr = (name) => prevElem.getAttribute(name);
|
|
5222
|
+
let nattr = (name) => +!!node.getAttribute(name);
|
|
5223
|
+
if (prevElem && (pattr("t-if") || pattr("t-elif"))) {
|
|
5224
|
+
if (pattr("t-foreach")) {
|
|
5225
|
+
throw new Error("t-if cannot stay at the same level as t-foreach when using t-elif or t-else");
|
|
5226
|
+
}
|
|
5227
|
+
if (["t-if", "t-elif", "t-else"].map(nattr).reduce(function (a, b) {
|
|
5228
|
+
return a + b;
|
|
5229
|
+
}) > 1) {
|
|
5230
|
+
throw new Error("Only one conditional branching directive is allowed per node");
|
|
5231
|
+
}
|
|
5232
|
+
// All text (with only spaces) and comment nodes (nodeType 8) between
|
|
5233
|
+
// branch nodes are removed
|
|
5234
|
+
let textNode;
|
|
5235
|
+
while ((textNode = node.previousSibling) !== prevElem) {
|
|
5236
|
+
if (textNode.nodeValue.trim().length && textNode.nodeType !== 8) {
|
|
5237
|
+
throw new Error("text is not allowed between branching directives");
|
|
5238
|
+
}
|
|
5239
|
+
textNode.remove();
|
|
5240
|
+
}
|
|
5241
|
+
}
|
|
5242
|
+
else {
|
|
5243
|
+
throw new Error("t-elif and t-else directives must be preceded by a t-if or t-elif directive");
|
|
5244
|
+
}
|
|
5220
5245
|
}
|
|
5221
|
-
return boundFn;
|
|
5222
5246
|
}
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5247
|
+
/**
|
|
5248
|
+
* Normalizes the content of an Element so that t-esc directives on components
|
|
5249
|
+
* are removed and instead places a <t t-esc=""> as the default slot of the
|
|
5250
|
+
* component. Also throws if the component already has content. This function
|
|
5251
|
+
* modifies the Element in place.
|
|
5252
|
+
*
|
|
5253
|
+
* @param el the element containing the tree that should be normalized
|
|
5254
|
+
*/
|
|
5255
|
+
function normalizeTEsc(el) {
|
|
5256
|
+
const elements = [...el.querySelectorAll("[t-esc]")].filter((el) => el.tagName[0] === el.tagName[0].toUpperCase() || el.hasAttribute("t-component"));
|
|
5257
|
+
for (const el of elements) {
|
|
5258
|
+
if (el.childNodes.length) {
|
|
5259
|
+
throw new Error("Cannot have t-esc on a component that already has content");
|
|
5231
5260
|
}
|
|
5232
|
-
|
|
5233
|
-
|
|
5261
|
+
const value = el.getAttribute("t-esc");
|
|
5262
|
+
el.removeAttribute("t-esc");
|
|
5263
|
+
const t = el.ownerDocument.createElement("t");
|
|
5264
|
+
if (value != null) {
|
|
5265
|
+
t.setAttribute("t-esc", value);
|
|
5234
5266
|
}
|
|
5235
|
-
|
|
5267
|
+
el.appendChild(t);
|
|
5268
|
+
}
|
|
5236
5269
|
}
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
};
|
|
5255
|
-
|
|
5256
|
-
const bdom = { text, createBlock, list, multi, html, toggler, component, comment };
|
|
5270
|
+
/**
|
|
5271
|
+
* Normalizes the tree inside a given element and do some preliminary validation
|
|
5272
|
+
* on it. This function modifies the Element in place.
|
|
5273
|
+
*
|
|
5274
|
+
* @param el the element containing the tree that should be normalized
|
|
5275
|
+
*/
|
|
5276
|
+
function normalizeXML(el) {
|
|
5277
|
+
normalizeTIf(el);
|
|
5278
|
+
normalizeTEsc(el);
|
|
5279
|
+
}
|
|
5280
|
+
/**
|
|
5281
|
+
* Parses an XML string into an XML document, throwing errors on parser errors
|
|
5282
|
+
* instead of returning an XML document containing the parseerror.
|
|
5283
|
+
*
|
|
5284
|
+
* @param xml the string to parse
|
|
5285
|
+
* @returns an XML document corresponding to the content of the string
|
|
5286
|
+
*/
|
|
5257
5287
|
function parseXML(xml) {
|
|
5258
5288
|
const parser = new DOMParser();
|
|
5259
5289
|
const doc = parser.parseFromString(xml, "text/xml");
|
|
@@ -5281,84 +5311,131 @@
|
|
|
5281
5311
|
throw new Error(msg);
|
|
5282
5312
|
}
|
|
5283
5313
|
return doc;
|
|
5284
|
-
}
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
|
|
5297
|
-
|
|
5298
|
-
|
|
5299
|
-
|
|
5300
|
-
|
|
5301
|
-
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
|
|
5314
|
+
}
|
|
5315
|
+
|
|
5316
|
+
function compile(template, options = {}) {
|
|
5317
|
+
// parsing
|
|
5318
|
+
const ast = parse(template);
|
|
5319
|
+
// some work
|
|
5320
|
+
const hasSafeContext = template instanceof Node
|
|
5321
|
+
? !(template instanceof Element) || template.querySelector("[t-set], [t-call]") === null
|
|
5322
|
+
: !template.includes("t-set") && !template.includes("t-call");
|
|
5323
|
+
// code generation
|
|
5324
|
+
const codeGenerator = new CodeGenerator(ast, { ...options, hasSafeContext });
|
|
5325
|
+
const code = codeGenerator.generateCode();
|
|
5326
|
+
// template function
|
|
5327
|
+
return new Function("app, bdom, helpers", code);
|
|
5328
|
+
}
|
|
5329
|
+
|
|
5330
|
+
const mainEventHandler = (data, ev, currentTarget) => {
|
|
5331
|
+
const { data: _data, modifiers } = filterOutModifiersFromData(data);
|
|
5332
|
+
data = _data;
|
|
5333
|
+
let stopped = false;
|
|
5334
|
+
if (modifiers.length) {
|
|
5335
|
+
let selfMode = false;
|
|
5336
|
+
const isSelf = ev.target === currentTarget;
|
|
5337
|
+
for (const mod of modifiers) {
|
|
5338
|
+
switch (mod) {
|
|
5339
|
+
case "self":
|
|
5340
|
+
selfMode = true;
|
|
5341
|
+
if (isSelf) {
|
|
5342
|
+
continue;
|
|
5343
|
+
}
|
|
5344
|
+
else {
|
|
5345
|
+
return stopped;
|
|
5346
|
+
}
|
|
5347
|
+
case "prevent":
|
|
5348
|
+
if ((selfMode && isSelf) || !selfMode)
|
|
5349
|
+
ev.preventDefault();
|
|
5350
|
+
continue;
|
|
5351
|
+
case "stop":
|
|
5352
|
+
if ((selfMode && isSelf) || !selfMode)
|
|
5353
|
+
ev.stopPropagation();
|
|
5354
|
+
stopped = true;
|
|
5355
|
+
continue;
|
|
5356
|
+
}
|
|
5315
5357
|
}
|
|
5316
|
-
this.rawTemplates[name] = template;
|
|
5317
5358
|
}
|
|
5318
|
-
|
|
5319
|
-
|
|
5320
|
-
|
|
5321
|
-
|
|
5359
|
+
// If handler is empty, the array slot 0 will also be empty, and data will not have the property 0
|
|
5360
|
+
// We check this rather than data[0] being truthy (or typeof function) so that it crashes
|
|
5361
|
+
// as expected when there is a handler expression that evaluates to a falsy value
|
|
5362
|
+
if (Object.hasOwnProperty.call(data, 0)) {
|
|
5363
|
+
const handler = data[0];
|
|
5364
|
+
if (typeof handler !== "function") {
|
|
5365
|
+
throw new Error(`Invalid handler (expected a function, received: '${handler}')`);
|
|
5322
5366
|
}
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
this.addTemplate(name, template, options);
|
|
5367
|
+
let node = data[1] ? data[1].__owl__ : null;
|
|
5368
|
+
if (node ? node.status === 1 /* MOUNTED */ : true) {
|
|
5369
|
+
handler.call(node ? node.component : null, ev);
|
|
5327
5370
|
}
|
|
5328
5371
|
}
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
|
|
5334
|
-
|
|
5335
|
-
|
|
5336
|
-
|
|
5372
|
+
return stopped;
|
|
5373
|
+
};
|
|
5374
|
+
|
|
5375
|
+
// -----------------------------------------------------------------------------
|
|
5376
|
+
// Scheduler
|
|
5377
|
+
// -----------------------------------------------------------------------------
|
|
5378
|
+
class Scheduler {
|
|
5379
|
+
constructor() {
|
|
5380
|
+
this.tasks = new Set();
|
|
5381
|
+
this.frame = 0;
|
|
5382
|
+
this.delayedRenders = [];
|
|
5383
|
+
this.requestAnimationFrame = Scheduler.requestAnimationFrame;
|
|
5384
|
+
}
|
|
5385
|
+
addFiber(fiber) {
|
|
5386
|
+
this.tasks.add(fiber.root);
|
|
5387
|
+
}
|
|
5388
|
+
/**
|
|
5389
|
+
* Process all current tasks. This only applies to the fibers that are ready.
|
|
5390
|
+
* Other tasks are left unchanged.
|
|
5391
|
+
*/
|
|
5392
|
+
flush() {
|
|
5393
|
+
if (this.delayedRenders.length) {
|
|
5394
|
+
let renders = this.delayedRenders;
|
|
5395
|
+
this.delayedRenders = [];
|
|
5396
|
+
for (let f of renders) {
|
|
5397
|
+
if (f.root && f.node.status !== 2 /* DESTROYED */ && f.node.fiber === f) {
|
|
5398
|
+
f.render();
|
|
5337
5399
|
}
|
|
5338
|
-
catch { }
|
|
5339
|
-
throw new Error(`Missing template: "${name}"${extraInfo}`);
|
|
5340
5400
|
}
|
|
5341
|
-
const templateFn = this._compileTemplate(name, rawTemplate);
|
|
5342
|
-
// first add a function to lazily get the template, in case there is a
|
|
5343
|
-
// recursive call to the template name
|
|
5344
|
-
const templates = this.templates;
|
|
5345
|
-
this.templates[name] = function (context, parent) {
|
|
5346
|
-
return templates[name].call(this, context, parent);
|
|
5347
|
-
};
|
|
5348
|
-
const template = templateFn(bdom, this.helpers);
|
|
5349
|
-
this.templates[name] = template;
|
|
5350
5401
|
}
|
|
5351
|
-
|
|
5402
|
+
if (this.frame === 0) {
|
|
5403
|
+
this.frame = this.requestAnimationFrame(() => {
|
|
5404
|
+
this.frame = 0;
|
|
5405
|
+
this.tasks.forEach((fiber) => this.processFiber(fiber));
|
|
5406
|
+
for (let task of this.tasks) {
|
|
5407
|
+
if (task.node.status === 2 /* DESTROYED */) {
|
|
5408
|
+
this.tasks.delete(task);
|
|
5409
|
+
}
|
|
5410
|
+
}
|
|
5411
|
+
});
|
|
5412
|
+
}
|
|
5352
5413
|
}
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5414
|
+
processFiber(fiber) {
|
|
5415
|
+
if (fiber.root !== fiber) {
|
|
5416
|
+
this.tasks.delete(fiber);
|
|
5417
|
+
return;
|
|
5418
|
+
}
|
|
5419
|
+
const hasError = fibersInError.has(fiber);
|
|
5420
|
+
if (hasError && fiber.counter !== 0) {
|
|
5421
|
+
this.tasks.delete(fiber);
|
|
5422
|
+
return;
|
|
5423
|
+
}
|
|
5424
|
+
if (fiber.node.status === 2 /* DESTROYED */) {
|
|
5425
|
+
this.tasks.delete(fiber);
|
|
5426
|
+
return;
|
|
5427
|
+
}
|
|
5428
|
+
if (fiber.counter === 0) {
|
|
5429
|
+
if (!hasError) {
|
|
5430
|
+
fiber.complete();
|
|
5431
|
+
}
|
|
5432
|
+
this.tasks.delete(fiber);
|
|
5433
|
+
}
|
|
5360
5434
|
}
|
|
5361
|
-
}
|
|
5435
|
+
}
|
|
5436
|
+
// capture the value of requestAnimationFrame as soon as possible, to avoid
|
|
5437
|
+
// interactions with other code, such as test frameworks that override them
|
|
5438
|
+
Scheduler.requestAnimationFrame = window.requestAnimationFrame.bind(window);
|
|
5362
5439
|
|
|
5363
5440
|
let hasBeenLogged = false;
|
|
5364
5441
|
const DEV_MSG = () => {
|
|
@@ -5377,6 +5454,7 @@ See https://github.com/odoo/owl/blob/${hash}/doc/reference/app.md#configuration
|
|
|
5377
5454
|
if (config.test) {
|
|
5378
5455
|
this.dev = true;
|
|
5379
5456
|
}
|
|
5457
|
+
this.warnIfNoStaticProps = config.warnIfNoStaticProps || false;
|
|
5380
5458
|
if (this.dev && !config.test && !hasBeenLogged) {
|
|
5381
5459
|
console.info(DEV_MSG());
|
|
5382
5460
|
hasBeenLogged = true;
|
|
@@ -5388,6 +5466,9 @@ See https://github.com/odoo/owl/blob/${hash}/doc/reference/app.md#configuration
|
|
|
5388
5466
|
}
|
|
5389
5467
|
mount(target, options) {
|
|
5390
5468
|
App.validateTarget(target);
|
|
5469
|
+
if (this.dev) {
|
|
5470
|
+
validateProps(this.Root, this.props, { __owl__: { app: this } });
|
|
5471
|
+
}
|
|
5391
5472
|
const node = this.makeNode(this.Root, this.props);
|
|
5392
5473
|
const prom = this.mountNode(node, target, options);
|
|
5393
5474
|
this.root = node;
|
|
@@ -5566,6 +5647,15 @@ See https://github.com/odoo/owl/blob/${hash}/doc/reference/app.md#configuration
|
|
|
5566
5647
|
};
|
|
5567
5648
|
const __info__ = {};
|
|
5568
5649
|
|
|
5650
|
+
TemplateSet.prototype._compileTemplate = function _compileTemplate(name, template) {
|
|
5651
|
+
return compile(template, {
|
|
5652
|
+
name,
|
|
5653
|
+
dev: this.dev,
|
|
5654
|
+
translateFn: this.translateFn,
|
|
5655
|
+
translatableAttributes: this.translatableAttributes,
|
|
5656
|
+
});
|
|
5657
|
+
};
|
|
5658
|
+
|
|
5569
5659
|
exports.App = App;
|
|
5570
5660
|
exports.Component = Component;
|
|
5571
5661
|
exports.EventBus = EventBus;
|
|
@@ -5596,15 +5686,16 @@ See https://github.com/odoo/owl/blob/${hash}/doc/reference/app.md#configuration
|
|
|
5596
5686
|
exports.useRef = useRef;
|
|
5597
5687
|
exports.useState = useState;
|
|
5598
5688
|
exports.useSubEnv = useSubEnv;
|
|
5689
|
+
exports.validate = validate;
|
|
5599
5690
|
exports.whenReady = whenReady;
|
|
5600
5691
|
exports.xml = xml;
|
|
5601
5692
|
|
|
5602
5693
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
5603
5694
|
|
|
5604
5695
|
|
|
5605
|
-
__info__.version = '2.0.0-beta-
|
|
5606
|
-
__info__.date = '2022-
|
|
5607
|
-
__info__.hash = '
|
|
5696
|
+
__info__.version = '2.0.0-beta-8';
|
|
5697
|
+
__info__.date = '2022-05-31T12:25:19.319Z';
|
|
5698
|
+
__info__.hash = 'b56a9c2';
|
|
5608
5699
|
__info__.url = 'https://github.com/odoo/owl';
|
|
5609
5700
|
|
|
5610
5701
|
|