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

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 (62) hide show
  1. package/README.md +100 -110
  2. package/dist/compile_templates.mjs +84 -80
  3. package/dist/owl-devtools.zip +0 -0
  4. package/dist/owl.cjs.js +1612 -1473
  5. package/dist/owl.es.js +1612 -1473
  6. package/dist/owl.iife.js +1612 -1473
  7. package/dist/owl.iife.min.js +1 -1
  8. package/dist/types/compiler/code_generator.d.ts +1 -2
  9. package/dist/types/compiler/inline_expressions.d.ts +7 -24
  10. package/dist/types/compiler/parser.d.ts +3 -8
  11. package/dist/types/owl.d.ts +33 -27
  12. package/dist/types/runtime/blockdom/attributes.d.ts +2 -0
  13. package/dist/types/runtime/component_node.d.ts +2 -0
  14. package/dist/types/runtime/plugin_manager.d.ts +2 -0
  15. package/dist/types/runtime/reactivity/computations.d.ts +1 -0
  16. package/dist/types/runtime/rendering/scheduler.d.ts +1 -1
  17. package/dist/types/runtime/rendering/template_helpers.d.ts +2 -2
  18. package/dist/types/runtime/types.d.ts +3 -0
  19. package/dist/types/version.d.ts +1 -1
  20. package/package.json +25 -2
  21. package/dist/compiler.js +0 -2371
  22. package/dist/owl.cjs.runtime.js +0 -4070
  23. package/dist/owl.es.runtime.js +0 -4026
  24. package/dist/owl.iife.runtime.js +0 -4074
  25. package/dist/owl.iife.runtime.min.js +0 -1
  26. package/dist/types/common/types.d.ts +0 -1
  27. package/dist/types/runtime/cancellableContext.d.ts +0 -15
  28. package/dist/types/runtime/cancellablePromise.d.ts +0 -15
  29. package/dist/types/runtime/error_handling.d.ts +0 -13
  30. package/dist/types/runtime/executionContext.d.ts +0 -0
  31. package/dist/types/runtime/fibers.d.ts +0 -37
  32. package/dist/types/runtime/listOperation.d.ts +0 -1
  33. package/dist/types/runtime/model.d.ts +0 -48
  34. package/dist/types/runtime/plugins.d.ts +0 -36
  35. package/dist/types/runtime/portal.d.ts +0 -12
  36. package/dist/types/runtime/reactivity/async_computed.d.ts +0 -1
  37. package/dist/types/runtime/reactivity/derived.d.ts +0 -7
  38. package/dist/types/runtime/reactivity/reactivity.d.ts +0 -46
  39. package/dist/types/runtime/reactivity/signals.d.ts +0 -30
  40. package/dist/types/runtime/reactivity/state.d.ts +0 -48
  41. package/dist/types/runtime/reactivity.d.ts +0 -57
  42. package/dist/types/runtime/relationalModel/discussModel.d.ts +0 -19
  43. package/dist/types/runtime/relationalModel/discussModelTypes.d.ts +0 -22
  44. package/dist/types/runtime/relationalModel/field.d.ts +0 -20
  45. package/dist/types/runtime/relationalModel/model.d.ts +0 -59
  46. package/dist/types/runtime/relationalModel/modelData.d.ts +0 -18
  47. package/dist/types/runtime/relationalModel/modelRegistry.d.ts +0 -3
  48. package/dist/types/runtime/relationalModel/modelUtils.d.ts +0 -4
  49. package/dist/types/runtime/relationalModel/store.d.ts +0 -16
  50. package/dist/types/runtime/relationalModel/types.d.ts +0 -83
  51. package/dist/types/runtime/relationalModel/util.d.ts +0 -1
  52. package/dist/types/runtime/relationalModel/web/WebDataPoint.d.ts +0 -25
  53. package/dist/types/runtime/relationalModel/web/WebRecord.d.ts +0 -131
  54. package/dist/types/runtime/relationalModel/web/WebStaticList.d.ts +0 -63
  55. package/dist/types/runtime/relationalModel/web/webModel.d.ts +0 -5
  56. package/dist/types/runtime/relationalModel/web/webModelTypes.d.ts +0 -139
  57. package/dist/types/runtime/scheduler.d.ts +0 -21
  58. package/dist/types/runtime/signals.d.ts +0 -19
  59. package/dist/types/runtime/suspense.d.ts +0 -5
  60. package/dist/types/runtime/task.d.ts +0 -12
  61. package/dist/types/runtime/template_helpers.d.ts +0 -58
  62. package/dist/types/utils/registry.d.ts +0 -15
