@odoo/owl 3.0.0-alpha.2 → 3.0.0-alpha.20

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