@odoo/owl 3.0.0-alpha.13 → 3.0.0-alpha.14

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.
Files changed (54) hide show
  1. package/dist/compile_templates.mjs +2442 -2435
  2. package/dist/owl.cjs.js +6459 -6381
  3. package/dist/owl.es.js +6459 -6379
  4. package/dist/owl.iife.js +6459 -6381
  5. package/dist/owl.iife.min.js +1 -1
  6. package/dist/types/common/owl_error.d.ts +3 -3
  7. package/dist/types/common/utils.d.ts +8 -8
  8. package/dist/types/compiler/code_generator.d.ts +150 -150
  9. package/dist/types/compiler/index.d.ts +13 -13
  10. package/dist/types/compiler/inline_expressions.d.ts +59 -59
  11. package/dist/types/compiler/parser.d.ts +171 -171
  12. package/dist/types/compiler/standalone/index.d.ts +2 -2
  13. package/dist/types/compiler/standalone/setup_jsdom.d.ts +1 -1
  14. package/dist/types/index.d.ts +1 -1
  15. package/dist/types/owl.d.ts +669 -671
  16. package/dist/types/runtime/app.d.ts +57 -57
  17. package/dist/types/runtime/blockdom/attributes.d.ts +6 -6
  18. package/dist/types/runtime/blockdom/block_compiler.d.ts +21 -21
  19. package/dist/types/runtime/blockdom/config.d.ts +8 -8
  20. package/dist/types/runtime/blockdom/event_catcher.d.ts +7 -7
  21. package/dist/types/runtime/blockdom/events.d.ts +8 -8
  22. package/dist/types/runtime/blockdom/html.d.ts +17 -17
  23. package/dist/types/runtime/blockdom/index.d.ts +26 -26
  24. package/dist/types/runtime/blockdom/list.d.ts +18 -18
  25. package/dist/types/runtime/blockdom/multi.d.ts +17 -17
  26. package/dist/types/runtime/blockdom/text.d.ts +26 -26
  27. package/dist/types/runtime/blockdom/toggler.d.ts +17 -17
  28. package/dist/types/runtime/component.d.ts +18 -18
  29. package/dist/types/runtime/component_node.d.ts +61 -61
  30. package/dist/types/runtime/event_handling.d.ts +1 -1
  31. package/dist/types/runtime/hooks.d.ts +34 -33
  32. package/dist/types/runtime/index.d.ts +43 -43
  33. package/dist/types/runtime/lifecycle_hooks.d.ts +10 -10
  34. package/dist/types/runtime/plugins.d.ts +30 -29
  35. package/dist/types/runtime/portal.d.ts +12 -12
  36. package/dist/types/runtime/props.d.ts +21 -17
  37. package/dist/types/runtime/reactivity/computations.d.ts +31 -31
  38. package/dist/types/runtime/reactivity/computed.d.ts +7 -7
  39. package/dist/types/runtime/reactivity/effect.d.ts +2 -2
  40. package/dist/types/runtime/reactivity/proxy.d.ts +48 -48
  41. package/dist/types/runtime/reactivity/signal.d.ts +18 -18
  42. package/dist/types/runtime/registry.d.ts +20 -20
  43. package/dist/types/runtime/rendering/error_handling.d.ts +13 -13
  44. package/dist/types/runtime/rendering/fibers.d.ts +37 -37
  45. package/dist/types/runtime/rendering/scheduler.d.ts +21 -21
  46. package/dist/types/runtime/rendering/template_helpers.d.ts +50 -50
  47. package/dist/types/runtime/resource.d.ts +15 -15
  48. package/dist/types/runtime/status.d.ts +9 -9
  49. package/dist/types/runtime/template_set.d.ts +39 -39
  50. package/dist/types/runtime/types.d.ts +62 -31
  51. package/dist/types/runtime/utils.d.ts +24 -24
  52. package/dist/types/runtime/validation.d.ts +19 -2
  53. package/dist/types/version.d.ts +1 -1
  54. package/package.json +13 -11
@@ -2,2455 +2,2462 @@ import { readFile, stat, readdir } from 'fs/promises';
2
2
  import path from 'path';
3
3
  import jsdom from 'jsdom';
4
4
 
5
- // -----------------------------------------------------------------------------
6
- // add global DOM stuff for compiler. Needs to be in a separate file so rollup
7
- // doesn't hoist the owl imports above this block of code.
8
- // -----------------------------------------------------------------------------
9
- var document$1 = new jsdom.JSDOM("", {});
10
- var window = document$1.window;
11
- global.document = window.document;
12
- global.window = window;
13
- global.DOMParser = window.DOMParser;
14
- global.Element = window.Element;
5
+ // -----------------------------------------------------------------------------
6
+ // add global DOM stuff for compiler. Needs to be in a separate file so rollup
7
+ // doesn't hoist the owl imports above this block of code.
8
+ // -----------------------------------------------------------------------------
9
+ var document$1 = new jsdom.JSDOM("", {});
10
+ var window = document$1.window;
11
+ global.document = window.document;
12
+ global.window = window;
13
+ global.DOMParser = window.DOMParser;
14
+ global.Element = window.Element;
15
15
  global.Node = window.Node;
16
16
 
