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