package/README.md CHANGED
@@ -1,149 +1,139 @@
1
- <h1 align="center">🦉 <a href="https://odoo.github.io/owl/">Owl Framework</a> 🦉</h1>
1
+ <h1 align="center">🦉 <a href="https://odoo.github.io/owl/">Owl</a> 🦉</h1>
2
2
 
3
- [![License: LGPL v3](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0)
3
+ <p align="center">
4
+ <strong>A modern, lightweight UI framework for applications that scale</strong>
5
+ </p>
6
+
7
+ [![License: LGPL v3](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0)
4
8
  [![npm version](https://badge.fury.io/js/@odoo%2Fowl.svg)](https://badge.fury.io/js/@odoo%2Fowl)
5
9
  [![Downloads](https://img.shields.io/npm/dm/@odoo%2Fowl.svg)](https://www.npmjs.com/package/@odoo/owl)
6
10
 
7
- _Class based components with hooks, reactive state and concurrent mode_
11
+ ---
8
12
 
9
- **Try it online!** you can experiment with the Owl framework in an online [playground](https://odoo.github.io/owl/playground).
13
+ > **Owl 3.0.0 Alpha:** This is an alpha release. The API and features are subject to change without notice.
10
14
 
11
- ## Project Overview
15
+ ---
12
16
 
13
- The Odoo Web Library (Owl) is a smallish (~<20kb gzipped) UI framework built by
14
- [Odoo](https://www.odoo.com/) for its products. Owl is a modern
15
- framework, written in Typescript, taking the best ideas from React and Vue in a
16
- simple and consistent way. Owl's main features are:
17
+ ## Try it now
17
18
 
18
- - a declarative component system,
19
- - a fine grained reactivity system similar to Vue,
20
- - hooks
21
- - fragments
22
- - asynchronous rendering
19
+ The fastest way to discover Owl is the **[online playground](https://odoo.github.io/owl/playground)**.
20
+ It features interactive examples, a live editor, and showcases all major features:
21
+ reactivity, components, plugins, and more. It also includes **guided tutorials**
22
+ and is the recommended way to learn about Owl.
23
23
 
24
- Owl components are defined with ES6 classes and xml templates, uses an
25
- underlying virtual DOM, integrates beautifully with hooks, and the rendering is
26
- asynchronous.
24
+ ## What is Owl?
27
25
 
28
- Quick links:
26
+ Owl is a modern UI framework (~20kb gzipped, zero dependencies) written in TypeScript,
27
+ built by [Odoo](https://www.odoo.com/). It powers Odoo's web client, one of the largest
28
+ open-source business applications, but is equally suited for small projects and prototypes.
29
29
 
30
- - [documentation](#documentation),
31
- - [changelog](CHANGELOG.md) (from Owl 1.x to 2.x),
32
- - [playground](https://odoo.github.io/owl/playground)
30
+ Key features:
33
31
 
34
- ## Example
32
+ - **Signal-based reactivity** — Explicit, composable, and debuggable state management
33
+ - **Plugin system** — Type-safe, composable sharing of state and services
34
+ - **Class-based components** — Familiar OOP patterns with ES6 classes
35
+ - **Declarative templates** — XML templates with a clean syntax
36
+ - **Async rendering** — Concurrent mode for smooth user experiences
35
37
 
36
- Here is a short example to illustrate interactive components:
38
+ ## Quick Example
37
39
 
38
40
  ```javascript
39
- const { Component, useState, mount, xml } = owl;
41
+ import { Component, signal, computed, mount, xml } from "@odoo/owl";
40
42
 
41
- class Counter extends Component {
43
+ class TodoList extends Component {
42
44
  static template = xml`
43
- <button t-on-click="() => state.value = state.value + props.increment">
44
- Click Me! [<t t-esc="state.value"/>]
45
- </button>`;
46
-
47
- state = useState({ value: 0 });
45
+ <input placeholder="Add todo..." t-on-keydown="this.onKeydown"/>
46
+ <ul>
47
+ <t t-foreach="this.todos()" t-as="todo" t-key="todo.id">
48
+ <li t-att-class="{ done: todo.done }">
49
+ <input type="checkbox" t-model="todo.done"/>
50
+ <t t-out="todo.text"/>
51
+ </li>
52
+ </t>
53
+ </ul>
54
+ <p t-if="this.remaining() > 0">
55
+ <t t-out="this.remaining()"/> item(s) remaining
56
+ </p>`;
57
+
58
+ todos = signal.Array([
59
+ { id: 1, text: "Learn Owl", done: false },
60
+ { id: 2, text: "Build something", done: false },
61
+ ]);
62
+
63
+ remaining = computed(() => this.todos().filter((t) => !t.done).length);
64
+
65
+ onKeydown(ev) {
66
+ if (ev.key === "Enter" && ev.target.value) {
67
+ this.todos.push({
68
+ id: Date.now(),
69
+ text: ev.target.value,
70
+ done: false,
71
+ });
72
+ ev.target.value = "";
73
+ }
74
+ }
48
75
  }
49
76
 
50
- class Root extends Component {
51
- static template = xml`
52
- <span>Hello Owl</span>
53
- <Counter increment="2"/>`;
54
-
55
- static components = { Counter };
56
- }
57
-
58
- mount(Root, document.body);
77
+ mount(TodoList, document.body);
59
78
  ```
60
79
 
61
- Note that the counter component is made reactive with the [`useState` hook](doc/reference/hooks.md#usestate).
62
- Also, all examples here uses the [`xml` helper](doc/reference/templates.md#inline-templates) to define inline templates.
63
- But this is not mandatory, many applications will load templates separately.
64
-
65
- More interesting examples can be found on the
66
- [playground](https://odoo.github.io/owl/playground) application.
80
+ This example demonstrates Owl's reactivity: `todos` is a signal, `remaining`
81
+ is a computed value that updates automatically, and the UI reacts to changes
82
+ without manual subscription management.
67
83
 
68
84
  ## Documentation
69
85
 
70
- ### Learning Owl
86
+ The documentation below is for **Owl 3**. For the Owl 2 documentation, see the
87
+ [owl-2.x branch](https://github.com/odoo/owl/tree/owl-2.x).
71
88
 
72
- Are you new to Owl? This is the place to start!
89
+ ### Getting Started
73
90
 
74
- - [Tutorial: create a TodoList application](doc/learning/tutorial_todoapp.md)
75
- - [How to start an Owl project](doc/learning/quick_start.md)
76
- - [How to test Components](doc/learning/how_to_test.md)
91
+ - **[Playground](https://odoo.github.io/owl/playground)** — Interactive examples and live coding
92
+ - [Tutorial: Getting Started](https://odoo.github.io/owl/playground#getting_started) — Learn Owl fundamentals step by step
93
+ - [Tutorial: Todo List](https://odoo.github.io/owl/playground#todo_list) — Build a full TodoMVC app
94
+ - [Tutorial: Hibou OS](https://odoo.github.io/owl/playground#hibou_os) — Build a desktop-like interface
77
95
 
78
96
  ### Reference
79
97
 
80
- - [Overview](doc/readme.md)
81
- - [App](doc/reference/app.md)
82
- - [Component](doc/reference/component.md)
83
- - [Component Lifecycle](doc/reference/component.md#lifecycle)
84
- - [Concurrency Model](doc/reference/concurrency_model.md)
85
- - [Dev mode](doc/reference/app.md#dev-mode)
86
- - [Dynamic sub components](doc/reference/component.md#dynamic-sub-components)
87
- - [Environment](doc/reference/environment.md)
88
- - [Error Handling](doc/reference/error_handling.md)
89
- - [Event Handling](doc/reference/event_handling.md)
90
- - [Form Input Bindings](doc/reference/input_bindings.md)
91
- - [Fragments](doc/reference/templates.md#fragments)
92
- - [Hooks](doc/reference/hooks.md)
93
- - [Loading Templates](doc/reference/app.md#loading-templates)
94
- - [Mounting a component](doc/reference/app.md#mount-helper)
95
- - [Portal](doc/reference/portal.md)
96
- - [Precompiling templates](doc/reference/precompiling_templates.md)
97
- - [Props](doc/reference/props.md)
98
- - [Props Validation](doc/reference/props.md#props-validation)
99
- - [Reactivity](doc/reference/reactivity.md)
100
- - [Rendering SVG](doc/reference/templates.md#rendering-svg)
101
- - [Refs](doc/reference/refs.md)
102
- - [Slots](doc/reference/slots.md)
103
- - [Sub components](doc/reference/component.md#sub-components)
104
- - [Sub templates](doc/reference/templates.md#sub-templates)
105
- - [Templates (Qweb)](doc/reference/templates.md)
106
- - [Translations](doc/reference/translations.md)
107
- - [Utils](doc/reference/utils.md)
108
-
109
- ### Other Topics
110
-
111
- - [Notes On Owl Architecture](doc/miscellaneous/architecture.md)
98
+ - [API Reference](doc/readme.md) — A complete list of everything exported by the Owl library
99
+ - [App](doc/reference/app.md) — Configure and mount an Owl application to the DOM
100
+ - [Component](doc/reference/component.md) — Define components with lifecycle methods and static properties
101
+ - [Error Handling](doc/reference/error_handling.md) — Catch and recover from errors in components
102
+ - [Event Handling](doc/reference/event_handling.md) — Handle DOM events with t-on directives
103
+ - [Form Bindings](doc/reference/form_bindings.md) — Bind form inputs to reactive state with t-model
104
+ - [Hooks](doc/reference/hooks.md) — Use lifecycle hooks and other built-in hooks in components
105
+ - [Plugins](doc/reference/plugins.md) — Share state and services across components with type-safe plugins
106
+ - [Props](doc/reference/props.md) — Pass data to child components with validation and defaults
107
+ - [Reactivity](doc/reference/reactivity.md) — Manage state with signals, computed values, and reactive objects
108
+ - [Refs](doc/reference/refs.md) — Access DOM elements from components with t-ref
109
+ - [Resources and Registries](doc/reference/resources_and_registries.md) — Ordered reactive collections for shared data
110
+ - [Slots](doc/reference/slots.md) — Compose components with named and dynamic slot content
111
+ - [Template Syntax](doc/reference/template_syntax.md) — Write XML templates with QWeb directives
112
+ - [Translations](doc/reference/translations.md) — Translate templates and dynamic strings
113
+ - [Types Validation](doc/reference/types_validation.md) — Validate data structures at runtime with a declarative schema
114
+
115
+ ### Misc
116
+
117
+ - [Owl 3.x Release Notes](release_notes.md) — Complete guide to all changes in Owl 3
118
+ - [Design Principles](doc/miscellaneous/design_principles.md)
119
+ - [Why we built Owl](doc/miscellaneous/why_owl.md)
120
+ - [Architecture Notes](doc/miscellaneous/architecture.md)
112
121
  - [Comparison with React/Vue](doc/miscellaneous/comparison.md)
113
- - [Why did Odoo build Owl?](doc/miscellaneous/why_owl.md)
114
- - [Changelog (from owl 1.x to 2.x)](CHANGELOG.md)
115
- - [Notes on compiled templates](doc/miscellaneous/compiled_template.md)
116
- - [Owl devtools extension](doc/tools/devtools.md)
117
122
 
118
- ## Installing Owl
123
+ ## Installation
119
124
 
120
- Owl is available on `npm` and can be installed with the following command:
121
-
122
- ```
125
+ ```bash
123
126
  npm install @odoo/owl
124
127
  ```
125
- If you want to use a simple `<script>` tag, the last release can be downloaded here:
126
-
127
- - [owl](https://github.com/odoo/owl/releases/latest)
128
-
129
- ## Installing Owl devtools
130
-
131
- The Owl devtools browser extension is also available in the [release](https://github.com/odoo/owl/releases/latest):
132
- Unzip the owl-devtools.zip file and follow the instructions depending on your browser:
133
-
134
- ### Chrome
135
128
 
136
- Go to your chrome extensions admin panel, activate developer mode and click on `Load unpacked`.
137
- Select the devtools-chrome folder and that's it, your extension is active!
138
- There is a convenient refresh button on the extension card (still on the same admin page) to update your code.
139
- Do note that if you got some problems, you may need to completly remove and reload the extension to completly refresh the extension.
129
+ Or download directly: [latest release](https://github.com/odoo/owl/releases/latest)
140
130
 
141
- ### Firefox
142
- Go to the address about:debugging#/runtime/this-firefox and click on `Load temporary Add-on...`.
143
- Select any file in the devtools-firefox folder and that's it, your extension is active!
144
- Here, you can use the reload button to refresh the extension.
131
+ ## Devtools
145
132
 
146
- Note that you may have to open another window or reload your tab to see the extension working.
147
- Also note that the extension will only be active on pages that have a sufficient version of owl.
133
+ The Owl devtools extension helps debug your applications with component tree
134
+ inspection, state visualization, and performance profiling. Download it from
135
+ the [releases page](https://github.com/odoo/owl/releases/latest).
148
136
 
137
+ ## License
149
138
 
139
+ Owl is released under the [LGPL v3](https://www.gnu.org/licenses/lgpl-3.0) license.
@@ -229,11 +229,18 @@ const isRightSeparator = (token) => token && (token.type === "RIGHT_BRACE" || to
229
229
  * the arrow operator, then we add the current (or some previous tokens) token to
230
230
  * the list of variables so it does not get replaced by a lookup in the context
231
231
  */
232
- function compileExprToArray(expr) {
232
+ // Leading spaces are trimmed during tokenization, so they need to be added back for some values
233
+ const paddedValues = new Map([["in ", " in "]]);
234
+ /**
235
+ * Processes a javascript expression: compiles variable lookups and detects
236
+ * top-level arrow functions with their free variables, all in a single pass.
237
+ */
238
+ function processExpr(expr) {
233
239
  const localVars = new Set();
234
240
  const tokens = tokenize(expr);
235
241
  let i = 0;
236
242
  let stack = []; // to track last opening (, [ or {
243
+ let topLevelArrowIndex = -1;
237
244
  while (i < tokens.length) {
238
245
  let token = tokens[i];
239
246
  let prevToken = tokens[i - 1];
@@ -274,18 +281,21 @@ function compileExprToArray(expr) {
274
281
  token.value = token.replace((expr) => compileExpr(expr));
275
282
  }
276
283
  if (nextToken && nextToken.type === "OPERATOR" && nextToken.value === "=>") {
284
+ if (stack.length === 0) {
285
+ topLevelArrowIndex = i + 1;
286
+ }
277
287
  if (token.type === "RIGHT_PAREN") {
278
288
  let j = i - 1;
279
289
  while (j > 0 && tokens[j].type !== "LEFT_PAREN") {
280
290
  if (tokens[j].type === "SYMBOL" && tokens[j].originalValue) {
281
291
  tokens[j].value = tokens[j].originalValue;
282
- localVars.add(tokens[j].value); //] = { id: tokens[j].value, expr: tokens[j].value };
292
+ localVars.add(tokens[j].value);
283
293
  }
284
294
  j--;
285
295
  }
286
296
  }
287
297
  else {
288
- localVars.add(token.value); //] = { id: token.value, expr: token.value };
298
+ localVars.add(token.value);
289
299
  }
290
300
  }
291
301
  if (isVar) {
@@ -306,14 +316,24 @@ function compileExprToArray(expr) {
306
316
  token.isLocal = true;
307
317
  }
308
318
  }
309
- return tokens;
319
+ // Collect free variables from arrow function body
320
+ let freeVariables = null;
321
+ if (topLevelArrowIndex !== -1) {
322
+ freeVariables = [];
323
+ const seen = new Set();
324
+ for (let i = topLevelArrowIndex + 1; i < tokens.length; i++) {
325
+ const t = tokens[i];
326
+ if (t.varName && !t.isLocal && t.varName !== "this" && !seen.has(t.varName)) {
327
+ seen.add(t.varName);
328
+ freeVariables.push(t.varName);
329
+ }
330
+ }
331
+ }
332
+ const compiled = tokens.map((t) => paddedValues.get(t.value) || t.value).join("");
333
+ return { expr: compiled, freeVariables };
310
334
  }
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
335
  function compileExpr(expr) {
314
- return compileExprToArray(expr)
315
- .map((t) => paddedValues.get(t.value) || t.value)
316
- .join("");
336
+ return processExpr(expr).expr;
317
337
  }
318
338
  const INTERP_REGEXP = /\{\{.*?\}\}|\#\{.*?\}/g;
319
339
  function replaceDynamicParts(s, replacer) {
@@ -332,8 +352,11 @@ const zero = Symbol("zero");
332
352
  const whitespaceRE = /\s+/g;
333
353
  // using a non-html document so that <inner/outer>HTML serializes as XML instead
334
354
  // 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"]);
355
+ let xmlDoc;
356
+ if (typeof document !== "undefined") {
357
+ xmlDoc = document.implementation.createDocument(null, null, null);
358
+ }
359
+ const MODS = new Set(["stop", "capture", "prevent", "self", "synthetic", "passive"]);
337
360
  let nextDataIds = {};
338
361
  function generateId(prefix = "") {
339
362
  nextDataIds[prefix] = (nextDataIds[prefix] || 0) + 1;
@@ -693,8 +716,6 @@ class CodeGenerator {
693
716
  return this.compileTTranslation(ast, ctx);
694
717
  case 16 /* ASTType.TTranslationContext */:
695
718
  return this.compileTTranslationContext(ast, ctx);
696
- case 17 /* ASTType.TPortal */:
697
- return this.compileTPortal(ast, ctx);
698
719
  }
699
720
  }
700
721
  compileDebug(ast, ctx) {
@@ -780,7 +801,8 @@ class CodeGenerator {
780
801
  hoistedExpr = `(ctx,${bareArrowMatch[1]})=>${rest}`;
781
802
  }
782
803
  else {
783
- hoistedExpr = `(ctx, ev) => (${compiled}).call(ctx['this'], ev)`;
804
+ this.helpers.add("callHandler");
805
+ hoistedExpr = `(ctx, ev) => callHandler(${compiled}, ctx, ev)`;
784
806
  }
785
807
  const id = generateId("hdlr_fn");
786
808
  this.staticDefs.push({ id, expr: hoistedExpr });
@@ -858,11 +880,22 @@ class CodeGenerator {
858
880
  // t-model
859
881
  let tModelSelectedExpr;
860
882
  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})`);
883
+ const { hasDynamicChildren, expr, eventType, shouldNumberize, shouldTrim, targetAttr, specialInitTargetAttr, isProxy, } = ast.model;
884
+ let readExpr;
885
+ let writeExpr;
886
+ if (isProxy) {
887
+ const expression = compileExpr(expr);
888
+ readExpr = expression;
889
+ writeExpr = (value) => `${expression} = ${value}`;
890
+ }
891
+ else {
892
+ const exprId = generateId("expr");
893
+ const expression = compileExpr(expr);
894
+ this.helpers.add("modelExpr");
895
+ this.define(exprId, `modelExpr(${expression})`);
896
+ readExpr = `${exprId}()`;
897
+ writeExpr = (value) => `${exprId}.set(${value})`;
898
+ }
866
899
  let idx;
867
900
  if (specialInitTargetAttr) {
868
901
  let targetExpr = targetAttr in attrs && `'${attrs[targetAttr]}'`;
@@ -873,23 +906,23 @@ class CodeGenerator {
873
906
  targetExpr = compileExpr(dynamicTgExpr);
874
907
  }
875
908
  }
876
- idx = block.insertData(`${exprId}() === ${targetExpr}`, "prop");
909
+ idx = block.insertData(`${readExpr} === ${targetExpr}`, "prop");
877
910
  attrs[`block-property-${idx}`] = specialInitTargetAttr;
878
911
  }
879
912
  else if (hasDynamicChildren) {
880
913
  const bValueId = generateId("bValue");
881
914
  tModelSelectedExpr = `${bValueId}`;
882
- this.define(tModelSelectedExpr, `${exprId}()`);
915
+ this.define(tModelSelectedExpr, readExpr);
883
916
  }
884
917
  else {
885
- idx = block.insertData(`${exprId}()`, "prop");
918
+ idx = block.insertData(readExpr, "prop");
886
919
  attrs[`block-property-${idx}`] = targetAttr;
887
920
  }
888
921
  this.helpers.add("toNumber");
889
922
  let valueCode = `ev.target.${targetAttr}`;
890
923
  valueCode = shouldTrim ? `${valueCode}.trim()` : valueCode;
891
924
  valueCode = shouldNumberize ? `toNumber(${valueCode})` : valueCode;
892
- const handler = `[(ctx, ev) => { ${exprId}.set(${valueCode}); }, ctx]`;
925
+ const handler = `[(ctx, ev) => { ${writeExpr(valueCode)}; }, ctx]`;
893
926
  idx = block.insertData(handler, "hdlr");
894
927
  attrs[`block-handler-${idx}`] = eventType;
895
928
  }
@@ -1182,12 +1215,11 @@ class CodeGenerator {
1182
1215
  if (ast.context) {
1183
1216
  const dynCtxVar = generateId("ctx");
1184
1217
  this.addLine(`const ${dynCtxVar} = ${compileExpr(ast.context)};`);
1185
- if (ast.attrs) {
1186
- ctxExpr = `Object.assign({}, ${dynCtxVar}${attrs.length ? ", " + ctxString : ""})`;
1218
+ if (attrs.length) {
1219
+ ctxExpr = `Object.assign({this: ${dynCtxVar}}, ${ctxString})`;
1187
1220
  }
1188
1221
  else {
1189
- const thisCtx = `{this: ${dynCtxVar}, __owl__: this.__owl__}`;
1190
- ctxExpr = `Object.assign({}, ${dynCtxVar}, ${thisCtx}${attrs.length ? ", " + ctxString : ""})`;
1222
+ ctxExpr = `{this: ${dynCtxVar}}`;
1191
1223
  }
1192
1224
  }
1193
1225
  else {
@@ -1332,9 +1364,29 @@ class CodeGenerator {
1332
1364
  let { block } = ctx;
1333
1365
  // props
1334
1366
  const hasSlotsProp = "slots" in (ast.props || {});
1335
- const props = ast.props
1336
- ? this.formatPropObject(ast.props, ast.propsTranslationCtx, ctx.translationCtx)
1337
- : [];
1367
+ const props = [];
1368
+ const propList = [];
1369
+ for (let p in ast.props || {}) {
1370
+ let [name, suffix] = p.split(".");
1371
+ if (suffix) {
1372
+ // .alike, .bind, .translate — delegate to formatProp, no propList entry
1373
+ props.push(this.formatProp(p, ast.props[p], ast.propsTranslationCtx, ctx.translationCtx));
1374
+ continue;
1375
+ }
1376
+ const { expr: compiledValue, freeVariables } = processExpr(ast.props[p]);
1377
+ const propName = /^[a-z_]+$/i.test(name) ? name : `'${name}'`;
1378
+ props.push(`${propName}: ${compiledValue || undefined}`);
1379
+ if (freeVariables) {
1380
+ for (const varName of freeVariables) {
1381
+ const syntheticKey = `\x01${name}.${varName}`;
1382
+ propList.push(`"${syntheticKey}"`);
1383
+ props.push(`"${syntheticKey}": ctx['${varName}']`);
1384
+ }
1385
+ }
1386
+ else {
1387
+ propList.push(`"${name}"`);
1388
+ }
1389
+ }
1338
1390
  // slots
1339
1391
  let slotDef = "";
1340
1392
  if (ast.slots) {
@@ -1391,13 +1443,6 @@ class CodeGenerator {
1391
1443
  keyArg = `${ctx.tKeyExpr} + ${keyArg}`;
1392
1444
  }
1393
1445
  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
1446
  this.helpers.add("createComponent");
1402
1447
  this.staticDefs.push({
1403
1448
  id,
@@ -1502,26 +1547,6 @@ class CodeGenerator {
1502
1547
  }
1503
1548
  return null;
1504
1549
  }
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
- }
1525
1550
  }
1526
1551
 
1527
1552
  /**
@@ -1593,7 +1618,6 @@ function parseNode(node, ctx) {
1593
1618
  parseTDebugLog(node, ctx) ||
1594
1619
  parseTForEach(node, ctx) ||
1595
1620
  parseTIf(node, ctx) ||
1596
- parseTPortal(node, ctx) ||
1597
1621
  parseTTranslation(node, ctx) ||
1598
1622
  parseTTranslationContext(node, ctx) ||
1599
1623
  parseTCall(node, ctx) ||
@@ -1740,6 +1764,7 @@ function parseDOMNode(node, ctx) {
1740
1764
  const hasTrimMod = attr.includes(".trim");
1741
1765
  const hasLazyMod = hasTrimMod || attr.includes(".lazy");
1742
1766
  const hasNumberMod = attr.includes(".number");
1767
+ const hasProxyMod = attr.includes(".proxy");
1743
1768
  const eventType = isRadioInput ? "click" : isSelect || hasLazyMod ? "change" : "input";
1744
1769
  model = {
1745
1770
  expr: value,
@@ -1749,6 +1774,7 @@ function parseDOMNode(node, ctx) {
1749
1774
  hasDynamicChildren: false,
1750
1775
  shouldTrim: hasTrimMod,
1751
1776
  shouldNumberize: hasNumberMod,
1777
+ isProxy: hasProxyMod,
1752
1778
  };
1753
1779
  if (isSelect) {
1754
1780
  // don't pollute the original ctx
@@ -2220,28 +2246,6 @@ function parseTTranslationContext(node, ctx) {
2220
2246
  return wrapInTTranslationContextAST(result, translationCtx);
2221
2247
  }
2222
2248
  // -----------------------------------------------------------------------------
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
2249
  // helpers
2246
2250
  // -----------------------------------------------------------------------------
2247
2251
  /**
Binary file