17
- // Custom error class that wraps error that happen in the owl lifecycle
18
- class OwlError extends Error {
17
+ // Custom error class that wraps error that happen in the owl lifecycle
18
+ class OwlError extends Error {
19
19
  }
20
20
 
21
- /**
22
- * Owl QWeb Expression Parser
23
- *
24
- * Owl needs in various contexts to be able to understand the structure of a
25
- * string representing a javascript expression. The usual goal is to be able
26
- * to rewrite some variables. For example, if a template has
27
- *
28
- * ```xml
29
- * <t t-if="computeSomething({val: state.val})">...</t>
30
- * ```
31
- *
32
- * this needs to be translated in something like this:
33
- *
34
- * ```js
35
- * if (context["computeSomething"]({val: context["state"].val})) { ... }
36
- * ```
37
- *
38
- * This file contains the implementation of an extremely naive tokenizer/parser
39
- * and evaluator for javascript expressions. The supported grammar is basically
40
- * only expressive enough to understand the shape of objects, of arrays, and
41
- * various operators.
42
- */
43
- //------------------------------------------------------------------------------
44
- // Misc types, constants and helpers
45
- //------------------------------------------------------------------------------
46
- const RESERVED_WORDS = "true,false,NaN,null,undefined,debugger,console,window,in,instanceof,new,function,return,eval,void,Math,RegExp,Array,Object,Date,__globals__".split(",");
47
- const WORD_REPLACEMENT = Object.assign(Object.create(null), {
48
- and: "&&",
49
- or: "||",
50
- gt: ">",
51
- gte: ">=",
52
- lt: "<",
53
- lte: "<=",
54
- });
55
- const STATIC_TOKEN_MAP = Object.assign(Object.create(null), {
56
- "{": "LEFT_BRACE",
57
- "}": "RIGHT_BRACE",
58
- "[": "LEFT_BRACKET",
59
- "]": "RIGHT_BRACKET",
60
- ":": "COLON",
61
- ",": "COMMA",
62
- "(": "LEFT_PAREN",
63
- ")": "RIGHT_PAREN",
64
- });
65
- // note that the space after typeof is relevant. It makes sure that the formatted
66
- // expression has a space after typeof. Currently we don't support delete and void
67
- const OPERATORS = "...,.,===,==,+,!==,!=,!,||,&&,>=,>,<=,<,?,-,*,/,%,typeof ,=>,=,;,in ,new ,|,&,^,~".split(",");
68
- let tokenizeString = function (expr) {
69
- let s = expr[0];
70
- let start = s;
71
- if (s !== "'" && s !== '"' && s !== "`") {
72
- return false;
73
- }
74
- let i = 1;
75
- let cur;
76
- while (expr[i] && expr[i] !== start) {
77
- cur = expr[i];
78
- s += cur;
79
- if (cur === "\\") {
80
- i++;
81
- cur = expr[i];
82
- if (!cur) {
83
- throw new OwlError("Invalid expression");
84
- }
85
- s += cur;
86
- }
87
- i++;
88
- }
89
- if (expr[i] !== start) {
90
- throw new OwlError("Invalid expression");
91
- }
92
- s += start;
93
- if (start === "`") {
94
- return {
95
- type: "TEMPLATE_STRING",
96
- value: s,
97
- replace(replacer) {
98
- return s.replace(/\$\{(.*?)\}/g, (match, group) => {
99
- return "${" + replacer(group) + "}";
100
- });
101
- },
102
- };
103
- }
104
- return { type: "VALUE", value: s };
105
- };
106
- let tokenizeNumber = function (expr) {
107
- let s = expr[0];
108
- if (s && s.match(/[0-9]/)) {
109
- let i = 1;
110
- while (expr[i] && expr[i].match(/[0-9]|\./)) {
111
- s += expr[i];
112
- i++;
113
- }
114
- return { type: "VALUE", value: s };
115
- }
116
- else {
117
- return false;
118
- }
119
- };
120
- let tokenizeSymbol = function (expr) {
121
- let s = expr[0];
122
- if (s && s.match(/[a-zA-Z_\$]/)) {
123
- let i = 1;
124
- while (expr[i] && expr[i].match(/[\w\$]/)) {
125
- s += expr[i];
126
- i++;
127
- }
128
- if (s in WORD_REPLACEMENT) {
129
- return { type: "OPERATOR", value: WORD_REPLACEMENT[s], size: s.length };
130
- }
131
- return { type: "SYMBOL", value: s };
132
- }
133
- else {
134
- return false;
135
- }
136
- };
137
- const tokenizeStatic = function (expr) {
138
- const char = expr[0];
139
- if (char && char in STATIC_TOKEN_MAP) {
140
- return { type: STATIC_TOKEN_MAP[char], value: char };
141
- }
142
- return false;
143
- };
144
- const tokenizeOperator = function (expr) {
145
- for (let op of OPERATORS) {
146
- if (expr.startsWith(op)) {
147
- return { type: "OPERATOR", value: op };
148
- }
149
- }
150
- return false;
151
- };
152
- const TOKENIZERS = [
153
- tokenizeString,
154
- tokenizeNumber,
155
- tokenizeOperator,
156
- tokenizeSymbol,
157
- tokenizeStatic,
158
- ];
159
- /**
160
- * Convert a javascript expression (as a string) into a list of tokens. For
161
- * example: `tokenize("1 + b")` will return:
162
- * ```js
163
- * [
164
- * {type: "VALUE", value: "1"},
165
- * {type: "OPERATOR", value: "+"},
166
- * {type: "SYMBOL", value: "b"}
167
- * ]
168
- * ```
169
- */
170
- function tokenize(expr) {
171
- const result = [];
172
- let token = true;
173
- let error;
174
- let current = expr;
175
- try {
176
- while (token) {
177
- current = current.trim();
178
- if (current) {
179
- for (let tokenizer of TOKENIZERS) {
180
- token = tokenizer(current);
181
- if (token) {
182
- result.push(token);
183
- current = current.slice(token.size || token.value.length);
184
- break;
185
- }
186
- }
187
- }
188
- else {
189
- token = false;
190
- }
191
- }
192
- }
193
- catch (e) {
194
- error = e; // Silence all errors and throw a generic error below
195
- }
196
- if (current.length || error) {
197
- throw new OwlError(`Tokenizer error: could not tokenize \`${expr}\``);
198
- }
199
- return result;
200
- }
201
- //------------------------------------------------------------------------------
202
- // Expression "evaluator"
203
- //------------------------------------------------------------------------------
204
- const isLeftSeparator = (token) => token && (token.type === "LEFT_BRACE" || token.type === "COMMA");
205
- const isRightSeparator = (token) => token && (token.type === "RIGHT_BRACE" || token.type === "COMMA");
206
- /**
207
- * This is the main function exported by this file. This is the code that will
208
- * process an expression (given as a string) and returns another expression with
209
- * proper lookups in the context.
210
- *
211
- * Usually, this kind of code would be very simple to do if we had an AST (so,
212
- * if we had a javascript parser), since then, we would only need to find the
213
- * variables and replace them. However, a parser is more complicated, and there
214
- * are no standard builtin parser API.
215
- *
216
- * Since this method is applied to simple javasript expressions, and the work to
217
- * be done is actually quite simple, we actually can get away with not using a
218
- * parser, which helps with the code size.
219
- *
220
- * Here is the heuristic used by this method to determine if a token is a
221
- * variable:
222
- * - by default, all symbols are considered a variable
223
- * - unless the previous token is a dot (in that case, this is a property: `a.b`)
224
- * - or if the previous token is a left brace or a comma, and the next token is
225
- * a colon (in that case, this is an object key: `{a: b}`)
226
- *
227
- * Some specific code is also required to support arrow functions. If we detect
228
- * the arrow operator, then we add the current (or some previous tokens) token to
229
- * the list of variables so it does not get replaced by a lookup in the context
230
- */
231
- function compileExprToArray(expr) {
232
- const localVars = new Set();
233
- const tokens = tokenize(expr);
234
- let i = 0;
235
- let stack = []; // to track last opening (, [ or {
236
- while (i < tokens.length) {
237
- let token = tokens[i];
238
- let prevToken = tokens[i - 1];
239
- let nextToken = tokens[i + 1];
240
- let groupType = stack[stack.length - 1];
241
- switch (token.type) {
242
- case "LEFT_BRACE":
243
- case "LEFT_BRACKET":
244
- case "LEFT_PAREN":
245
- stack.push(token.type);
246
- break;
247
- case "RIGHT_BRACE":
248
- case "RIGHT_BRACKET":
249
- case "RIGHT_PAREN":
250
- stack.pop();
251
- }
252
- let isVar = token.type === "SYMBOL" && !RESERVED_WORDS.includes(token.value);
253
- if (isVar) {
254
- if (prevToken) {
255
- // normalize missing tokens: {a} should be equivalent to {a:a}
256
- if (groupType === "LEFT_BRACE" &&
257
- isLeftSeparator(prevToken) &&
258
- isRightSeparator(nextToken)) {
259
- tokens.splice(i + 1, 0, { type: "COLON", value: ":" }, { ...token });
260
- nextToken = tokens[i + 1];
261
- }
262
- if (prevToken.type === "OPERATOR" && prevToken.value === ".") {
263
- isVar = false;
264
- }
265
- else if (prevToken.type === "LEFT_BRACE" || prevToken.type === "COMMA") {
266
- if (nextToken && nextToken.type === "COLON") {
267
- isVar = false;
268
- }
269
- }
270
- }
271
- }
272
- if (token.type === "TEMPLATE_STRING") {
273
- token.value = token.replace((expr) => compileExpr(expr));
274
- }
275
- if (nextToken && nextToken.type === "OPERATOR" && nextToken.value === "=>") {
276
- if (token.type === "RIGHT_PAREN") {
277
- let j = i - 1;
278
- while (j > 0 && tokens[j].type !== "LEFT_PAREN") {
279
- if (tokens[j].type === "SYMBOL" && tokens[j].originalValue) {
280
- tokens[j].value = tokens[j].originalValue;
281
- localVars.add(tokens[j].value); //] = { id: tokens[j].value, expr: tokens[j].value };
282
- }
283
- j--;
284
- }
285
- }
286
- else {
287
- localVars.add(token.value); //] = { id: token.value, expr: token.value };
288
- }
289
- }
290
- if (isVar) {
291
- token.varName = token.value;
292
- if (!localVars.has(token.value)) {
293
- token.originalValue = token.value;
294
- token.value = `ctx['${token.value}']`;
295
- }
296
- }
297
- i++;
298
- }
299
- // Mark all variables that have been used locally.
300
- // This assumes the expression has only one scope (incorrect but "good enough for now")
301
- for (const token of tokens) {
302
- if (token.type === "SYMBOL" && token.varName && localVars.has(token.value)) {
303
- token.originalValue = token.value;
304
- token.value = `_${token.value}`;
305
- token.isLocal = true;
306
- }
307
- }
308
- return tokens;
309
- }
310
- // Leading spaces are trimmed during tokenization, so they need to be added back for some values
311
- const paddedValues = new Map([["in ", " in "]]);
312
- function compileExpr(expr) {
313
- return compileExprToArray(expr)
314
- .map((t) => paddedValues.get(t.value) || t.value)
315
- .join("");
316
- }
317
- const INTERP_REGEXP = /\{\{.*?\}\}|\#\{.*?\}/g;
318
- function replaceDynamicParts(s, replacer) {
319
- let matches = s.match(INTERP_REGEXP);
320
- if (matches && matches[0].length === s.length) {
321
- return `(${replacer(s.slice(2, matches[0][0] === "{" ? -2 : -1))})`;
322
- }
323
- let r = s.replace(INTERP_REGEXP, (s) => "${" + replacer(s.slice(2, s[0] === "{" ? -2 : -1)) + "}");
324
- return "`" + r + "`";
325
- }
326
- function interpolate(s) {
327
- return replaceDynamicParts(s, compileExpr);
21
+ /**
22
+ * Owl QWeb Expression Parser
23
+ *
24
+ * Owl needs in various contexts to be able to understand the structure of a
25
+ * string representing a javascript expression. The usual goal is to be able
26
+ * to rewrite some variables. For example, if a template has
27
+ *
28
+ * ```xml
29
+ * <t t-if="computeSomething({val: state.val})">...</t>
30
+ * ```
31
+ *
32
+ * this needs to be translated in something like this:
33
+ *
34
+ * ```js
35
+ * if (context["computeSomething"]({val: context["state"].val})) { ... }
36
+ * ```
37
+ *
38
+ * This file contains the implementation of an extremely naive tokenizer/parser
39
+ * and evaluator for javascript expressions. The supported grammar is basically
40
+ * only expressive enough to understand the shape of objects, of arrays, and
41
+ * various operators.
42
+ */
43
+ //------------------------------------------------------------------------------
44
+ // Misc types, constants and helpers
45
+ //------------------------------------------------------------------------------
46
+ const RESERVED_WORDS = "true,false,NaN,null,undefined,debugger,console,window,in,instanceof,new,function,return,eval,void,Math,RegExp,Array,Object,Date,__globals__".split(",");
47
+ const WORD_REPLACEMENT = Object.assign(Object.create(null), {
48
+ and: "&&",
49
+ or: "||",
50
+ gt: ">",
51
+ gte: ">=",
52
+ lt: "<",
53
+ lte: "<=",
54
+ });
55
+ const STATIC_TOKEN_MAP = Object.assign(Object.create(null), {
56
+ "{": "LEFT_BRACE",
57
+ "}": "RIGHT_BRACE",
58
+ "[": "LEFT_BRACKET",
59
+ "]": "RIGHT_BRACKET",
60
+ ":": "COLON",
61
+ ",": "COMMA",
62
+ "(": "LEFT_PAREN",
63
+ ")": "RIGHT_PAREN",
64
+ });
65
+ // note that the space after typeof is relevant. It makes sure that the formatted
66
+ // expression has a space after typeof. Currently we don't support delete and void
67
+ const OPERATORS = "...,.,===,==,+,!==,!=,!,||,&&,>=,>,<=,<,?,-,*,/,%,typeof ,=>,=,;,in ,new ,|,&,^,~".split(",");
68
+ let tokenizeString = function (expr) {
69
+ let s = expr[0];
70
+ let start = s;
71
+ if (s !== "'" && s !== '"' && s !== "`") {
72
+ return false;
73
+ }
74
+ let i = 1;
75
+ let cur;
76
+ while (expr[i] && expr[i] !== start) {
77
+ cur = expr[i];
78
+ s += cur;
79
+ if (cur === "\\") {
80
+ i++;
81
+ cur = expr[i];
82
+ if (!cur) {
83
+ throw new OwlError("Invalid expression");
84
+ }
85
+ s += cur;
86
+ }
87
+ i++;
88
+ }
89
+ if (expr[i] !== start) {
90
+ throw new OwlError("Invalid expression");
91
+ }
92
+ s += start;
93
+ if (start === "`") {
94
+ return {
95
+ type: "TEMPLATE_STRING",
96
+ value: s,
97
+ replace(replacer) {
98
+ return s.replace(/\$\{(.*?)\}/g, (match, group) => {
99
+ return "${" + replacer(group) + "}";
100
+ });
101
+ },
102
+ };
103
+ }
104
+ return { type: "VALUE", value: s };
105
+ };
106
+ let tokenizeNumber = function (expr) {
107
+ let s = expr[0];
108
+ if (s && s.match(/[0-9]/)) {
109
+ let i = 1;
110
+ while (expr[i] && expr[i].match(/[0-9]|\./)) {
111
+ s += expr[i];
112
+ i++;
113
+ }
114
+ return { type: "VALUE", value: s };
115
+ }
116
+ else {
117
+ return false;
118
+ }
119
+ };
120
+ let tokenizeSymbol = function (expr) {
121
+ let s = expr[0];
122
+ if (s && s.match(/[a-zA-Z_\$]/)) {
123
+ let i = 1;
124
+ while (expr[i] && expr[i].match(/[\w\$]/)) {
125
+ s += expr[i];
126
+ i++;
127
+ }
128
+ if (s in WORD_REPLACEMENT) {
129
+ return { type: "OPERATOR", value: WORD_REPLACEMENT[s], size: s.length };
130
+ }
131
+ return { type: "SYMBOL", value: s };
132
+ }
133
+ else {
134
+ return false;
135
+ }
136
+ };
137
+ const tokenizeStatic = function (expr) {
138
+ const char = expr[0];
139
+ if (char && char in STATIC_TOKEN_MAP) {
140
+ return { type: STATIC_TOKEN_MAP[char], value: char };
141
+ }
142
+ return false;
143
+ };
144
+ const tokenizeOperator = function (expr) {
145
+ for (let op of OPERATORS) {
146
+ if (expr.startsWith(op)) {
147
+ return { type: "OPERATOR", value: op };
148
+ }
149
+ }
150
+ return false;
151
+ };
152
+ const TOKENIZERS = [
153
+ tokenizeString,
154
+ tokenizeNumber,
155
+ tokenizeOperator,
156
+ tokenizeSymbol,
157
+ tokenizeStatic,
158
+ ];
159
+ /**
160
+ * Convert a javascript expression (as a string) into a list of tokens. For
161
+ * example: `tokenize("1 + b")` will return:
162
+ * ```js
163
+ * [
164
+ * {type: "VALUE", value: "1"},
165
+ * {type: "OPERATOR", value: "+"},
166
+ * {type: "SYMBOL", value: "b"}
167
+ * ]
168
+ * ```
169
+ */
170
+ function tokenize(expr) {
171
+ const result = [];
172
+ let token = true;
173
+ let error;
174
+ let current = expr;
175
+ try {
176
+ while (token) {
177
+ current = current.trim();
178
+ if (current) {
179
+ for (let tokenizer of TOKENIZERS) {
180
+ token = tokenizer(current);
181
+ if (token) {
182
+ result.push(token);
183
+ current = current.slice(token.size || token.value.length);
184
+ break;
185
+ }
186
+ }
187
+ }
188
+ else {
189
+ token = false;
190
+ }
191
+ }
192
+ }
193
+ catch (e) {
194
+ error = e; // Silence all errors and throw a generic error below
195
+ }
196
+ if (current.length || error) {
197
+ throw new OwlError(`Tokenizer error: could not tokenize \`${expr}\``);
198
+ }
199
+ return result;
200
+ }
201
+ //------------------------------------------------------------------------------
202
+ // Expression "evaluator"
203
+ //------------------------------------------------------------------------------
204
+ const isLeftSeparator = (token) => token && (token.type === "LEFT_BRACE" || token.type === "COMMA");
205
+ const isRightSeparator = (token) => token && (token.type === "RIGHT_BRACE" || token.type === "COMMA");
206
+ /**
207
+ * This is the main function exported by this file. This is the code that will
208
+ * process an expression (given as a string) and returns another expression with
209
+ * proper lookups in the context.
210
+ *
211
+ * Usually, this kind of code would be very simple to do if we had an AST (so,
212
+ * if we had a javascript parser), since then, we would only need to find the
213
+ * variables and replace them. However, a parser is more complicated, and there
214
+ * are no standard builtin parser API.
215
+ *
216
+ * Since this method is applied to simple javasript expressions, and the work to
217
+ * be done is actually quite simple, we actually can get away with not using a
218
+ * parser, which helps with the code size.
219
+ *
220
+ * Here is the heuristic used by this method to determine if a token is a
221
+ * variable:
222
+ * - by default, all symbols are considered a variable
223
+ * - unless the previous token is a dot (in that case, this is a property: `a.b`)
224
+ * - or if the previous token is a left brace or a comma, and the next token is
225
+ * a colon (in that case, this is an object key: `{a: b}`)
226
+ *
227
+ * Some specific code is also required to support arrow functions. If we detect
228
+ * the arrow operator, then we add the current (or some previous tokens) token to
229
+ * the list of variables so it does not get replaced by a lookup in the context
230
+ */
231
+ function compileExprToArray(expr) {
232
+ const localVars = new Set();
233
+ const tokens = tokenize(expr);
234
+ let i = 0;
235
+ let stack = []; // to track last opening (, [ or {
236
+ while (i < tokens.length) {
237
+ let token = tokens[i];
238
+ let prevToken = tokens[i - 1];
239
+ let nextToken = tokens[i + 1];
240
+ let groupType = stack[stack.length - 1];
241
+ switch (token.type) {
242
+ case "LEFT_BRACE":
243
+ case "LEFT_BRACKET":
244
+ case "LEFT_PAREN":
245
+ stack.push(token.type);
246
+ break;
247
+ case "RIGHT_BRACE":
248
+ case "RIGHT_BRACKET":
249
+ case "RIGHT_PAREN":
250
+ stack.pop();
251
+ }
252
+ let isVar = token.type === "SYMBOL" && !RESERVED_WORDS.includes(token.value);
253
+ if (isVar) {
254
+ if (prevToken) {
255
+ // normalize missing tokens: {a} should be equivalent to {a:a}
256
+ if (groupType === "LEFT_BRACE" &&
257
+ isLeftSeparator(prevToken) &&
258
+ isRightSeparator(nextToken)) {
259
+ tokens.splice(i + 1, 0, { type: "COLON", value: ":" }, { ...token });
260
+ nextToken = tokens[i + 1];
261
+ }
262
+ if (prevToken.type === "OPERATOR" && prevToken.value === ".") {
263
+ isVar = false;
264
+ }
265
+ else if (prevToken.type === "LEFT_BRACE" || prevToken.type === "COMMA") {
266
+ if (nextToken && nextToken.type === "COLON") {
267
+ isVar = false;
268
+ }
269
+ }
270
+ }
271
+ }
272
+ if (token.type === "TEMPLATE_STRING") {
273
+ token.value = token.replace((expr) => compileExpr(expr));
274
+ }
275
+ if (nextToken && nextToken.type === "OPERATOR" && nextToken.value === "=>") {
276
+ if (token.type === "RIGHT_PAREN") {
277
+ let j = i - 1;
278
+ while (j > 0 && tokens[j].type !== "LEFT_PAREN") {
279
+ if (tokens[j].type === "SYMBOL" && tokens[j].originalValue) {
280
+ tokens[j].value = tokens[j].originalValue;
281
+ localVars.add(tokens[j].value); //] = { id: tokens[j].value, expr: tokens[j].value };
282
+ }
283
+ j--;
284
+ }
285
+ }
286
+ else {
287
+ localVars.add(token.value); //] = { id: token.value, expr: token.value };
288
+ }
289
+ }
290
+ if (isVar) {
291
+ token.varName = token.value;
292
+ if (!localVars.has(token.value)) {
293
+ token.originalValue = token.value;
294
+ token.value = `ctx['${token.value}']`;
295
+ }
296
+ }
297
+ i++;
298
+ }
299
+ // Mark all variables that have been used locally.
300
+ // This assumes the expression has only one scope (incorrect but "good enough for now")
301
+ for (const token of tokens) {
302
+ if (token.type === "SYMBOL" && token.varName && localVars.has(token.value)) {
303
+ token.originalValue = token.value;
304
+ token.value = `_${token.value}`;
305
+ token.isLocal = true;
306
+ }
307
+ }
308
+ return tokens;
309
+ }
310
+ // Leading spaces are trimmed during tokenization, so they need to be added back for some values
311
+ const paddedValues = new Map([["in ", " in "]]);
312
+ function compileExpr(expr) {
313
+ return compileExprToArray(expr)
314
+ .map((t) => paddedValues.get(t.value) || t.value)
315
+ .join("");
316
+ }
317
+ const INTERP_REGEXP = /\{\{.*?\}\}|\#\{.*?\}/g;
318
+ function replaceDynamicParts(s, replacer) {
319
+ let matches = s.match(INTERP_REGEXP);
320
+ if (matches && matches[0].length === s.length) {
321
+ return `(${replacer(s.slice(2, matches[0][0] === "{" ? -2 : -1))})`;
322
+ }
323
+ let r = s.replace(INTERP_REGEXP, (s) => "${" + replacer(s.slice(2, s[0] === "{" ? -2 : -1)) + "}");
324
+ return "`" + r + "`";
325
+ }
326
+ function interpolate(s) {
327
+ return replaceDynamicParts(s, compileExpr);
328
328
  }
329
329
 
330
- const whitespaceRE = /\s+/g;
331
- // using a non-html document so that <inner/outer>HTML serializes as XML instead
332
- // of HTML (as we will parse it as xml later)
333
- const xmlDoc = document.implementation.createDocument(null, null, null);
334
- const MODS = new Set(["stop", "capture", "prevent", "self", "synthetic"]);
335
- let nextDataIds = {};
336
- function generateId(prefix = "") {
337
- nextDataIds[prefix] = (nextDataIds[prefix] || 0) + 1;
338
- return prefix + nextDataIds[prefix];
339
- }
340
- function isProp(tag, key) {
341
- switch (tag) {
342
- case "input":
343
- return (key === "checked" ||
344
- key === "indeterminate" ||
345
- key === "value" ||
346
- key === "readonly" ||
347
- key === "readOnly" ||
348
- key === "disabled");
349
- case "option":
350
- return key === "selected" || key === "disabled";
351
- case "textarea":
352
- return key === "value" || key === "readonly" || key === "readOnly" || key === "disabled";
353
- case "select":
354
- return key === "value" || key === "disabled";
355
- case "button":
356
- case "optgroup":
357
- return key === "disabled";
358
- }
359
- return false;
360
- }
361
- /**
362
- * Returns a template literal that evaluates to str. You can add interpolation
363
- * sigils into the string if required
364
- */
365
- function toStringExpression(str) {
366
- return `\`${str.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/, "\\${")}\``;
367
- }
368
- // -----------------------------------------------------------------------------
369
- // BlockDescription
370
- // -----------------------------------------------------------------------------
371
- class BlockDescription {
372
- constructor(target, type) {
373
- this.dynamicTagName = null;
374
- this.isRoot = false;
375
- this.hasDynamicChildren = false;
376
- this.children = [];
377
- this.data = [];
378
- this.childNumber = 0;
379
- this.parentVar = "";
380
- this.id = BlockDescription.nextBlockId++;
381
- this.varName = "b" + this.id;
382
- this.blockName = "block" + this.id;
383
- this.target = target;
384
- this.type = type;
385
- }
386
- insertData(str, prefix = "d") {
387
- const id = generateId(prefix);
388
- this.target.addLine(`let ${id} = ${str};`);
389
- return this.data.push(id) - 1;
390
- }
391
- insert(dom) {
392
- if (this.currentDom) {
393
- this.currentDom.appendChild(dom);
394
- }
395
- else {
396
- this.dom = dom;
397
- }
398
- }
399
- generateExpr(expr) {
400
- if (this.type === "block") {
401
- const hasChildren = this.children.length;
402
- let params = this.data.length ? `[${this.data.join(", ")}]` : hasChildren ? "[]" : "";
403
- if (hasChildren) {
404
- params += ", [" + this.children.map((c) => c.varName).join(", ") + "]";
405
- }
406
- if (this.dynamicTagName) {
407
- return `toggler(${this.dynamicTagName}, ${this.blockName}(${this.dynamicTagName})(${params}))`;
408
- }
409
- return `${this.blockName}(${params})`;
410
- }
411
- else if (this.type === "list") {
412
- return `list(c_block${this.id})`;
413
- }
414
- return expr;
415
- }
416
- asXmlString() {
417
- // Can't use outerHTML on text/comment nodes
418
- // append dom to any element and use innerHTML instead
419
- const t = xmlDoc.createElement("t");
420
- t.appendChild(this.dom);
421
- return t.innerHTML;
422
- }
423
- }
424
- BlockDescription.nextBlockId = 1;
425
- function createContext(parentCtx, params) {
426
- return Object.assign({
427
- block: null,
428
- index: 0,
429
- forceNewBlock: true,
430
- translate: parentCtx.translate,
431
- translationCtx: parentCtx.translationCtx,
432
- tKeyExpr: null,
433
- nameSpace: parentCtx.nameSpace,
434
- tModelSelectedExpr: parentCtx.tModelSelectedExpr,
435
- }, params);
436
- }
437
- class CodeTarget {
438
- constructor(name, on) {
439
- this.indentLevel = 0;
440
- this.loopLevel = 0;
441
- this.code = [];
442
- this.hasRoot = false;
443
- this.hasCache = false;
444
- this.shouldProtectScope = false;
445
- this.name = name;
446
- this.on = on || null;
447
- }
448
- addLine(line, idx) {
449
- const prefix = new Array(this.indentLevel + 2).join(" ");
450
- if (idx === undefined) {
451
- this.code.push(prefix + line);
452
- }
453
- else {
454
- this.code.splice(idx, 0, prefix + line);
455
- }
456
- }
457
- generateCode() {
458
- let result = [];
459
- result.push(`function ${this.name}(ctx, node, key = "") {`);
460
- if (this.shouldProtectScope) {
461
- result.push(` ctx = Object.create(ctx);`);
462
- result.push(` ctx[isBoundary] = 1`);
463
- }
464
- if (this.hasCache) {
465
- result.push(` let cache = ctx.cache || {};`);
466
- result.push(` let nextCache = ctx.cache = {};`);
467
- }
468
- for (let line of this.code) {
469
- result.push(line);
470
- }
471
- if (!this.hasRoot) {
472
- result.push(`return text('');`);
473
- }
474
- result.push(`}`);
475
- return result.join("\n ");
476
- }
477
- currentKey(ctx) {
478
- let key = this.loopLevel ? `key${this.loopLevel}` : "key";
479
- if (ctx.tKeyExpr) {
480
- key = `${ctx.tKeyExpr} + ${key}`;
481
- }
482
- return key;
483
- }
484
- }
485
- const TRANSLATABLE_ATTRS = [
486
- "alt",
487
- "aria-label",
488
- "aria-placeholder",
489
- "aria-roledescription",
490
- "aria-valuetext",
491
- "label",
492
- "placeholder",
493
- "title",
494
- ];
495
- const translationRE = /^(\s*)([\s\S]+?)(\s*)$/;
496
- class CodeGenerator {
497
- constructor(ast, options) {
498
- this.blocks = [];
499
- this.nextBlockId = 1;
500
- this.isDebug = false;
501
- this.targets = [];
502
- this.target = new CodeTarget("template");
503
- this.translatableAttributes = TRANSLATABLE_ATTRS;
504
- this.staticDefs = [];
505
- this.slotNames = new Set();
506
- this.helpers = new Set();
507
- this.translateFn = options.translateFn || ((s) => s);
508
- if (options.translatableAttributes) {
509
- const attrs = new Set(TRANSLATABLE_ATTRS);
510
- for (let attr of options.translatableAttributes) {
511
- if (attr.startsWith("-")) {
512
- attrs.delete(attr.slice(1));
513
- }
514
- else {
515
- attrs.add(attr);
516
- }
517
- }
518
- this.translatableAttributes = [...attrs];
519
- }
520
- this.hasSafeContext = options.hasSafeContext || false;
521
- this.dev = options.dev || false;
522
- this.ast = ast;
523
- this.templateName = options.name;
524
- if (options.hasGlobalValues) {
525
- this.helpers.add("__globals__");
526
- }
527
- }
528
- generateCode() {
529
- const ast = this.ast;
530
- this.isDebug = ast.type === 11 /* ASTType.TDebug */;
531
- BlockDescription.nextBlockId = 1;
532
- nextDataIds = {};
533
- this.compileAST(ast, {
534
- block: null,
535
- index: 0,
536
- forceNewBlock: false,
537
- isLast: true,
538
- translate: true,
539
- translationCtx: "",
540
- tKeyExpr: null,
541
- });
542
- // define blocks and utility functions
543
- let mainCode = [` let { text, createBlock, list, multi, html, toggler, comment } = bdom;`];
544
- if (this.helpers.size) {
545
- mainCode.push(`let { ${[...this.helpers].join(", ")} } = helpers;`);
546
- }
547
- if (this.templateName) {
548
- mainCode.push(`// Template name: "${this.templateName}"`);
549
- }
550
- for (let { id, expr } of this.staticDefs) {
551
- mainCode.push(`const ${id} = ${expr};`);
552
- }
553
- // define all blocks
554
- if (this.blocks.length) {
555
- mainCode.push(``);
556
- for (let block of this.blocks) {
557
- if (block.dom) {
558
- let xmlString = toStringExpression(block.asXmlString());
559
- if (block.dynamicTagName) {
560
- xmlString = xmlString.replace(/^`<\w+/, `\`<\${tag || '${block.dom.nodeName}'}`);
561
- xmlString = xmlString.replace(/\w+>`$/, `\${tag || '${block.dom.nodeName}'}>\``);
562
- mainCode.push(`let ${block.blockName} = tag => createBlock(${xmlString});`);
563
- }
564
- else {
565
- mainCode.push(`let ${block.blockName} = createBlock(${xmlString});`);
566
- }
567
- }
568
- }
569
- }
570
- // define all slots/defaultcontent function
571
- if (this.targets.length) {
572
- for (let fn of this.targets) {
573
- mainCode.push("");
574
- mainCode = mainCode.concat(fn.generateCode());
575
- }
576
- }
577
- // generate main code
578
- mainCode.push("");
579
- mainCode = mainCode.concat("return " + this.target.generateCode());
580
- const code = mainCode.join("\n ");
581
- if (this.isDebug) {
582
- const msg = `[Owl Debug]\n${code}`;
583
- console.log(msg);
584
- }
585
- return code;
586
- }
587
- compileInNewTarget(prefix, ast, ctx, on) {
588
- const name = generateId(prefix);
589
- const initialTarget = this.target;
590
- const target = new CodeTarget(name, on);
591
- this.targets.push(target);
592
- this.target = target;
593
- this.compileAST(ast, createContext(ctx));
594
- this.target = initialTarget;
595
- return name;
596
- }
597
- addLine(line, idx) {
598
- this.target.addLine(line, idx);
599
- }
600
- define(varName, expr) {
601
- this.addLine(`const ${varName} = ${expr};`);
602
- }
603
- insertAnchor(block, index = block.children.length) {
604
- const tag = `block-child-${index}`;
605
- const anchor = xmlDoc.createElement(tag);
606
- block.insert(anchor);
607
- }
608
- createBlock(parentBlock, type, ctx) {
609
- const hasRoot = this.target.hasRoot;
610
- const block = new BlockDescription(this.target, type);
611
- if (!hasRoot) {
612
- this.target.hasRoot = true;
613
- block.isRoot = true;
614
- }
615
- if (parentBlock) {
616
- parentBlock.children.push(block);
617
- if (parentBlock.type === "list") {
618
- block.parentVar = `c_block${parentBlock.id}`;
619
- }
620
- }
621
- return block;
622
- }
623
- insertBlock(expression, block, ctx) {
624
- let blockExpr = block.generateExpr(expression);
625
- if (block.parentVar) {
626
- let key = this.target.currentKey(ctx);
627
- this.helpers.add("withKey");
628
- this.addLine(`${block.parentVar}[${ctx.index}] = withKey(${blockExpr}, ${key});`);
629
- return;
630
- }
631
- if (ctx.tKeyExpr) {
632
- blockExpr = `toggler(${ctx.tKeyExpr}, ${blockExpr})`;
633
- }
634
- if (block.isRoot) {
635
- if (this.target.on) {
636
- blockExpr = this.wrapWithEventCatcher(blockExpr, this.target.on);
637
- }
638
- this.addLine(`return ${blockExpr};`);
639
- }
640
- else {
641
- this.define(block.varName, blockExpr);
642
- }
643
- }
644
- /**
645
- * Captures variables that are used inside of an expression. This is useful
646
- * because in compiled code, almost all variables are accessed through the ctx
647
- * object. In the case of functions, that lookup in the context can be delayed
648
- * which can cause issues if the value has changed since the function was
649
- * defined.
650
- *
651
- * @param expr the expression to capture
652
- * @param forceCapture whether the expression should capture its scope even if
653
- * it doesn't contain a function. Useful when the expression will be used as
654
- * a function body.
655
- * @returns a new expression that uses the captured values
656
- */
657
- captureExpression(expr, forceCapture = false) {
658
- if (!forceCapture && !expr.includes("=>")) {
659
- return compileExpr(expr);
660
- }
661
- const tokens = compileExprToArray(expr);
662
- const mapping = new Map();
663
- return tokens
664
- .map((tok) => {
665
- if (tok.varName && !tok.isLocal) {
666
- if (!mapping.has(tok.varName)) {
667
- const varId = generateId("v");
668
- mapping.set(tok.varName, varId);
669
- this.define(varId, tok.value);
670
- }
671
- tok.value = mapping.get(tok.varName);
672
- }
673
- return tok.value;
674
- })
675
- .join("");
676
- }
677
- translate(str, translationCtx) {
678
- const match = translationRE.exec(str);
679
- return match[1] + this.translateFn(match[2], translationCtx) + match[3];
680
- }
681
- /**
682
- * @returns the newly created block name, if any
683
- */
684
- compileAST(ast, ctx) {
685
- switch (ast.type) {
686
- case 1 /* ASTType.Comment */:
687
- return this.compileComment(ast, ctx);
688
- case 0 /* ASTType.Text */:
689
- return this.compileText(ast, ctx);
690
- case 2 /* ASTType.DomNode */:
691
- return this.compileTDomNode(ast, ctx);
692
- case 7 /* ASTType.TOut */:
693
- return this.compileTOut(ast, ctx);
694
- case 4 /* ASTType.TIf */:
695
- return this.compileTIf(ast, ctx);
696
- case 8 /* ASTType.TForEach */:
697
- return this.compileTForeach(ast, ctx);
698
- case 9 /* ASTType.TKey */:
699
- return this.compileTKey(ast, ctx);
700
- case 3 /* ASTType.Multi */:
701
- return this.compileMulti(ast, ctx);
702
- case 6 /* ASTType.TCall */:
703
- return this.compileTCall(ast, ctx);
704
- case 14 /* ASTType.TCallBlock */:
705
- return this.compileTCallBlock(ast, ctx);
706
- case 5 /* ASTType.TSet */:
707
- return this.compileTSet(ast, ctx);
708
- case 10 /* ASTType.TComponent */:
709
- return this.compileComponent(ast, ctx);
710
- case 11 /* ASTType.TDebug */:
711
- return this.compileDebug(ast, ctx);
712
- case 12 /* ASTType.TLog */:
713
- return this.compileLog(ast, ctx);
714
- case 13 /* ASTType.TCallSlot */:
715
- return this.compileTCallSlot(ast, ctx);
716
- case 15 /* ASTType.TTranslation */:
717
- return this.compileTTranslation(ast, ctx);
718
- case 16 /* ASTType.TTranslationContext */:
719
- return this.compileTTranslationContext(ast, ctx);
720
- case 17 /* ASTType.TPortal */:
721
- return this.compileTPortal(ast, ctx);
722
- }
723
- }
724
- compileDebug(ast, ctx) {
725
- this.addLine(`debugger;`);
726
- if (ast.content) {
727
- return this.compileAST(ast.content, ctx);
728
- }
729
- return null;
730
- }
731
- compileLog(ast, ctx) {
732
- this.addLine(`console.log(${compileExpr(ast.expr)});`);
733
- if (ast.content) {
734
- return this.compileAST(ast.content, ctx);
735
- }
736
- return null;
737
- }
738
- compileComment(ast, ctx) {
739
- let { block, forceNewBlock } = ctx;
740
- const isNewBlock = !block || forceNewBlock;
741
- if (isNewBlock) {
742
- block = this.createBlock(block, "comment", ctx);
743
- this.insertBlock(`comment(${toStringExpression(ast.value)})`, block, {
744
- ...ctx,
745
- forceNewBlock: forceNewBlock && !block,
746
- });
747
- }
748
- else {
749
- const text = xmlDoc.createComment(ast.value);
750
- block.insert(text);
751
- }
752
- return block.varName;
753
- }
754
- compileText(ast, ctx) {
755
- let { block, forceNewBlock } = ctx;
756
- let value = ast.value;
757
- if (value && ctx.translate !== false) {
758
- value = this.translate(value, ctx.translationCtx);
759
- }
760
- if (!ctx.inPreTag) {
761
- value = value.replace(whitespaceRE, " ");
762
- }
763
- if (!block || forceNewBlock) {
764
- block = this.createBlock(block, "text", ctx);
765
- this.insertBlock(`text(${toStringExpression(value)})`, block, {
766
- ...ctx,
767
- forceNewBlock: forceNewBlock && !block,
768
- });
769
- }
770
- else {
771
- const createFn = ast.type === 0 /* ASTType.Text */ ? xmlDoc.createTextNode : xmlDoc.createComment;
772
- block.insert(createFn.call(xmlDoc, value));
773
- }
774
- return block.varName;
775
- }
776
- generateHandlerCode(rawEvent, handler) {
777
- const modifiers = rawEvent
778
- .split(".")
779
- .slice(1)
780
- .map((m) => {
781
- if (!MODS.has(m)) {
782
- throw new OwlError(`Unknown event modifier: '${m}'`);
783
- }
784
- return `"${m}"`;
785
- });
786
- let modifiersCode = "";
787
- if (modifiers.length) {
788
- modifiersCode = `${modifiers.join(",")}, `;
789
- }
790
- return `[${modifiersCode}${this.captureExpression(handler)}, ctx]`;
791
- }
792
- compileTDomNode(ast, ctx) {
793
- var _a;
794
- let { block, forceNewBlock } = ctx;
795
- const isNewBlock = !block || forceNewBlock || ast.dynamicTag !== null || ast.ns;
796
- let codeIdx = this.target.code.length;
797
- if (isNewBlock) {
798
- if ((ast.dynamicTag || ctx.tKeyExpr || ast.ns) && ctx.block) {
799
- this.insertAnchor(ctx.block);
800
- }
801
- block = this.createBlock(block, "block", ctx);
802
- this.blocks.push(block);
803
- if (ast.dynamicTag) {
804
- const tagExpr = generateId("tag");
805
- this.define(tagExpr, compileExpr(ast.dynamicTag));
806
- block.dynamicTagName = tagExpr;
807
- }
808
- }
809
- // attributes
810
- const attrs = {};
811
- for (let key in ast.attrs) {
812
- let expr, attrName;
813
- if (key.startsWith("t-attf")) {
814
- expr = interpolate(ast.attrs[key]);
815
- const idx = block.insertData(expr, "attr");
816
- attrName = key.slice(7);
817
- attrs["block-attribute-" + idx] = attrName;
818
- }
819
- else if (key.startsWith("t-att")) {
820
- attrName = key === "t-att" ? null : key.slice(6);
821
- expr = compileExpr(ast.attrs[key]);
822
- if (attrName && isProp(ast.tag, attrName)) {
823
- if (attrName === "readonly") {
824
- // the property has a different name than the attribute
825
- attrName = "readOnly";
826
- }
827
- // we force a new string or new boolean to bypass the equality check in blockdom when patching same value
828
- if (attrName === "value") {
829
- // When the expression is falsy (except 0), fall back to an empty string
830
- expr = `new String((${expr}) === 0 ? 0 : ((${expr}) || ""))`;
831
- }
832
- else {
833
- expr = `new Boolean(${expr})`;
834
- }
835
- const idx = block.insertData(expr, "prop");
836
- attrs[`block-property-${idx}`] = attrName;
837
- }
838
- else {
839
- const idx = block.insertData(expr, "attr");
840
- if (key === "t-att") {
841
- attrs[`block-attributes`] = String(idx);
842
- }
843
- else {
844
- attrs[`block-attribute-${idx}`] = attrName;
845
- }
846
- }
847
- }
848
- else if (this.translatableAttributes.includes(key)) {
849
- const attrTranslationCtx = ((_a = ast.attrsTranslationCtx) === null || _a === void 0 ? void 0 : _a[key]) || ctx.translationCtx;
850
- attrs[key] = this.translateFn(ast.attrs[key], attrTranslationCtx);
851
- }
852
- else {
853
- expr = `"${ast.attrs[key]}"`;
854
- attrName = key;
855
- attrs[key] = ast.attrs[key];
856
- }
857
- if (attrName === "value" && ctx.tModelSelectedExpr) {
858
- let selectedId = block.insertData(`${ctx.tModelSelectedExpr} === ${expr}`, "attr");
859
- attrs[`block-attribute-${selectedId}`] = "selected";
860
- }
861
- }
862
- // t-model
863
- let tModelSelectedExpr;
864
- if (ast.model) {
865
- const { hasDynamicChildren, expr, eventType, shouldNumberize, shouldTrim, targetAttr, specialInitTargetAttr, } = ast.model;
866
- const expression = compileExpr(expr);
867
- const exprId = generateId("expr");
868
- this.helpers.add("modelExpr");
869
- this.define(exprId, `modelExpr(${expression})`);
870
- let idx;
871
- if (specialInitTargetAttr) {
872
- let targetExpr = targetAttr in attrs && `'${attrs[targetAttr]}'`;
873
- if (!targetExpr && ast.attrs) {
874
- // look at the dynamic attribute counterpart
875
- const dynamicTgExpr = ast.attrs[`t-att-${targetAttr}`];
876
- if (dynamicTgExpr) {
877
- targetExpr = compileExpr(dynamicTgExpr);
878
- }
879
- }
880
- idx = block.insertData(`${exprId}() === ${targetExpr}`, "prop");
881
- attrs[`block-property-${idx}`] = specialInitTargetAttr;
882
- }
883
- else if (hasDynamicChildren) {
884
- const bValueId = generateId("bValue");
885
- tModelSelectedExpr = `${bValueId}`;
886
- this.define(tModelSelectedExpr, `${exprId}()`);
887
- }
888
- else {
889
- idx = block.insertData(`${exprId}()`, "prop");
890
- attrs[`block-property-${idx}`] = targetAttr;
891
- }
892
- this.helpers.add("toNumber");
893
- let valueCode = `ev.target.${targetAttr}`;
894
- valueCode = shouldTrim ? `${valueCode}.trim()` : valueCode;
895
- valueCode = shouldNumberize ? `toNumber(${valueCode})` : valueCode;
896
- const handler = `[(ev) => { ${exprId}.set(${valueCode}); }]`;
897
- idx = block.insertData(handler, "hdlr");
898
- attrs[`block-handler-${idx}`] = eventType;
899
- }
900
- // event handlers
901
- for (let ev in ast.on) {
902
- const name = this.generateHandlerCode(ev, ast.on[ev]);
903
- const idx = block.insertData(name, "hdlr");
904
- attrs[`block-handler-${idx}`] = ev;
905
- }
906
- // t-ref
907
- if (ast.ref) {
908
- const refExpr = compileExpr(ast.ref);
909
- this.helpers.add("createRef");
910
- const setRefStr = `createRef(${refExpr})`;
911
- const idx = block.insertData(setRefStr, "ref");
912
- attrs["block-ref"] = String(idx);
913
- }
914
- const nameSpace = ast.ns || ctx.nameSpace;
915
- const dom = nameSpace
916
- ? xmlDoc.createElementNS(nameSpace, ast.tag)
917
- : xmlDoc.createElement(ast.tag);
918
- for (const [attr, val] of Object.entries(attrs)) {
919
- if (!(attr === "class" && val === "")) {
920
- dom.setAttribute(attr, val);
921
- }
922
- }
923
- block.insert(dom);
924
- if (ast.content.length) {
925
- const initialDom = block.currentDom;
926
- block.currentDom = dom;
927
- const children = ast.content;
928
- for (let i = 0; i < children.length; i++) {
929
- const child = ast.content[i];
930
- const subCtx = createContext(ctx, {
931
- block,
932
- index: block.childNumber,
933
- forceNewBlock: false,
934
- isLast: ctx.isLast && i === children.length - 1,
935
- tKeyExpr: ctx.tKeyExpr,
936
- nameSpace,
937
- tModelSelectedExpr,
938
- inPreTag: ctx.inPreTag || ast.tag === "pre",
939
- });
940
- this.compileAST(child, subCtx);
941
- }
942
- block.currentDom = initialDom;
943
- }
944
- if (isNewBlock) {
945
- this.insertBlock(`${block.blockName}(ddd)`, block, ctx);
946
- // may need to rewrite code!
947
- if (block.children.length && block.hasDynamicChildren) {
948
- const code = this.target.code;
949
- const children = block.children.slice();
950
- let current = children.shift();
951
- for (let i = codeIdx; i < code.length; i++) {
952
- if (code[i].trimStart().startsWith(`const ${current.varName} `)) {
953
- code[i] = code[i].replace(`const ${current.varName}`, current.varName);
954
- current = children.shift();
955
- if (!current)
956
- break;
957
- }
958
- }
959
- this.addLine(`let ${block.children.map((c) => c.varName).join(", ")};`, codeIdx);
960
- }
961
- }
962
- return block.varName;
963
- }
964
- compileTOut(ast, ctx) {
965
- let { block } = ctx;
966
- if (block) {
967
- this.insertAnchor(block);
968
- }
969
- block = this.createBlock(block, "html", ctx);
970
- let blockStr;
971
- if (ast.expr === "0") {
972
- this.helpers.add("zero");
973
- blockStr = `ctx[zero]`;
974
- }
975
- else if (ast.body) {
976
- let bodyValue = null;
977
- bodyValue = BlockDescription.nextBlockId;
978
- const subCtx = createContext(ctx);
979
- this.compileAST({ type: 3 /* ASTType.Multi */, content: ast.body }, subCtx);
980
- this.helpers.add("safeOutput");
981
- blockStr = `safeOutput(${compileExpr(ast.expr)}, b${bodyValue})`;
982
- }
983
- else {
984
- this.helpers.add("safeOutput");
985
- blockStr = `safeOutput(${compileExpr(ast.expr)})`;
986
- }
987
- this.insertBlock(blockStr, block, ctx);
988
- return block.varName;
989
- }
990
- compileTIfBranch(content, block, ctx) {
991
- this.target.indentLevel++;
992
- let childN = block.children.length;
993
- this.compileAST(content, createContext(ctx, { block, index: ctx.index }));
994
- if (block.children.length > childN) {
995
- // we have some content => need to insert an anchor at correct index
996
- this.insertAnchor(block, childN);
997
- }
998
- this.target.indentLevel--;
999
- }
1000
- compileTIf(ast, ctx, nextNode) {
1001
- let { block, forceNewBlock } = ctx;
1002
- const codeIdx = this.target.code.length;
1003
- const isNewBlock = !block || (block.type !== "multi" && forceNewBlock);
1004
- if (block) {
1005
- block.hasDynamicChildren = true;
1006
- }
1007
- if (!block || (block.type !== "multi" && forceNewBlock)) {
1008
- block = this.createBlock(block, "multi", ctx);
1009
- }
1010
- this.addLine(`if (${compileExpr(ast.condition)}) {`);
1011
- this.compileTIfBranch(ast.content, block, ctx);
1012
- if (ast.tElif) {
1013
- for (let clause of ast.tElif) {
1014
- this.addLine(`} else if (${compileExpr(clause.condition)}) {`);
1015
- this.compileTIfBranch(clause.content, block, ctx);
1016
- }
1017
- }
1018
- if (ast.tElse) {
1019
- this.addLine(`} else {`);
1020
- this.compileTIfBranch(ast.tElse, block, ctx);
1021
- }
1022
- this.addLine("}");
1023
- if (isNewBlock) {
1024
- // note: this part is duplicated from end of compiledomnode:
1025
- if (block.children.length) {
1026
- const code = this.target.code;
1027
- const children = block.children.slice();
1028
- let current = children.shift();
1029
- for (let i = codeIdx; i < code.length; i++) {
1030
- if (code[i].trimStart().startsWith(`const ${current.varName} `)) {
1031
- code[i] = code[i].replace(`const ${current.varName}`, current.varName);
1032
- current = children.shift();
1033
- if (!current)
1034
- break;
1035
- }
1036
- }
1037
- this.addLine(`let ${block.children.map((c) => c.varName).join(", ")};`, codeIdx);
1038
- }
1039
- // note: this part is duplicated from end of compilemulti:
1040
- const args = block.children.map((c) => c.varName).join(", ");
1041
- this.insertBlock(`multi([${args}])`, block, ctx);
1042
- }
1043
- return block.varName;
1044
- }
1045
- compileTForeach(ast, ctx) {
1046
- let { block } = ctx;
1047
- if (block) {
1048
- this.insertAnchor(block);
1049
- }
1050
- block = this.createBlock(block, "list", ctx);
1051
- this.target.loopLevel++;
1052
- const loopVar = `i${this.target.loopLevel}`;
1053
- this.addLine(`ctx = Object.create(ctx);`);
1054
- const vals = `v_block${block.id}`;
1055
- const keys = `k_block${block.id}`;
1056
- const l = `l_block${block.id}`;
1057
- const c = `c_block${block.id}`;
1058
- this.helpers.add("prepareList");
1059
- this.define(`[${keys}, ${vals}, ${l}, ${c}]`, `prepareList(${compileExpr(ast.collection)});`);
1060
- // Throw errors on duplicate keys in dev mode
1061
- if (this.dev) {
1062
- this.define(`keys${block.id}`, `new Set()`);
1063
- }
1064
- this.addLine(`for (let ${loopVar} = 0; ${loopVar} < ${l}; ${loopVar}++) {`);
1065
- this.target.indentLevel++;
1066
- this.addLine(`ctx[\`${ast.elem}\`] = ${keys}[${loopVar}];`);
1067
- if (!ast.hasNoFirst) {
1068
- this.addLine(`ctx[\`${ast.elem}_first\`] = ${loopVar} === 0;`);
1069
- }
1070
- if (!ast.hasNoLast) {
1071
- this.addLine(`ctx[\`${ast.elem}_last\`] = ${loopVar} === ${keys}.length - 1;`);
1072
- }
1073
- if (!ast.hasNoIndex) {
1074
- this.addLine(`ctx[\`${ast.elem}_index\`] = ${loopVar};`);
1075
- }
1076
- if (!ast.hasNoValue) {
1077
- this.addLine(`ctx[\`${ast.elem}_value\`] = ${vals}[${loopVar}];`);
1078
- }
1079
- this.define(`key${this.target.loopLevel}`, ast.key ? compileExpr(ast.key) : loopVar);
1080
- if (this.dev) {
1081
- // Throw error on duplicate keys in dev mode
1082
- this.helpers.add("OwlError");
1083
- this.addLine(`if (keys${block.id}.has(String(key${this.target.loopLevel}))) { throw new OwlError(\`Got duplicate key in t-foreach: \${key${this.target.loopLevel}}\`)}`);
1084
- this.addLine(`keys${block.id}.add(String(key${this.target.loopLevel}));`);
1085
- }
1086
- let id;
1087
- if (ast.memo) {
1088
- this.target.hasCache = true;
1089
- id = generateId();
1090
- this.define(`memo${id}`, compileExpr(ast.memo));
1091
- this.define(`vnode${id}`, `cache[key${this.target.loopLevel}];`);
1092
- this.addLine(`if (vnode${id}) {`);
1093
- this.target.indentLevel++;
1094
- this.addLine(`if (shallowEqual(vnode${id}.memo, memo${id})) {`);
1095
- this.target.indentLevel++;
1096
- this.addLine(`${c}[${loopVar}] = vnode${id};`);
1097
- this.addLine(`nextCache[key${this.target.loopLevel}] = vnode${id};`);
1098
- this.addLine(`continue;`);
1099
- this.target.indentLevel--;
1100
- this.addLine("}");
1101
- this.target.indentLevel--;
1102
- this.addLine("}");
1103
- }
1104
- const subCtx = createContext(ctx, { block, index: loopVar });
1105
- this.compileAST(ast.body, subCtx);
1106
- if (ast.memo) {
1107
- this.addLine(`nextCache[key${this.target.loopLevel}] = Object.assign(${c}[${loopVar}], {memo: memo${id}});`);
1108
- }
1109
- this.target.indentLevel--;
1110
- this.target.loopLevel--;
1111
- this.addLine(`}`);
1112
- if (!ctx.isLast) {
1113
- this.addLine(`ctx = ctx.__proto__;`);
1114
- }
1115
- this.insertBlock("l", block, ctx);
1116
- return block.varName;
1117
- }
1118
- compileTKey(ast, ctx) {
1119
- const tKeyExpr = generateId("tKey_");
1120
- this.define(tKeyExpr, compileExpr(ast.expr));
1121
- ctx = createContext(ctx, {
1122
- tKeyExpr,
1123
- block: ctx.block,
1124
- index: ctx.index,
1125
- });
1126
- return this.compileAST(ast.content, ctx);
1127
- }
1128
- compileMulti(ast, ctx) {
1129
- let { block, forceNewBlock } = ctx;
1130
- const isNewBlock = !block || forceNewBlock;
1131
- let codeIdx = this.target.code.length;
1132
- if (isNewBlock) {
1133
- const n = ast.content.filter((c) => !c.hasNoRepresentation).length;
1134
- let result = null;
1135
- if (n <= 1) {
1136
- for (let child of ast.content) {
1137
- const blockName = this.compileAST(child, ctx);
1138
- result = result || blockName;
1139
- }
1140
- return result;
1141
- }
1142
- block = this.createBlock(block, "multi", ctx);
1143
- }
1144
- let index = 0;
1145
- for (let i = 0, l = ast.content.length; i < l; i++) {
1146
- const child = ast.content[i];
1147
- const forceNewBlock = !child.hasNoRepresentation;
1148
- const subCtx = createContext(ctx, {
1149
- block,
1150
- index,
1151
- forceNewBlock,
1152
- isLast: ctx.isLast && i === l - 1,
1153
- });
1154
- this.compileAST(child, subCtx);
1155
- if (forceNewBlock) {
1156
- index++;
1157
- }
1158
- }
1159
- if (isNewBlock) {
1160
- if (block.hasDynamicChildren && block.children.length) {
1161
- const code = this.target.code;
1162
- const children = block.children.slice();
1163
- let current = children.shift();
1164
- for (let i = codeIdx; i < code.length; i++) {
1165
- if (code[i].trimStart().startsWith(`const ${current.varName} `)) {
1166
- code[i] = code[i].replace(`const ${current.varName}`, current.varName);
1167
- current = children.shift();
1168
- if (!current)
1169
- break;
1170
- }
1171
- }
1172
- this.addLine(`let ${block.children.map((c) => c.varName).join(", ")};`, codeIdx);
1173
- }
1174
- const args = block.children.map((c) => c.varName).join(", ");
1175
- this.insertBlock(`multi([${args}])`, block, ctx);
1176
- }
1177
- return block.varName;
1178
- }
1179
- compileTCall(ast, ctx) {
1180
- let { block, forceNewBlock } = ctx;
1181
- let ctxVar = ctx.ctxVar || "ctx";
1182
- if (ast.context) {
1183
- ctxVar = generateId("ctx");
1184
- this.addLine(`let ${ctxVar} = {this: ${compileExpr(ast.context)}, __owl__: this.__owl__};`);
1185
- }
1186
- const isDynamic = INTERP_REGEXP.test(ast.name);
1187
- const subTemplate = isDynamic ? interpolate(ast.name) : "`" + ast.name + "`";
1188
- if (block && !forceNewBlock) {
1189
- this.insertAnchor(block);
1190
- }
1191
- block = this.createBlock(block, "multi", ctx);
1192
- if (ast.body) {
1193
- this.addLine(`${ctxVar} = Object.create(${ctxVar});`);
1194
- this.addLine(`${ctxVar}[isBoundary] = 1;`);
1195
- this.helpers.add("isBoundary");
1196
- const subCtx = createContext(ctx, { ctxVar });
1197
- const bl = this.compileMulti({ type: 3 /* ASTType.Multi */, content: ast.body }, subCtx);
1198
- if (bl) {
1199
- this.helpers.add("zero");
1200
- this.addLine(`${ctxVar}[zero] = ${bl};`);
1201
- }
1202
- }
1203
- const key = this.generateComponentKey();
1204
- if (isDynamic) {
1205
- const templateVar = generateId("template");
1206
- if (!this.staticDefs.find((d) => d.id === "call")) {
1207
- this.staticDefs.push({ id: "call", expr: `app.callTemplate.bind(app)` });
1208
- }
1209
- this.define(templateVar, subTemplate);
1210
- this.insertBlock(`call(this, ${templateVar}, ${ctxVar}, node, ${key})`, block, {
1211
- ...ctx,
1212
- forceNewBlock: !block,
1213
- });
1214
- }
1215
- else {
1216
- const id = generateId(`callTemplate_`);
1217
- this.staticDefs.push({ id, expr: `app.getTemplate(${subTemplate})` });
1218
- this.insertBlock(`${id}.call(this, ${ctxVar}, node, ${key})`, block, {
1219
- ...ctx,
1220
- forceNewBlock: !block,
1221
- });
1222
- }
1223
- if (ast.body && !ctx.isLast) {
1224
- this.addLine(`${ctxVar} = ${ctxVar}.__proto__;`);
1225
- }
1226
- return block.varName;
1227
- }
1228
- compileTCallBlock(ast, ctx) {
1229
- let { block, forceNewBlock } = ctx;
1230
- if (block) {
1231
- if (!forceNewBlock) {
1232
- this.insertAnchor(block);
1233
- }
1234
- }
1235
- block = this.createBlock(block, "multi", ctx);
1236
- this.insertBlock(compileExpr(ast.name), block, { ...ctx, forceNewBlock: !block });
1237
- return block.varName;
1238
- }
1239
- compileTSet(ast, ctx) {
1240
- this.target.shouldProtectScope = true;
1241
- this.helpers.add("isBoundary").add("withDefault");
1242
- const expr = ast.value ? compileExpr(ast.value || "") : "null";
1243
- if (ast.body) {
1244
- this.helpers.add("LazyValue");
1245
- const bodyAst = { type: 3 /* ASTType.Multi */, content: ast.body };
1246
- const name = this.compileInNewTarget("value", bodyAst, ctx);
1247
- let key = this.target.currentKey(ctx);
1248
- let value = `new LazyValue(${name}, ctx, this, node, ${key})`;
1249
- value = ast.value ? (value ? `withDefault(${expr}, ${value})` : expr) : value;
1250
- this.addLine(`ctx[\`${ast.name}\`] = ${value};`);
1251
- }
1252
- else {
1253
- let value;
1254
- if (ast.defaultValue) {
1255
- const defaultValue = toStringExpression(ctx.translate ? this.translate(ast.defaultValue, ctx.translationCtx) : ast.defaultValue);
1256
- if (ast.value) {
1257
- value = `withDefault(${expr}, ${defaultValue})`;
1258
- }
1259
- else {
1260
- value = defaultValue;
1261
- }
1262
- }
1263
- else {
1264
- value = expr;
1265
- }
1266
- this.helpers.add("setContextValue");
1267
- this.addLine(`setContextValue(${ctx.ctxVar || "ctx"}, "${ast.name}", ${value});`);
1268
- }
1269
- return null;
1270
- }
1271
- generateComponentKey(currentKey = "key") {
1272
- const parts = [generateId("__")];
1273
- for (let i = 0; i < this.target.loopLevel; i++) {
1274
- parts.push(`\${key${i + 1}}`);
1275
- }
1276
- return `${currentKey} + \`${parts.join("__")}\``;
1277
- }
1278
- /**
1279
- * Formats a prop name and value into a string suitable to be inserted in the
1280
- * generated code. For example:
1281
- *
1282
- * Name Value Result
1283
- * ---------------------------------------------------------
1284
- * "number" "state" "number: ctx['state']"
1285
- * "something" "" "something: undefined"
1286
- * "some-prop" "state" "'some-prop': ctx['state']"
1287
- * "onClick.bind" "onClick" "onClick: bind(ctx, ctx['onClick'])"
1288
- */
1289
- formatProp(name, value, attrsTranslationCtx, translationCtx) {
1290
- if (name.endsWith(".translate")) {
1291
- const attrTranslationCtx = (attrsTranslationCtx === null || attrsTranslationCtx === void 0 ? void 0 : attrsTranslationCtx[name]) || translationCtx;
1292
- value = toStringExpression(this.translateFn(value, attrTranslationCtx));
1293
- }
1294
- else {
1295
- value = this.captureExpression(value);
1296
- }
1297
- if (name.includes(".")) {
1298
- let [_name, suffix] = name.split(".");
1299
- name = _name;
1300
- switch (suffix) {
1301
- case "bind":
1302
- value = `(${value}).bind(this)`;
1303
- break;
1304
- case "alike":
1305
- case "translate":
1306
- break;
1307
- default:
1308
- throw new OwlError(`Invalid prop suffix: ${suffix}`);
1309
- }
1310
- }
1311
- name = /^[a-z_]+$/i.test(name) ? name : `'${name}'`;
1312
- return `${name}: ${value || undefined}`;
1313
- }
1314
- formatPropObject(obj, attrsTranslationCtx, translationCtx) {
1315
- return Object.entries(obj).map(([k, v]) => this.formatProp(k, v, attrsTranslationCtx, translationCtx));
1316
- }
1317
- getPropString(props, dynProps) {
1318
- let propString = `{${props.join(",")}}`;
1319
- if (dynProps) {
1320
- propString = `Object.assign({}, ${compileExpr(dynProps)}${props.length ? ", " + propString : ""})`;
1321
- }
1322
- return propString;
1323
- }
1324
- compileComponent(ast, ctx) {
1325
- let { block } = ctx;
1326
- // props
1327
- const hasSlotsProp = "slots" in (ast.props || {});
1328
- const props = ast.props
1329
- ? this.formatPropObject(ast.props, ast.propsTranslationCtx, ctx.translationCtx)
1330
- : [];
1331
- // slots
1332
- let slotDef = "";
1333
- if (ast.slots) {
1334
- let ctxStr = "ctx";
1335
- if (this.target.loopLevel || !this.hasSafeContext) {
1336
- ctxStr = generateId("ctx");
1337
- this.helpers.add("capture");
1338
- this.define(ctxStr, `capture(ctx)`);
1339
- }
1340
- let slotStr = [];
1341
- for (let slotName in ast.slots) {
1342
- const slotAst = ast.slots[slotName];
1343
- const params = [];
1344
- if (slotAst.content) {
1345
- const name = this.compileInNewTarget("slot", slotAst.content, ctx, slotAst.on);
1346
- params.push(`__render: ${name}.bind(this), __ctx: ${ctxStr}`);
1347
- }
1348
- const scope = ast.slots[slotName].scope;
1349
- if (scope) {
1350
- params.push(`__scope: "${scope}"`);
1351
- }
1352
- if (ast.slots[slotName].attrs) {
1353
- params.push(...this.formatPropObject(ast.slots[slotName].attrs, ast.slots[slotName].attrsTranslationCtx, ctx.translationCtx));
1354
- }
1355
- const slotInfo = `{${params.join(", ")}}`;
1356
- slotStr.push(`'${slotName}': ${slotInfo}`);
1357
- }
1358
- slotDef = `{${slotStr.join(", ")}}`;
1359
- }
1360
- if (slotDef && !(ast.dynamicProps || hasSlotsProp)) {
1361
- this.helpers.add("markRaw");
1362
- props.push(`slots: markRaw(${slotDef})`);
1363
- }
1364
- let propString = this.getPropString(props, ast.dynamicProps);
1365
- let propVar;
1366
- if ((slotDef && (ast.dynamicProps || hasSlotsProp)) || this.dev) {
1367
- propVar = generateId("props");
1368
- this.define(propVar, propString);
1369
- propString = propVar;
1370
- }
1371
- if (slotDef && (ast.dynamicProps || hasSlotsProp)) {
1372
- this.helpers.add("markRaw");
1373
- this.addLine(`${propVar}.slots = markRaw(Object.assign(${slotDef}, ${propVar}.slots))`);
1374
- }
1375
- // cmap key
1376
- let expr;
1377
- if (ast.isDynamic) {
1378
- expr = generateId("Comp");
1379
- this.define(expr, compileExpr(ast.name));
1380
- }
1381
- else {
1382
- expr = `\`${ast.name}\``;
1383
- }
1384
- if (block && (ctx.forceNewBlock === false || ctx.tKeyExpr)) {
1385
- // todo: check the forcenewblock condition
1386
- this.insertAnchor(block);
1387
- }
1388
- let keyArg = this.generateComponentKey();
1389
- if (ctx.tKeyExpr) {
1390
- keyArg = `${ctx.tKeyExpr} + ${keyArg}`;
1391
- }
1392
- let id = generateId("comp");
1393
- const propList = [];
1394
- for (let p in ast.props || {}) {
1395
- let [name, suffix] = p.split(".");
1396
- if (!suffix) {
1397
- propList.push(`"${name}"`);
1398
- }
1399
- }
1400
- this.staticDefs.push({
1401
- id,
1402
- expr: `app.createComponent(${ast.isDynamic ? null : expr}, ${!ast.isDynamic}, ${!!ast.slots}, ${!!ast.dynamicProps}, [${propList}])`,
1403
- });
1404
- if (ast.isDynamic) {
1405
- // If the component class changes, this can cause delayed renders to go
1406
- // through if the key doesn't change. Use the component name for now.
1407
- // This means that two component classes with the same name isn't supported
1408
- // in t-component. We can generate a unique id per class later if needed.
1409
- keyArg = `(${expr}).name + ${keyArg}`;
1410
- }
1411
- let blockExpr = `${id}(${propString}, ${keyArg}, node, this, ${ast.isDynamic ? expr : null})`;
1412
- if (ast.isDynamic) {
1413
- blockExpr = `toggler(${expr}, ${blockExpr})`;
1414
- }
1415
- // event handling
1416
- if (ast.on) {
1417
- blockExpr = this.wrapWithEventCatcher(blockExpr, ast.on);
1418
- }
1419
- block = this.createBlock(block, "multi", ctx);
1420
- this.insertBlock(blockExpr, block, ctx);
1421
- return block.varName;
1422
- }
1423
- wrapWithEventCatcher(expr, on) {
1424
- this.helpers.add("createCatcher");
1425
- let name = generateId("catcher");
1426
- let spec = {};
1427
- let handlers = [];
1428
- for (let ev in on) {
1429
- let handlerId = generateId("hdlr");
1430
- let idx = handlers.push(handlerId) - 1;
1431
- spec[ev] = idx;
1432
- const handler = this.generateHandlerCode(ev, on[ev]);
1433
- this.define(handlerId, handler);
1434
- }
1435
- this.staticDefs.push({ id: name, expr: `createCatcher(${JSON.stringify(spec)})` });
1436
- return `${name}(${expr}, [${handlers.join(",")}])`;
1437
- }
1438
- compileTCallSlot(ast, ctx) {
1439
- this.helpers.add("callSlot");
1440
- let { block } = ctx;
1441
- let blockString;
1442
- let slotName;
1443
- let dynamic = false;
1444
- let isMultiple = false;
1445
- if (ast.name.match(INTERP_REGEXP)) {
1446
- dynamic = true;
1447
- isMultiple = true;
1448
- slotName = interpolate(ast.name);
1449
- }
1450
- else {
1451
- slotName = "'" + ast.name + "'";
1452
- isMultiple = isMultiple || this.slotNames.has(ast.name);
1453
- this.slotNames.add(ast.name);
1454
- }
1455
- const attrs = { ...ast.attrs };
1456
- const dynProps = attrs["t-props"];
1457
- delete attrs["t-props"];
1458
- let key = this.target.loopLevel ? `key${this.target.loopLevel}` : "key";
1459
- if (isMultiple) {
1460
- key = this.generateComponentKey(key);
1461
- }
1462
- const props = ast.attrs
1463
- ? this.formatPropObject(attrs, ast.attrsTranslationCtx, ctx.translationCtx)
1464
- : [];
1465
- const scope = this.getPropString(props, dynProps);
1466
- if (ast.defaultContent) {
1467
- const name = this.compileInNewTarget("defaultContent", ast.defaultContent, ctx);
1468
- blockString = `callSlot(ctx, node, ${key}, ${slotName}, ${dynamic}, ${scope}, ${name}.bind(this))`;
1469
- }
1470
- else {
1471
- if (dynamic) {
1472
- let name = generateId("slot");
1473
- this.define(name, slotName);
1474
- blockString = `toggler(${name}, callSlot(ctx, node, ${key}, ${name}, ${dynamic}, ${scope}))`;
1475
- }
1476
- else {
1477
- blockString = `callSlot(ctx, node, ${key}, ${slotName}, ${dynamic}, ${scope})`;
1478
- }
1479
- }
1480
- // event handling
1481
- if (ast.on) {
1482
- blockString = this.wrapWithEventCatcher(blockString, ast.on);
1483
- }
1484
- if (block) {
1485
- this.insertAnchor(block);
1486
- }
1487
- block = this.createBlock(block, "multi", ctx);
1488
- this.insertBlock(blockString, block, { ...ctx, forceNewBlock: false });
1489
- return block.varName;
1490
- }
1491
- compileTTranslation(ast, ctx) {
1492
- if (ast.content) {
1493
- return this.compileAST(ast.content, Object.assign({}, ctx, { translate: false }));
1494
- }
1495
- return null;
1496
- }
1497
- compileTTranslationContext(ast, ctx) {
1498
- if (ast.content) {
1499
- return this.compileAST(ast.content, Object.assign({}, ctx, { translationCtx: ast.translationCtx }));
1500
- }
1501
- return null;
1502
- }
1503
- compileTPortal(ast, ctx) {
1504
- if (!this.staticDefs.find((d) => d.id === "Portal")) {
1505
- this.staticDefs.push({ id: "Portal", expr: `app.Portal` });
1506
- }
1507
- let { block } = ctx;
1508
- const name = this.compileInNewTarget("slot", ast.content, ctx);
1509
- let ctxStr = "ctx";
1510
- if (this.target.loopLevel || !this.hasSafeContext) {
1511
- ctxStr = generateId("ctx");
1512
- this.helpers.add("capture");
1513
- this.define(ctxStr, `capture(ctx)`);
1514
- }
1515
- let id = generateId("comp");
1516
- this.staticDefs.push({
1517
- id,
1518
- expr: `app.createComponent(null, false, true, false, false)`,
1519
- });
1520
- const target = compileExpr(ast.target);
1521
- const key = this.generateComponentKey();
1522
- const blockString = `${id}({target: ${target},slots: {'default': {__render: ${name}.bind(this), __ctx: ${ctxStr}}}}, ${key}, node, ctx, Portal)`;
1523
- if (block) {
1524
- this.insertAnchor(block);
1525
- }
1526
- block = this.createBlock(block, "multi", ctx);
1527
- this.insertBlock(blockString, block, { ...ctx, forceNewBlock: false });
1528
- return block.varName;
1529
- }
330
+ const whitespaceRE = /\s+/g;
331
+ // using a non-html document so that <inner/outer>HTML serializes as XML instead
332
+ // of HTML (as we will parse it as xml later)
333
+ const xmlDoc = document.implementation.createDocument(null, null, null);
334
+ const MODS = new Set(["stop", "capture", "prevent", "self", "synthetic"]);
335
+ let nextDataIds = {};
336
+ function generateId(prefix = "") {
337
+ nextDataIds[prefix] = (nextDataIds[prefix] || 0) + 1;
338
+ return prefix + nextDataIds[prefix];
339
+ }
340
+ function isProp(tag, key) {
341
+ switch (tag) {
342
+ case "input":
343
+ return (key === "checked" ||
344
+ key === "indeterminate" ||
345
+ key === "value" ||
346
+ key === "readonly" ||
347
+ key === "readOnly" ||
348
+ key === "disabled");
349
+ case "option":
350
+ return key === "selected" || key === "disabled";
351
+ case "textarea":
352
+ return key === "value" || key === "readonly" || key === "readOnly" || key === "disabled";
353
+ case "select":
354
+ return key === "value" || key === "disabled";
355
+ case "button":
356
+ case "optgroup":
357
+ return key === "disabled";
358
+ }
359
+ return false;
360
+ }
361
+ /**
362
+ * Returns a template literal that evaluates to str. You can add interpolation
363
+ * sigils into the string if required
364
+ */
365
+ function toStringExpression(str) {
366
+ return `\`${str.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/, "\\${")}\``;
367
+ }
368
+ // -----------------------------------------------------------------------------
369
+ // BlockDescription
370
+ // -----------------------------------------------------------------------------
371
+ class BlockDescription {
372
+ constructor(target, type) {
373
+ this.dynamicTagName = null;
374
+ this.isRoot = false;
375
+ this.hasDynamicChildren = false;
376
+ this.children = [];
377
+ this.data = [];
378
+ this.childNumber = 0;
379
+ this.parentVar = "";
380
+ this.id = BlockDescription.nextBlockId++;
381
+ this.varName = "b" + this.id;
382
+ this.blockName = "block" + this.id;
383
+ this.target = target;
384
+ this.type = type;
385
+ }
386
+ insertData(str, prefix = "d") {
387
+ const id = generateId(prefix);
388
+ this.target.addLine(`let ${id} = ${str};`);
389
+ return this.data.push(id) - 1;
390
+ }
391
+ insert(dom) {
392
+ if (this.currentDom) {
393
+ this.currentDom.appendChild(dom);
394
+ }
395
+ else {
396
+ this.dom = dom;
397
+ }
398
+ }
399
+ generateExpr(expr) {
400
+ if (this.type === "block") {
401
+ const hasChildren = this.children.length;
402
+ let params = this.data.length ? `[${this.data.join(", ")}]` : hasChildren ? "[]" : "";
403
+ if (hasChildren) {
404
+ params += ", [" + this.children.map((c) => c.varName).join(", ") + "]";
405
+ }
406
+ if (this.dynamicTagName) {
407
+ return `toggler(${this.dynamicTagName}, ${this.blockName}(${this.dynamicTagName})(${params}))`;
408
+ }
409
+ return `${this.blockName}(${params})`;
410
+ }
411
+ else if (this.type === "list") {
412
+ return `list(c_block${this.id})`;
413
+ }
414
+ return expr;
415
+ }
416
+ asXmlString() {
417
+ // Can't use outerHTML on text/comment nodes
418
+ // append dom to any element and use innerHTML instead
419
+ const t = xmlDoc.createElement("t");
420
+ t.appendChild(this.dom);
421
+ return t.innerHTML;
422
+ }
423
+ }
424
+ BlockDescription.nextBlockId = 1;
425
+ function createContext(parentCtx, params) {
426
+ return Object.assign({
427
+ block: null,
428
+ index: 0,
429
+ forceNewBlock: true,
430
+ translate: parentCtx.translate,
431
+ translationCtx: parentCtx.translationCtx,
432
+ tKeyExpr: null,
433
+ nameSpace: parentCtx.nameSpace,
434
+ tModelSelectedExpr: parentCtx.tModelSelectedExpr,
435
+ }, params);
436
+ }
437
+ class CodeTarget {
438
+ constructor(name, on) {
439
+ this.indentLevel = 0;
440
+ this.loopLevel = 0;
441
+ this.code = [];
442
+ this.hasRoot = false;
443
+ this.hasCache = false;
444
+ this.shouldProtectScope = false;
445
+ this.name = name;
446
+ this.on = on || null;
447
+ }
448
+ addLine(line, idx) {
449
+ const prefix = new Array(this.indentLevel + 2).join(" ");
450
+ if (idx === undefined) {
451
+ this.code.push(prefix + line);
452
+ }
453
+ else {
454
+ this.code.splice(idx, 0, prefix + line);
455
+ }
456
+ }
457
+ generateCode() {
458
+ let result = [];
459
+ result.push(`function ${this.name}(ctx, node, key = "") {`);
460
+ if (this.shouldProtectScope) {
461
+ result.push(` ctx = Object.create(ctx);`);
462
+ result.push(` ctx[isBoundary] = 1`);
463
+ }
464
+ if (this.hasCache) {
465
+ result.push(` let cache = ctx.cache || {};`);
466
+ result.push(` let nextCache = ctx.cache = {};`);
467
+ }
468
+ for (let line of this.code) {
469
+ result.push(line);
470
+ }
471
+ if (!this.hasRoot) {
472
+ result.push(`return text('');`);
473
+ }
474
+ result.push(`}`);
475
+ return result.join("\n ");
476
+ }
477
+ currentKey(ctx) {
478
+ let key = this.loopLevel ? `key${this.loopLevel}` : "key";
479
+ if (ctx.tKeyExpr) {
480
+ key = `${ctx.tKeyExpr} + ${key}`;
481
+ }
482
+ return key;
483
+ }
484
+ }
485
+ const TRANSLATABLE_ATTRS = [
486
+ "alt",
487
+ "aria-label",
488
+ "aria-placeholder",
489
+ "aria-roledescription",
490
+ "aria-valuetext",
491
+ "label",
492
+ "placeholder",
493
+ "title",
494
+ ];
495
+ const translationRE = /^(\s*)([\s\S]+?)(\s*)$/;
496
+ class CodeGenerator {
497
+ constructor(ast, options) {
498
+ this.blocks = [];
499
+ this.nextBlockId = 1;
500
+ this.isDebug = false;
501
+ this.targets = [];
502
+ this.target = new CodeTarget("template");
503
+ this.translatableAttributes = TRANSLATABLE_ATTRS;
504
+ this.staticDefs = [];
505
+ this.slotNames = new Set();
506
+ this.helpers = new Set();
507
+ this.translateFn = options.translateFn || ((s) => s);
508
+ if (options.translatableAttributes) {
509
+ const attrs = new Set(TRANSLATABLE_ATTRS);
510
+ for (let attr of options.translatableAttributes) {
511
+ if (attr.startsWith("-")) {
512
+ attrs.delete(attr.slice(1));
513
+ }
514
+ else {
515
+ attrs.add(attr);
516
+ }
517
+ }
518
+ this.translatableAttributes = [...attrs];
519
+ }
520
+ this.hasSafeContext = options.hasSafeContext || false;
521
+ this.dev = options.dev || false;
522
+ this.ast = ast;
523
+ this.templateName = options.name;
524
+ if (options.hasGlobalValues) {
525
+ this.helpers.add("__globals__");
526
+ }
527
+ }
528
+ generateCode() {
529
+ const ast = this.ast;
530
+ this.isDebug = ast.type === 11 /* ASTType.TDebug */;
531
+ BlockDescription.nextBlockId = 1;
532
+ nextDataIds = {};
533
+ this.compileAST(ast, {
534
+ block: null,
535
+ index: 0,
536
+ forceNewBlock: false,
537
+ isLast: true,
538
+ translate: true,
539
+ translationCtx: "",
540
+ tKeyExpr: null,
541
+ });
542
+ // define blocks and utility functions
543
+ let mainCode = [` let { text, createBlock, list, multi, html, toggler, comment } = bdom;`];
544
+ if (this.helpers.size) {
545
+ mainCode.push(`let { ${[...this.helpers].join(", ")} } = helpers;`);
546
+ }
547
+ if (this.templateName) {
548
+ mainCode.push(`// Template name: "${this.templateName}"`);
549
+ }
550
+ for (let { id, expr } of this.staticDefs) {
551
+ mainCode.push(`const ${id} = ${expr};`);
552
+ }
553
+ // define all blocks
554
+ if (this.blocks.length) {
555
+ mainCode.push(``);
556
+ for (let block of this.blocks) {
557
+ if (block.dom) {
558
+ let xmlString = toStringExpression(block.asXmlString());
559
+ if (block.dynamicTagName) {
560
+ xmlString = xmlString.replace(/^`<\w+/, `\`<\${tag || '${block.dom.nodeName}'}`);
561
+ xmlString = xmlString.replace(/\w+>`$/, `\${tag || '${block.dom.nodeName}'}>\``);
562
+ mainCode.push(`let ${block.blockName} = tag => createBlock(${xmlString});`);
563
+ }
564
+ else {
565
+ mainCode.push(`let ${block.blockName} = createBlock(${xmlString});`);
566
+ }
567
+ }
568
+ }
569
+ }
570
+ // define all slots/defaultcontent function
571
+ if (this.targets.length) {
572
+ for (let fn of this.targets) {
573
+ mainCode.push("");
574
+ mainCode = mainCode.concat(fn.generateCode());
575
+ }
576
+ }
577
+ // generate main code
578
+ mainCode.push("");
579
+ mainCode = mainCode.concat("return " + this.target.generateCode());
580
+ const code = mainCode.join("\n ");
581
+ if (this.isDebug) {
582
+ const msg = `[Owl Debug]\n${code}`;
583
+ console.log(msg);
584
+ }
585
+ return code;
586
+ }
587
+ compileInNewTarget(prefix, ast, ctx, on) {
588
+ const name = generateId(prefix);
589
+ const initialTarget = this.target;
590
+ const target = new CodeTarget(name, on);
591
+ this.targets.push(target);
592
+ this.target = target;
593
+ this.compileAST(ast, createContext(ctx));
594
+ this.target = initialTarget;
595
+ return name;
596
+ }
597
+ addLine(line, idx) {
598
+ this.target.addLine(line, idx);
599
+ }
600
+ define(varName, expr) {
601
+ this.addLine(`const ${varName} = ${expr};`);
602
+ }
603
+ insertAnchor(block, index = block.children.length) {
604
+ const tag = `block-child-${index}`;
605
+ const anchor = xmlDoc.createElement(tag);
606
+ block.insert(anchor);
607
+ }
608
+ createBlock(parentBlock, type, ctx) {
609
+ const hasRoot = this.target.hasRoot;
610
+ const block = new BlockDescription(this.target, type);
611
+ if (!hasRoot) {
612
+ this.target.hasRoot = true;
613
+ block.isRoot = true;
614
+ }
615
+ if (parentBlock) {
616
+ parentBlock.children.push(block);
617
+ if (parentBlock.type === "list") {
618
+ block.parentVar = `c_block${parentBlock.id}`;
619
+ }
620
+ }
621
+ return block;
622
+ }
623
+ insertBlock(expression, block, ctx) {
624
+ let blockExpr = block.generateExpr(expression);
625
+ if (block.parentVar) {
626
+ let key = this.target.currentKey(ctx);
627
+ this.helpers.add("withKey");
628
+ this.addLine(`${block.parentVar}[${ctx.index}] = withKey(${blockExpr}, ${key});`);
629
+ return;
630
+ }
631
+ if (ctx.tKeyExpr) {
632
+ blockExpr = `toggler(${ctx.tKeyExpr}, ${blockExpr})`;
633
+ }
634
+ if (block.isRoot) {
635
+ if (this.target.on) {
636
+ blockExpr = this.wrapWithEventCatcher(blockExpr, this.target.on);
637
+ }
638
+ this.addLine(`return ${blockExpr};`);
639
+ }
640
+ else {
641
+ this.define(block.varName, blockExpr);
642
+ }
643
+ }
644
+ /**
645
+ * Captures variables that are used inside of an expression. This is useful
646
+ * because in compiled code, almost all variables are accessed through the ctx
647
+ * object. In the case of functions, that lookup in the context can be delayed
648
+ * which can cause issues if the value has changed since the function was
649
+ * defined.
650
+ *
651
+ * @param expr the expression to capture
652
+ * @param forceCapture whether the expression should capture its scope even if
653
+ * it doesn't contain a function. Useful when the expression will be used as
654
+ * a function body.
655
+ * @returns a new expression that uses the captured values
656
+ */
657
+ captureExpression(expr, forceCapture = false) {
658
+ if (!forceCapture && !expr.includes("=>")) {
659
+ return compileExpr(expr);
660
+ }
661
+ const tokens = compileExprToArray(expr);
662
+ const mapping = new Map();
663
+ return tokens
664
+ .map((tok) => {
665
+ if (tok.varName && !tok.isLocal) {
666
+ if (!mapping.has(tok.varName)) {
667
+ const varId = generateId("v");
668
+ mapping.set(tok.varName, varId);
669
+ this.define(varId, tok.value);
670
+ }
671
+ tok.value = mapping.get(tok.varName);
672
+ }
673
+ return tok.value;
674
+ })
675
+ .join("");
676
+ }
677
+ translate(str, translationCtx) {
678
+ const match = translationRE.exec(str);
679
+ return match[1] + this.translateFn(match[2], translationCtx) + match[3];
680
+ }
681
+ /**
682
+ * @returns the newly created block name, if any
683
+ */
684
+ compileAST(ast, ctx) {
685
+ switch (ast.type) {
686
+ case 1 /* ASTType.Comment */:
687
+ return this.compileComment(ast, ctx);
688
+ case 0 /* ASTType.Text */:
689
+ return this.compileText(ast, ctx);
690
+ case 2 /* ASTType.DomNode */:
691
+ return this.compileTDomNode(ast, ctx);
692
+ case 7 /* ASTType.TOut */:
693
+ return this.compileTOut(ast, ctx);
694
+ case 4 /* ASTType.TIf */:
695
+ return this.compileTIf(ast, ctx);
696
+ case 8 /* ASTType.TForEach */:
697
+ return this.compileTForeach(ast, ctx);
698
+ case 9 /* ASTType.TKey */:
699
+ return this.compileTKey(ast, ctx);
700
+ case 3 /* ASTType.Multi */:
701
+ return this.compileMulti(ast, ctx);
702
+ case 6 /* ASTType.TCall */:
703
+ return this.compileTCall(ast, ctx);
704
+ case 14 /* ASTType.TCallBlock */:
705
+ return this.compileTCallBlock(ast, ctx);
706
+ case 5 /* ASTType.TSet */:
707
+ return this.compileTSet(ast, ctx);
708
+ case 10 /* ASTType.TComponent */:
709
+ return this.compileComponent(ast, ctx);
710
+ case 11 /* ASTType.TDebug */:
711
+ return this.compileDebug(ast, ctx);
712
+ case 12 /* ASTType.TLog */:
713
+ return this.compileLog(ast, ctx);
714
+ case 13 /* ASTType.TCallSlot */:
715
+ return this.compileTCallSlot(ast, ctx);
716
+ case 15 /* ASTType.TTranslation */:
717
+ return this.compileTTranslation(ast, ctx);
718
+ case 16 /* ASTType.TTranslationContext */:
719
+ return this.compileTTranslationContext(ast, ctx);
720
+ case 17 /* ASTType.TPortal */:
721
+ return this.compileTPortal(ast, ctx);
722
+ }
723
+ }
724
+ compileDebug(ast, ctx) {
725
+ this.addLine(`debugger;`);
726
+ if (ast.content) {
727
+ return this.compileAST(ast.content, ctx);
728
+ }
729
+ return null;
730
+ }
731
+ compileLog(ast, ctx) {
732
+ this.addLine(`console.log(${compileExpr(ast.expr)});`);
733
+ if (ast.content) {
734
+ return this.compileAST(ast.content, ctx);
735
+ }
736
+ return null;
737
+ }
738
+ compileComment(ast, ctx) {
739
+ let { block, forceNewBlock } = ctx;
740
+ const isNewBlock = !block || forceNewBlock;
741
+ if (isNewBlock) {
742
+ block = this.createBlock(block, "comment", ctx);
743
+ this.insertBlock(`comment(${toStringExpression(ast.value)})`, block, {
744
+ ...ctx,
745
+ forceNewBlock: forceNewBlock && !block,
746
+ });
747
+ }
748
+ else {
749
+ const text = xmlDoc.createComment(ast.value);
750
+ block.insert(text);
751
+ }
752
+ return block.varName;
753
+ }
754
+ compileText(ast, ctx) {
755
+ let { block, forceNewBlock } = ctx;
756
+ let value = ast.value;
757
+ if (value && ctx.translate !== false) {
758
+ value = this.translate(value, ctx.translationCtx);
759
+ }
760
+ if (!ctx.inPreTag) {
761
+ value = value.replace(whitespaceRE, " ");
762
+ }
763
+ if (!block || forceNewBlock) {
764
+ block = this.createBlock(block, "text", ctx);
765
+ this.insertBlock(`text(${toStringExpression(value)})`, block, {
766
+ ...ctx,
767
+ forceNewBlock: forceNewBlock && !block,
768
+ });
769
+ }
770
+ else {
771
+ const createFn = ast.type === 0 /* ASTType.Text */ ? xmlDoc.createTextNode : xmlDoc.createComment;
772
+ block.insert(createFn.call(xmlDoc, value));
773
+ }
774
+ return block.varName;
775
+ }
776
+ generateHandlerCode(rawEvent, handler) {
777
+ const modifiers = rawEvent
778
+ .split(".")
779
+ .slice(1)
780
+ .map((m) => {
781
+ if (!MODS.has(m)) {
782
+ throw new OwlError(`Unknown event modifier: '${m}'`);
783
+ }
784
+ return `"${m}"`;
785
+ });
786
+ let modifiersCode = "";
787
+ if (modifiers.length) {
788
+ modifiersCode = `${modifiers.join(",")}, `;
789
+ }
790
+ return `[${modifiersCode}${this.captureExpression(handler)}, ctx]`;
791
+ }
792
+ compileTDomNode(ast, ctx) {
793
+ var _a;
794
+ let { block, forceNewBlock } = ctx;
795
+ const isNewBlock = !block || forceNewBlock || ast.dynamicTag !== null || ast.ns;
796
+ let codeIdx = this.target.code.length;
797
+ if (isNewBlock) {
798
+ if ((ast.dynamicTag || ctx.tKeyExpr || ast.ns) && ctx.block) {
799
+ this.insertAnchor(ctx.block);
800
+ }
801
+ block = this.createBlock(block, "block", ctx);
802
+ this.blocks.push(block);
803
+ if (ast.dynamicTag) {
804
+ const tagExpr = generateId("tag");
805
+ this.define(tagExpr, compileExpr(ast.dynamicTag));
806
+ block.dynamicTagName = tagExpr;
807
+ }
808
+ }
809
+ // attributes
810
+ const attrs = {};
811
+ for (let key in ast.attrs) {
812
+ let expr, attrName;
813
+ if (key.startsWith("t-attf")) {
814
+ expr = interpolate(ast.attrs[key]);
815
+ const idx = block.insertData(expr, "attr");
816
+ attrName = key.slice(7);
817
+ attrs["block-attribute-" + idx] = attrName;
818
+ }
819
+ else if (key.startsWith("t-att")) {
820
+ attrName = key === "t-att" ? null : key.slice(6);
821
+ expr = compileExpr(ast.attrs[key]);
822
+ if (attrName && isProp(ast.tag, attrName)) {
823
+ if (attrName === "readonly") {
824
+ // the property has a different name than the attribute
825
+ attrName = "readOnly";
826
+ }
827
+ // we force a new string or new boolean to bypass the equality check in blockdom when patching same value
828
+ if (attrName === "value") {
829
+ // When the expression is falsy (except 0), fall back to an empty string
830
+ expr = `new String((${expr}) === 0 ? 0 : ((${expr}) || ""))`;
831
+ }
832
+ else {
833
+ expr = `new Boolean(${expr})`;
834
+ }
835
+ const idx = block.insertData(expr, "prop");
836
+ attrs[`block-property-${idx}`] = attrName;
837
+ }
838
+ else {
839
+ const idx = block.insertData(expr, "attr");
840
+ if (key === "t-att") {
841
+ attrs[`block-attributes`] = String(idx);
842
+ }
843
+ else {
844
+ attrs[`block-attribute-${idx}`] = attrName;
845
+ }
846
+ }
847
+ }
848
+ else if (this.translatableAttributes.includes(key)) {
849
+ const attrTranslationCtx = ((_a = ast.attrsTranslationCtx) === null || _a === void 0 ? void 0 : _a[key]) || ctx.translationCtx;
850
+ attrs[key] = this.translateFn(ast.attrs[key], attrTranslationCtx);
851
+ }
852
+ else {
853
+ expr = `"${ast.attrs[key]}"`;
854
+ attrName = key;
855
+ attrs[key] = ast.attrs[key];
856
+ }
857
+ if (attrName === "value" && ctx.tModelSelectedExpr) {
858
+ let selectedId = block.insertData(`${ctx.tModelSelectedExpr} === ${expr}`, "attr");
859
+ attrs[`block-attribute-${selectedId}`] = "selected";
860
+ }
861
+ }
862
+ // t-model
863
+ let tModelSelectedExpr;
864
+ if (ast.model) {
865
+ const { hasDynamicChildren, expr, eventType, shouldNumberize, shouldTrim, targetAttr, specialInitTargetAttr, } = ast.model;
866
+ const expression = compileExpr(expr);
867
+ const exprId = generateId("expr");
868
+ this.helpers.add("modelExpr");
869
+ this.define(exprId, `modelExpr(${expression})`);
870
+ let idx;
871
+ if (specialInitTargetAttr) {
872
+ let targetExpr = targetAttr in attrs && `'${attrs[targetAttr]}'`;
873
+ if (!targetExpr && ast.attrs) {
874
+ // look at the dynamic attribute counterpart
875
+ const dynamicTgExpr = ast.attrs[`t-att-${targetAttr}`];
876
+ if (dynamicTgExpr) {
877
+ targetExpr = compileExpr(dynamicTgExpr);
878
+ }
879
+ }
880
+ idx = block.insertData(`${exprId}() === ${targetExpr}`, "prop");
881
+ attrs[`block-property-${idx}`] = specialInitTargetAttr;
882
+ }
883
+ else if (hasDynamicChildren) {
884
+ const bValueId = generateId("bValue");
885
+ tModelSelectedExpr = `${bValueId}`;
886
+ this.define(tModelSelectedExpr, `${exprId}()`);
887
+ }
888
+ else {
889
+ idx = block.insertData(`${exprId}()`, "prop");
890
+ attrs[`block-property-${idx}`] = targetAttr;
891
+ }
892
+ this.helpers.add("toNumber");
893
+ let valueCode = `ev.target.${targetAttr}`;
894
+ valueCode = shouldTrim ? `${valueCode}.trim()` : valueCode;
895
+ valueCode = shouldNumberize ? `toNumber(${valueCode})` : valueCode;
896
+ const handler = `[(ev) => { ${exprId}.set(${valueCode}); }]`;
897
+ idx = block.insertData(handler, "hdlr");
898
+ attrs[`block-handler-${idx}`] = eventType;
899
+ }
900
+ // event handlers
901
+ for (let ev in ast.on) {
902
+ const name = this.generateHandlerCode(ev, ast.on[ev]);
903
+ const idx = block.insertData(name, "hdlr");
904
+ attrs[`block-handler-${idx}`] = ev;
905
+ }
906
+ // t-ref
907
+ if (ast.ref) {
908
+ const refExpr = compileExpr(ast.ref);
909
+ this.helpers.add("createRef");
910
+ const setRefStr = `createRef(${refExpr})`;
911
+ const idx = block.insertData(setRefStr, "ref");
912
+ attrs["block-ref"] = String(idx);
913
+ }
914
+ const nameSpace = ast.ns || ctx.nameSpace;
915
+ const dom = nameSpace
916
+ ? xmlDoc.createElementNS(nameSpace, ast.tag)
917
+ : xmlDoc.createElement(ast.tag);
918
+ for (const [attr, val] of Object.entries(attrs)) {
919
+ if (!(attr === "class" && val === "")) {
920
+ dom.setAttribute(attr, val);
921
+ }
922
+ }
923
+ block.insert(dom);
924
+ if (ast.content.length) {
925
+ const initialDom = block.currentDom;
926
+ block.currentDom = dom;
927
+ const children = ast.content;
928
+ for (let i = 0; i < children.length; i++) {
929
+ const child = ast.content[i];
930
+ const subCtx = createContext(ctx, {
931
+ block,
932
+ index: block.childNumber,
933
+ forceNewBlock: false,
934
+ isLast: ctx.isLast && i === children.length - 1,
935
+ tKeyExpr: ctx.tKeyExpr,
936
+ nameSpace,
937
+ tModelSelectedExpr,
938
+ inPreTag: ctx.inPreTag || ast.tag === "pre",
939
+ });
940
+ this.compileAST(child, subCtx);
941
+ }
942
+ block.currentDom = initialDom;
943
+ }
944
+ if (isNewBlock) {
945
+ this.insertBlock(`${block.blockName}(ddd)`, block, ctx);
946
+ // may need to rewrite code!
947
+ if (block.children.length && block.hasDynamicChildren) {
948
+ const code = this.target.code;
949
+ const children = block.children.slice();
950
+ let current = children.shift();
951
+ for (let i = codeIdx; i < code.length; i++) {
952
+ if (code[i].trimStart().startsWith(`const ${current.varName} `)) {
953
+ code[i] = code[i].replace(`const ${current.varName}`, current.varName);
954
+ current = children.shift();
955
+ if (!current)
956
+ break;
957
+ }
958
+ }
959
+ this.addLine(`let ${block.children.map((c) => c.varName).join(", ")};`, codeIdx);
960
+ }
961
+ }
962
+ return block.varName;
963
+ }
964
+ compileTOut(ast, ctx) {
965
+ let { block } = ctx;
966
+ if (block) {
967
+ this.insertAnchor(block);
968
+ }
969
+ block = this.createBlock(block, "html", ctx);
970
+ let blockStr;
971
+ if (ast.expr === "0") {
972
+ this.helpers.add("zero");
973
+ blockStr = `ctx[zero]`;
974
+ }
975
+ else if (ast.body) {
976
+ let bodyValue = null;
977
+ bodyValue = BlockDescription.nextBlockId;
978
+ const subCtx = createContext(ctx);
979
+ this.compileAST({ type: 3 /* ASTType.Multi */, content: ast.body }, subCtx);
980
+ this.helpers.add("safeOutput");
981
+ blockStr = `safeOutput(${compileExpr(ast.expr)}, b${bodyValue})`;
982
+ }
983
+ else {
984
+ this.helpers.add("safeOutput");
985
+ blockStr = `safeOutput(${compileExpr(ast.expr)})`;
986
+ }
987
+ this.insertBlock(blockStr, block, ctx);
988
+ return block.varName;
989
+ }
990
+ compileTIfBranch(content, block, ctx) {
991
+ this.target.indentLevel++;
992
+ let childN = block.children.length;
993
+ this.compileAST(content, createContext(ctx, { block, index: ctx.index }));
994
+ if (block.children.length > childN) {
995
+ // we have some content => need to insert an anchor at correct index
996
+ this.insertAnchor(block, childN);
997
+ }
998
+ this.target.indentLevel--;
999
+ }
1000
+ compileTIf(ast, ctx, nextNode) {
1001
+ let { block, forceNewBlock } = ctx;
1002
+ const codeIdx = this.target.code.length;
1003
+ const isNewBlock = !block || (block.type !== "multi" && forceNewBlock);
1004
+ if (block) {
1005
+ block.hasDynamicChildren = true;
1006
+ }
1007
+ if (!block || (block.type !== "multi" && forceNewBlock)) {
1008
+ block = this.createBlock(block, "multi", ctx);
1009
+ }
1010
+ this.addLine(`if (${compileExpr(ast.condition)}) {`);
1011
+ this.compileTIfBranch(ast.content, block, ctx);
1012
+ if (ast.tElif) {
1013
+ for (let clause of ast.tElif) {
1014
+ this.addLine(`} else if (${compileExpr(clause.condition)}) {`);
1015
+ this.compileTIfBranch(clause.content, block, ctx);
1016
+ }
1017
+ }
1018
+ if (ast.tElse) {
1019
+ this.addLine(`} else {`);
1020
+ this.compileTIfBranch(ast.tElse, block, ctx);
1021
+ }
1022
+ this.addLine("}");
1023
+ if (isNewBlock) {
1024
+ // note: this part is duplicated from end of compiledomnode:
1025
+ if (block.children.length) {
1026
+ const code = this.target.code;
1027
+ const children = block.children.slice();
1028
+ let current = children.shift();
1029
+ for (let i = codeIdx; i < code.length; i++) {
1030
+ if (code[i].trimStart().startsWith(`const ${current.varName} `)) {
1031
+ code[i] = code[i].replace(`const ${current.varName}`, current.varName);
1032
+ current = children.shift();
1033
+ if (!current)
1034
+ break;
1035
+ }
1036
+ }
1037
+ this.addLine(`let ${block.children.map((c) => c.varName).join(", ")};`, codeIdx);
1038
+ }
1039
+ // note: this part is duplicated from end of compilemulti:
1040
+ const args = block.children.map((c) => c.varName).join(", ");
1041
+ this.insertBlock(`multi([${args}])`, block, ctx);
1042
+ }
1043
+ return block.varName;
1044
+ }
1045
+ compileTForeach(ast, ctx) {
1046
+ let { block } = ctx;
1047
+ if (block) {
1048
+ this.insertAnchor(block);
1049
+ }
1050
+ block = this.createBlock(block, "list", ctx);
1051
+ this.target.loopLevel++;
1052
+ const loopVar = `i${this.target.loopLevel}`;
1053
+ this.addLine(`ctx = Object.create(ctx);`);
1054
+ const vals = `v_block${block.id}`;
1055
+ const keys = `k_block${block.id}`;
1056
+ const l = `l_block${block.id}`;
1057
+ const c = `c_block${block.id}`;
1058
+ this.helpers.add("prepareList");
1059
+ this.define(`[${keys}, ${vals}, ${l}, ${c}]`, `prepareList(${compileExpr(ast.collection)});`);
1060
+ // Throw errors on duplicate keys in dev mode
1061
+ if (this.dev) {
1062
+ this.define(`keys${block.id}`, `new Set()`);
1063
+ }
1064
+ this.addLine(`for (let ${loopVar} = 0; ${loopVar} < ${l}; ${loopVar}++) {`);
1065
+ this.target.indentLevel++;
1066
+ this.addLine(`ctx[\`${ast.elem}\`] = ${keys}[${loopVar}];`);
1067
+ if (!ast.hasNoFirst) {
1068
+ this.addLine(`ctx[\`${ast.elem}_first\`] = ${loopVar} === 0;`);
1069
+ }
1070
+ if (!ast.hasNoLast) {
1071
+ this.addLine(`ctx[\`${ast.elem}_last\`] = ${loopVar} === ${keys}.length - 1;`);
1072
+ }
1073
+ if (!ast.hasNoIndex) {
1074
+ this.addLine(`ctx[\`${ast.elem}_index\`] = ${loopVar};`);
1075
+ }
1076
+ if (!ast.hasNoValue) {
1077
+ this.addLine(`ctx[\`${ast.elem}_value\`] = ${vals}[${loopVar}];`);
1078
+ }
1079
+ this.define(`key${this.target.loopLevel}`, ast.key ? compileExpr(ast.key) : loopVar);
1080
+ if (this.dev) {
1081
+ // Throw error on duplicate keys in dev mode
1082
+ this.helpers.add("OwlError");
1083
+ this.addLine(`if (keys${block.id}.has(String(key${this.target.loopLevel}))) { throw new OwlError(\`Got duplicate key in t-foreach: \${key${this.target.loopLevel}}\`)}`);
1084
+ this.addLine(`keys${block.id}.add(String(key${this.target.loopLevel}));`);
1085
+ }
1086
+ let id;
1087
+ if (ast.memo) {
1088
+ this.target.hasCache = true;
1089
+ id = generateId();
1090
+ this.define(`memo${id}`, compileExpr(ast.memo));
1091
+ this.define(`vnode${id}`, `cache[key${this.target.loopLevel}];`);
1092
+ this.addLine(`if (vnode${id}) {`);
1093
+ this.target.indentLevel++;
1094
+ this.addLine(`if (shallowEqual(vnode${id}.memo, memo${id})) {`);
1095
+ this.target.indentLevel++;
1096
+ this.addLine(`${c}[${loopVar}] = vnode${id};`);
1097
+ this.addLine(`nextCache[key${this.target.loopLevel}] = vnode${id};`);
1098
+ this.addLine(`continue;`);
1099
+ this.target.indentLevel--;
1100
+ this.addLine("}");
1101
+ this.target.indentLevel--;
1102
+ this.addLine("}");
1103
+ }
1104
+ const subCtx = createContext(ctx, { block, index: loopVar });
1105
+ this.compileAST(ast.body, subCtx);
1106
+ if (ast.memo) {
1107
+ this.addLine(`nextCache[key${this.target.loopLevel}] = Object.assign(${c}[${loopVar}], {memo: memo${id}});`);
1108
+ }
1109
+ this.target.indentLevel--;
1110
+ this.target.loopLevel--;
1111
+ this.addLine(`}`);
1112
+ if (!ctx.isLast) {
1113
+ this.addLine(`ctx = ctx.__proto__;`);
1114
+ }
1115
+ this.insertBlock("l", block, ctx);
1116
+ return block.varName;
1117
+ }
1118
+ compileTKey(ast, ctx) {
1119
+ const tKeyExpr = generateId("tKey_");
1120
+ this.define(tKeyExpr, compileExpr(ast.expr));
1121
+ ctx = createContext(ctx, {
1122
+ tKeyExpr,
1123
+ block: ctx.block,
1124
+ index: ctx.index,
1125
+ });
1126
+ return this.compileAST(ast.content, ctx);
1127
+ }
1128
+ compileMulti(ast, ctx) {
1129
+ let { block, forceNewBlock } = ctx;
1130
+ const isNewBlock = !block || forceNewBlock;
1131
+ let codeIdx = this.target.code.length;
1132
+ if (isNewBlock) {
1133
+ const n = ast.content.filter((c) => !c.hasNoRepresentation).length;
1134
+ let result = null;
1135
+ if (n <= 1) {
1136
+ for (let child of ast.content) {
1137
+ const blockName = this.compileAST(child, ctx);
1138
+ result = result || blockName;
1139
+ }
1140
+ return result;
1141
+ }
1142
+ block = this.createBlock(block, "multi", ctx);
1143
+ }
1144
+ let index = 0;
1145
+ for (let i = 0, l = ast.content.length; i < l; i++) {
1146
+ const child = ast.content[i];
1147
+ const forceNewBlock = !child.hasNoRepresentation;
1148
+ const subCtx = createContext(ctx, {
1149
+ block,
1150
+ index,
1151
+ forceNewBlock,
1152
+ isLast: ctx.isLast && i === l - 1,
1153
+ });
1154
+ this.compileAST(child, subCtx);
1155
+ if (forceNewBlock) {
1156
+ index++;
1157
+ }
1158
+ }
1159
+ if (isNewBlock) {
1160
+ if (block.hasDynamicChildren && block.children.length) {
1161
+ const code = this.target.code;
1162
+ const children = block.children.slice();
1163
+ let current = children.shift();
1164
+ for (let i = codeIdx; i < code.length; i++) {
1165
+ if (code[i].trimStart().startsWith(`const ${current.varName} `)) {
1166
+ code[i] = code[i].replace(`const ${current.varName}`, current.varName);
1167
+ current = children.shift();
1168
+ if (!current)
1169
+ break;
1170
+ }
1171
+ }
1172
+ this.addLine(`let ${block.children.map((c) => c.varName).join(", ")};`, codeIdx);
1173
+ }
1174
+ const args = block.children.map((c) => c.varName).join(", ");
1175
+ this.insertBlock(`multi([${args}])`, block, ctx);
1176
+ }
1177
+ return block.varName;
1178
+ }
1179
+ compileTCall(ast, ctx) {
1180
+ let { block, forceNewBlock } = ctx;
1181
+ let ctxVar = ctx.ctxVar || "ctx";
1182
+ if (ast.context) {
1183
+ ctxVar = generateId("ctx");
1184
+ this.addLine(`let ${ctxVar} = {this: ${compileExpr(ast.context)}, __owl__: this.__owl__};`);
1185
+ }
1186
+ const isDynamic = INTERP_REGEXP.test(ast.name);
1187
+ const subTemplate = isDynamic ? interpolate(ast.name) : "`" + ast.name + "`";
1188
+ if (block && !forceNewBlock) {
1189
+ this.insertAnchor(block);
1190
+ }
1191
+ block = this.createBlock(block, "multi", ctx);
1192
+ if (ast.body) {
1193
+ this.addLine(`${ctxVar} = Object.create(${ctxVar});`);
1194
+ this.addLine(`${ctxVar}[isBoundary] = 1;`);
1195
+ this.helpers.add("isBoundary");
1196
+ const subCtx = createContext(ctx, { ctxVar });
1197
+ const bl = this.compileMulti({ type: 3 /* ASTType.Multi */, content: ast.body }, subCtx);
1198
+ if (bl) {
1199
+ this.helpers.add("zero");
1200
+ this.addLine(`${ctxVar}[zero] = ${bl};`);
1201
+ }
1202
+ }
1203
+ const key = this.generateComponentKey();
1204
+ if (isDynamic) {
1205
+ const templateVar = generateId("template");
1206
+ if (!this.staticDefs.find((d) => d.id === "call")) {
1207
+ this.staticDefs.push({ id: "call", expr: `app.callTemplate.bind(app)` });
1208
+ }
1209
+ this.define(templateVar, subTemplate);
1210
+ this.insertBlock(`call(this, ${templateVar}, ${ctxVar}, node, ${key})`, block, {
1211
+ ...ctx,
1212
+ forceNewBlock: !block,
1213
+ });
1214
+ }
1215
+ else {
1216
+ const id = generateId(`callTemplate_`);
1217
+ this.staticDefs.push({ id, expr: `app.getTemplate(${subTemplate})` });
1218
+ this.insertBlock(`${id}.call(this, ${ctxVar}, node, ${key})`, block, {
1219
+ ...ctx,
1220
+ forceNewBlock: !block,
1221
+ });
1222
+ }
1223
+ if (ast.body && !ctx.isLast) {
1224
+ this.addLine(`${ctxVar} = ${ctxVar}.__proto__;`);
1225
+ }
1226
+ return block.varName;
1227
+ }
1228
+ compileTCallBlock(ast, ctx) {
1229
+ let { block, forceNewBlock } = ctx;
1230
+ if (block) {
1231
+ if (!forceNewBlock) {
1232
+ this.insertAnchor(block);
1233
+ }
1234
+ }
1235
+ block = this.createBlock(block, "multi", ctx);
1236
+ this.insertBlock(compileExpr(ast.name), block, { ...ctx, forceNewBlock: !block });
1237
+ return block.varName;
1238
+ }
1239
+ compileTSet(ast, ctx) {
1240
+ this.target.shouldProtectScope = true;
1241
+ this.helpers.add("isBoundary").add("withDefault");
1242
+ const expr = ast.value ? compileExpr(ast.value || "") : "null";
1243
+ if (ast.body) {
1244
+ this.helpers.add("LazyValue");
1245
+ const bodyAst = { type: 3 /* ASTType.Multi */, content: ast.body };
1246
+ const name = this.compileInNewTarget("value", bodyAst, ctx);
1247
+ let key = this.target.currentKey(ctx);
1248
+ let value = `new LazyValue(${name}, ctx, this, node, ${key})`;
1249
+ value = ast.value ? (value ? `withDefault(${expr}, ${value})` : expr) : value;
1250
+ this.addLine(`ctx[\`${ast.name}\`] = ${value};`);
1251
+ }
1252
+ else {
1253
+ let value;
1254
+ if (ast.defaultValue) {
1255
+ const defaultValue = toStringExpression(ctx.translate ? this.translate(ast.defaultValue, ctx.translationCtx) : ast.defaultValue);
1256
+ if (ast.value) {
1257
+ value = `withDefault(${expr}, ${defaultValue})`;
1258
+ }
1259
+ else {
1260
+ value = defaultValue;
1261
+ }
1262
+ }
1263
+ else {
1264
+ value = expr;
1265
+ }
1266
+ this.helpers.add("setContextValue");
1267
+ this.addLine(`setContextValue(${ctx.ctxVar || "ctx"}, "${ast.name}", ${value});`);
1268
+ }
1269
+ return null;
1270
+ }
1271
+ generateComponentKey(currentKey = "key") {
1272
+ const parts = [generateId("__")];
1273
+ for (let i = 0; i < this.target.loopLevel; i++) {
1274
+ parts.push(`\${key${i + 1}}`);
1275
+ }
1276
+ return `${currentKey} + \`${parts.join("__")}\``;
1277
+ }
1278
+ /**
1279
+ * Formats a prop name and value into a string suitable to be inserted in the
1280
+ * generated code. For example:
1281
+ *
1282
+ * Name Value Result
1283
+ * ---------------------------------------------------------
1284
+ * "number" "state" "number: ctx['state']"
1285
+ * "something" "" "something: undefined"
1286
+ * "some-prop" "state" "'some-prop': ctx['state']"
1287
+ * "onClick.bind" "onClick" "onClick: bind(ctx, ctx['onClick'])"
1288
+ */
1289
+ formatProp(name, value, attrsTranslationCtx, translationCtx) {
1290
+ if (name.endsWith(".translate")) {
1291
+ const attrTranslationCtx = (attrsTranslationCtx === null || attrsTranslationCtx === void 0 ? void 0 : attrsTranslationCtx[name]) || translationCtx;
1292
+ value = toStringExpression(this.translateFn(value, attrTranslationCtx));
1293
+ }
1294
+ else {
1295
+ value = this.captureExpression(value);
1296
+ }
1297
+ if (name.includes(".")) {
1298
+ let [_name, suffix] = name.split(".");
1299
+ name = _name;
1300
+ switch (suffix) {
1301
+ case "bind":
1302
+ value = `(${value}).bind(this)`;
1303
+ break;
1304
+ case "alike":
1305
+ case "translate":
1306
+ break;
1307
+ default:
1308
+ throw new OwlError(`Invalid prop suffix: ${suffix}`);
1309
+ }
1310
+ }
1311
+ name = /^[a-z_]+$/i.test(name) ? name : `'${name}'`;
1312
+ return `${name}: ${value || undefined}`;
1313
+ }
1314
+ formatPropObject(obj, attrsTranslationCtx, translationCtx) {
1315
+ return Object.entries(obj).map(([k, v]) => this.formatProp(k, v, attrsTranslationCtx, translationCtx));
1316
+ }
1317
+ getPropString(props, dynProps) {
1318
+ let propString = `{${props.join(",")}}`;
1319
+ if (dynProps) {
1320
+ propString = `Object.assign({}, ${compileExpr(dynProps)}${props.length ? ", " + propString : ""})`;
1321
+ }
1322
+ return propString;
1323
+ }
1324
+ compileComponent(ast, ctx) {
1325
+ let { block } = ctx;
1326
+ // props
1327
+ const hasSlotsProp = "slots" in (ast.props || {});
1328
+ const props = ast.props
1329
+ ? this.formatPropObject(ast.props, ast.propsTranslationCtx, ctx.translationCtx)
1330
+ : [];
1331
+ // slots
1332
+ let slotDef = "";
1333
+ if (ast.slots) {
1334
+ let ctxStr = "ctx";
1335
+ if (this.target.loopLevel || !this.hasSafeContext) {
1336
+ ctxStr = generateId("ctx");
1337
+ this.helpers.add("capture");
1338
+ this.define(ctxStr, `capture(ctx)`);
1339
+ }
1340
+ let slotStr = [];
1341
+ for (let slotName in ast.slots) {
1342
+ const slotAst = ast.slots[slotName];
1343
+ const params = [];
1344
+ if (slotAst.content) {
1345
+ const name = this.compileInNewTarget("slot", slotAst.content, ctx, slotAst.on);
1346
+ params.push(`__render: ${name}.bind(this), __ctx: ${ctxStr}`);
1347
+ }
1348
+ const scope = ast.slots[slotName].scope;
1349
+ if (scope) {
1350
+ params.push(`__scope: "${scope}"`);
1351
+ }
1352
+ if (ast.slots[slotName].attrs) {
1353
+ params.push(...this.formatPropObject(ast.slots[slotName].attrs, ast.slots[slotName].attrsTranslationCtx, ctx.translationCtx));
1354
+ }
1355
+ const slotInfo = `{${params.join(", ")}}`;
1356
+ slotStr.push(`'${slotName}': ${slotInfo}`);
1357
+ }
1358
+ slotDef = `{${slotStr.join(", ")}}`;
1359
+ }
1360
+ if (slotDef && !(ast.dynamicProps || hasSlotsProp)) {
1361
+ this.helpers.add("markRaw");
1362
+ props.push(`slots: markRaw(${slotDef})`);
1363
+ }
1364
+ let propString = this.getPropString(props, ast.dynamicProps);
1365
+ let propVar;
1366
+ if ((slotDef && (ast.dynamicProps || hasSlotsProp)) || this.dev) {
1367
+ propVar = generateId("props");
1368
+ this.define(propVar, propString);
1369
+ propString = propVar;
1370
+ }
1371
+ if (slotDef && (ast.dynamicProps || hasSlotsProp)) {
1372
+ this.helpers.add("markRaw");
1373
+ this.addLine(`${propVar}.slots = markRaw(Object.assign(${slotDef}, ${propVar}.slots))`);
1374
+ }
1375
+ // cmap key
1376
+ let expr;
1377
+ if (ast.isDynamic) {
1378
+ expr = generateId("Comp");
1379
+ this.define(expr, compileExpr(ast.name));
1380
+ }
1381
+ else {
1382
+ expr = `\`${ast.name}\``;
1383
+ }
1384
+ if (block && (ctx.forceNewBlock === false || ctx.tKeyExpr)) {
1385
+ // todo: check the forcenewblock condition
1386
+ this.insertAnchor(block);
1387
+ }
1388
+ let keyArg = this.generateComponentKey();
1389
+ if (ctx.tKeyExpr) {
1390
+ keyArg = `${ctx.tKeyExpr} + ${keyArg}`;
1391
+ }
1392
+ let id = generateId("comp");
1393
+ const propList = [];
1394
+ for (let p in ast.props || {}) {
1395
+ let [name, suffix] = p.split(".");
1396
+ if (!suffix) {
1397
+ propList.push(`"${name}"`);
1398
+ }
1399
+ }
1400
+ this.staticDefs.push({
1401
+ id,
1402
+ expr: `app.createComponent(${ast.isDynamic ? null : expr}, ${!ast.isDynamic}, ${!!ast.slots}, ${!!ast.dynamicProps}, [${propList}])`,
1403
+ });
1404
+ if (ast.isDynamic) {
1405
+ // If the component class changes, this can cause delayed renders to go
1406
+ // through if the key doesn't change. Use the component name for now.
1407
+ // This means that two component classes with the same name isn't supported
1408
+ // in t-component. We can generate a unique id per class later if needed.
1409
+ keyArg = `(${expr}).name + ${keyArg}`;
1410
+ }
1411
+ let blockExpr = `${id}(${propString}, ${keyArg}, node, this, ${ast.isDynamic ? expr : null})`;
1412
+ if (ast.isDynamic) {
1413
+ blockExpr = `toggler(${expr}, ${blockExpr})`;
1414
+ }
1415
+ // event handling
1416
+ if (ast.on) {
1417
+ blockExpr = this.wrapWithEventCatcher(blockExpr, ast.on);
1418
+ }
1419
+ block = this.createBlock(block, "multi", ctx);
1420
+ this.insertBlock(blockExpr, block, ctx);
1421
+ return block.varName;
1422
+ }
1423
+ wrapWithEventCatcher(expr, on) {
1424
+ this.helpers.add("createCatcher");
1425
+ let name = generateId("catcher");
1426
+ let spec = {};
1427
+ let handlers = [];
1428
+ for (let ev in on) {
1429
+ let handlerId = generateId("hdlr");
1430
+ let idx = handlers.push(handlerId) - 1;
1431
+ spec[ev] = idx;
1432
+ const handler = this.generateHandlerCode(ev, on[ev]);
1433
+ this.define(handlerId, handler);
1434
+ }
1435
+ this.staticDefs.push({ id: name, expr: `createCatcher(${JSON.stringify(spec)})` });
1436
+ return `${name}(${expr}, [${handlers.join(",")}])`;
1437
+ }
1438
+ compileTCallSlot(ast, ctx) {
1439
+ this.helpers.add("callSlot");
1440
+ let { block } = ctx;
1441
+ let blockString;
1442
+ let slotName;
1443
+ let dynamic = false;
1444
+ let isMultiple = false;
1445
+ if (ast.name.match(INTERP_REGEXP)) {
1446
+ dynamic = true;
1447
+ isMultiple = true;
1448
+ slotName = interpolate(ast.name);
1449
+ }
1450
+ else {
1451
+ slotName = "'" + ast.name + "'";
1452
+ isMultiple = isMultiple || this.slotNames.has(ast.name);
1453
+ this.slotNames.add(ast.name);
1454
+ }
1455
+ const attrs = { ...ast.attrs };
1456
+ const dynProps = attrs["t-props"];
1457
+ delete attrs["t-props"];
1458
+ let key = this.target.loopLevel ? `key${this.target.loopLevel}` : "key";
1459
+ if (isMultiple) {
1460
+ key = this.generateComponentKey(key);
1461
+ }
1462
+ const props = ast.attrs
1463
+ ? this.formatPropObject(attrs, ast.attrsTranslationCtx, ctx.translationCtx)
1464
+ : [];
1465
+ const scope = this.getPropString(props, dynProps);
1466
+ if (ast.defaultContent) {
1467
+ const name = this.compileInNewTarget("defaultContent", ast.defaultContent, ctx);
1468
+ blockString = `callSlot(ctx, node, ${key}, ${slotName}, ${dynamic}, ${scope}, ${name}.bind(this))`;
1469
+ }
1470
+ else {
1471
+ if (dynamic) {
1472
+ let name = generateId("slot");
1473
+ this.define(name, slotName);
1474
+ blockString = `toggler(${name}, callSlot(ctx, node, ${key}, ${name}, ${dynamic}, ${scope}))`;
1475
+ }
1476
+ else {
1477
+ blockString = `callSlot(ctx, node, ${key}, ${slotName}, ${dynamic}, ${scope})`;
1478
+ }
1479
+ }
1480
+ // event handling
1481
+ if (ast.on) {
1482
+ blockString = this.wrapWithEventCatcher(blockString, ast.on);
1483
+ }
1484
+ if (block) {
1485
+ this.insertAnchor(block);
1486
+ }
1487
+ block = this.createBlock(block, "multi", ctx);
1488
+ this.insertBlock(blockString, block, { ...ctx, forceNewBlock: false });
1489
+ return block.varName;
1490
+ }
1491
+ compileTTranslation(ast, ctx) {
1492
+ if (ast.content) {
1493
+ return this.compileAST(ast.content, Object.assign({}, ctx, { translate: false }));
1494
+ }
1495
+ return null;
1496
+ }
1497
+ compileTTranslationContext(ast, ctx) {
1498
+ if (ast.content) {
1499
+ return this.compileAST(ast.content, Object.assign({}, ctx, { translationCtx: ast.translationCtx }));
1500
+ }
1501
+ return null;
1502
+ }
1503
+ compileTPortal(ast, ctx) {
1504
+ if (!this.staticDefs.find((d) => d.id === "Portal")) {
1505
+ this.staticDefs.push({ id: "Portal", expr: `app.Portal` });
1506
+ }
1507
+ let { block } = ctx;
1508
+ const name = this.compileInNewTarget("slot", ast.content, ctx);
1509
+ let ctxStr = "ctx";
1510
+ if (this.target.loopLevel || !this.hasSafeContext) {
1511
+ ctxStr = generateId("ctx");
1512
+ this.helpers.add("capture");
1513
+ this.define(ctxStr, `capture(ctx)`);
1514
+ }
1515
+ let id = generateId("comp");
1516
+ this.staticDefs.push({
1517
+ id,
1518
+ expr: `app.createComponent(null, false, true, false, false)`,
1519
+ });
1520
+ const target = compileExpr(ast.target);
1521
+ const key = this.generateComponentKey();
1522
+ const blockString = `${id}({target: ${target},slots: {'default': {__render: ${name}.bind(this), __ctx: ${ctxStr}}}}, ${key}, node, ctx, Portal)`;
1523
+ if (block) {
1524
+ this.insertAnchor(block);
1525
+ }
1526
+ block = this.createBlock(block, "multi", ctx);
1527
+ this.insertBlock(blockString, block, { ...ctx, forceNewBlock: false });
1528
+ return block.varName;
1529
+ }
1530
1530
  }
1531
1531
 
1532
- /**
1533
- * Parses an XML string into an XML document, throwing errors on parser errors
1534
- * instead of returning an XML document containing the parseerror.
1535
- *
1536
- * @param xml the string to parse
1537
- * @returns an XML document corresponding to the content of the string
1538
- */
1539
- function parseXML(xml) {
1540
- const parser = new DOMParser();
1541
- const doc = parser.parseFromString(xml, "text/xml");
1542
- if (doc.getElementsByTagName("parsererror").length) {
1543
- let msg = "Invalid XML in template.";
1544
- const parsererrorText = doc.getElementsByTagName("parsererror")[0].textContent;
1545
- if (parsererrorText) {
1546
- msg += "\nThe parser has produced the following error message:\n" + parsererrorText;
1547
- const re = /\d+/g;
1548
- const firstMatch = re.exec(parsererrorText);
1549
- if (firstMatch) {
1550
- const lineNumber = Number(firstMatch[0]);
1551
- const line = xml.split("\n")[lineNumber - 1];
1552
- const secondMatch = re.exec(parsererrorText);
1553
- if (line && secondMatch) {
1554
- const columnIndex = Number(secondMatch[0]) - 1;
1555
- if (line[columnIndex]) {
1556
- msg +=
1557
- `\nThe error might be located at xml line ${lineNumber} column ${columnIndex}\n` +
1558
- `${line}\n${"-".repeat(columnIndex - 1)}^`;
1559
- }
1560
- }
1561
- }
1562
- }
1563
- throw new OwlError(msg);
1564
- }
1565
- return doc;
1532
+ /**
1533
+ * Parses an XML string into an XML document, throwing errors on parser errors
1534
+ * instead of returning an XML document containing the parseerror.
1535
+ *
1536
+ * @param xml the string to parse
1537
+ * @returns an XML document corresponding to the content of the string
1538
+ */
1539
+ function parseXML(xml) {
1540
+ const parser = new DOMParser();
1541
+ const doc = parser.parseFromString(xml, "text/xml");
1542
+ if (doc.getElementsByTagName("parsererror").length) {
1543
+ let msg = "Invalid XML in template.";
1544
+ const parsererrorText = doc.getElementsByTagName("parsererror")[0].textContent;
1545
+ if (parsererrorText) {
1546
+ msg += "\nThe parser has produced the following error message:\n" + parsererrorText;
1547
+ const re = /\d+/g;
1548
+ const firstMatch = re.exec(parsererrorText);
1549
+ if (firstMatch) {
1550
+ const lineNumber = Number(firstMatch[0]);
1551
+ const line = xml.split("\n")[lineNumber - 1];
1552
+ const secondMatch = re.exec(parsererrorText);
1553
+ if (line && secondMatch) {
1554
+ const columnIndex = Number(secondMatch[0]) - 1;
1555
+ if (line[columnIndex]) {
1556
+ msg +=
1557
+ `\nThe error might be located at xml line ${lineNumber} column ${columnIndex}\n` +
1558
+ `${line}\n${"-".repeat(columnIndex - 1)}^`;
1559
+ }
1560
+ }
1561
+ }
1562
+ }
1563
+ throw new OwlError(msg);
1564
+ }
1565
+ return doc;
1566
1566
  }
1567
1567
 
1568
- // -----------------------------------------------------------------------------
1569
- // Parser
1570
- // -----------------------------------------------------------------------------
1571
- const cache = new WeakMap();
1572
- function parse(xml, customDir) {
1573
- const ctx = {
1574
- inPreTag: false,
1575
- customDirectives: customDir,
1576
- };
1577
- if (typeof xml === "string") {
1578
- const elem = parseXML(`<t>${xml}</t>`).firstChild;
1579
- return _parse(elem, ctx);
1580
- }
1581
- let ast = cache.get(xml);
1582
- if (!ast) {
1583
- // we clone here the xml to prevent modifying it in place
1584
- ast = _parse(xml.cloneNode(true), ctx);
1585
- cache.set(xml, ast);
1586
- }
1587
- return ast;
1588
- }
1589
- function _parse(xml, ctx) {
1590
- normalizeXML(xml);
1591
- return parseNode(xml, ctx) || { type: 0 /* ASTType.Text */, value: "" };
1592
- }
1593
- function parseNode(node, ctx) {
1594
- if (!(node instanceof Element)) {
1595
- return parseTextCommentNode(node, ctx);
1596
- }
1597
- return (parseTCustom(node, ctx) ||
1598
- parseTDebugLog(node, ctx) ||
1599
- parseTForEach(node, ctx) ||
1600
- parseTIf(node, ctx) ||
1601
- parseTPortal(node, ctx) ||
1602
- parseTCall(node, ctx) ||
1603
- parseTCallBlock(node) ||
1604
- parseTTranslation(node, ctx) ||
1605
- parseTTranslationContext(node, ctx) ||
1606
- parseTKey(node, ctx) ||
1607
- parseTOutNode(node, ctx) ||
1608
- parseTCallSlot(node, ctx) ||
1609
- parseComponent(node, ctx) ||
1610
- parseDOMNode(node, ctx) ||
1611
- parseTSetNode(node, ctx) ||
1612
- parseTNode(node, ctx));
1613
- }
1614
- // -----------------------------------------------------------------------------
1615
- // <t /> tag
1616
- // -----------------------------------------------------------------------------
1617
- function parseTNode(node, ctx) {
1618
- if (node.tagName !== "t") {
1619
- return null;
1620
- }
1621
- return parseChildNodes(node, ctx);
1622
- }
1623
- // -----------------------------------------------------------------------------
1624
- // Text and Comment Nodes
1625
- // -----------------------------------------------------------------------------
1626
- const lineBreakRE = /[\r\n]/;
1627
- function parseTextCommentNode(node, ctx) {
1628
- if (node.nodeType === Node.TEXT_NODE) {
1629
- let value = node.textContent || "";
1630
- if (!ctx.inPreTag && lineBreakRE.test(value) && !value.trim()) {
1631
- return null;
1632
- }
1633
- return { type: 0 /* ASTType.Text */, value };
1634
- }
1635
- else if (node.nodeType === Node.COMMENT_NODE) {
1636
- return { type: 1 /* ASTType.Comment */, value: node.textContent || "" };
1637
- }
1638
- return null;
1639
- }
1640
- function parseTCustom(node, ctx) {
1641
- if (!ctx.customDirectives) {
1642
- return null;
1643
- }
1644
- const nodeAttrsNames = node.getAttributeNames();
1645
- for (let attr of nodeAttrsNames) {
1646
- if (attr === "t-custom" || attr === "t-custom-") {
1647
- throw new OwlError("Missing custom directive name with t-custom directive");
1648
- }
1649
- if (attr.startsWith("t-custom-")) {
1650
- const directiveName = attr.split(".")[0].slice(9);
1651
- const customDirective = ctx.customDirectives[directiveName];
1652
- if (!customDirective) {
1653
- throw new OwlError(`Custom directive "${directiveName}" is not defined`);
1654
- }
1655
- const value = node.getAttribute(attr);
1656
- const modifiers = attr.split(".").slice(1);
1657
- node.removeAttribute(attr);
1658
- try {
1659
- customDirective(node, value, modifiers);
1660
- }
1661
- catch (error) {
1662
- throw new OwlError(`Custom directive "${directiveName}" throw the following error: ${error}`);
1663
- }
1664
- return parseNode(node, ctx);
1665
- }
1666
- }
1667
- return null;
1668
- }
1669
- // -----------------------------------------------------------------------------
1670
- // debugging
1671
- // -----------------------------------------------------------------------------
1672
- function parseTDebugLog(node, ctx) {
1673
- if (node.hasAttribute("t-debug")) {
1674
- node.removeAttribute("t-debug");
1675
- const content = parseNode(node, ctx);
1676
- const ast = {
1677
- type: 11 /* ASTType.TDebug */,
1678
- content,
1679
- };
1680
- if (content === null || content === void 0 ? void 0 : content.hasNoRepresentation) {
1681
- ast.hasNoRepresentation = true;
1682
- }
1683
- return ast;
1684
- }
1685
- if (node.hasAttribute("t-log")) {
1686
- const expr = node.getAttribute("t-log");
1687
- node.removeAttribute("t-log");
1688
- const content = parseNode(node, ctx);
1689
- const ast = {
1690
- type: 12 /* ASTType.TLog */,
1691
- expr,
1692
- content,
1693
- };
1694
- if (content === null || content === void 0 ? void 0 : content.hasNoRepresentation) {
1695
- ast.hasNoRepresentation = true;
1696
- }
1697
- return ast;
1698
- }
1699
- return null;
1700
- }
1701
- // -----------------------------------------------------------------------------
1702
- // Regular dom node
1703
- // -----------------------------------------------------------------------------
1704
- const ROOT_SVG_TAGS = new Set(["svg", "g", "path"]);
1705
- function parseDOMNode(node, ctx) {
1706
- const { tagName } = node;
1707
- const dynamicTag = node.getAttribute("t-tag");
1708
- node.removeAttribute("t-tag");
1709
- if (tagName === "t" && !dynamicTag) {
1710
- return null;
1711
- }
1712
- if (tagName.startsWith("block-")) {
1713
- throw new OwlError(`Invalid tag name: '${tagName}'`);
1714
- }
1715
- ctx = Object.assign({}, ctx);
1716
- if (tagName === "pre") {
1717
- ctx.inPreTag = true;
1718
- }
1719
- let ns = !ctx.nameSpace && ROOT_SVG_TAGS.has(tagName) ? "http://www.w3.org/2000/svg" : null;
1720
- const ref = node.getAttribute("t-ref");
1721
- node.removeAttribute("t-ref");
1722
- const nodeAttrsNames = node.getAttributeNames();
1723
- let attrs = null;
1724
- let attrsTranslationCtx = null;
1725
- let on = null;
1726
- let model = null;
1727
- for (let attr of nodeAttrsNames) {
1728
- const value = node.getAttribute(attr);
1729
- if (attr === "t-on" || attr === "t-on-") {
1730
- throw new OwlError("Missing event name with t-on directive");
1731
- }
1732
- if (attr.startsWith("t-on-")) {
1733
- on = on || {};
1734
- on[attr.slice(5)] = value;
1735
- }
1736
- else if (attr.startsWith("t-model")) {
1737
- if (!["input", "select", "textarea"].includes(tagName)) {
1738
- throw new OwlError("The t-model directive only works with <input>, <textarea> and <select>");
1739
- }
1740
- const typeAttr = node.getAttribute("type");
1741
- const isInput = tagName === "input";
1742
- const isSelect = tagName === "select";
1743
- const isCheckboxInput = isInput && typeAttr === "checkbox";
1744
- const isRadioInput = isInput && typeAttr === "radio";
1745
- const hasTrimMod = attr.includes(".trim");
1746
- const hasLazyMod = hasTrimMod || attr.includes(".lazy");
1747
- const hasNumberMod = attr.includes(".number");
1748
- const eventType = isRadioInput ? "click" : isSelect || hasLazyMod ? "change" : "input";
1749
- model = {
1750
- expr: value,
1751
- targetAttr: isCheckboxInput ? "checked" : "value",
1752
- specialInitTargetAttr: isRadioInput ? "checked" : null,
1753
- eventType,
1754
- hasDynamicChildren: false,
1755
- shouldTrim: hasTrimMod,
1756
- shouldNumberize: hasNumberMod,
1757
- };
1758
- if (isSelect) {
1759
- // don't pollute the original ctx
1760
- ctx = Object.assign({}, ctx);
1761
- ctx.tModelInfo = model;
1762
- }
1763
- }
1764
- else if (attr.startsWith("block-")) {
1765
- throw new OwlError(`Invalid attribute: '${attr}'`);
1766
- }
1767
- else if (attr === "xmlns") {
1768
- ns = value;
1769
- }
1770
- else if (attr.startsWith("t-translation-context-")) {
1771
- const attrName = attr.slice(22);
1772
- attrsTranslationCtx = attrsTranslationCtx || {};
1773
- attrsTranslationCtx[attrName] = value;
1774
- }
1775
- else if (attr !== "t-name") {
1776
- if (attr.startsWith("t-") && !attr.startsWith("t-att")) {
1777
- throw new OwlError(`Unknown QWeb directive: '${attr}'`);
1778
- }
1779
- const tModel = ctx.tModelInfo;
1780
- if (tModel && ["t-att-value", "t-attf-value"].includes(attr)) {
1781
- tModel.hasDynamicChildren = true;
1782
- }
1783
- attrs = attrs || {};
1784
- attrs[attr] = value;
1785
- }
1786
- }
1787
- if (ns) {
1788
- ctx.nameSpace = ns;
1789
- }
1790
- const children = parseChildren(node, ctx);
1791
- return {
1792
- type: 2 /* ASTType.DomNode */,
1793
- tag: tagName,
1794
- dynamicTag,
1795
- attrs,
1796
- attrsTranslationCtx,
1797
- on,
1798
- ref,
1799
- content: children,
1800
- model,
1801
- ns,
1802
- };
1803
- }
1804
- // -----------------------------------------------------------------------------
1805
- // t-out
1806
- // -----------------------------------------------------------------------------
1807
- function parseTOutNode(node, ctx) {
1808
- if (!node.hasAttribute("t-out") && !node.hasAttribute("t-esc")) {
1809
- return null;
1810
- }
1811
- if (node.hasAttribute("t-esc")) {
1812
- console.warn(`t-esc has been deprecated in favor of t-out. If the value to render is not wrapped by the "markup" function, it will be escaped`);
1813
- }
1814
- const expr = (node.getAttribute("t-out") || node.getAttribute("t-esc"));
1815
- node.removeAttribute("t-out");
1816
- node.removeAttribute("t-esc");
1817
- const tOut = { type: 7 /* ASTType.TOut */, expr, body: null };
1818
- const ref = node.getAttribute("t-ref");
1819
- node.removeAttribute("t-ref");
1820
- const ast = parseNode(node, ctx);
1821
- if (!ast) {
1822
- return tOut;
1823
- }
1824
- if (ast.type === 2 /* ASTType.DomNode */) {
1825
- tOut.body = ast.content.length ? ast.content : null;
1826
- return {
1827
- ...ast,
1828
- ref,
1829
- content: [tOut],
1830
- };
1831
- }
1832
- return tOut;
1833
- }
1834
- // -----------------------------------------------------------------------------
1835
- // t-foreach and t-key
1836
- // -----------------------------------------------------------------------------
1837
- function parseTForEach(node, ctx) {
1838
- if (!node.hasAttribute("t-foreach")) {
1839
- return null;
1840
- }
1841
- const html = node.outerHTML;
1842
- const collection = node.getAttribute("t-foreach");
1843
- node.removeAttribute("t-foreach");
1844
- const elem = node.getAttribute("t-as") || "";
1845
- node.removeAttribute("t-as");
1846
- const key = node.getAttribute("t-key");
1847
- if (!key) {
1848
- throw new OwlError(`"Directive t-foreach should always be used with a t-key!" (expression: t-foreach="${collection}" t-as="${elem}")`);
1849
- }
1850
- node.removeAttribute("t-key");
1851
- const memo = node.getAttribute("t-memo") || "";
1852
- node.removeAttribute("t-memo");
1853
- const body = parseNode(node, ctx);
1854
- if (!body) {
1855
- return null;
1856
- }
1857
- const hasNoTCall = !html.includes("t-call");
1858
- const hasNoFirst = hasNoTCall && !html.includes(`${elem}_first`);
1859
- const hasNoLast = hasNoTCall && !html.includes(`${elem}_last`);
1860
- const hasNoIndex = hasNoTCall && !html.includes(`${elem}_index`);
1861
- const hasNoValue = hasNoTCall && !html.includes(`${elem}_value`);
1862
- return {
1863
- type: 8 /* ASTType.TForEach */,
1864
- collection,
1865
- elem,
1866
- body,
1867
- memo,
1868
- key,
1869
- hasNoFirst,
1870
- hasNoLast,
1871
- hasNoIndex,
1872
- hasNoValue,
1873
- };
1874
- }
1875
- function parseTKey(node, ctx) {
1876
- if (!node.hasAttribute("t-key")) {
1877
- return null;
1878
- }
1879
- const key = node.getAttribute("t-key");
1880
- node.removeAttribute("t-key");
1881
- const content = parseNode(node, ctx);
1882
- if (!content) {
1883
- return null;
1884
- }
1885
- const ast = {
1886
- type: 9 /* ASTType.TKey */,
1887
- expr: key,
1888
- content,
1889
- };
1890
- if (content.hasNoRepresentation) {
1891
- ast.hasNoRepresentation = true;
1892
- }
1893
- return ast;
1894
- }
1895
- // -----------------------------------------------------------------------------
1896
- // t-call
1897
- // -----------------------------------------------------------------------------
1898
- function parseTCall(node, ctx) {
1899
- if (!node.hasAttribute("t-call")) {
1900
- return null;
1901
- }
1902
- const subTemplate = node.getAttribute("t-call");
1903
- const context = node.getAttribute("t-call-context");
1904
- node.removeAttribute("t-call");
1905
- node.removeAttribute("t-call-context");
1906
- if (node.tagName !== "t") {
1907
- const ast = parseNode(node, ctx);
1908
- const tcall = { type: 6 /* ASTType.TCall */, name: subTemplate, body: null, context };
1909
- if (ast && ast.type === 2 /* ASTType.DomNode */) {
1910
- ast.content = [tcall];
1911
- return ast;
1912
- }
1913
- if (ast && ast.type === 10 /* ASTType.TComponent */) {
1914
- return {
1915
- ...ast,
1916
- slots: {
1917
- default: {
1918
- content: tcall,
1919
- scope: null,
1920
- on: null,
1921
- attrs: null,
1922
- attrsTranslationCtx: null,
1923
- },
1924
- },
1925
- };
1926
- }
1927
- }
1928
- const body = parseChildren(node, ctx);
1929
- return {
1930
- type: 6 /* ASTType.TCall */,
1931
- name: subTemplate,
1932
- body: body.length ? body : null,
1933
- context,
1934
- };
1935
- }
1936
- // -----------------------------------------------------------------------------
1937
- // t-call-block
1938
- // -----------------------------------------------------------------------------
1939
- function parseTCallBlock(node, ctx) {
1940
- if (!node.hasAttribute("t-call-block")) {
1941
- return null;
1942
- }
1943
- const name = node.getAttribute("t-call-block");
1944
- return {
1945
- type: 14 /* ASTType.TCallBlock */,
1946
- name,
1947
- };
1948
- }
1949
- // -----------------------------------------------------------------------------
1950
- // t-if
1951
- // -----------------------------------------------------------------------------
1952
- function parseTIf(node, ctx) {
1953
- if (!node.hasAttribute("t-if")) {
1954
- return null;
1955
- }
1956
- const condition = node.getAttribute("t-if");
1957
- node.removeAttribute("t-if");
1958
- const content = parseNode(node, ctx) || { type: 0 /* ASTType.Text */, value: "" };
1959
- let nextElement = node.nextElementSibling;
1960
- // t-elifs
1961
- const tElifs = [];
1962
- while (nextElement && nextElement.hasAttribute("t-elif")) {
1963
- const condition = nextElement.getAttribute("t-elif");
1964
- nextElement.removeAttribute("t-elif");
1965
- const tElif = parseNode(nextElement, ctx);
1966
- const next = nextElement.nextElementSibling;
1967
- nextElement.remove();
1968
- nextElement = next;
1969
- if (tElif) {
1970
- tElifs.push({ condition, content: tElif });
1971
- }
1972
- }
1973
- // t-else
1974
- let tElse = null;
1975
- if (nextElement && nextElement.hasAttribute("t-else")) {
1976
- nextElement.removeAttribute("t-else");
1977
- tElse = parseNode(nextElement, ctx);
1978
- nextElement.remove();
1979
- }
1980
- return {
1981
- type: 4 /* ASTType.TIf */,
1982
- condition,
1983
- content,
1984
- tElif: tElifs.length ? tElifs : null,
1985
- tElse,
1986
- };
1987
- }
1988
- // -----------------------------------------------------------------------------
1989
- // t-set directive
1990
- // -----------------------------------------------------------------------------
1991
- function parseTSetNode(node, ctx) {
1992
- if (!node.hasAttribute("t-set")) {
1993
- return null;
1994
- }
1995
- const name = node.getAttribute("t-set");
1996
- const value = node.getAttribute("t-value") || null;
1997
- const defaultValue = node.innerHTML === node.textContent ? node.textContent || null : null;
1998
- let body = null;
1999
- if (node.textContent !== node.innerHTML) {
2000
- body = parseChildren(node, ctx);
2001
- }
2002
- return { type: 5 /* ASTType.TSet */, name, value, defaultValue, body, hasNoRepresentation: true };
2003
- }
2004
- // -----------------------------------------------------------------------------
2005
- // Components
2006
- // -----------------------------------------------------------------------------
2007
- // Error messages when trying to use an unsupported directive on a component
2008
- const directiveErrorMap = new Map([
2009
- [
2010
- "t-ref",
2011
- "t-ref is no longer supported on components. Consider exposing only the public part of the component's API through a callback prop.",
2012
- ],
2013
- ["t-att", "t-att makes no sense on component: props are already treated as expressions"],
2014
- [
2015
- "t-attf",
2016
- "t-attf is not supported on components: use template strings for string interpolation in props",
2017
- ],
2018
- ]);
2019
- function parseComponent(node, ctx) {
2020
- let name = node.tagName;
2021
- const firstLetter = name[0];
2022
- let isDynamic = node.hasAttribute("t-component");
2023
- if (isDynamic && name !== "t") {
2024
- throw new OwlError(`Directive 't-component' can only be used on <t> nodes (used on a <${name}>)`);
2025
- }
2026
- if (!(firstLetter === firstLetter.toUpperCase() || isDynamic)) {
2027
- return null;
2028
- }
2029
- if (isDynamic) {
2030
- name = node.getAttribute("t-component");
2031
- node.removeAttribute("t-component");
2032
- }
2033
- const dynamicProps = node.getAttribute("t-props");
2034
- node.removeAttribute("t-props");
2035
- const defaultSlotScope = node.getAttribute("t-slot-scope");
2036
- node.removeAttribute("t-slot-scope");
2037
- let on = null;
2038
- let props = null;
2039
- let propsTranslationCtx = null;
2040
- for (let name of node.getAttributeNames()) {
2041
- const value = node.getAttribute(name);
2042
- if (name.startsWith("t-translation-context-")) {
2043
- const attrName = name.slice(22);
2044
- propsTranslationCtx = propsTranslationCtx || {};
2045
- propsTranslationCtx[attrName] = value;
2046
- }
2047
- else if (name.startsWith("t-")) {
2048
- if (name.startsWith("t-on-")) {
2049
- on = on || {};
2050
- on[name.slice(5)] = value;
2051
- }
2052
- else {
2053
- const message = directiveErrorMap.get(name.split("-").slice(0, 2).join("-"));
2054
- throw new OwlError(message || `unsupported directive on Component: ${name}`);
2055
- }
2056
- }
2057
- else {
2058
- props = props || {};
2059
- props[name] = value;
2060
- }
2061
- }
2062
- let slots = null;
2063
- if (node.hasChildNodes()) {
2064
- const clone = node.cloneNode(true);
2065
- // named slots
2066
- const slotNodes = Array.from(clone.querySelectorAll("[t-set-slot]"));
2067
- for (let slotNode of slotNodes) {
2068
- if (slotNode.tagName !== "t") {
2069
- throw new OwlError(`Directive 't-set-slot' can only be used on <t> nodes (used on a <${slotNode.tagName}>)`);
2070
- }
2071
- const name = slotNode.getAttribute("t-set-slot");
2072
- // check if this is defined in a sub component (in which case it should
2073
- // be ignored)
2074
- let el = slotNode.parentElement;
2075
- let isInSubComponent = false;
2076
- while (el && el !== clone) {
2077
- if (el.hasAttribute("t-component") || el.tagName[0] === el.tagName[0].toUpperCase()) {
2078
- isInSubComponent = true;
2079
- break;
2080
- }
2081
- el = el.parentElement;
2082
- }
2083
- if (isInSubComponent || !el) {
2084
- continue;
2085
- }
2086
- slotNode.removeAttribute("t-set-slot");
2087
- slotNode.remove();
2088
- const slotAst = parseNode(slotNode, ctx);
2089
- let on = null;
2090
- let attrs = null;
2091
- let attrsTranslationCtx = null;
2092
- let scope = null;
2093
- for (let attributeName of slotNode.getAttributeNames()) {
2094
- const value = slotNode.getAttribute(attributeName);
2095
- if (attributeName === "t-slot-scope") {
2096
- scope = value;
2097
- continue;
2098
- }
2099
- else if (attributeName.startsWith("t-translation-context-")) {
2100
- const attrName = attributeName.slice(22);
2101
- attrsTranslationCtx = attrsTranslationCtx || {};
2102
- attrsTranslationCtx[attrName] = value;
2103
- }
2104
- else if (attributeName.startsWith("t-on-")) {
2105
- on = on || {};
2106
- on[attributeName.slice(5)] = value;
2107
- }
2108
- else {
2109
- attrs = attrs || {};
2110
- attrs[attributeName] = value;
2111
- }
2112
- }
2113
- slots = slots || {};
2114
- slots[name] = { content: slotAst, on, attrs, attrsTranslationCtx, scope };
2115
- }
2116
- // default slot
2117
- const defaultContent = parseChildNodes(clone, ctx);
2118
- slots = slots || {};
2119
- // t-set-slot="default" has priority over content
2120
- if (defaultContent && !slots.default) {
2121
- slots.default = {
2122
- content: defaultContent,
2123
- on,
2124
- attrs: null,
2125
- attrsTranslationCtx: null,
2126
- scope: defaultSlotScope,
2127
- };
2128
- }
2129
- }
2130
- return {
2131
- type: 10 /* ASTType.TComponent */,
2132
- name,
2133
- isDynamic,
2134
- dynamicProps,
2135
- props,
2136
- propsTranslationCtx,
2137
- slots,
2138
- on,
2139
- };
2140
- }
2141
- // -----------------------------------------------------------------------------
2142
- // Slots
2143
- // -----------------------------------------------------------------------------
2144
- function parseTCallSlot(node, ctx) {
2145
- if (!node.hasAttribute("t-call-slot")) {
2146
- return null;
2147
- }
2148
- const name = node.getAttribute("t-call-slot");
2149
- node.removeAttribute("t-call-slot");
2150
- let attrs = null;
2151
- let attrsTranslationCtx = null;
2152
- let on = null;
2153
- for (let attributeName of node.getAttributeNames()) {
2154
- const value = node.getAttribute(attributeName);
2155
- if (attributeName.startsWith("t-on-")) {
2156
- on = on || {};
2157
- on[attributeName.slice(5)] = value;
2158
- }
2159
- else if (attributeName.startsWith("t-translation-context-")) {
2160
- const attrName = attributeName.slice(22);
2161
- attrsTranslationCtx = attrsTranslationCtx || {};
2162
- attrsTranslationCtx[attrName] = value;
2163
- }
2164
- else {
2165
- attrs = attrs || {};
2166
- attrs[attributeName] = value;
2167
- }
2168
- }
2169
- return {
2170
- type: 13 /* ASTType.TCallSlot */,
2171
- name,
2172
- attrs,
2173
- attrsTranslationCtx,
2174
- on,
2175
- defaultContent: parseChildNodes(node, ctx),
2176
- };
2177
- }
2178
- // -----------------------------------------------------------------------------
2179
- // Translation
2180
- // -----------------------------------------------------------------------------
2181
- function wrapInTTranslationAST(r) {
2182
- const ast = { type: 15 /* ASTType.TTranslation */, content: r };
2183
- if (r === null || r === void 0 ? void 0 : r.hasNoRepresentation) {
2184
- ast.hasNoRepresentation = true;
2185
- }
2186
- return ast;
2187
- }
2188
- function parseTTranslation(node, ctx) {
2189
- if (node.getAttribute("t-translation") !== "off") {
2190
- return null;
2191
- }
2192
- node.removeAttribute("t-translation");
2193
- const result = parseNode(node, ctx);
2194
- if ((result === null || result === void 0 ? void 0 : result.type) === 3 /* ASTType.Multi */) {
2195
- const children = result.content.map(wrapInTTranslationAST);
2196
- return makeASTMulti(children);
2197
- }
2198
- return wrapInTTranslationAST(result);
2199
- }
2200
- // -----------------------------------------------------------------------------
2201
- // Translation Context
2202
- // -----------------------------------------------------------------------------
2203
- function wrapInTTranslationContextAST(r, translationCtx) {
2204
- const ast = {
2205
- type: 16 /* ASTType.TTranslationContext */,
2206
- content: r,
2207
- translationCtx,
2208
- };
2209
- if (r === null || r === void 0 ? void 0 : r.hasNoRepresentation) {
2210
- ast.hasNoRepresentation = true;
2211
- }
2212
- return ast;
2213
- }
2214
- function parseTTranslationContext(node, ctx) {
2215
- const translationCtx = node.getAttribute("t-translation-context");
2216
- if (!translationCtx) {
2217
- return null;
2218
- }
2219
- node.removeAttribute("t-translation-context");
2220
- const result = parseNode(node, ctx);
2221
- if ((result === null || result === void 0 ? void 0 : result.type) === 3 /* ASTType.Multi */) {
2222
- const children = result.content.map((c) => wrapInTTranslationContextAST(c, translationCtx));
2223
- return makeASTMulti(children);
2224
- }
2225
- return wrapInTTranslationContextAST(result, translationCtx);
2226
- }
2227
- // -----------------------------------------------------------------------------
2228
- // Portal
2229
- // -----------------------------------------------------------------------------
2230
- function parseTPortal(node, ctx) {
2231
- if (!node.hasAttribute("t-portal")) {
2232
- return null;
2233
- }
2234
- const target = node.getAttribute("t-portal");
2235
- node.removeAttribute("t-portal");
2236
- const content = parseNode(node, ctx);
2237
- if (!content) {
2238
- return {
2239
- type: 0 /* ASTType.Text */,
2240
- value: "",
2241
- };
2242
- }
2243
- return {
2244
- type: 17 /* ASTType.TPortal */,
2245
- target,
2246
- content,
2247
- };
2248
- }
2249
- // -----------------------------------------------------------------------------
2250
- // helpers
2251
- // -----------------------------------------------------------------------------
2252
- /**
2253
- * Parse all the child nodes of a given node and return a list of ast elements
2254
- */
2255
- function parseChildren(node, ctx) {
2256
- const children = [];
2257
- for (let child of node.childNodes) {
2258
- const childAst = parseNode(child, ctx);
2259
- if (childAst) {
2260
- if (childAst.type === 3 /* ASTType.Multi */) {
2261
- children.push(...childAst.content);
2262
- }
2263
- else {
2264
- children.push(childAst);
2265
- }
2266
- }
2267
- }
2268
- return children;
2269
- }
2270
- function makeASTMulti(children) {
2271
- const ast = { type: 3 /* ASTType.Multi */, content: children };
2272
- if (children.every((c) => c.hasNoRepresentation)) {
2273
- ast.hasNoRepresentation = true;
2274
- }
2275
- return ast;
2276
- }
2277
- /**
2278
- * Parse all the child nodes of a given node and return an ast if possible.
2279
- * In the case there are multiple children, they are wrapped in a astmulti.
2280
- */
2281
- function parseChildNodes(node, ctx) {
2282
- const children = parseChildren(node, ctx);
2283
- switch (children.length) {
2284
- case 0:
2285
- return null;
2286
- case 1:
2287
- return children[0];
2288
- default:
2289
- return makeASTMulti(children);
2290
- }
2291
- }
2292
- /**
2293
- * Normalizes the content of an Element so that t-if/t-elif/t-else directives
2294
- * immediately follow one another (by removing empty text nodes or comments).
2295
- * Throws an error when a conditional branching statement is malformed. This
2296
- * function modifies the Element in place.
2297
- *
2298
- * @param el the element containing the tree that should be normalized
2299
- */
2300
- function normalizeTIf(el) {
2301
- let tbranch = el.querySelectorAll("[t-elif], [t-else]");
2302
- for (let i = 0, ilen = tbranch.length; i < ilen; i++) {
2303
- let node = tbranch[i];
2304
- let prevElem = node.previousElementSibling;
2305
- let pattr = (name) => prevElem.getAttribute(name);
2306
- let nattr = (name) => +!!node.getAttribute(name);
2307
- if (prevElem && (pattr("t-if") || pattr("t-elif"))) {
2308
- if (pattr("t-foreach")) {
2309
- throw new OwlError("t-if cannot stay at the same level as t-foreach when using t-elif or t-else");
2310
- }
2311
- if (["t-if", "t-elif", "t-else"].map(nattr).reduce(function (a, b) {
2312
- return a + b;
2313
- }) > 1) {
2314
- throw new OwlError("Only one conditional branching directive is allowed per node");
2315
- }
2316
- // All text (with only spaces) and comment nodes (nodeType 8) between
2317
- // branch nodes are removed
2318
- let textNode;
2319
- while ((textNode = node.previousSibling) !== prevElem) {
2320
- if (textNode.nodeValue.trim().length && textNode.nodeType !== 8) {
2321
- throw new OwlError("text is not allowed between branching directives");
2322
- }
2323
- textNode.remove();
2324
- }
2325
- }
2326
- else {
2327
- throw new OwlError("t-elif and t-else directives must be preceded by a t-if or t-elif directive");
2328
- }
2329
- }
2330
- }
2331
- /**
2332
- * Normalizes the content of an Element so that t-out directives on components
2333
- * are removed and instead places a <t t-out=""> as the default slot of the
2334
- * component. Also throws if the component already has content. This function
2335
- * modifies the Element in place.
2336
- *
2337
- * @param el the element containing the tree that should be normalized
2338
- */
2339
- function normalizeTOut(el) {
2340
- const elements = [...el.querySelectorAll(`[t-out]`)].filter((el) => el.tagName[0] === el.tagName[0].toUpperCase() || el.hasAttribute("t-component"));
2341
- for (const el of elements) {
2342
- if (el.childNodes.length) {
2343
- throw new OwlError(`Cannot have t-out on a component that already has content`);
2344
- }
2345
- const value = el.getAttribute("t-out");
2346
- el.removeAttribute("t-out");
2347
- const t = el.ownerDocument.createElement("t");
2348
- if (value != null) {
2349
- t.setAttribute("t-out", value);
2350
- }
2351
- el.appendChild(t);
2352
- }
2353
- }
2354
- /**
2355
- * Normalizes the tree inside a given element and do some preliminary validation
2356
- * on it. This function modifies the Element in place.
2357
- *
2358
- * @param el the element containing the tree that should be normalized
2359
- */
2360
- function normalizeXML(el) {
2361
- normalizeTIf(el);
2362
- normalizeTOut(el);
1568
+ // -----------------------------------------------------------------------------
1569
+ // Parser
1570
+ // -----------------------------------------------------------------------------
1571
+ const cache = new WeakMap();
1572
+ function parse(xml, customDir) {
1573
+ const ctx = {
1574
+ inPreTag: false,
1575
+ customDirectives: customDir,
1576
+ };
1577
+ if (typeof xml === "string") {
1578
+ const elem = parseXML(`<t>${xml}</t>`).firstChild;
1579
+ return _parse(elem, ctx);
1580
+ }
1581
+ let ast = cache.get(xml);
1582
+ if (!ast) {
1583
+ // we clone here the xml to prevent modifying it in place
1584
+ ast = _parse(xml.cloneNode(true), ctx);
1585
+ cache.set(xml, ast);
1586
+ }
1587
+ return ast;
1588
+ }
1589
+ function _parse(xml, ctx) {
1590
+ normalizeXML(xml);
1591
+ return parseNode(xml, ctx) || { type: 0 /* ASTType.Text */, value: "" };
1592
+ }
1593
+ function parseNode(node, ctx) {
1594
+ if (!(node instanceof Element)) {
1595
+ return parseTextCommentNode(node, ctx);
1596
+ }
1597
+ return (parseTCustom(node, ctx) ||
1598
+ parseTDebugLog(node, ctx) ||
1599
+ parseTForEach(node, ctx) ||
1600
+ parseTIf(node, ctx) ||
1601
+ parseTPortal(node, ctx) ||
1602
+ parseTCall(node, ctx) ||
1603
+ parseTCallBlock(node) ||
1604
+ parseTTranslation(node, ctx) ||
1605
+ parseTTranslationContext(node, ctx) ||
1606
+ parseTKey(node, ctx) ||
1607
+ parseTOutNode(node, ctx) ||
1608
+ parseTCallSlot(node, ctx) ||
1609
+ parseComponent(node, ctx) ||
1610
+ parseDOMNode(node, ctx) ||
1611
+ parseTSetNode(node, ctx) ||
1612
+ parseTNode(node, ctx));
1613
+ }
1614
+ // -----------------------------------------------------------------------------
1615
+ // <t /> tag
1616
+ // -----------------------------------------------------------------------------
1617
+ function parseTNode(node, ctx) {
1618
+ if (node.tagName !== "t") {
1619
+ return null;
1620
+ }
1621
+ return parseChildNodes(node, ctx);
1622
+ }
1623
+ // -----------------------------------------------------------------------------
1624
+ // Text and Comment Nodes
1625
+ // -----------------------------------------------------------------------------
1626
+ const lineBreakRE = /[\r\n]/;
1627
+ function parseTextCommentNode(node, ctx) {
1628
+ if (node.nodeType === Node.TEXT_NODE) {
1629
+ let value = node.textContent || "";
1630
+ if (!ctx.inPreTag && lineBreakRE.test(value) && !value.trim()) {
1631
+ return null;
1632
+ }
1633
+ return { type: 0 /* ASTType.Text */, value };
1634
+ }
1635
+ else if (node.nodeType === Node.COMMENT_NODE) {
1636
+ return { type: 1 /* ASTType.Comment */, value: node.textContent || "" };
1637
+ }
1638
+ return null;
1639
+ }
1640
+ function parseTCustom(node, ctx) {
1641
+ if (!ctx.customDirectives) {
1642
+ return null;
1643
+ }
1644
+ const nodeAttrsNames = node.getAttributeNames();
1645
+ for (let attr of nodeAttrsNames) {
1646
+ if (attr === "t-custom" || attr === "t-custom-") {
1647
+ throw new OwlError("Missing custom directive name with t-custom directive");
1648
+ }
1649
+ if (attr.startsWith("t-custom-")) {
1650
+ const directiveName = attr.split(".")[0].slice(9);
1651
+ const customDirective = ctx.customDirectives[directiveName];
1652
+ if (!customDirective) {
1653
+ throw new OwlError(`Custom directive "${directiveName}" is not defined`);
1654
+ }
1655
+ const value = node.getAttribute(attr);
1656
+ const modifiers = attr.split(".").slice(1);
1657
+ node.removeAttribute(attr);
1658
+ try {
1659
+ customDirective(node, value, modifiers);
1660
+ }
1661
+ catch (error) {
1662
+ throw new OwlError(`Custom directive "${directiveName}" throw the following error: ${error}`);
1663
+ }
1664
+ return parseNode(node, ctx);
1665
+ }
1666
+ }
1667
+ return null;
1668
+ }
1669
+ // -----------------------------------------------------------------------------
1670
+ // debugging
1671
+ // -----------------------------------------------------------------------------
1672
+ function parseTDebugLog(node, ctx) {
1673
+ if (node.hasAttribute("t-debug")) {
1674
+ node.removeAttribute("t-debug");
1675
+ const content = parseNode(node, ctx);
1676
+ const ast = {
1677
+ type: 11 /* ASTType.TDebug */,
1678
+ content,
1679
+ };
1680
+ if (content === null || content === void 0 ? void 0 : content.hasNoRepresentation) {
1681
+ ast.hasNoRepresentation = true;
1682
+ }
1683
+ return ast;
1684
+ }
1685
+ if (node.hasAttribute("t-log")) {
1686
+ const expr = node.getAttribute("t-log");
1687
+ node.removeAttribute("t-log");
1688
+ const content = parseNode(node, ctx);
1689
+ const ast = {
1690
+ type: 12 /* ASTType.TLog */,
1691
+ expr,
1692
+ content,
1693
+ };
1694
+ if (content === null || content === void 0 ? void 0 : content.hasNoRepresentation) {
1695
+ ast.hasNoRepresentation = true;
1696
+ }
1697
+ return ast;
1698
+ }
1699
+ return null;
1700
+ }
1701
+ // -----------------------------------------------------------------------------
1702
+ // Regular dom node
1703
+ // -----------------------------------------------------------------------------
1704
+ const ROOT_SVG_TAGS = new Set(["svg", "g", "path"]);
1705
+ function parseDOMNode(node, ctx) {
1706
+ const { tagName } = node;
1707
+ const dynamicTag = node.getAttribute("t-tag");
1708
+ node.removeAttribute("t-tag");
1709
+ if (tagName === "t" && !dynamicTag) {
1710
+ return null;
1711
+ }
1712
+ if (tagName.startsWith("block-")) {
1713
+ throw new OwlError(`Invalid tag name: '${tagName}'`);
1714
+ }
1715
+ ctx = Object.assign({}, ctx);
1716
+ if (tagName === "pre") {
1717
+ ctx.inPreTag = true;
1718
+ }
1719
+ let ns = !ctx.nameSpace && ROOT_SVG_TAGS.has(tagName) ? "http://www.w3.org/2000/svg" : null;
1720
+ const ref = node.getAttribute("t-ref");
1721
+ node.removeAttribute("t-ref");
1722
+ const nodeAttrsNames = node.getAttributeNames();
1723
+ let attrs = null;
1724
+ let attrsTranslationCtx = null;
1725
+ let on = null;
1726
+ let model = null;
1727
+ for (let attr of nodeAttrsNames) {
1728
+ const value = node.getAttribute(attr);
1729
+ if (attr === "t-on" || attr === "t-on-") {
1730
+ throw new OwlError("Missing event name with t-on directive");
1731
+ }
1732
+ if (attr.startsWith("t-on-")) {
1733
+ on = on || {};
1734
+ on[attr.slice(5)] = value;
1735
+ }
1736
+ else if (attr.startsWith("t-model")) {
1737
+ if (!["input", "select", "textarea"].includes(tagName)) {
1738
+ throw new OwlError("The t-model directive only works with <input>, <textarea> and <select>");
1739
+ }
1740
+ const typeAttr = node.getAttribute("type");
1741
+ const isInput = tagName === "input";
1742
+ const isSelect = tagName === "select";
1743
+ const isCheckboxInput = isInput && typeAttr === "checkbox";
1744
+ const isRadioInput = isInput && typeAttr === "radio";
1745
+ const hasTrimMod = attr.includes(".trim");
1746
+ const hasLazyMod = hasTrimMod || attr.includes(".lazy");
1747
+ const hasNumberMod = attr.includes(".number");
1748
+ const eventType = isRadioInput ? "click" : isSelect || hasLazyMod ? "change" : "input";
1749
+ model = {
1750
+ expr: value,
1751
+ targetAttr: isCheckboxInput ? "checked" : "value",
1752
+ specialInitTargetAttr: isRadioInput ? "checked" : null,
1753
+ eventType,
1754
+ hasDynamicChildren: false,
1755
+ shouldTrim: hasTrimMod,
1756
+ shouldNumberize: hasNumberMod,
1757
+ };
1758
+ if (isSelect) {
1759
+ // don't pollute the original ctx
1760
+ ctx = Object.assign({}, ctx);
1761
+ ctx.tModelInfo = model;
1762
+ }
1763
+ }
1764
+ else if (attr.startsWith("block-")) {
1765
+ throw new OwlError(`Invalid attribute: '${attr}'`);
1766
+ }
1767
+ else if (attr === "xmlns") {
1768
+ ns = value;
1769
+ }
1770
+ else if (attr.startsWith("t-translation-context-")) {
1771
+ const attrName = attr.slice(22);
1772
+ attrsTranslationCtx = attrsTranslationCtx || {};
1773
+ attrsTranslationCtx[attrName] = value;
1774
+ }
1775
+ else if (attr !== "t-name") {
1776
+ if (attr.startsWith("t-") && !attr.startsWith("t-att")) {
1777
+ throw new OwlError(`Unknown QWeb directive: '${attr}'`);
1778
+ }
1779
+ const tModel = ctx.tModelInfo;
1780
+ if (tModel && ["t-att-value", "t-attf-value"].includes(attr)) {
1781
+ tModel.hasDynamicChildren = true;
1782
+ }
1783
+ attrs = attrs || {};
1784
+ attrs[attr] = value;
1785
+ }
1786
+ }
1787
+ if (ns) {
1788
+ ctx.nameSpace = ns;
1789
+ }
1790
+ const children = parseChildren(node, ctx);
1791
+ return {
1792
+ type: 2 /* ASTType.DomNode */,
1793
+ tag: tagName,
1794
+ dynamicTag,
1795
+ attrs,
1796
+ attrsTranslationCtx,
1797
+ on,
1798
+ ref,
1799
+ content: children,
1800
+ model,
1801
+ ns,
1802
+ };
1803
+ }
1804
+ // -----------------------------------------------------------------------------
1805
+ // t-out
1806
+ // -----------------------------------------------------------------------------
1807
+ function parseTOutNode(node, ctx) {
1808
+ if (!node.hasAttribute("t-out") && !node.hasAttribute("t-esc")) {
1809
+ return null;
1810
+ }
1811
+ if (node.hasAttribute("t-esc")) {
1812
+ console.warn(`t-esc has been deprecated in favor of t-out. If the value to render is not wrapped by the "markup" function, it will be escaped`);
1813
+ }
1814
+ const expr = (node.getAttribute("t-out") || node.getAttribute("t-esc"));
1815
+ node.removeAttribute("t-out");
1816
+ node.removeAttribute("t-esc");
1817
+ const tOut = { type: 7 /* ASTType.TOut */, expr, body: null };
1818
+ const ref = node.getAttribute("t-ref");
1819
+ node.removeAttribute("t-ref");
1820
+ const ast = parseNode(node, ctx);
1821
+ if (!ast) {
1822
+ return tOut;
1823
+ }
1824
+ if (ast.type === 2 /* ASTType.DomNode */) {
1825
+ tOut.body = ast.content.length ? ast.content : null;
1826
+ return {
1827
+ ...ast,
1828
+ ref,
1829
+ content: [tOut],
1830
+ };
1831
+ }
1832
+ return tOut;
1833
+ }
1834
+ // -----------------------------------------------------------------------------
1835
+ // t-foreach and t-key
1836
+ // -----------------------------------------------------------------------------
1837
+ function parseTForEach(node, ctx) {
1838
+ if (!node.hasAttribute("t-foreach")) {
1839
+ return null;
1840
+ }
1841
+ const html = node.outerHTML;
1842
+ const collection = node.getAttribute("t-foreach");
1843
+ node.removeAttribute("t-foreach");
1844
+ const elem = node.getAttribute("t-as") || "";
1845
+ node.removeAttribute("t-as");
1846
+ const key = node.getAttribute("t-key");
1847
+ if (!key) {
1848
+ throw new OwlError(`"Directive t-foreach should always be used with a t-key!" (expression: t-foreach="${collection}" t-as="${elem}")`);
1849
+ }
1850
+ node.removeAttribute("t-key");
1851
+ const memo = node.getAttribute("t-memo") || "";
1852
+ node.removeAttribute("t-memo");
1853
+ const body = parseNode(node, ctx);
1854
+ if (!body) {
1855
+ return null;
1856
+ }
1857
+ const hasNoTCall = !html.includes("t-call");
1858
+ const hasNoFirst = hasNoTCall && !html.includes(`${elem}_first`);
1859
+ const hasNoLast = hasNoTCall && !html.includes(`${elem}_last`);
1860
+ const hasNoIndex = hasNoTCall && !html.includes(`${elem}_index`);
1861
+ const hasNoValue = hasNoTCall && !html.includes(`${elem}_value`);
1862
+ return {
1863
+ type: 8 /* ASTType.TForEach */,
1864
+ collection,
1865
+ elem,
1866
+ body,
1867
+ memo,
1868
+ key,
1869
+ hasNoFirst,
1870
+ hasNoLast,
1871
+ hasNoIndex,
1872
+ hasNoValue,
1873
+ };
1874
+ }
1875
+ function parseTKey(node, ctx) {
1876
+ if (!node.hasAttribute("t-key")) {
1877
+ return null;
1878
+ }
1879
+ const key = node.getAttribute("t-key");
1880
+ node.removeAttribute("t-key");
1881
+ const content = parseNode(node, ctx);
1882
+ if (!content) {
1883
+ return null;
1884
+ }
1885
+ const ast = {
1886
+ type: 9 /* ASTType.TKey */,
1887
+ expr: key,
1888
+ content,
1889
+ };
1890
+ if (content.hasNoRepresentation) {
1891
+ ast.hasNoRepresentation = true;
1892
+ }
1893
+ return ast;
1894
+ }
1895
+ // -----------------------------------------------------------------------------
1896
+ // t-call
1897
+ // -----------------------------------------------------------------------------
1898
+ function parseTCall(node, ctx) {
1899
+ if (!node.hasAttribute("t-call")) {
1900
+ return null;
1901
+ }
1902
+ const subTemplate = node.getAttribute("t-call");
1903
+ const context = node.getAttribute("t-call-context");
1904
+ node.removeAttribute("t-call");
1905
+ node.removeAttribute("t-call-context");
1906
+ if (node.tagName !== "t") {
1907
+ const ast = parseNode(node, ctx);
1908
+ const tcall = { type: 6 /* ASTType.TCall */, name: subTemplate, body: null, context };
1909
+ if (ast && ast.type === 2 /* ASTType.DomNode */) {
1910
+ ast.content = [tcall];
1911
+ return ast;
1912
+ }
1913
+ if (ast && ast.type === 10 /* ASTType.TComponent */) {
1914
+ return {
1915
+ ...ast,
1916
+ slots: {
1917
+ default: {
1918
+ content: tcall,
1919
+ scope: null,
1920
+ on: null,
1921
+ attrs: null,
1922
+ attrsTranslationCtx: null,
1923
+ },
1924
+ },
1925
+ };
1926
+ }
1927
+ }
1928
+ const body = parseChildren(node, ctx);
1929
+ return {
1930
+ type: 6 /* ASTType.TCall */,
1931
+ name: subTemplate,
1932
+ body: body.length ? body : null,
1933
+ context,
1934
+ };
1935
+ }
1936
+ // -----------------------------------------------------------------------------
1937
+ // t-call-block
1938
+ // -----------------------------------------------------------------------------
1939
+ function parseTCallBlock(node, ctx) {
1940
+ if (!node.hasAttribute("t-call-block")) {
1941
+ return null;
1942
+ }
1943
+ const name = node.getAttribute("t-call-block");
1944
+ return {
1945
+ type: 14 /* ASTType.TCallBlock */,
1946
+ name,
1947
+ };
1948
+ }
1949
+ // -----------------------------------------------------------------------------
1950
+ // t-if
1951
+ // -----------------------------------------------------------------------------
1952
+ function parseTIf(node, ctx) {
1953
+ if (!node.hasAttribute("t-if")) {
1954
+ return null;
1955
+ }
1956
+ const condition = node.getAttribute("t-if");
1957
+ node.removeAttribute("t-if");
1958
+ const content = parseNode(node, ctx) || { type: 0 /* ASTType.Text */, value: "" };
1959
+ let nextElement = node.nextElementSibling;
1960
+ // t-elifs
1961
+ const tElifs = [];
1962
+ while (nextElement && nextElement.hasAttribute("t-elif")) {
1963
+ const condition = nextElement.getAttribute("t-elif");
1964
+ nextElement.removeAttribute("t-elif");
1965
+ const tElif = parseNode(nextElement, ctx);
1966
+ const next = nextElement.nextElementSibling;
1967
+ nextElement.remove();
1968
+ nextElement = next;
1969
+ if (tElif) {
1970
+ tElifs.push({ condition, content: tElif });
1971
+ }
1972
+ }
1973
+ // t-else
1974
+ let tElse = null;
1975
+ if (nextElement && nextElement.hasAttribute("t-else")) {
1976
+ nextElement.removeAttribute("t-else");
1977
+ tElse = parseNode(nextElement, ctx);
1978
+ nextElement.remove();
1979
+ }
1980
+ return {
1981
+ type: 4 /* ASTType.TIf */,
1982
+ condition,
1983
+ content,
1984
+ tElif: tElifs.length ? tElifs : null,
1985
+ tElse,
1986
+ };
1987
+ }
1988
+ // -----------------------------------------------------------------------------
1989
+ // t-set directive
1990
+ // -----------------------------------------------------------------------------
1991
+ function parseTSetNode(node, ctx) {
1992
+ if (!node.hasAttribute("t-set")) {
1993
+ return null;
1994
+ }
1995
+ const name = node.getAttribute("t-set");
1996
+ const value = node.getAttribute("t-value") || null;
1997
+ const defaultValue = node.innerHTML === node.textContent ? node.textContent || null : null;
1998
+ let body = null;
1999
+ if (node.textContent !== node.innerHTML) {
2000
+ body = parseChildren(node, ctx);
2001
+ }
2002
+ return { type: 5 /* ASTType.TSet */, name, value, defaultValue, body, hasNoRepresentation: true };
2003
+ }
2004
+ // -----------------------------------------------------------------------------
2005
+ // Components
2006
+ // -----------------------------------------------------------------------------
2007
+ // Error messages when trying to use an unsupported directive on a component
2008
+ const directiveErrorMap = new Map([
2009
+ [
2010
+ "t-ref",
2011
+ "t-ref is no longer supported on components. Consider exposing only the public part of the component's API through a callback prop.",
2012
+ ],
2013
+ ["t-att", "t-att makes no sense on component: props are already treated as expressions"],
2014
+ [
2015
+ "t-attf",
2016
+ "t-attf is not supported on components: use template strings for string interpolation in props",
2017
+ ],
2018
+ ]);
2019
+ function parseComponent(node, ctx) {
2020
+ let name = node.tagName;
2021
+ const firstLetter = name[0];
2022
+ let isDynamic = node.hasAttribute("t-component");
2023
+ if (isDynamic && name !== "t") {
2024
+ throw new OwlError(`Directive 't-component' can only be used on <t> nodes (used on a <${name}>)`);
2025
+ }
2026
+ if (!(firstLetter === firstLetter.toUpperCase() || isDynamic)) {
2027
+ return null;
2028
+ }
2029
+ if (isDynamic) {
2030
+ name = node.getAttribute("t-component");
2031
+ node.removeAttribute("t-component");
2032
+ }
2033
+ const dynamicProps = node.getAttribute("t-props");
2034
+ node.removeAttribute("t-props");
2035
+ const defaultSlotScope = node.getAttribute("t-slot-scope");
2036
+ node.removeAttribute("t-slot-scope");
2037
+ let on = null;
2038
+ let props = null;
2039
+ let propsTranslationCtx = null;
2040
+ for (let name of node.getAttributeNames()) {
2041
+ const value = node.getAttribute(name);
2042
+ if (name.startsWith("t-translation-context-")) {
2043
+ const attrName = name.slice(22);
2044
+ propsTranslationCtx = propsTranslationCtx || {};
2045
+ propsTranslationCtx[attrName] = value;
2046
+ }
2047
+ else if (name.startsWith("t-")) {
2048
+ if (name.startsWith("t-on-")) {
2049
+ on = on || {};
2050
+ on[name.slice(5)] = value;
2051
+ }
2052
+ else {
2053
+ const message = directiveErrorMap.get(name.split("-").slice(0, 2).join("-"));
2054
+ throw new OwlError(message || `unsupported directive on Component: ${name}`);
2055
+ }
2056
+ }
2057
+ else {
2058
+ props = props || {};
2059
+ props[name] = value;
2060
+ }
2061
+ }
2062
+ let slots = null;
2063
+ if (node.hasChildNodes()) {
2064
+ const clone = node.cloneNode(true);
2065
+ // named slots
2066
+ const slotNodes = Array.from(clone.querySelectorAll("[t-set-slot]"));
2067
+ for (let slotNode of slotNodes) {
2068
+ if (slotNode.tagName !== "t") {
2069
+ throw new OwlError(`Directive 't-set-slot' can only be used on <t> nodes (used on a <${slotNode.tagName}>)`);
2070
+ }
2071
+ const name = slotNode.getAttribute("t-set-slot");
2072
+ // check if this is defined in a sub component (in which case it should
2073
+ // be ignored)
2074
+ let el = slotNode.parentElement;
2075
+ let isInSubComponent = false;
2076
+ while (el && el !== clone) {
2077
+ if (el.hasAttribute("t-component") || el.tagName[0] === el.tagName[0].toUpperCase()) {
2078
+ isInSubComponent = true;
2079
+ break;
2080
+ }
2081
+ el = el.parentElement;
2082
+ }
2083
+ if (isInSubComponent || !el) {
2084
+ continue;
2085
+ }
2086
+ slotNode.removeAttribute("t-set-slot");
2087
+ slotNode.remove();
2088
+ const slotAst = parseNode(slotNode, ctx);
2089
+ let on = null;
2090
+ let attrs = null;
2091
+ let attrsTranslationCtx = null;
2092
+ let scope = null;
2093
+ for (let attributeName of slotNode.getAttributeNames()) {
2094
+ const value = slotNode.getAttribute(attributeName);
2095
+ if (attributeName === "t-slot-scope") {
2096
+ scope = value;
2097
+ continue;
2098
+ }
2099
+ else if (attributeName.startsWith("t-translation-context-")) {
2100
+ const attrName = attributeName.slice(22);
2101
+ attrsTranslationCtx = attrsTranslationCtx || {};
2102
+ attrsTranslationCtx[attrName] = value;
2103
+ }
2104
+ else if (attributeName.startsWith("t-on-")) {
2105
+ on = on || {};
2106
+ on[attributeName.slice(5)] = value;
2107
+ }
2108
+ else {
2109
+ attrs = attrs || {};
2110
+ attrs[attributeName] = value;
2111
+ }
2112
+ }
2113
+ slots = slots || {};
2114
+ slots[name] = { content: slotAst, on, attrs, attrsTranslationCtx, scope };
2115
+ }
2116
+ // default slot
2117
+ const defaultContent = parseChildNodes(clone, ctx);
2118
+ slots = slots || {};
2119
+ // t-set-slot="default" has priority over content
2120
+ if (defaultContent && !slots.default) {
2121
+ slots.default = {
2122
+ content: defaultContent,
2123
+ on,
2124
+ attrs: null,
2125
+ attrsTranslationCtx: null,
2126
+ scope: defaultSlotScope,
2127
+ };
2128
+ }
2129
+ }
2130
+ return {
2131
+ type: 10 /* ASTType.TComponent */,
2132
+ name,
2133
+ isDynamic,
2134
+ dynamicProps,
2135
+ props,
2136
+ propsTranslationCtx,
2137
+ slots,
2138
+ on,
2139
+ };
2140
+ }
2141
+ // -----------------------------------------------------------------------------
2142
+ // Slots
2143
+ // -----------------------------------------------------------------------------
2144
+ function parseTCallSlot(node, ctx) {
2145
+ if (!node.hasAttribute("t-call-slot") || !node.hasAttribute("t-slot")) {
2146
+ return null;
2147
+ }
2148
+ if (node.hasAttribute("t-slot")) {
2149
+ console.warn(`t-slot has been renamed t-call-slot.`);
2150
+ }
2151
+ const name = node.getAttribute("t-call-slot");
2152
+ node.removeAttribute("t-call-slot");
2153
+ let attrs = null;
2154
+ let attrsTranslationCtx = null;
2155
+ let on = null;
2156
+ for (let attributeName of node.getAttributeNames()) {
2157
+ const value = node.getAttribute(attributeName);
2158
+ if (attributeName.startsWith("t-on-")) {
2159
+ on = on || {};
2160
+ on[attributeName.slice(5)] = value;
2161
+ }
2162
+ else if (attributeName.startsWith("t-translation-context-")) {
2163
+ const attrName = attributeName.slice(22);
2164
+ attrsTranslationCtx = attrsTranslationCtx || {};
2165
+ attrsTranslationCtx[attrName] = value;
2166
+ }
2167
+ else {
2168
+ attrs = attrs || {};
2169
+ attrs[attributeName] = value;
2170
+ }
2171
+ }
2172
+ return {
2173
+ type: 13 /* ASTType.TCallSlot */,
2174
+ name,
2175
+ attrs,
2176
+ attrsTranslationCtx,
2177
+ on,
2178
+ defaultContent: parseChildNodes(node, ctx),
2179
+ };
2180
+ }
2181
+ // -----------------------------------------------------------------------------
2182
+ // Translation
2183
+ // -----------------------------------------------------------------------------
2184
+ function wrapInTTranslationAST(r) {
2185
+ const ast = { type: 15 /* ASTType.TTranslation */, content: r };
2186
+ if (r === null || r === void 0 ? void 0 : r.hasNoRepresentation) {
2187
+ ast.hasNoRepresentation = true;
2188
+ }
2189
+ return ast;
2190
+ }
2191
+ function parseTTranslation(node, ctx) {
2192
+ if (node.getAttribute("t-translation") !== "off") {
2193
+ return null;
2194
+ }
2195
+ node.removeAttribute("t-translation");
2196
+ const result = parseNode(node, ctx);
2197
+ if ((result === null || result === void 0 ? void 0 : result.type) === 3 /* ASTType.Multi */) {
2198
+ const children = result.content.map(wrapInTTranslationAST);
2199
+ return makeASTMulti(children);
2200
+ }
2201
+ return wrapInTTranslationAST(result);
2202
+ }
2203
+ // -----------------------------------------------------------------------------
2204
+ // Translation Context
2205
+ // -----------------------------------------------------------------------------
2206
+ function wrapInTTranslationContextAST(r, translationCtx) {
2207
+ const ast = {
2208
+ type: 16 /* ASTType.TTranslationContext */,
2209
+ content: r,
2210
+ translationCtx,
2211
+ };
2212
+ if (r === null || r === void 0 ? void 0 : r.hasNoRepresentation) {
2213
+ ast.hasNoRepresentation = true;
2214
+ }
2215
+ return ast;
2216
+ }
2217
+ function parseTTranslationContext(node, ctx) {
2218
+ const translationCtx = node.getAttribute("t-translation-context");
2219
+ if (!translationCtx) {
2220
+ return null;
2221
+ }
2222
+ node.removeAttribute("t-translation-context");
2223
+ const result = parseNode(node, ctx);
2224
+ if ((result === null || result === void 0 ? void 0 : result.type) === 3 /* ASTType.Multi */) {
2225
+ const children = result.content.map((c) => wrapInTTranslationContextAST(c, translationCtx));
2226
+ return makeASTMulti(children);
2227
+ }
2228
+ return wrapInTTranslationContextAST(result, translationCtx);
2229
+ }
2230
+ // -----------------------------------------------------------------------------
2231
+ // Portal
2232
+ // -----------------------------------------------------------------------------
2233
+ function parseTPortal(node, ctx) {
2234
+ if (!node.hasAttribute("t-portal")) {
2235
+ return null;
2236
+ }
2237
+ const target = node.getAttribute("t-portal");
2238
+ node.removeAttribute("t-portal");
2239
+ const content = parseNode(node, ctx);
2240
+ if (!content) {
2241
+ return {
2242
+ type: 0 /* ASTType.Text */,
2243
+ value: "",
2244
+ };
2245
+ }
2246
+ return {
2247
+ type: 17 /* ASTType.TPortal */,
2248
+ target,
2249
+ content,
2250
+ };
2251
+ }
2252
+ // -----------------------------------------------------------------------------
2253
+ // helpers
2254
+ // -----------------------------------------------------------------------------
2255
+ /**
2256
+ * Parse all the child nodes of a given node and return a list of ast elements
2257
+ */
2258
+ function parseChildren(node, ctx) {
2259
+ const children = [];
2260
+ for (let child of node.childNodes) {
2261
+ const childAst = parseNode(child, ctx);
2262
+ if (childAst) {
2263
+ if (childAst.type === 3 /* ASTType.Multi */) {
2264
+ children.push(...childAst.content);
2265
+ }
2266
+ else {
2267
+ children.push(childAst);
2268
+ }
2269
+ }
2270
+ }
2271
+ return children;
2272
+ }
2273
+ function makeASTMulti(children) {
2274
+ const ast = { type: 3 /* ASTType.Multi */, content: children };
2275
+ if (children.every((c) => c.hasNoRepresentation)) {
2276
+ ast.hasNoRepresentation = true;
2277
+ }
2278
+ return ast;
2279
+ }
2280
+ /**
2281
+ * Parse all the child nodes of a given node and return an ast if possible.
2282
+ * In the case there are multiple children, they are wrapped in a astmulti.
2283
+ */
2284
+ function parseChildNodes(node, ctx) {
2285
+ const children = parseChildren(node, ctx);
2286
+ switch (children.length) {
2287
+ case 0:
2288
+ return null;
2289
+ case 1:
2290
+ return children[0];
2291
+ default:
2292
+ return makeASTMulti(children);
2293
+ }
2294
+ }
2295
+ /**
2296
+ * Normalizes the content of an Element so that t-if/t-elif/t-else directives
2297
+ * immediately follow one another (by removing empty text nodes or comments).
2298
+ * Throws an error when a conditional branching statement is malformed. This
2299
+ * function modifies the Element in place.
2300
+ *
2301
+ * @param el the element containing the tree that should be normalized
2302
+ */
2303
+ function normalizeTIf(el) {
2304
+ let tbranch = el.querySelectorAll("[t-elif], [t-else]");
2305
+ for (let i = 0, ilen = tbranch.length; i < ilen; i++) {
2306
+ let node = tbranch[i];
2307
+ let prevElem = node.previousElementSibling;
2308
+ let pattr = (name) => prevElem.getAttribute(name);
2309
+ let nattr = (name) => +!!node.getAttribute(name);
2310
+ if (prevElem && (pattr("t-if") || pattr("t-elif"))) {
2311
+ if (pattr("t-foreach")) {
2312
+ throw new OwlError("t-if cannot stay at the same level as t-foreach when using t-elif or t-else");
2313
+ }
2314
+ if (["t-if", "t-elif", "t-else"].map(nattr).reduce(function (a, b) {
2315
+ return a + b;
2316
+ }) > 1) {
2317
+ throw new OwlError("Only one conditional branching directive is allowed per node");
2318
+ }
2319
+ // All text (with only spaces) and comment nodes (nodeType 8) between
2320
+ // branch nodes are removed
2321
+ let textNode;
2322
+ while ((textNode = node.previousSibling) !== prevElem) {
2323
+ if (textNode.nodeValue.trim().length && textNode.nodeType !== 8) {
2324
+ throw new OwlError("text is not allowed between branching directives");
2325
+ }
2326
+ textNode.remove();
2327
+ }
2328
+ }
2329
+ else {
2330
+ throw new OwlError("t-elif and t-else directives must be preceded by a t-if or t-elif directive");
2331
+ }
2332
+ }
2333
+ }
2334
+ /**
2335
+ * Normalizes the content of an Element so that t-out directives on components
2336
+ * are removed and instead places a <t t-out=""> as the default slot of the
2337
+ * component. Also throws if the component already has content. This function
2338
+ * modifies the Element in place.
2339
+ *
2340
+ * @param el the element containing the tree that should be normalized
2341
+ */
2342
+ function normalizeTOut(el) {
2343
+ const elements = [...el.querySelectorAll(`[t-out]`)].filter((el) => el.tagName[0] === el.tagName[0].toUpperCase() || el.hasAttribute("t-component"));
2344
+ for (const el of elements) {
2345
+ if (el.childNodes.length) {
2346
+ throw new OwlError(`Cannot have t-out on a component that already has content`);
2347
+ }
2348
+ const value = el.getAttribute("t-out");
2349
+ el.removeAttribute("t-out");
2350
+ const t = el.ownerDocument.createElement("t");
2351
+ if (value != null) {
2352
+ t.setAttribute("t-out", value);
2353
+ }
2354
+ el.appendChild(t);
2355
+ }
2356
+ }
2357
+ /**
2358
+ * Normalizes the tree inside a given element and do some preliminary validation
2359
+ * on it. This function modifies the Element in place.
2360
+ *
2361
+ * @param el the element containing the tree that should be normalized
2362
+ */
2363
+ function normalizeXML(el) {
2364
+ normalizeTIf(el);
2365
+ normalizeTOut(el);
2363
2366
  }
2364
2367
 
2365
- function compile(template, options = {
2366
- hasGlobalValues: false,
2367
- }) {
2368
- // parsing
2369
- const ast = parse(template, options.customDirectives);
2370
- // some work
2371
- const hasSafeContext = template instanceof Node
2372
- ? !(template instanceof Element) || template.querySelector("[t-set], [t-call]") === null
2373
- : !template.includes("t-set=") && !template.includes("t-call=");
2374
- // code generation
2375
- const codeGenerator = new CodeGenerator(ast, { ...options, hasSafeContext });
2376
- const code = codeGenerator.generateCode();
2377
- // template function
2378
- try {
2379
- return new Function("app, bdom, helpers", code);
2380
- }
2381
- catch (originalError) {
2382
- const { name } = options;
2383
- const nameStr = name ? `template "${name}"` : "anonymous template";
2384
- const err = new OwlError(`Failed to compile ${nameStr}: ${originalError.message}\n\ngenerated code:\nfunction(app, bdom, helpers) {\n${code}\n}`);
2385
- err.cause = originalError;
2386
- throw err;
2387
- }
2368
+ function compile(template, options = {
2369
+ hasGlobalValues: false,
2370
+ }) {
2371
+ // parsing
2372
+ const ast = parse(template, options.customDirectives);
2373
+ // some work
2374
+ const hasSafeContext = template instanceof Node
2375
+ ? !(template instanceof Element) || template.querySelector("[t-set], [t-call]") === null
2376
+ : !template.includes("t-set=") && !template.includes("t-call=");
2377
+ // code generation
2378
+ const codeGenerator = new CodeGenerator(ast, { ...options, hasSafeContext });
2379
+ const code = codeGenerator.generateCode();
2380
+ // template function
2381
+ try {
2382
+ return new Function("app, bdom, helpers", code);
2383
+ }
2384
+ catch (originalError) {
2385
+ const { name } = options;
2386
+ const nameStr = name ? `template "${name}"` : "anonymous template";
2387
+ const err = new OwlError(`Failed to compile ${nameStr}: ${originalError.message}\n\ngenerated code:\nfunction(app, bdom, helpers) {\n${code}\n}`);
2388
+ err.cause = originalError;
2389
+ throw err;
2390
+ }
2388
2391
  }
2389
2392
 
2390
- // -----------------------------------------------------------------------------
2391
- // -----------------------------------------------------------------------------
2392
- // helpers
2393
- // -----------------------------------------------------------------------------
2394
- async function getXmlFiles(paths) {
2395
- return (await Promise.all(paths.map(async (file) => {
2396
- const stats = await stat(path.join(file));
2397
- if (stats.isDirectory()) {
2398
- return await getXmlFiles((await readdir(file)).map((fileName) => path.join(file, fileName)));
2399
- }
2400
- if (file.endsWith(".xml")) {
2401
- return file;
2402
- }
2403
- return [];
2404
- }))).flat();
2405
- }
2406
- // adapted from https://medium.com/@mhagemann/the-ultimate-way-to-slugify-a-url-string-in-javascript-b8e4a0d849e1
2407
- const a = "·-_,:;";
2408
- const p = new RegExp(a.split("").join("|"), "g");
2409
- function slugify(str) {
2410
- return str
2411
- .replace(/\//g, "") // remove /
2412
- .replace(/\./g, "_") // Replace . with _
2413
- .replace(p, (c) => "_") // Replace special characters
2414
- .replace(/&/g, "_and_") // Replace & with ‘and’
2415
- .replace(/[^\w\-]+/g, ""); // Remove all non-word characters
2416
- }
2417
- // -----------------------------------------------------------------------------
2418
- // main
2419
- // -----------------------------------------------------------------------------
2420
- async function compileTemplates(paths) {
2421
- const files = await getXmlFiles(paths);
2422
- process.stdout.write(`Processing ${files.length} files`);
2423
- let xmlStrings = await Promise.all(files.map((file) => readFile(file, "utf8")));
2424
- const templates = [];
2425
- const errors = [];
2426
- for (let i = 0; i < files.length; i++) {
2427
- const fileName = files[i];
2428
- const fileContent = xmlStrings[i];
2429
- process.stdout.write(`.`);
2430
- const parser = new DOMParser();
2431
- const doc = parser.parseFromString(fileContent, "text/xml");
2432
- for (const template of doc.querySelectorAll("[t-name]")) {
2433
- const name = template.getAttribute("t-name");
2434
- if (template.hasAttribute("owl")) {
2435
- template.removeAttribute("owl");
2436
- }
2437
- const fnName = slugify(name);
2438
- try {
2439
- const fn = compile(template).toString().replace("anonymous", fnName);
2440
- templates.push(`"${name}": ${fn},\n`);
2441
- }
2442
- catch (e) {
2443
- errors.push({ name, fileName, e });
2444
- }
2445
- }
2446
- }
2447
- process.stdout.write(`\n`);
2448
- for (let { name, fileName, e } of errors) {
2449
- console.warn(`Error while compiling '${name}' (in file ${fileName})`);
2450
- console.error(e);
2451
- }
2452
- console.log(`${templates.length} templates compiled`);
2453
- return `export const templates = {\n ${templates.join("\n")} \n}`;
2393
+ // -----------------------------------------------------------------------------
2394
+ // This file exports a function that allows compiling templates ahead of time.
2395
+ // It is used by the "compile_owl_template" command registered in the "bin"
2396
+ // section of owl's package.json
2397
+ // -----------------------------------------------------------------------------
2398
+ // -----------------------------------------------------------------------------
2399
+ // helpers
2400
+ // -----------------------------------------------------------------------------
2401
+ async function getXmlFiles(paths) {
2402
+ return (await Promise.all(paths.map(async (file) => {
2403
+ const stats = await stat(path.join(file));
2404
+ if (stats.isDirectory()) {
2405
+ return await getXmlFiles((await readdir(file)).map((fileName) => path.join(file, fileName)));
2406
+ }
2407
+ if (file.endsWith(".xml")) {
2408
+ return file;
2409
+ }
2410
+ return [];
2411
+ }))).flat();
2412
+ }
2413
+ // adapted from https://medium.com/@mhagemann/the-ultimate-way-to-slugify-a-url-string-in-javascript-b8e4a0d849e1
2414
+ const a = "·-_,:;";
2415
+ const p = new RegExp(a.split("").join("|"), "g");
2416
+ function slugify(str) {
2417
+ return str
2418
+ .replace(/\//g, "") // remove /
2419
+ .replace(/\./g, "_") // Replace . with _
2420
+ .replace(p, (c) => "_") // Replace special characters
2421
+ .replace(/&/g, "_and_") // Replace & with ‘and’
2422
+ .replace(/[^\w\-]+/g, ""); // Remove all non-word characters
2423
+ }
2424
+ // -----------------------------------------------------------------------------
2425
+ // main
2426
+ // -----------------------------------------------------------------------------
2427
+ async function compileTemplates(paths) {
2428
+ const files = await getXmlFiles(paths);
2429
+ process.stdout.write(`Processing ${files.length} files`);
2430
+ let xmlStrings = await Promise.all(files.map((file) => readFile(file, "utf8")));
2431
+ const templates = [];
2432
+ const errors = [];
2433
+ for (let i = 0; i < files.length; i++) {
2434
+ const fileName = files[i];
2435
+ const fileContent = xmlStrings[i];
2436
+ process.stdout.write(`.`);
2437
+ const parser = new DOMParser();
2438
+ const doc = parser.parseFromString(fileContent, "text/xml");
2439
+ for (const template of doc.querySelectorAll("[t-name]")) {
2440
+ const name = template.getAttribute("t-name");
2441
+ if (template.hasAttribute("owl")) {
2442
+ template.removeAttribute("owl");
2443
+ }
2444
+ const fnName = slugify(name);
2445
+ try {
2446
+ const fn = compile(template).toString().replace("anonymous", fnName);
2447
+ templates.push(`"${name}": ${fn},\n`);
2448
+ }
2449
+ catch (e) {
2450
+ errors.push({ name, fileName, e });
2451
+ }
2452
+ }
2453
+ }
2454
+ process.stdout.write(`\n`);
2455
+ for (let { name, fileName, e } of errors) {
2456
+ console.warn(`Error while compiling '${name}' (in file ${fileName})`);
2457
+ console.error(e);
2458
+ }
2459
+ console.log(`${templates.length} templates compiled`);
2460
+ return `export const templates = {\n ${templates.join("\n")} \n}`;
2454
2461
  }
2455
2462
 
2456
2463
  export { compileTemplates };