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