mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
Compare commits
210 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c8f7274977 | |||
| 924dffeb36 | |||
| 0dbc807e01 | |||
| 4b88787a72 | |||
| 6f0a4b1c92 | |||
| 3f575148b6 | |||
| 4a971f2963 | |||
| d7850aaf7a | |||
| 0e13b859d0 | |||
| c813a1ce03 | |||
| 54d69c2dde | |||
| 11d4aae8d2 | |||
| b159a073d9 | |||
| d9b189bcba | |||
| 7ef1fe0b99 | |||
| 211ecdf689 | |||
| b208894d38 | |||
| 0737bb39b4 | |||
| de584b01a8 | |||
| 525029b682 | |||
| b88f7a03e9 | |||
| 055cbda750 | |||
| 45f6a8e97a | |||
| 6091912c54 | |||
| 586f90f6d1 | |||
| e62a0ca2d9 | |||
| 3a4ee160ed | |||
| 5f24caf40d | |||
| 8e36a11b6e | |||
| 9a54afcdf5 | |||
| 3eefa4b153 | |||
| 2ffb94ed98 | |||
| 6a15874d23 | |||
| 07d34248ae | |||
| dfc090bb12 | |||
| 7b3593ff8f | |||
| b06791c950 | |||
| f7843c7c00 | |||
| ca9d4ea762 | |||
| 7a407a762d | |||
| 8847824fb7 | |||
| 55b068faeb | |||
| dbee8f50d1 | |||
| f5fec02d4c | |||
| 651c53ebbf | |||
| 3119497508 | |||
| 057e6944d6 | |||
| 83d38a6f48 | |||
| 9577d70b4b | |||
| 6ff6d59895 | |||
| 7ba0c9f8ba | |||
| 79a51f2317 | |||
| 483335df6c | |||
| 287a6d5ac4 | |||
| 68684a9a45 | |||
| e5d773daa8 | |||
| 00ffab1f64 | |||
| a226f92def | |||
| 1749bce155 | |||
| c00127fa1e | |||
| 0b4b7899f6 | |||
| 5c324fa075 | |||
| 3945c009ed | |||
| 5b1132f01a | |||
| f014b1a33e | |||
| 3f09a953b0 | |||
| b8e7ee5ff7 | |||
| 7fdca35a74 | |||
| 489e20843c | |||
| 34b781aeed | |||
| e8b0f31da6 | |||
| d160c4a628 | |||
| 4542171a31 | |||
| 0b1c4dd4ef | |||
| f4da50d350 | |||
| f2921abda8 | |||
| 107200fd94 | |||
| 0f8c859d5f | |||
| cca8438d38 | |||
| a727347d60 | |||
| f19de73b0f | |||
| a8ffcafd23 | |||
| a2f01ef4ad | |||
| d7de25e867 | |||
| 424bc480e2 | |||
| 421f400766 | |||
| c37f3445f6 | |||
| 4cde06d685 | |||
| 7b95315b87 | |||
| 09761ddd8e | |||
| b3c6ec2b48 | |||
| b20462ada6 | |||
| 25000b36be | |||
| 144d4a253c | |||
| 9d48bda227 | |||
| c8db663869 | |||
| 247200194f | |||
| 60b8817ac0 | |||
| e008966291 | |||
| e2fedc6eff | |||
| b3fb9a35bf | |||
| 240259568e | |||
| 18fb67eaad | |||
| e946967867 | |||
| 777b2aae5e | |||
| 0831bb54e3 | |||
| 03f34a38dc | |||
| dcefd26bee | |||
| 922dab8e8f | |||
| 99740f9993 | |||
| 0b79b1c8fd | |||
| 5b0dce94cf | |||
| a1d435c5a5 | |||
| 13c3178760 | |||
| 2b565ce1b4 | |||
| cfcf2c6714 | |||
| 92174559a1 | |||
| 840348892f | |||
| b988a68b6f | |||
| 7869a1f2c6 | |||
| 9e81d14d50 | |||
| 0e4a55ba09 | |||
| 6e9b68dafa | |||
| ba7c9063c0 | |||
| 3a361876ad | |||
| 58b8572f0a | |||
| c23637e6d8 | |||
| 4a4b1fbba5 | |||
| 536d9e1762 | |||
| 6ed68372a6 | |||
| efd934d2b1 | |||
| dabc24cee3 | |||
| 7f49796a07 | |||
| 757dffefac | |||
| bdfe058279 | |||
| 345c44b952 | |||
| ea74739d46 | |||
| 93b53d8017 | |||
| c2284bc6f5 | |||
| 48650da62e | |||
| 2edd6cb8f3 | |||
| 4b0a37c542 | |||
| 50c0e30936 | |||
| e97335d7ab | |||
| 6ea40a66da | |||
| 2806cda420 | |||
| cb0b525f32 | |||
| 30d28670e8 | |||
| 7b761a2f8b | |||
| d834bb2ac4 | |||
| 6ae92fa4b3 | |||
| 3ceb118ace | |||
| fa426b7fc0 | |||
| d7403871fc | |||
| 6aeff21d9a | |||
| 429e145b68 | |||
| fb6aab8b70 | |||
| 9e285e1e0a | |||
| 63bfdfb8db | |||
| d4c4fe853e | |||
| 9163bb1e08 | |||
| a187a376a0 | |||
| 06ea6d2490 | |||
| 04334e23d7 | |||
| 4efe1ae166 | |||
| ad4673cb36 | |||
| e611c20ab4 | |||
| d60a5a414e | |||
| d47dcf6be2 | |||
| 3e70b17da1 | |||
| c718d8e6c6 | |||
| a122a94180 | |||
| ca139166ab | |||
| ae71db28e4 | |||
| 779fc6b02b | |||
| 84913593b2 | |||
| 8d4eb21536 | |||
| 4bdfaf96f3 | |||
| 233c953243 | |||
| ee5ad354ff | |||
| 4e4b85e2a9 | |||
| 6479631983 | |||
| 2a0c410014 | |||
| bb4aecf638 | |||
| a1adfd5a1b | |||
| dd4848f602 | |||
| b25f476c22 | |||
| 90cdb97b49 | |||
| 7d7568d254 | |||
| 06d207f60e | |||
| ff564421da | |||
| c3695ec8db | |||
| 6e0834ce5e | |||
| d1425c7100 | |||
| e01fe301c0 | |||
| ba2fe3ff55 | |||
| 700d574314 | |||
| ba32772a92 | |||
| e4a0277f68 | |||
| abe3748825 | |||
| ee64de8b3f | |||
| ed831320db | |||
| 5f1e1e189f | |||
| 55ef9ca116 | |||
| 642ecf0ccd | |||
| c9cc789f1d | |||
| b8649f1add | |||
| e2d98e4c26 | |||
| e6bb4ef286 | |||
| 7ac20f4fc2 |
@@ -5,7 +5,7 @@ name: Node.js CI
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ master ]
|
branches: [ master, owl-next ]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|||||||
+3
-14
@@ -30,32 +30,19 @@ All changes are documented here in no particular order.
|
|||||||
- breaking: `catchError` method is replaced by `onError` hook ([details](#36-catcherror-method-is-replaced-by-onerror-hook))
|
- breaking: `catchError` method is replaced by `onError` hook ([details](#36-catcherror-method-is-replaced-by-onerror-hook))
|
||||||
- breaking: Support for inline css (`css` tag and static `style`) has been removed ([details](#37-support-for-inline-css-css-tag-and-static-style-has-been-removed))
|
- breaking: Support for inline css (`css` tag and static `style`) has been removed ([details](#37-support-for-inline-css-css-tag-and-static-style-has-been-removed))
|
||||||
- new: prop validation system can now describe that additional props are allowed (with `*`) ([doc](doc/reference/props.md#props-validation))
|
- new: prop validation system can now describe that additional props are allowed (with `*`) ([doc](doc/reference/props.md#props-validation))
|
||||||
- breaking: prop validation system does not allow default prop on a mandatory (not optional) prop ([doc](doc/reference/props.md#props-validation))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**Templates**
|
**Templates**
|
||||||
|
|
||||||
- breaking: `t-foreach` should always have a corresponding `t-key` ([details](#20-t-foreach-should-always-have-a-corresponding-t-key))
|
- breaking: `t-foreach` should always have a corresponding `t-key` ([details](#20-t-foreach-should-always-have-a-corresponding-t-key))
|
||||||
- breaking: `t-ref` does not work on components ([details](#29-t-ref-does-not-work-on-component))
|
- breaking: `t-ref` does not work on components ([details](#29-t-ref-does-not-work-on-component))
|
||||||
|
- breaking: `t-on` does not accept expressions, only functions ([details](#30-t-on-does-not-accept-expressions-only-functions))
|
||||||
- breaking: `t-raw` directive has been removed (replaced by `t-out`) ([details](#38-t-raw-directive-has-been-removed-replaced-by-t-out))
|
- breaking: `t-raw` directive has been removed (replaced by `t-out`) ([details](#38-t-raw-directive-has-been-removed-replaced-by-t-out))
|
||||||
- new: add support for synthetic events ([doc](doc/reference/event_handling.md#synthetic-events))
|
- new: add support for synthetic events ([doc](doc/reference/event_handling.md#synthetic-events))
|
||||||
- breaking: style/class on components are now regular props ([details](#10-styleclass-on-components-are-now-regular-props))
|
- breaking: style/class on components are now regular props ([details](#10-styleclass-on-components-are-now-regular-props))
|
||||||
- new: components can use the `.bind` suffix to bind function props ([doc](doc/reference/props.md#binding-function-props))
|
- new: components can use the `.bind` suffix to bind function props ([doc](doc/reference/props.md#binding-function-props))
|
||||||
- breaking: `t-on` does not accept expressions, only functions ([details](#30-t-on-does-not-accept-expressions-only-functions))
|
|
||||||
- breaking: `t-on` does not work on components any more ([details](#12-t-on-does-not-work-on-components-any-more))
|
- breaking: `t-on` does not work on components any more ([details](#12-t-on-does-not-work-on-components-any-more))
|
||||||
- new: an error is thrown if an handler defined in a `t-on-` directive is not a function (failed silently previously in some cases)
|
|
||||||
- breaking: `t-component` no longer accepts strings ([details](#17-t-component-no-longer-accepts-strings))
|
- breaking: `t-component` no longer accepts strings ([details](#17-t-component-no-longer-accepts-strings))
|
||||||
- new: the `this` variable in template expressions is now bound to the component
|
|
||||||
|
|
||||||
|
|
||||||
**Reactivity**
|
|
||||||
|
|
||||||
- finer grained reactivity: owl 2 tracks change per key/component
|
|
||||||
- finer grained reactivity: sub components can reobserve state ([doc](doc/reference/reactivity.md))
|
|
||||||
- new: `reactive` function: create reactive state (without being linked to a component) ([doc](doc/reference/reactivity.md#reactive))
|
|
||||||
- new: `markRaw` function: mark an object or array so that it is ignored by the reactivity system ([doc](doc/reference/reactivity.md#markraw))
|
|
||||||
- new: `toRaw` function: given a reactive objet, return the raw (non reactive) underlying object ([doc](doc/reference/reactivity.md#toraw))
|
|
||||||
|
|
||||||
|
|
||||||
**Slots**
|
**Slots**
|
||||||
@@ -79,6 +66,8 @@ All changes are documented here in no particular order.
|
|||||||
|
|
||||||
- improved performance
|
- improved performance
|
||||||
- much simpler code
|
- much simpler code
|
||||||
|
- finer grained reactivity: owl 2 tracks change per key/component
|
||||||
|
- finer grained reactivity: sub components can reobserve state
|
||||||
- new App class to encapsulate a root Owl component (with the config for that application) ([doc](doc/reference/app.md))
|
- new App class to encapsulate a root Owl component (with the config for that application) ([doc](doc/reference/app.md))
|
||||||
- new `Memo` component
|
- new `Memo` component
|
||||||
- new `useEffect` hook ([doc](doc/reference/hooks.md#useeffect))
|
- new `useEffect` hook ([doc](doc/reference/hooks.md#useeffect))
|
||||||
|
|||||||
@@ -121,5 +121,5 @@ npm install @odoo/owl
|
|||||||
|
|
||||||
If you want to use a simple `<script>` tag, the last release can be downloaded here:
|
If you want to use a simple `<script>` tag, the last release can be downloaded here:
|
||||||
|
|
||||||
- [owl-1.4.10](https://github.com/odoo/owl/releases/tag/v1.4.10)
|
- [owl-1.4.7](https://github.com/odoo/owl/releases/tag/v1.4.7)
|
||||||
|
|
||||||
|
|||||||
+16
-21
@@ -3,7 +3,6 @@
|
|||||||
## Content
|
## Content
|
||||||
|
|
||||||
- [Overview](#overview)
|
- [Overview](#overview)
|
||||||
- [API](#api)
|
|
||||||
- [Configuration](#configuration)
|
- [Configuration](#configuration)
|
||||||
- [`mount` helper](#mount-helper)
|
- [`mount` helper](#mount-helper)
|
||||||
- [Loading templates](#loading-templates)
|
- [Loading templates](#loading-templates)
|
||||||
@@ -27,10 +26,24 @@ The basic workflow is: create an `App` instance configured with the root
|
|||||||
component, the templates, and possibly other settings. Then, we mount that
|
component, the templates, and possibly other settings. Then, we mount that
|
||||||
instance somewhere in the DOM.
|
instance somewhere in the DOM.
|
||||||
|
|
||||||
## API
|
## Configuration
|
||||||
|
|
||||||
- **`constructor(Root[, config])`**: first argument should be a component class (not
|
- **`constructor(Root[, config])`**: first argument should be a component class (not
|
||||||
an instance), and the optional second argument is a configuration object (see below).
|
an instance), and the optional second argument is a configuration object.
|
||||||
|
|
||||||
|
The `config` object is an object with some of the following keys:
|
||||||
|
|
||||||
|
- **`env (object)`**: if given, this will be the shared `env` given to each component
|
||||||
|
- **`props (object)`**: the props given to the root component
|
||||||
|
- **`dev (boolean, default=false)`**: if `true`, the application is rendered in `dev`
|
||||||
|
mode, which activates some additional checks (in particular, the props validation
|
||||||
|
code is only performed in dev mode)
|
||||||
|
- **`translatableAttributes (string[])`**: a list of additional attributes that should
|
||||||
|
be translated (see [translations](translations.md))
|
||||||
|
- **`translateFn (function)`**: a function that will be called by owl to translate
|
||||||
|
templates (see [translations](translations.md))
|
||||||
|
- **`templates (string | xml document)`**: all the templates that will be used by
|
||||||
|
the components created by the application.
|
||||||
|
|
||||||
- **`mount(target, options)`**: first argument is an html element, and the optional
|
- **`mount(target, options)`**: first argument is an html element, and the optional
|
||||||
second argument is an object with mounting options (see below). Mount the app
|
second argument is an object with mounting options (see below). Mount the app
|
||||||
@@ -45,24 +58,6 @@ instance somewhere in the DOM.
|
|||||||
|
|
||||||
- **`destroy()`**: destroys the application
|
- **`destroy()`**: destroys the application
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
The `config` object is an object with some of the following keys:
|
|
||||||
|
|
||||||
- **`env (object)`**: if given, this will be the shared `env` given to each component
|
|
||||||
- **`props (object)`**: the props given to the root component
|
|
||||||
- **`dev (boolean, default=false)`**: if `true`, the application is rendered in `dev`
|
|
||||||
mode, which activates some additional checks (in particular, the props validation
|
|
||||||
code is only performed in dev mode)
|
|
||||||
- **`test (boolean, default=false)`**: `test` mode is the same as `dev` mode, except
|
|
||||||
that Owll will not log a message to warn that Owl is in `dev` mode.
|
|
||||||
- **`translatableAttributes (string[])`**: a list of additional attributes that should
|
|
||||||
be translated (see [translations](translations.md))
|
|
||||||
- **`translateFn (function)`**: a function that will be called by owl to translate
|
|
||||||
templates (see [translations](translations.md))
|
|
||||||
- **`templates (string | xml document)`**: all the templates that will be used by
|
|
||||||
the components created by the application.
|
|
||||||
|
|
||||||
## `mount` helper
|
## `mount` helper
|
||||||
|
|
||||||
Note that there is a `mount` helper to do that in just a line:
|
Note that there is a `mount` helper to do that in just a line:
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# 🦉 Event Bus 🦉
|
||||||
|
|
||||||
|
It is sometimes useful to use a `Bus` to communicate informations between various
|
||||||
|
parts of the code. Owl has a very simple bus class, which manages subscriptions,
|
||||||
|
triggering events, and callbacks.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const bus = new owl.core.EventBus();
|
||||||
|
|
||||||
|
bus.on("some-event", null, function (...args) {
|
||||||
|
console.log(...args);
|
||||||
|
});
|
||||||
|
|
||||||
|
bus.trigger("some-event", 1, 2, 3);
|
||||||
|
// [1,2,3] will be logged to the console
|
||||||
|
```
|
||||||
|
|
||||||
|
Its API is:
|
||||||
|
|
||||||
|
| Method | Description |
|
||||||
|
| -------------------------------- | --------------------------------- |
|
||||||
|
| `on(eventType, owner, callback)` | add a listener |
|
||||||
|
| `off(eventType, owner)` | remove all listeners for an owner |
|
||||||
|
| `trigger(eventType, ...args)` | trigger an event |
|
||||||
|
| `clear` | remove all subscriptions |
|
||||||
@@ -168,15 +168,11 @@ For each key, a `prop` definition is either a boolean, a constructor, a list of
|
|||||||
- `shape`: if the type was `Object`, then the `shape` key describes the interface of the object. If it is not set, then we only validate the object, not its elements,
|
- `shape`: if the type was `Object`, then the `shape` key describes the interface of the object. If it is not set, then we only validate the object, not its elements,
|
||||||
- `validate`: this is a function which should return a boolean to determine if
|
- `validate`: this is a function which should return a boolean to determine if
|
||||||
the value is valid or not. Useful for custom validation logic.
|
the value is valid or not. Useful for custom validation logic.
|
||||||
- `optional`: if true, the prop is not mandatory
|
|
||||||
|
|
||||||
There is a special `*` prop that means that additional prop are allowed. This is
|
There is a special `*` prop that means that additional prop are allowed. This is
|
||||||
sometimes useful for generic components that will propagate some or all their
|
sometimes useful for generic components that will propagate some or all their
|
||||||
props to their child components.
|
props to their child components.
|
||||||
|
|
||||||
Note that default values cannot be defined for a mandatory props. Doing so will
|
|
||||||
result in a prop validation error.
|
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
@@ -194,8 +190,7 @@ class ComponentB extends owl.Component {
|
|||||||
element: {type: Object, shape: {id: Boolean, text: String }
|
element: {type: Object, shape: {id: Boolean, text: String }
|
||||||
},
|
},
|
||||||
date: Date,
|
date: Date,
|
||||||
combinedVal: [Number, Boolean],
|
combinedVal: [Number, Boolean]
|
||||||
optionalProp: { type: Number, optional: true }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
...
|
...
|
||||||
|
|||||||
+10
-26
@@ -87,8 +87,8 @@ grandparent of the slot content).
|
|||||||
|
|
||||||
## Default Slot
|
## Default Slot
|
||||||
|
|
||||||
All elements inside the component which are not a named slot will be treated as
|
The first element inside the component which is not a named slot will
|
||||||
part of the content of the `default` slot. For example:
|
be considered the `default` slot. For example:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<div t-name="Parent">
|
<div t-name="Parent">
|
||||||
@@ -110,7 +110,6 @@ One can mix default slot and named slots:
|
|||||||
default content
|
default content
|
||||||
<t t-set-slot="footer">
|
<t t-set-slot="footer">
|
||||||
content for footer slot here
|
content for footer slot here
|
||||||
</t>
|
|
||||||
</Child>
|
</Child>
|
||||||
</div>
|
</div>
|
||||||
```
|
```
|
||||||
@@ -210,45 +209,30 @@ use this `Notebook` component:
|
|||||||
</Notebook>
|
</Notebook>
|
||||||
```
|
```
|
||||||
|
|
||||||
Slot params works like normal props, so one can use the `.bind` suffix to
|
|
||||||
bind a function if needed.
|
|
||||||
|
|
||||||
## Slot scopes
|
## Slot scopes
|
||||||
|
|
||||||
For other kinds of advanced use cases, the content of a slot may depends on some
|
For other kind of advanced use cases, the content of a slot may depends on some
|
||||||
information specific to the generic component. This is the opposite of the slot
|
specific information specific to the generic component. This is the opposite
|
||||||
params.
|
of the slot params.
|
||||||
|
|
||||||
To solve this kind of problems, one can use the `t-slot-scope` directive along
|
To solve this kind of problems, one can use the `t-slot-scope` directive along
|
||||||
with the `t-set-slot`. This defines the name of a variable that can access
|
with the `t-set-slot`. This defines the name of a variable that can access
|
||||||
everything given by the child component:
|
everything given by the child component:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<MyComponent>
|
<div>
|
||||||
<t t-set-slot="foo" t-slot-scope="scope">
|
<t t-set-slot="foo" t-slot-scope="scope">
|
||||||
content
|
content
|
||||||
<t t-esc="scope.bool"/>
|
<t t-esc="scope.bool"/>
|
||||||
<t t-esc="scope.num"/>
|
<t t-esc="scope.num"/>
|
||||||
</t>
|
</t>
|
||||||
</MyComponent>
|
</div>
|
||||||
```
|
```
|
||||||
|
|
||||||
And the child component that includes the slot can provide values like this:
|
And the child component that includes the slot can provide values like this:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<t t-slot="foo" bool="other_var" num="5">
|
<div>
|
||||||
|
<t t-slot="foo" bool="other_var" num="5">
|
||||||
|
</div>
|
||||||
```
|
```
|
||||||
|
|
||||||
In the case of the default slot, you may declare the slot scope directly on the
|
|
||||||
component itself:
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<MyComponent t-slot-scope="scope">
|
|
||||||
content
|
|
||||||
<t t-esc="scope.bool"/>
|
|
||||||
<t t-esc="scope.num"/>
|
|
||||||
</MyComponent>
|
|
||||||
```
|
|
||||||
|
|
||||||
Slot values works like normal props, so one can use the `.bind` suffix to
|
|
||||||
bind a function if needed.
|
|
||||||
|
|||||||
@@ -54,17 +54,6 @@ For example, one may want to also translate `data-title` attributes. To do that,
|
|||||||
we can define additional attributes with the `translatableAttributes` option:
|
we can define additional attributes with the `translatableAttributes` option:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const app = new App(Root, { templates, tranaslateFn, translatableAttributes: ["data-title"] });
|
const app = new App(Root, { templates, tranaslateFn, translatalbeAttributes: ["data-title"] });
|
||||||
// ...
|
// ...
|
||||||
```
|
```
|
||||||
|
|
||||||
It is also possible to remove an attribute from the default list by prefixing it with `-`:
|
|
||||||
|
|
||||||
```js
|
|
||||||
const app = new App(Root, {
|
|
||||||
templates,
|
|
||||||
tranaslateFn,
|
|
||||||
translatableAttributes: ["data-title", "-title"],
|
|
||||||
});
|
|
||||||
// data-title attribute will be translated, but not title attribute...
|
|
||||||
```
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@odoo/owl",
|
"name": "@odoo/owl",
|
||||||
"version": "2.0.0-alpha.2",
|
"version": "2.0.0-alpha1",
|
||||||
"description": "Odoo Web Library (OWL)",
|
"description": "Odoo Web Library (OWL)",
|
||||||
"main": "dist/owl.cjs.js",
|
"main": "dist/owl.cjs.js",
|
||||||
"browser": "dist/owl.iife.js",
|
"browser": "dist/owl.iife.js",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
# 🦉 OWL Roadmap 🦉
|
# 🦉 OWL Roadmap 🦉
|
||||||
|
|
||||||
- Current version: 1.4.10
|
- Current version: 1.4.7
|
||||||
- Status: stable
|
- Status: stable
|
||||||
|
|
||||||
This roadmap is only an attempt at predicting Owl's future. Everything may
|
This roadmap is only an attempt at predicting Owl's future. Everything may
|
||||||
|
|||||||
+14
-16
@@ -4,7 +4,6 @@ import { MountOptions } from "../component/fibers";
|
|||||||
import { Scheduler } from "../component/scheduler";
|
import { Scheduler } from "../component/scheduler";
|
||||||
import { TemplateSet, TemplateSetConfig } from "./template_set";
|
import { TemplateSet, TemplateSetConfig } from "./template_set";
|
||||||
import { nodeErrorHandlers } from "../component/error_handling";
|
import { nodeErrorHandlers } from "../component/error_handling";
|
||||||
import { validateTarget } from "../utils";
|
|
||||||
|
|
||||||
// reimplement dev mode stuff see last change in 0f7a8289a6fb8387c3c1af41c6664b2a8448758f
|
// reimplement dev mode stuff see last change in 0f7a8289a6fb8387c3c1af41c6664b2a8448758f
|
||||||
|
|
||||||
@@ -15,25 +14,18 @@ export interface Env {
|
|||||||
export interface AppConfig<P, E> extends TemplateSetConfig {
|
export interface AppConfig<P, E> extends TemplateSetConfig {
|
||||||
props?: P;
|
props?: P;
|
||||||
env?: E;
|
env?: E;
|
||||||
test?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DEV_MSG = () => {
|
export const DEV_MSG = `Owl is running in 'dev' mode.
|
||||||
const hash = (window as any).owl ? (window as any).owl.__info__.hash : "master";
|
|
||||||
|
|
||||||
return `Owl is running in 'dev' mode.
|
|
||||||
|
|
||||||
This is not suitable for production use.
|
This is not suitable for production use.
|
||||||
See https://github.com/odoo/owl/blob/${hash}/doc/reference/app.md#configuration for more information.`;
|
See https://github.com/odoo/owl/blob/master/doc/reference/config.md#mode for more information.`;
|
||||||
};
|
|
||||||
|
|
||||||
export class App<
|
export class App<
|
||||||
T extends abstract new (...args: any) => any = any,
|
T extends abstract new (...args: any) => any = any,
|
||||||
P = any,
|
P = any,
|
||||||
E = any
|
E = any
|
||||||
> extends TemplateSet {
|
> extends TemplateSet {
|
||||||
static validateTarget = validateTarget;
|
|
||||||
|
|
||||||
Root: ComponentConstructor<P, E>;
|
Root: ComponentConstructor<P, E>;
|
||||||
props: P;
|
props: P;
|
||||||
env: E;
|
env: E;
|
||||||
@@ -43,11 +35,8 @@ export class App<
|
|||||||
constructor(Root: ComponentConstructor<P, E>, config: AppConfig<P, E> = {}) {
|
constructor(Root: ComponentConstructor<P, E>, config: AppConfig<P, E> = {}) {
|
||||||
super(config);
|
super(config);
|
||||||
this.Root = Root;
|
this.Root = Root;
|
||||||
if (config.test) {
|
if (config.dev) {
|
||||||
this.dev = true;
|
console.info(DEV_MSG);
|
||||||
}
|
|
||||||
if (this.dev && !config.test) {
|
|
||||||
console.info(DEV_MSG());
|
|
||||||
}
|
}
|
||||||
const descrs = Object.getOwnPropertyDescriptors(config.env || {});
|
const descrs = Object.getOwnPropertyDescriptors(config.env || {});
|
||||||
this.env = Object.freeze(Object.defineProperties({}, descrs)) as E;
|
this.env = Object.freeze(Object.defineProperties({}, descrs)) as E;
|
||||||
@@ -55,13 +44,22 @@ export class App<
|
|||||||
}
|
}
|
||||||
|
|
||||||
mount(target: HTMLElement, options?: MountOptions): Promise<Component<P, E> & InstanceType<T>> {
|
mount(target: HTMLElement, options?: MountOptions): Promise<Component<P, E> & InstanceType<T>> {
|
||||||
App.validateTarget(target);
|
this.checkTarget(target);
|
||||||
const node = this.makeNode(this.Root, this.props);
|
const node = this.makeNode(this.Root, this.props);
|
||||||
const prom = this.mountNode(node, target, options);
|
const prom = this.mountNode(node, target, options);
|
||||||
this.root = node;
|
this.root = node;
|
||||||
return prom;
|
return prom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkTarget(target: HTMLElement) {
|
||||||
|
if (!(target instanceof HTMLElement)) {
|
||||||
|
throw new Error("Cannot mount component: the target is not a valid DOM element");
|
||||||
|
}
|
||||||
|
if (!document.body.contains(target)) {
|
||||||
|
throw new Error("Cannot mount a component on a detached dom node");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
makeNode(Component: ComponentConstructor, props: any): ComponentNode {
|
makeNode(Component: ComponentConstructor, props: any): ComponentNode {
|
||||||
return new ComponentNode(Component, props, this);
|
return new ComponentNode(Component, props, this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ export function safeOutput(value: any): ReturnType<typeof toggler> {
|
|||||||
} else if (value instanceof LazyValue) {
|
} else if (value instanceof LazyValue) {
|
||||||
safeKey = `lazy_value`;
|
safeKey = `lazy_value`;
|
||||||
block = value.evaluate();
|
block = value.evaluate();
|
||||||
} else if (value instanceof String || typeof value === "string") {
|
} else if (typeof value === "string") {
|
||||||
safeKey = "string_unsafe";
|
safeKey = "string_unsafe";
|
||||||
block = text(value);
|
block = text(value);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ export function updateClass(this: HTMLElement, val: any, oldVal: any) {
|
|||||||
|
|
||||||
export function makePropSetter(name: string): Setter<HTMLElement> {
|
export function makePropSetter(name: string): Setter<HTMLElement> {
|
||||||
return function setProp(this: HTMLElement, value: any) {
|
return function setProp(this: HTMLElement, value: any) {
|
||||||
(this as any)[name] = value || "";
|
(this as any)[name] = value;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+47
-119
@@ -299,13 +299,12 @@ interface BlockCtx {
|
|||||||
locations: IndexedLocation[];
|
locations: IndexedLocation[];
|
||||||
children: Child[];
|
children: Child[];
|
||||||
cbRefs: number[];
|
cbRefs: number[];
|
||||||
refList: (() => void)[][];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildContext(tree: IntermediateTree, ctx?: BlockCtx, fromIdx?: number): BlockCtx {
|
function buildContext(tree: IntermediateTree, ctx?: BlockCtx, fromIdx?: number): BlockCtx {
|
||||||
if (!ctx) {
|
if (!ctx) {
|
||||||
const children = new Array(tree.info.filter((v) => v.type === "child").length);
|
const children = new Array(tree.info.filter((v) => v.type === "child").length);
|
||||||
ctx = { collectors: [], locations: [], children, cbRefs: [], refN: tree.refN, refList: [] };
|
ctx = { collectors: [], locations: [], children, cbRefs: [], refN: tree.refN };
|
||||||
fromIdx = 0;
|
fromIdx = 0;
|
||||||
}
|
}
|
||||||
if (tree.refN) {
|
if (tree.refN) {
|
||||||
@@ -409,11 +408,11 @@ function updateCtx(ctx: BlockCtx, tree: IntermediateTree) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "ref":
|
case "ref":
|
||||||
const index = ctx.cbRefs.push(info.idx) - 1;
|
ctx.cbRefs.push(info.idx);
|
||||||
ctx.locations.push({
|
ctx.locations.push({
|
||||||
idx: info.idx,
|
idx: info.idx,
|
||||||
refIdx: info.refIdx!,
|
refIdx: info.refIdx!,
|
||||||
setData: makeRefSetter(index, ctx.refList),
|
setData: setRef,
|
||||||
updateData: NO_OP,
|
updateData: NO_OP,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -427,21 +426,12 @@ function buildBlock(template: HTMLElement, ctx: BlockCtx): BlockType {
|
|||||||
let B = createBlockClass(template, ctx);
|
let B = createBlockClass(template, ctx);
|
||||||
|
|
||||||
if (ctx.cbRefs.length) {
|
if (ctx.cbRefs.length) {
|
||||||
const cbRefs = ctx.cbRefs;
|
const refs = ctx.cbRefs;
|
||||||
const refList = ctx.refList;
|
|
||||||
let cbRefsNumber = cbRefs.length;
|
|
||||||
B = class extends B {
|
B = class extends B {
|
||||||
mount(parent: HTMLElement, afterNode: Node | null) {
|
|
||||||
refList.push(new Array(cbRefsNumber));
|
|
||||||
super.mount(parent, afterNode);
|
|
||||||
for (let cbRef of refList.pop()!) {
|
|
||||||
cbRef();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
remove() {
|
remove() {
|
||||||
super.remove();
|
super.remove();
|
||||||
for (let cbRef of cbRefs) {
|
for (let ref of refs) {
|
||||||
let fn = (this as any).data[cbRef];
|
let fn = (this as any).data[ref];
|
||||||
fn(null);
|
fn(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -486,12 +476,12 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass {
|
|||||||
const nodeInsertBefore = nodeProto.insertBefore;
|
const nodeInsertBefore = nodeProto.insertBefore;
|
||||||
const elementRemove = elementProto.remove;
|
const elementRemove = elementProto.remove;
|
||||||
|
|
||||||
class Block {
|
return class Block {
|
||||||
el: HTMLElement | undefined;
|
el: HTMLElement | undefined;
|
||||||
parentEl?: HTMLElement | undefined;
|
|
||||||
data: any[] | undefined;
|
|
||||||
children?: (VNode | undefined)[];
|
|
||||||
refs: Node[] | undefined;
|
refs: Node[] | undefined;
|
||||||
|
data: any[] | undefined;
|
||||||
|
parentEl?: HTMLElement | undefined;
|
||||||
|
children?: (VNode | undefined)[];
|
||||||
|
|
||||||
constructor(data?: any[]) {
|
constructor(data?: any[]) {
|
||||||
this.data = data;
|
this.data = data;
|
||||||
@@ -512,110 +502,46 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass {
|
|||||||
nodeInsertBefore.call(this.parentEl, this.el!, target);
|
nodeInsertBefore.call(this.parentEl, this.el!, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
toString() {
|
|
||||||
const div = document.createElement("div");
|
|
||||||
this.mount(div, null);
|
|
||||||
return div.innerHTML;
|
|
||||||
}
|
|
||||||
|
|
||||||
mount(parent: HTMLElement, afterNode: Node | null) {
|
mount(parent: HTMLElement, afterNode: Node | null) {
|
||||||
const el = nodeCloneNode.call(template, true) as HTMLElement;
|
const el = nodeCloneNode.call(template, true);
|
||||||
nodeInsertBefore.call(parent, el, afterNode);
|
nodeInsertBefore.call(parent, el, afterNode);
|
||||||
this.el = el;
|
if (isDynamic) {
|
||||||
this.parentEl = parent;
|
// collecting references
|
||||||
}
|
const refs: Node[] = new Array(refN);
|
||||||
patch(other: Block, withBeforeRemove: boolean) {}
|
this.refs = refs;
|
||||||
|
refs[0] = el;
|
||||||
hydrate(parent: HTMLElement, el: HTMLElement) {
|
for (let i = 0; i < colN; i++) {
|
||||||
this.parentEl = parent;
|
const w = collectors[i];
|
||||||
this.el = el;
|
refs[w.idx] = w.getVal.call(refs[w.prevIdx]);
|
||||||
const refs: Node[] = new Array(refN);
|
|
||||||
this.refs = refs;
|
|
||||||
refs[0] = el;
|
|
||||||
for (let i = 0; i < colN; i++) {
|
|
||||||
const w = collectors[i];
|
|
||||||
refs[w.idx] = w.getVal.call(refs[w.prevIdx]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// applying data to all update points
|
|
||||||
if (locN) {
|
|
||||||
const data = this.data!;
|
|
||||||
for (let i = 0; i < locN; i++) {
|
|
||||||
const loc = locations[i];
|
|
||||||
loc.setData.call(refs[loc.refIdx], data[i]);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// preparing all children
|
// applying data to all update points
|
||||||
if (childN) {
|
if (locN) {
|
||||||
const children = this.children;
|
const data = this.data!;
|
||||||
for (let i = 0; i < childN; i++) {
|
for (let i = 0; i < locN; i++) {
|
||||||
const child = children![i];
|
const loc = locations[i];
|
||||||
if (child) {
|
loc.setData.call(refs[loc.refIdx], data[i]);
|
||||||
const loc = childrenLocs[i];
|
|
||||||
let target: HTMLElement;
|
|
||||||
if (loc.afterRefIdx) {
|
|
||||||
target = refs[loc.afterRefIdx] as HTMLElement;
|
|
||||||
const afterNode = document.createTextNode("");
|
|
||||||
target.parentElement!.insertBefore(afterNode, target.nextSibling);
|
|
||||||
refs[loc.afterRefIdx!] = afterNode;
|
|
||||||
} else {
|
|
||||||
target = refs[loc.parentRefIdx].firstChild! as HTMLElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
// const target = (loc.afterRefIdx ? refs[loc.afterRefIdx] : null) as HTMLElement;
|
|
||||||
// const afterNode = document.createTextNode("");
|
|
||||||
// target.parentElement!.insertBefore(afterNode, target.nextSibling);
|
|
||||||
// refs[loc.afterRefIdx!] = afterNode;
|
|
||||||
child.isOnlyChild = loc.isOnlyChild;
|
|
||||||
(child as any).hydrate(target.parentElement, target);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isDynamic) {
|
// preparing all children
|
||||||
Block.prototype.mount = function mount(parent: HTMLElement, afterNode: Node | null) {
|
if (childN) {
|
||||||
const el = nodeCloneNode.call(template, true);
|
const children = this.children;
|
||||||
// collecting references
|
for (let i = 0; i < childN; i++) {
|
||||||
const refs: Node[] = new Array(refN);
|
const child = children![i];
|
||||||
this.refs = refs;
|
if (child) {
|
||||||
refs[0] = el;
|
const loc = childrenLocs[i];
|
||||||
for (let i = 0; i < colN; i++) {
|
const afterNode = loc.afterRefIdx ? refs[loc.afterRefIdx] : null;
|
||||||
const w = collectors[i];
|
child.isOnlyChild = loc.isOnlyChild;
|
||||||
refs[w.idx] = w.getVal.call(refs[w.prevIdx]);
|
child.mount(refs[loc.parentRefIdx] as any, afterNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
// applying data to all update points
|
|
||||||
if (locN) {
|
|
||||||
const data = this.data!;
|
|
||||||
for (let i = 0; i < locN; i++) {
|
|
||||||
const loc = locations[i];
|
|
||||||
loc.setData.call(refs[loc.refIdx], data[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeInsertBefore.call(parent, el, afterNode);
|
|
||||||
|
|
||||||
// preparing all children
|
|
||||||
if (childN) {
|
|
||||||
const children = this.children;
|
|
||||||
for (let i = 0; i < childN; i++) {
|
|
||||||
const child = children![i];
|
|
||||||
if (child) {
|
|
||||||
const loc = childrenLocs[i];
|
|
||||||
const afterNode = loc.afterRefIdx ? refs[loc.afterRefIdx] : null;
|
|
||||||
child.isOnlyChild = loc.isOnlyChild;
|
|
||||||
child.mount(refs[loc.parentRefIdx] as any, afterNode);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.el = el as HTMLElement;
|
this.el = el as HTMLElement;
|
||||||
this.parentEl = parent;
|
this.parentEl = parent;
|
||||||
};
|
}
|
||||||
|
patch(other: Block, withBeforeRemove: boolean) {
|
||||||
Block.prototype.patch = function patch(other: Block, withBeforeRemove: boolean) {
|
|
||||||
if (this === other) {
|
if (this === other) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -660,17 +586,19 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
toString() {
|
||||||
return Block;
|
const div = document.createElement("div");
|
||||||
|
this.mount(div, null);
|
||||||
|
return div.innerHTML;
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function setText(this: Text, value: any) {
|
function setText(this: Text, value: any) {
|
||||||
characterDataSetData.call(this, toText(value));
|
characterDataSetData.call(this, toText(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeRefSetter(index: number, refs: (() => void)[][]): Setter<HTMLElement> {
|
function setRef(this: HTMLElement, fn: any) {
|
||||||
return function setRef(this: HTMLElement, fn: any) {
|
fn(this);
|
||||||
refs[refs.length - 1][index] = () => fn(this);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,3 @@ export function withKey(vnode: VNode, key: any) {
|
|||||||
vnode.key = key;
|
vnode.key = key;
|
||||||
return vnode;
|
return vnode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hydrate(vnode: VNode, target: HTMLElement) {
|
|
||||||
(vnode as any).hydrate(target.parentElement, target);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ const characterDataSetData = getDescriptor(characterDataProto, "data").set!;
|
|||||||
const nodeRemoveChild = nodeProto.removeChild;
|
const nodeRemoveChild = nodeProto.removeChild;
|
||||||
|
|
||||||
abstract class VSimpleNode {
|
abstract class VSimpleNode {
|
||||||
text: string | String;
|
text: string;
|
||||||
parentEl?: HTMLElement | undefined;
|
parentEl?: HTMLElement | undefined;
|
||||||
el?: any;
|
el?: any;
|
||||||
|
|
||||||
constructor(text: string | String) {
|
constructor(text: string) {
|
||||||
this.text = text;
|
this.text = text;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,12 +23,6 @@ abstract class VSimpleNode {
|
|||||||
this.el = node;
|
this.el = node;
|
||||||
}
|
}
|
||||||
|
|
||||||
hydrate(parent: HTMLElement, elem: Node) {
|
|
||||||
this.parentEl = parent;
|
|
||||||
this.el = elem;
|
|
||||||
// this.mountNode(elem, parent, elem.nextSibling);
|
|
||||||
}
|
|
||||||
|
|
||||||
moveBefore(other: VText | null, afterNode: Node | null) {
|
moveBefore(other: VText | null, afterNode: Node | null) {
|
||||||
const target = other ? other.el! : afterNode;
|
const target = other ? other.el! : afterNode;
|
||||||
nodeInsertBefore.call(this.parentEl, this.el!, target);
|
nodeInsertBefore.call(this.parentEl, this.el!, target);
|
||||||
@@ -71,7 +65,7 @@ class VComment extends VSimpleNode {
|
|||||||
patch() {}
|
patch() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function text(str: string | String): VNode<VText> {
|
export function text(str: string): VNode<VText> {
|
||||||
return new VText(str);
|
return new VText(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -214,24 +214,14 @@ export class CodeGenerator {
|
|||||||
templateName?: string;
|
templateName?: string;
|
||||||
dev: boolean;
|
dev: boolean;
|
||||||
translateFn: (s: string) => string;
|
translateFn: (s: string) => string;
|
||||||
translatableAttributes: string[] = TRANSLATABLE_ATTRS;
|
translatableAttributes: string[];
|
||||||
ast: AST;
|
ast: AST;
|
||||||
staticCalls: { id: string; template: string }[] = [];
|
staticCalls: { id: string; template: string }[] = [];
|
||||||
helpers: Set<string> = new Set();
|
helpers: Set<string> = new Set();
|
||||||
|
|
||||||
constructor(ast: AST, options: CodeGenOptions) {
|
constructor(ast: AST, options: CodeGenOptions) {
|
||||||
this.translateFn = options.translateFn || ((s: string) => s);
|
this.translateFn = options.translateFn || ((s: string) => s);
|
||||||
if (options.translatableAttributes) {
|
this.translatableAttributes = options.translatableAttributes || TRANSLATABLE_ATTRS;
|
||||||
const attrs = new Set(TRANSLATABLE_ATTRS);
|
|
||||||
for (let attr of options.translatableAttributes) {
|
|
||||||
if (attr.startsWith("-")) {
|
|
||||||
attrs.delete(attr.slice(1));
|
|
||||||
} else {
|
|
||||||
attrs.add(attr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.translatableAttributes = [...attrs];
|
|
||||||
}
|
|
||||||
this.hasSafeContext = options.hasSafeContext || false;
|
this.hasSafeContext = options.hasSafeContext || false;
|
||||||
this.dev = options.dev || false;
|
this.dev = options.dev || false;
|
||||||
this.ast = ast;
|
this.ast = ast;
|
||||||
@@ -1050,50 +1040,29 @@ export class CodeGenerator {
|
|||||||
return parts.join("__");
|
return parts.join("__");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Formats a prop name and value into a string suitable to be inserted in the
|
|
||||||
* generated code. For example:
|
|
||||||
*
|
|
||||||
* Name Value Result
|
|
||||||
* ---------------------------------------------------------
|
|
||||||
* "number" "state" "number: ctx['state']"
|
|
||||||
* "something" "" "something: undefined"
|
|
||||||
* "some-prop" "state" "'some-prop': ctx['state']"
|
|
||||||
* "onClick.bind" "onClick" "onClick: bind(ctx, ctx['onClick'])"
|
|
||||||
*/
|
|
||||||
formatProp(name: string, value: string): string {
|
|
||||||
value = this.captureExpression(value);
|
|
||||||
if (name.includes(".")) {
|
|
||||||
let [_name, suffix] = name.split(".");
|
|
||||||
if (suffix === "bind") {
|
|
||||||
this.helpers.add("bind");
|
|
||||||
name = _name;
|
|
||||||
value = `bind(ctx, ${value || undefined})`;
|
|
||||||
} else {
|
|
||||||
throw new Error("Invalid prop suffix");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
name = /^[a-z_]+$/i.test(name) ? name : `'${name}'`;
|
|
||||||
return `${name}: ${value || undefined}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
formatPropObject(obj: { [prop: string]: any }): string {
|
|
||||||
const params = [];
|
|
||||||
for (const [n, v] of Object.entries(obj)) {
|
|
||||||
params.push(this.formatProp(n, v));
|
|
||||||
}
|
|
||||||
return params.join(", ");
|
|
||||||
}
|
|
||||||
|
|
||||||
compileComponent(ast: ASTComponent, ctx: Context) {
|
compileComponent(ast: ASTComponent, ctx: Context) {
|
||||||
let { block } = ctx;
|
let { block } = ctx;
|
||||||
|
|
||||||
// props
|
// props
|
||||||
const hasSlotsProp = "slots" in ast.props;
|
|
||||||
const props: string[] = [];
|
const props: string[] = [];
|
||||||
const propExpr = this.formatPropObject(ast.props);
|
let hasSlotsProp = false;
|
||||||
if (propExpr) {
|
for (let propName in ast.props) {
|
||||||
props.push(propExpr);
|
let propValue = this.captureExpression(ast.props[propName]) || undefined;
|
||||||
|
if (propName.includes(".")) {
|
||||||
|
let [name, suffix] = propName.split(".");
|
||||||
|
if (suffix === "bind") {
|
||||||
|
this.helpers.add("bind");
|
||||||
|
propName = name;
|
||||||
|
propValue = `bind(ctx, ${propValue})`;
|
||||||
|
} else {
|
||||||
|
throw new Error("Invalid prop suffix");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
propName = /^[a-z_]+$/i.test(propName) ? propName : `'${propName}'`;
|
||||||
|
props.push(`${propName}: ${propValue}`);
|
||||||
|
if (propName === "slots") {
|
||||||
|
hasSlotsProp = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// slots
|
// slots
|
||||||
@@ -1116,7 +1085,9 @@ export class CodeGenerator {
|
|||||||
params.push(`__scope: "${scope}"`);
|
params.push(`__scope: "${scope}"`);
|
||||||
}
|
}
|
||||||
if (ast.slots[slotName].attrs) {
|
if (ast.slots[slotName].attrs) {
|
||||||
params.push(this.formatPropObject(ast.slots[slotName].attrs!));
|
for (const [n, v] of Object.entries(ast.slots[slotName].attrs!)) {
|
||||||
|
params.push(`${n}: ${compileExpr(v) || undefined}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const slotInfo = `{${params.join(", ")}}`;
|
const slotInfo = `{${params.join(", ")}}`;
|
||||||
slotStr.push(`'${slotName}': ${slotInfo}`);
|
slotStr.push(`'${slotName}': ${slotInfo}`);
|
||||||
@@ -1195,7 +1166,15 @@ export class CodeGenerator {
|
|||||||
slotName = "'" + ast.name + "'";
|
slotName = "'" + ast.name + "'";
|
||||||
}
|
}
|
||||||
|
|
||||||
const scope = ast.attrs ? `{${this.formatPropObject(ast.attrs)}}` : null;
|
let scope = null;
|
||||||
|
if (ast.attrs) {
|
||||||
|
const params = [];
|
||||||
|
for (const [n, v] of Object.entries(ast.attrs!)) {
|
||||||
|
params.push(`${n}: ${compileExpr(v) || undefined}`);
|
||||||
|
}
|
||||||
|
scope = `{${params.join(", ")}}`;
|
||||||
|
}
|
||||||
|
|
||||||
if (ast.defaultContent) {
|
if (ast.defaultContent) {
|
||||||
const name = this.compileInNewTarget("defaultContent", ast.defaultContent, ctx);
|
const name = this.compileInNewTarget("defaultContent", ast.defaultContent, ctx);
|
||||||
blockString = `callSlot(ctx, node, key, ${slotName}, ${dynamic}, ${scope}, ${name})`;
|
blockString = `callSlot(ctx, node, key, ${slotName}, ${dynamic}, ${scope}, ${name})`;
|
||||||
|
|||||||
@@ -677,9 +677,6 @@ function parseComponent(node: Element, ctx: ParsingContext): AST | null {
|
|||||||
const dynamicProps = node.getAttribute("t-props");
|
const dynamicProps = node.getAttribute("t-props");
|
||||||
node.removeAttribute("t-props");
|
node.removeAttribute("t-props");
|
||||||
|
|
||||||
const defaultSlotScope = node.getAttribute("t-slot-scope");
|
|
||||||
node.removeAttribute("t-slot-scope");
|
|
||||||
|
|
||||||
const props: ASTComponent["props"] = {};
|
const props: ASTComponent["props"] = {};
|
||||||
for (let name of node.getAttributeNames()) {
|
for (let name of node.getAttributeNames()) {
|
||||||
const value = node.getAttribute(name)!;
|
const value = node.getAttribute(name)!;
|
||||||
@@ -745,9 +742,6 @@ function parseComponent(node: Element, ctx: ParsingContext): AST | null {
|
|||||||
const defaultContent = parseChildNodes(clone, ctx);
|
const defaultContent = parseChildNodes(clone, ctx);
|
||||||
if (defaultContent) {
|
if (defaultContent) {
|
||||||
slots.default = { content: defaultContent };
|
slots.default = { content: defaultContent };
|
||||||
if (defaultSlotScope) {
|
|
||||||
slots.default.scope = defaultSlotScope;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { type: ASTType.TComponent, name, isDynamic, dynamicProps, props, slots };
|
return { type: ASTType.TComponent, name, isDynamic, dynamicProps, props, slots };
|
||||||
|
|||||||
@@ -15,10 +15,7 @@ import { STATUS } from "./status";
|
|||||||
|
|
||||||
let currentNode: ComponentNode | null = null;
|
let currentNode: ComponentNode | null = null;
|
||||||
|
|
||||||
export function getCurrent(): ComponentNode {
|
export function getCurrent(): ComponentNode | null {
|
||||||
if (!currentNode) {
|
|
||||||
throw new Error("No active component (a hook function should only be called in 'setup')");
|
|
||||||
}
|
|
||||||
return currentNode;
|
return currentNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,7 +108,6 @@ export class ComponentNode<P = any, E = any> implements VNode<ComponentNode<P, E
|
|||||||
this.component = new C(props, env, this) as any;
|
this.component = new C(props, env, this) as any;
|
||||||
this.renderFn = app.getTemplate(C.template).bind(this.component, this.component, this);
|
this.renderFn = app.getTemplate(C.template).bind(this.component, this.component, this);
|
||||||
this.component.setup();
|
this.component.setup();
|
||||||
currentNode = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mountComponent(target: any, options?: MountOptions) {
|
mountComponent(target: any, options?: MountOptions) {
|
||||||
@@ -272,15 +268,6 @@ export class ComponentNode<P = any, E = any> implements VNode<ComponentNode<P, E
|
|||||||
this.fiber = null;
|
this.fiber = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
hydrate(parent: HTMLElement, el: HTMLElement) {
|
|
||||||
const bdom = this.fiber!.bdom!;
|
|
||||||
this.bdom = bdom;
|
|
||||||
(bdom as any).hydrate(parent, el);
|
|
||||||
this.status = STATUS.MOUNTED;
|
|
||||||
this.fiber!.appliedToDom = true;
|
|
||||||
this.fiber = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
moveBefore(other: ComponentNode | null, afterNode: Node | null) {
|
moveBefore(other: ComponentNode | null, afterNode: Node | null) {
|
||||||
this.bdom!.moveBefore(other ? other.bdom : null, afterNode);
|
this.bdom!.moveBefore(other ? other.bdom : null, afterNode);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { BDom, hydrate, mount } from "../blockdom";
|
import { BDom, mount } from "../blockdom";
|
||||||
import type { ComponentNode } from "./component_node";
|
import type { ComponentNode } from "./component_node";
|
||||||
import { fibersInError, handleError } from "./error_handling";
|
import { fibersInError, handleError } from "./error_handling";
|
||||||
import { STATUS } from "./status";
|
import { STATUS } from "./status";
|
||||||
@@ -144,25 +144,21 @@ type Position = "first-child" | "last-child";
|
|||||||
|
|
||||||
export interface MountOptions {
|
export interface MountOptions {
|
||||||
position?: Position;
|
position?: Position;
|
||||||
hydrate?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MountFiber extends RootFiber {
|
export class MountFiber extends RootFiber {
|
||||||
target: HTMLElement;
|
target: HTMLElement;
|
||||||
position: Position;
|
position: Position;
|
||||||
hydrate?: boolean;
|
|
||||||
|
|
||||||
constructor(node: ComponentNode, target: HTMLElement, options: MountOptions = {}) {
|
constructor(node: ComponentNode, target: HTMLElement, options: MountOptions = {}) {
|
||||||
super(node, null);
|
super(node, null);
|
||||||
this.target = target;
|
this.target = target;
|
||||||
this.hydrate = options.hydrate;
|
|
||||||
this.position = options.position || "last-child";
|
this.position = options.position || "last-child";
|
||||||
}
|
}
|
||||||
complete() {
|
complete() {
|
||||||
let current: Fiber | undefined = this;
|
let current: Fiber | undefined = this;
|
||||||
try {
|
try {
|
||||||
const node = this.node;
|
const node = this.node;
|
||||||
(node.app.constructor as any).validateTarget(this.target);
|
|
||||||
if (node.bdom) {
|
if (node.bdom) {
|
||||||
// this is a complicated situation: if we mount a fiber with an existing
|
// this is a complicated situation: if we mount a fiber with an existing
|
||||||
// bdom, this means that this same fiber was already completed, mounted,
|
// bdom, this means that this same fiber was already completed, mounted,
|
||||||
@@ -171,9 +167,7 @@ export class MountFiber extends RootFiber {
|
|||||||
node.updateDom();
|
node.updateDom();
|
||||||
} else {
|
} else {
|
||||||
node.bdom = this.bdom;
|
node.bdom = this.bdom;
|
||||||
if (this.hydrate) {
|
if (this.position === "last-child" || this.target.childNodes.length === 0) {
|
||||||
hydrate(node.bdom!, this.target);
|
|
||||||
} else if (this.position === "last-child" || this.target.childNodes.length === 0) {
|
|
||||||
mount(node.bdom!, this.target);
|
mount(node.bdom!, this.target);
|
||||||
} else {
|
} else {
|
||||||
const firstChild = this.target.childNodes[0];
|
const firstChild = this.target.childNodes[0];
|
||||||
|
|||||||
@@ -1,102 +1,67 @@
|
|||||||
import { getCurrent } from "./component_node";
|
import { getCurrent } from "./component_node";
|
||||||
import { nodeErrorHandlers } from "./error_handling";
|
import { nodeErrorHandlers } from "./error_handling";
|
||||||
|
|
||||||
function wrapError(fn: (...args: any[]) => any, hookName: string) {
|
|
||||||
const error = new Error(`The following error occurred in ${hookName}: `) as Error & {
|
|
||||||
cause: any;
|
|
||||||
};
|
|
||||||
return (...args: any[]) => {
|
|
||||||
try {
|
|
||||||
const result = fn(...args);
|
|
||||||
if (result instanceof Promise) {
|
|
||||||
return result.catch((cause) => {
|
|
||||||
error.cause = cause;
|
|
||||||
if (cause instanceof Error) {
|
|
||||||
error.message += `"${cause.message}"`;
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
} catch (cause) {
|
|
||||||
if (cause instanceof Error) {
|
|
||||||
error.message += `"${cause.message}"`;
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// hooks
|
// hooks
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
export function onWillStart(fn: () => Promise<void> | void | any) {
|
export function onWillStart(fn: () => Promise<void> | void | any) {
|
||||||
const node = getCurrent();
|
const node = getCurrent()!;
|
||||||
const decorate = node.app.dev ? wrapError : (fn: any) => fn;
|
node.willStart.push(fn.bind(node.component));
|
||||||
node.willStart.push(decorate(fn.bind(node.component), "onWillStart"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function onWillUpdateProps(fn: (nextProps: any) => Promise<void> | void | any) {
|
export function onWillUpdateProps(fn: (nextProps: any) => Promise<void> | void | any) {
|
||||||
const node = getCurrent();
|
const node = getCurrent()!;
|
||||||
const decorate = node.app.dev ? wrapError : (fn: any) => fn;
|
node.willUpdateProps.push(fn.bind(node.component));
|
||||||
node.willUpdateProps.push(decorate(fn.bind(node.component), "onWillUpdateProps"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function onMounted(fn: () => void | any) {
|
export function onMounted(fn: () => void | any) {
|
||||||
const node = getCurrent();
|
const node = getCurrent()!;
|
||||||
const decorate = node.app.dev ? wrapError : (fn: any) => fn;
|
node.mounted.push(fn.bind(node.component));
|
||||||
node.mounted.push(decorate(fn.bind(node.component), "onMounted"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function onWillPatch(fn: () => Promise<void> | any | void) {
|
export function onWillPatch(fn: () => Promise<void> | any | void) {
|
||||||
const node = getCurrent();
|
const node = getCurrent()!;
|
||||||
const decorate = node.app.dev ? wrapError : (fn: any) => fn;
|
node.willPatch.unshift(fn.bind(node.component));
|
||||||
node.willPatch.unshift(decorate(fn.bind(node.component), "onWillPatch"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function onPatched(fn: () => void | any) {
|
export function onPatched(fn: () => void | any) {
|
||||||
const node = getCurrent();
|
const node = getCurrent()!;
|
||||||
const decorate = node.app.dev ? wrapError : (fn: any) => fn;
|
node.patched.push(fn.bind(node.component));
|
||||||
node.patched.push(decorate(fn.bind(node.component), "onPatched"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function onWillUnmount(fn: () => Promise<void> | void | any) {
|
export function onWillUnmount(fn: () => Promise<void> | void | any) {
|
||||||
const node = getCurrent();
|
const node = getCurrent()!;
|
||||||
const decorate = node.app.dev ? wrapError : (fn: any) => fn;
|
node.willUnmount.unshift(fn.bind(node.component));
|
||||||
node.willUnmount.unshift(decorate(fn.bind(node.component), "onWillUnmount"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function onWillDestroy(fn: () => Promise<void> | void | any) {
|
export function onWillDestroy(fn: () => Promise<void> | void | any) {
|
||||||
const node = getCurrent();
|
const node = getCurrent()!;
|
||||||
const decorate = node.app.dev ? wrapError : (fn: any) => fn;
|
node.willDestroy.push(fn.bind(node.component));
|
||||||
node.willDestroy.push(decorate(fn.bind(node.component), "onWillDestroy"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function onWillRender(fn: () => void | any) {
|
export function onWillRender(fn: () => void | any) {
|
||||||
const node = getCurrent();
|
const node = getCurrent()!;
|
||||||
const renderFn = node.renderFn;
|
const renderFn = node.renderFn;
|
||||||
const decorate = node.app.dev ? wrapError : (fn: any) => fn;
|
node.renderFn = () => {
|
||||||
node.renderFn = decorate(() => {
|
|
||||||
fn.call(node.component);
|
fn.call(node.component);
|
||||||
return renderFn();
|
return renderFn();
|
||||||
}, "onWillRender");
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function onRendered(fn: () => void | any) {
|
export function onRendered(fn: () => void | any) {
|
||||||
const node = getCurrent();
|
const node = getCurrent()!;
|
||||||
const renderFn = node.renderFn;
|
const renderFn = node.renderFn;
|
||||||
const decorate = node.app.dev ? wrapError : (fn: any) => fn;
|
node.renderFn = () => {
|
||||||
node.renderFn = decorate(() => {
|
|
||||||
const result = renderFn();
|
const result = renderFn();
|
||||||
fn.call(node.component);
|
fn.call(node.component);
|
||||||
return result;
|
return result;
|
||||||
}, "onRendered");
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
type OnErrorCallback = (error: any) => void | any;
|
type OnErrorCallback = (error: any) => void | any;
|
||||||
export function onError(callback: OnErrorCallback) {
|
export function onError(callback: OnErrorCallback) {
|
||||||
const node = getCurrent();
|
const node = getCurrent()!;
|
||||||
let handlers = nodeErrorHandlers.get(node);
|
let handlers = nodeErrorHandlers.get(node);
|
||||||
if (!handlers) {
|
if (!handlers) {
|
||||||
handlers = [];
|
handlers = [];
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ export function validateProps<P>(name: string | ComponentConstructor<P>, props:
|
|||||||
}
|
}
|
||||||
applyDefaultProps(props, ComponentClass);
|
applyDefaultProps(props, ComponentClass);
|
||||||
|
|
||||||
const defaultProps = ComponentClass.defaultProps || {};
|
|
||||||
let propsDef = getPropDescription(ComponentClass.props);
|
let propsDef = getPropDescription(ComponentClass.props);
|
||||||
const allowAdditionalProps = "*" in propsDef;
|
const allowAdditionalProps = "*" in propsDef;
|
||||||
|
|
||||||
@@ -55,18 +54,8 @@ export function validateProps<P>(name: string | ComponentConstructor<P>, props:
|
|||||||
if (propName === "*") {
|
if (propName === "*") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const propDef = propsDef[propName];
|
|
||||||
let isMandatory = !!propDef;
|
|
||||||
if (typeof propDef === "object" && "optional" in propDef) {
|
|
||||||
isMandatory = !propDef.optional;
|
|
||||||
}
|
|
||||||
if (isMandatory && propName in defaultProps) {
|
|
||||||
throw new Error(
|
|
||||||
`A default value cannot be defined for a mandatory prop (name: '${propName}', component: ${ComponentClass.name})`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if ((props as any)[propName] === undefined) {
|
if ((props as any)[propName] === undefined) {
|
||||||
if (isMandatory) {
|
if (propsDef[propName] && !propsDef[propName].optional) {
|
||||||
throw new Error(`Missing props '${propName}' (component '${ComponentClass.name}')`);
|
throw new Error(`Missing props '${propName}' (component '${ComponentClass.name}')`);
|
||||||
} else {
|
} else {
|
||||||
continue;
|
continue;
|
||||||
@@ -74,7 +63,7 @@ export function validateProps<P>(name: string | ComponentConstructor<P>, props:
|
|||||||
}
|
}
|
||||||
let isValid;
|
let isValid;
|
||||||
try {
|
try {
|
||||||
isValid = isValidProp((props as any)[propName], propDef);
|
isValid = isValidProp((props as any)[propName], propsDef[propName]);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
(e as Error).message = `Invalid prop '${propName}' in component ${ComponentClass.name} (${
|
(e as Error).message = `Invalid prop '${propName}' in component ${ComponentClass.name} (${
|
||||||
(e as Error).message
|
(e as Error).message
|
||||||
|
|||||||
+7
-6
@@ -11,7 +11,7 @@ import { onMounted, onPatched, onWillUnmount } from "./component/lifecycle_hooks
|
|||||||
* html node or component.
|
* html node or component.
|
||||||
*/
|
*/
|
||||||
export function useRef<T extends HTMLElement = HTMLElement>(name: string): { el: T | null } {
|
export function useRef<T extends HTMLElement = HTMLElement>(name: string): { el: T | null } {
|
||||||
const node = getCurrent();
|
const node = getCurrent()!;
|
||||||
const refs = node.refs;
|
const refs = node.refs;
|
||||||
return {
|
return {
|
||||||
get el(): T | null {
|
get el(): T | null {
|
||||||
@@ -29,7 +29,7 @@ export function useRef<T extends HTMLElement = HTMLElement>(name: string): { el:
|
|||||||
* need a reference to the env of the component calling them.
|
* need a reference to the env of the component calling them.
|
||||||
*/
|
*/
|
||||||
export function useEnv<E extends Env>(): E {
|
export function useEnv<E extends Env>(): E {
|
||||||
return getCurrent().component.env as any;
|
return getCurrent()!.component.env as any;
|
||||||
}
|
}
|
||||||
|
|
||||||
function extendEnv(currentEnv: Object, extension: Object): Object {
|
function extendEnv(currentEnv: Object, extension: Object): Object {
|
||||||
@@ -44,13 +44,14 @@ function extendEnv(currentEnv: Object, extension: Object): Object {
|
|||||||
* constructor method.
|
* constructor method.
|
||||||
*/
|
*/
|
||||||
export function useSubEnv(envExtension: Env) {
|
export function useSubEnv(envExtension: Env) {
|
||||||
const node = getCurrent();
|
const node = getCurrent()!;
|
||||||
|
const newEnv = extendEnv(node.component.env as any, envExtension);
|
||||||
node.component.env = extendEnv(node.component.env as any, envExtension);
|
node.component.env = extendEnv(node.component.env as any, envExtension);
|
||||||
useChildSubEnv(envExtension);
|
node.childEnv = newEnv;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useChildSubEnv(envExtension: Env) {
|
export function useChildSubEnv(envExtension: Env) {
|
||||||
const node = getCurrent();
|
const node = getCurrent()!;
|
||||||
node.childEnv = extendEnv(node.childEnv, envExtension);
|
node.childEnv = extendEnv(node.childEnv, envExtension);
|
||||||
}
|
}
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
@@ -121,7 +122,7 @@ export function useExternalListener(
|
|||||||
handler: EventListener,
|
handler: EventListener,
|
||||||
eventParams?: AddEventListenerOptions
|
eventParams?: AddEventListenerOptions
|
||||||
) {
|
) {
|
||||||
const node = getCurrent();
|
const node = getCurrent()!;
|
||||||
const boundHandler = handler.bind(node.component);
|
const boundHandler = handler.bind(node.component);
|
||||||
onMounted(() => target.addEventListener(eventName, boundHandler, eventParams));
|
onMounted(() => target.addEventListener(eventName, boundHandler, eventParams));
|
||||||
onWillUnmount(() => target.removeEventListener(eventName, boundHandler, eventParams));
|
onWillUnmount(() => target.removeEventListener(eventName, boundHandler, eventParams));
|
||||||
|
|||||||
+1
-1
@@ -44,7 +44,7 @@ export { Memo } from "./memo";
|
|||||||
export { xml } from "./app/template_set";
|
export { xml } from "./app/template_set";
|
||||||
export { useState, reactive, markRaw, toRaw } from "./reactivity";
|
export { useState, reactive, markRaw, toRaw } from "./reactivity";
|
||||||
export { useEffect, useEnv, useExternalListener, useRef, useChildSubEnv, useSubEnv } from "./hooks";
|
export { useEffect, useEnv, useExternalListener, useRef, useChildSubEnv, useSubEnv } from "./hooks";
|
||||||
export { EventBus, whenReady, loadFile, markup } from "./utils";
|
export { batched, EventBus, whenReady, loadFile, markup } from "./utils";
|
||||||
export {
|
export {
|
||||||
onWillStart,
|
onWillStart,
|
||||||
onMounted,
|
onMounted,
|
||||||
|
|||||||
+1
-1
@@ -239,7 +239,7 @@ const batchedRenderFunctions = new WeakMap<ComponentNode, Callback>();
|
|||||||
* @see reactive
|
* @see reactive
|
||||||
*/
|
*/
|
||||||
export function useState<T extends object>(state: T): Reactive<T> | NonReactive<T> {
|
export function useState<T extends object>(state: T): Reactive<T> | NonReactive<T> {
|
||||||
const node = getCurrent();
|
const node = getCurrent()!;
|
||||||
if (!batchedRenderFunctions.has(node)) {
|
if (!batchedRenderFunctions.has(node)) {
|
||||||
batchedRenderFunctions.set(
|
batchedRenderFunctions.set(
|
||||||
node,
|
node,
|
||||||
|
|||||||
@@ -24,15 +24,6 @@ export function batched(callback: Callback): Callback {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function validateTarget(target: HTMLElement) {
|
|
||||||
if (!(target instanceof HTMLElement)) {
|
|
||||||
throw new Error("Cannot mount component: the target is not a valid DOM element");
|
|
||||||
}
|
|
||||||
if (!document.body.contains(target)) {
|
|
||||||
throw new Error("Cannot mount a component on a detached dom node");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class EventBus extends EventTarget {
|
export class EventBus extends EventTarget {
|
||||||
trigger(name: string, payload?: any) {
|
trigger(name: string, payload?: any) {
|
||||||
this.dispatchEvent(new CustomEvent(name, { detail: payload }));
|
this.dispatchEvent(new CustomEvent(name, { detail: payload }));
|
||||||
|
|||||||
@@ -169,15 +169,6 @@ describe("properties", () => {
|
|||||||
expect(input.value).toBe("potato");
|
expect(input.value).toBe("potato");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("input with value attribute, and undefined given", () => {
|
|
||||||
const block = createBlock(`<input block-attribute-0="value"/>`);
|
|
||||||
|
|
||||||
const tree = block([undefined]);
|
|
||||||
mount(tree, fixture);
|
|
||||||
const input = fixture.querySelector("input")!;
|
|
||||||
expect(input.value).toBe("");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("input type=checkbox with checked attribute", () => {
|
test("input type=checkbox with checked attribute", () => {
|
||||||
// render input with initial value
|
// render input with initial value
|
||||||
const block = createBlock(`<input type="checkbox" block-attribute-0="checked"/>`);
|
const block = createBlock(`<input type="checkbox" block-attribute-0="checked"/>`);
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { createBlock, mount, patch, remove } from "../../src/blockdom";
|
import { createBlock, mount, patch, remove } from "../../src/blockdom";
|
||||||
import { logStep } from "../helpers";
|
|
||||||
import { makeTestFixture } from "./helpers";
|
import { makeTestFixture } from "./helpers";
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -56,15 +55,3 @@ test("is in dom when callback is called", async () => {
|
|||||||
|
|
||||||
mount(tree, fixture);
|
mount(tree, fixture);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("callback ref in callback ref with same block", async () => {
|
|
||||||
const block = createBlock('<p block-ref="0"><block-text-1/><block-child-0/></p>');
|
|
||||||
let refFn = (el: HTMLParagraphElement) => logStep(el.outerHTML);
|
|
||||||
|
|
||||||
const child = block([refFn, "child"], []);
|
|
||||||
const parent = block([refFn, "parent"], [child]);
|
|
||||||
mount(parent, fixture);
|
|
||||||
|
|
||||||
expect(fixture.innerHTML).toBe("<p>parent<p>child</p></p>");
|
|
||||||
expect(["<p>child</p>", "<p>parent<p>child</p></p>"]).toBeLogged();
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
import { hydrate, patch, text, createBlock } from "../../src/blockdom";
|
|
||||||
import { makeTestFixture } from "./helpers";
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// Setup and helpers
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
let fixture: HTMLElement;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
fixture = makeTestFixture();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
fixture.remove();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("hydration", () => {
|
|
||||||
test("simple text node", async () => {
|
|
||||||
fixture.innerHTML = "some text";
|
|
||||||
const target = fixture.firstChild as any;
|
|
||||||
|
|
||||||
const tree = text("some text");
|
|
||||||
expect(tree.el).toBe(undefined);
|
|
||||||
hydrate(tree, target);
|
|
||||||
expect(fixture.innerHTML).toBe("some text");
|
|
||||||
expect(tree.el).toBe(target);
|
|
||||||
|
|
||||||
patch(tree, text("checkmate"));
|
|
||||||
expect(fixture.innerHTML).toBe("checkmate");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("simple static block", async () => {
|
|
||||||
fixture.innerHTML = "<div>some text</div>";
|
|
||||||
const target = fixture.firstChild as any;
|
|
||||||
|
|
||||||
const block = createBlock("<div>some text</div>");
|
|
||||||
|
|
||||||
const tree = block();
|
|
||||||
expect(tree.el).toBe(undefined);
|
|
||||||
hydrate(tree, target);
|
|
||||||
expect(fixture.innerHTML).toBe("<div>some text</div>");
|
|
||||||
expect(tree.el).toBe(target);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("simple dynamic block", async () => {
|
|
||||||
fixture.innerHTML = "<div>some text</div>";
|
|
||||||
const target = fixture.firstChild as any;
|
|
||||||
|
|
||||||
const block = createBlock("<div><block-text-0/></div>");
|
|
||||||
|
|
||||||
const tree = block(["some text"]);
|
|
||||||
expect(tree.el).toBe(undefined);
|
|
||||||
hydrate(tree, target);
|
|
||||||
expect(fixture.innerHTML).toBe("<div>some text</div>");
|
|
||||||
expect(tree.el).toBe(target);
|
|
||||||
|
|
||||||
patch(tree, block(["giuoco piano"]));
|
|
||||||
expect(fixture.innerHTML).toBe("<div>giuoco piano</div>");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("block with sub block", async () => {
|
|
||||||
fixture.innerHTML = "<div>queen<p>gambit</p></div>";
|
|
||||||
const target = fixture.firstChild as any;
|
|
||||||
|
|
||||||
const block1 = createBlock("<div><block-text-0/><block-child-0/></div>");
|
|
||||||
const block2 = createBlock("<p><block-text-0/></p>");
|
|
||||||
|
|
||||||
const tree = block1(["queen"], [block2(["gambit"])]);
|
|
||||||
expect(tree.el).toBe(undefined);
|
|
||||||
hydrate(tree, target);
|
|
||||||
expect(fixture.innerHTML).toBe("<div>queen<p>gambit</p></div>");
|
|
||||||
expect(tree.el).toBe(target);
|
|
||||||
|
|
||||||
patch(tree, block1(["king"], [block2(["pawn"])]));
|
|
||||||
expect(fixture.innerHTML).toBe("<div>king<p>pawn</p></div>");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -34,7 +34,7 @@ exports[`misc complex template 1`] = `
|
|||||||
for (let i1 = 0; i1 < l_block4; i1++) {
|
for (let i1 = 0; i1 < l_block4; i1++) {
|
||||||
ctx[\`slot\`] = v_block4[i1];
|
ctx[\`slot\`] = v_block4[i1];
|
||||||
let key1 = ctx['slot'].id;
|
let key1 = ctx['slot'].id;
|
||||||
c_block4[i1] = withKey(component(\`SlotButton\`, {class: ctx['slot_container'], slot: ctx['slot']}, key + \`__1__\${key1}\`, node, ctx), key1);
|
c_block4[i1] = withKey(component(\`SlotButton\`, {class: ctx['slot_container'],slot: ctx['slot']}, key + \`__1__\${key1}\`, node, ctx), key1);
|
||||||
}
|
}
|
||||||
ctx = ctx.__proto__;
|
ctx = ctx.__proto__;
|
||||||
b4 = list(c_block4);
|
b4 = list(c_block4);
|
||||||
@@ -316,8 +316,8 @@ exports[`misc other complex template 1`] = `
|
|||||||
if (!ctx['project']) {
|
if (!ctx['project']) {
|
||||||
b24 = block24();
|
b24 = block24();
|
||||||
} else {
|
} else {
|
||||||
let b26 = component(\`BundlesList\`, {bundles: ctx['bundles'].sticky, category_custom_views: ctx['category_custom_views'], search: ctx['search']}, key + \`__2\`, node, ctx);
|
let b26 = component(\`BundlesList\`, {bundles: ctx['bundles'].sticky,category_custom_views: ctx['category_custom_views'],search: ctx['search']}, key + \`__2\`, node, ctx);
|
||||||
let b27 = component(\`BundlesList\`, {bundles: ctx['bundles'].dev, search: ctx['search']}, key + \`__3\`, node, ctx);
|
let b27 = component(\`BundlesList\`, {bundles: ctx['bundles'].dev,search: ctx['search']}, key + \`__3\`, node, ctx);
|
||||||
b25 = block25([], [b26, b27]);
|
b25 = block25([], [b26, b27]);
|
||||||
}
|
}
|
||||||
return block1([attr1, txt1, hdlr2, hdlr3, attr8, hdlr4, hdlr5, ref1, hdlr6, ref2], [b2, b4, b14, b17, b22, b23, b24, b25]);
|
return block1([attr1, txt1, hdlr2, hdlr3, attr8, hdlr4, hdlr5, ref1, hdlr6, ref2], [b2, b4, b14, b17, b22, b23, b24, b25]);
|
||||||
|
|||||||
@@ -407,36 +407,6 @@ exports[`t-out variable 1`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`t-out with a String class 1`] = `
|
|
||||||
"function anonymous(bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
|
||||||
let { safeOutput } = helpers;
|
|
||||||
|
|
||||||
let block1 = createBlock(\`<span><block-child-0/></span>\`);
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
let b2 = safeOutput(ctx['var']);
|
|
||||||
return block1([], [b2]);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`t-out with an extended String class 1`] = `
|
|
||||||
"function anonymous(bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
|
||||||
let { safeOutput } = helpers;
|
|
||||||
|
|
||||||
let block1 = createBlock(\`<span><block-child-0/></span>\`);
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
let b2 = safeOutput(ctx['var']);
|
|
||||||
return block1([], [b2]);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`t-raw is deprecated should warn 1`] = `
|
exports[`t-raw is deprecated should warn 1`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`translation support can set and remove translatable attributes 1`] = `
|
exports[`translation support can set translatable attributes 1`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
let block1 = createBlock(\`<div tomato=\\"word\\" potato=\\"mot\\" title=\\"mot\\" label=\\"word\\">text</div>\`);
|
let block1 = createBlock(\`<div tomato=\\"word\\" potato=\\"mot\\" title=\\"word\\">text</div>\`);
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
return block1();
|
return block1();
|
||||||
|
|||||||
@@ -37,26 +37,6 @@ describe("t-out", () => {
|
|||||||
expect(renderToString(template, { var: "ok" })).toBe("<span>ok</span>");
|
expect(renderToString(template, { var: "ok" })).toBe("<span>ok</span>");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("with a String class", () => {
|
|
||||||
const template = `<span><t t-out="var"/></span>`;
|
|
||||||
expect(renderToString(template, { var: new String("ok") })).toBe("<span>ok</span>");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("with an extended String class", () => {
|
|
||||||
class LoveString extends String {
|
|
||||||
valueOf(): string {
|
|
||||||
return `<3 ${super.valueOf()} <3`;
|
|
||||||
}
|
|
||||||
toString(): string {
|
|
||||||
return this.valueOf();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const template = `<span><t t-out="var"/></span>`;
|
|
||||||
expect(renderToString(template, { var: new LoveString("ok") })).toBe(
|
|
||||||
"<span><3 ok <3</span>"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("not escaping", () => {
|
test("not escaping", () => {
|
||||||
const template = `<div><t t-out="var"/></div>`;
|
const template = `<div><t t-out="var"/></div>`;
|
||||||
expect(renderToString(template, { var: markup("<ok></ok>") })).toBe("<div><ok></ok></div>");
|
expect(renderToString(template, { var: markup("<ok></ok>") })).toBe("<div><ok></ok></div>");
|
||||||
|
|||||||
@@ -59,20 +59,18 @@ describe("translation support", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("can set and remove translatable attributes", async () => {
|
test("can set translatable attributes", async () => {
|
||||||
class SomeComponent extends Component {
|
class SomeComponent extends Component {
|
||||||
static template = xml`
|
static template = xml`
|
||||||
<div tomato="word" potato="word" title="word" label="word">text</div>
|
<div tomato="word" potato="word" title="word">text</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
await mount(SomeComponent, fixture, {
|
await mount(SomeComponent, fixture, {
|
||||||
translateFn: (expr: string) => (expr === "word" ? "mot" : expr),
|
translateFn: (expr: string) => (expr === "word" ? "mot" : expr),
|
||||||
translatableAttributes: ["potato", "-label"],
|
translatableAttributes: ["potato"],
|
||||||
});
|
});
|
||||||
expect(fixture.innerHTML).toBe(
|
expect(fixture.innerHTML).toBe('<div tomato="word" potato="mot" title="word">text</div>');
|
||||||
'<div tomato="word" potato="mot" title="mot" label="word">text</div>'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("translation is done on the trimmed text, with extra spaces readded after", async () => {
|
test("translation is done on the trimmed text, with extra spaces readded after", async () => {
|
||||||
|
|||||||
@@ -81,19 +81,6 @@ exports[`basics a class component inside a class component, no external dom 2`]
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`basics a component cannot be mounted in a detached node (even if node is detached later) 1`] = `
|
|
||||||
"function anonymous(bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
|
||||||
|
|
||||||
let block1 = createBlock(\`<div/>\`);
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
return block1();
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`basics a component inside a component 1`] = `
|
exports[`basics a component inside a component 1`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
@@ -994,7 +981,7 @@ exports[`basics update props of component without concrete own node 2`] = `
|
|||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
const tKey_1 = ctx['props'].subKey;
|
const tKey_1 = ctx['props'].subKey;
|
||||||
return toggler(tKey_1, component(\`Custom\`, {key: ctx['props'].key, subKey: ctx['props'].subKey}, tKey_1 + key + \`__1\`, node, ctx));
|
return toggler(tKey_1, component(\`Custom\`, {key: ctx['props'].key,subKey: ctx['props'].subKey}, tKey_1 + key + \`__1\`, node, ctx));
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ exports[`concurrent renderings scenario 1 2`] = `
|
|||||||
let block1 = createBlock(\`<p><block-child-0/></p>\`);
|
let block1 = createBlock(\`<p><block-child-0/></p>\`);
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2 = component(\`ComponentC\`, {fromA: ctx['props'].fromA, fromB: ctx['state'].fromB}, key + \`__1\`, node, ctx);
|
let b2 = component(\`ComponentC\`, {fromA: ctx['props'].fromA,fromB: ctx['state'].fromB}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -227,7 +227,7 @@ exports[`concurrent renderings scenario 2 2`] = `
|
|||||||
let block1 = createBlock(\`<p><block-child-0/></p>\`);
|
let block1 = createBlock(\`<p><block-child-0/></p>\`);
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2 = component(\`ComponentC\`, {fromA: ctx['props'].fromA, fromB: ctx['state'].fromB}, key + \`__1\`, node, ctx);
|
let b2 = component(\`ComponentC\`, {fromA: ctx['props'].fromA,fromB: ctx['state'].fromB}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -270,7 +270,7 @@ exports[`concurrent renderings scenario 2bis 2`] = `
|
|||||||
let block1 = createBlock(\`<p><block-child-0/></p>\`);
|
let block1 = createBlock(\`<p><block-child-0/></p>\`);
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2 = component(\`ComponentC\`, {fromA: ctx['props'].fromA, fromB: ctx['state'].fromB}, key + \`__1\`, node, ctx);
|
let b2 = component(\`ComponentC\`, {fromA: ctx['props'].fromA,fromB: ctx['state'].fromB}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -327,7 +327,7 @@ exports[`concurrent renderings scenario 3 3`] = `
|
|||||||
let block1 = createBlock(\`<span><block-child-0/></span>\`);
|
let block1 = createBlock(\`<span><block-child-0/></span>\`);
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2 = component(\`ComponentD\`, {fromA: ctx['props'].fromA, fromC: ctx['state'].fromC}, key + \`__1\`, node, ctx);
|
let b2 = component(\`ComponentD\`, {fromA: ctx['props'].fromA,fromC: ctx['state'].fromC}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -384,7 +384,7 @@ exports[`concurrent renderings scenario 4 3`] = `
|
|||||||
let block1 = createBlock(\`<span><block-child-0/></span>\`);
|
let block1 = createBlock(\`<span><block-child-0/></span>\`);
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2 = component(\`ComponentD\`, {fromA: ctx['props'].fromA, fromC: ctx['state'].fromC}, key + \`__1\`, node, ctx);
|
let b2 = component(\`ComponentD\`, {fromA: ctx['props'].fromA,fromC: ctx['state'].fromC}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -557,7 +557,7 @@ exports[`concurrent renderings scenario 9 3`] = `
|
|||||||
let block1 = createBlock(\`<p><block-child-0/></p>\`);
|
let block1 = createBlock(\`<p><block-child-0/></p>\`);
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2 = component(\`ComponentD\`, {fromA: ctx['props'].fromA, fromC: ctx['state'].fromC}, key + \`__1\`, node, ctx);
|
let b2 = component(\`ComponentD\`, {fromA: ctx['props'].fromA,fromC: ctx['state'].fromC}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -734,7 +734,7 @@ exports[`concurrent renderings scenario 14 2`] = `
|
|||||||
let block1 = createBlock(\`<p><block-child-0/></p>\`);
|
let block1 = createBlock(\`<p><block-child-0/></p>\`);
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2 = component(\`C\`, {fromB: ctx['state'].fromB, fromA: ctx['props'].fromA}, key + \`__1\`, node, ctx);
|
let b2 = component(\`C\`, {fromB: ctx['state'].fromB,fromA: ctx['props'].fromA}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -778,7 +778,7 @@ exports[`concurrent renderings scenario 15 2`] = `
|
|||||||
let block1 = createBlock(\`<p><block-child-0/></p>\`);
|
let block1 = createBlock(\`<p><block-child-0/></p>\`);
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2 = component(\`C\`, {fromB: ctx['state'].fromB, fromA: ctx['props'].fromA}, key + \`__1\`, node, ctx);
|
let b2 = component(\`C\`, {fromB: ctx['state'].fromB,fromA: ctx['props'].fromA}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -817,7 +817,7 @@ exports[`concurrent renderings scenario 16 2`] = `
|
|||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
return component(\`C\`, {fromB: ctx['state'].fromB, fromA: ctx['props'].fromA}, key + \`__1\`, node, ctx);
|
return component(\`C\`, {fromB: ctx['state'].fromB,fromA: ctx['props'].fromA}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -85,17 +85,6 @@ exports[`basics simple catchError 2`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`can catch errors calling a hook outside setup should crash 1`] = `
|
|
||||||
"function anonymous(bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
return text(ctx['state'].value);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`can catch errors can catch an error in a component render function 1`] = `
|
exports[`can catch errors can catch an error in a component render function 1`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
@@ -1092,19 +1081,6 @@ exports[`errors and promises an error in mounted call will reject the mount prom
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`errors and promises an error in onMounted callback will have the component's setup in its stack trace 1`] = `
|
|
||||||
"function anonymous(bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
|
||||||
|
|
||||||
let block1 = createBlock(\`<div>abc</div>\`);
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
return block1();
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`errors and promises an error in patched call will reject the render promise 1`] = `
|
exports[`errors and promises an error in patched call will reject the render promise 1`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -103,37 +103,6 @@ exports[`hooks mounted callbacks should be called in reverse order from willUnmo
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`hooks parent and child env (with useChildSubEnv then useSubEnv) 1`] = `
|
|
||||||
"function anonymous(bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
let b2 = text(ctx['env'].val);
|
|
||||||
let b3 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
|
||||||
return multi([b2, b3]);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`hooks parent and child env (with useChildSubEnv then useSubEnv) 2`] = `
|
|
||||||
"function anonymous(bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
|
||||||
|
|
||||||
let block2 = createBlock(\`<div><block-text-0/></div>\`);
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
let b2;
|
|
||||||
if (ctx['env'].hasParent) {
|
|
||||||
let txt1 = ctx['env'].val;
|
|
||||||
b2 = block2([txt1]);
|
|
||||||
}
|
|
||||||
return multi([b2]);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`hooks parent and child env (with useChildSubEnv) 1`] = `
|
exports[`hooks parent and child env (with useChildSubEnv) 1`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`hydration can hydrate a component with a handler 1`] = `
|
|
||||||
"function anonymous(bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
|
||||||
|
|
||||||
let block1 = createBlock(\`<div block-handler-0=\\"click\\"><block-text-1/></div>\`);
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
let hdlr1 = [ctx['inc'], ctx];
|
|
||||||
let txt1 = ctx['state'].value;
|
|
||||||
return block1([hdlr1, txt1]);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`hydration can hydrate a component with a sub component 1`] = `
|
|
||||||
"function anonymous(bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
|
||||||
|
|
||||||
let block1 = createBlock(\`<p><block-child-0/></p>\`);
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
let b2 = component(\`Counter\`, {}, key + \`__1\`, node, ctx);
|
|
||||||
return block1([], [b2]);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`hydration can hydrate a component with a sub component 2`] = `
|
|
||||||
"function anonymous(bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
|
||||||
|
|
||||||
let block1 = createBlock(\`<button block-handler-0=\\"click\\"><block-text-1/></button>\`);
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
let hdlr1 = [ctx['inc'], ctx];
|
|
||||||
let txt1 = ctx['state'].value;
|
|
||||||
return block1([hdlr1, txt1]);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`hydration can hydrate a simple static component 1`] = `
|
|
||||||
"function anonymous(bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
|
||||||
|
|
||||||
let block1 = createBlock(\`<div>giuoco pianissimo</div>\`);
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
return block1();
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
@@ -273,7 +273,7 @@ exports[`bound functions is referentially equal after update 1`] = `
|
|||||||
let { bind } = helpers;
|
let { bind } = helpers;
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
return component(\`Child\`, {val: ctx['state'].val, fn: bind(ctx, ctx['someFunction'])}, key + \`__1\`, node, ctx);
|
return component(\`Child\`, {val: ctx['state'].val,fn: bind(ctx, ctx['someFunction'])}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -1,19 +1,6 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`default props a default prop cannot be defined on a mandatory prop 1`] = `
|
exports[`default props can set default required boolean values 1`] = `
|
||||||
"function anonymous(bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
const props1 = {};
|
|
||||||
helpers.validateProps(\`Child\`, props1, ctx);
|
|
||||||
return component(\`Child\`, props1, key + \`__1\`, node, ctx);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`default props can set default boolean values 1`] = `
|
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
@@ -29,7 +16,7 @@ exports[`default props can set default boolean values 1`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`default props can set default boolean values 2`] = `
|
exports[`default props can set default required boolean values 2`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
@@ -115,7 +102,7 @@ exports[`props validation can specify that additional props are allowed (array)
|
|||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
const props1 = {message: 'm', otherProp: 'o'};
|
const props1 = {message: 'm',otherProp: 'o'};
|
||||||
helpers.validateProps(\`Child\`, props1, ctx);
|
helpers.validateProps(\`Child\`, props1, ctx);
|
||||||
return component(\`Child\`, props1, key + \`__1\`, node, ctx);
|
return component(\`Child\`, props1, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
@@ -141,7 +128,7 @@ exports[`props validation can specify that additional props are allowed (object)
|
|||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
const props1 = {message: 'm', otherProp: 'o'};
|
const props1 = {message: 'm',otherProp: 'o'};
|
||||||
helpers.validateProps(\`Child\`, props1, ctx);
|
helpers.validateProps(\`Child\`, props1, ctx);
|
||||||
return component(\`Child\`, props1, key + \`__1\`, node, ctx);
|
return component(\`Child\`, props1, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,26 +38,6 @@ exports[`refs can use 2 refs with same name in a t-if/t-else situation 1`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`refs refs and recursive templates 1`] = `
|
|
||||||
"function anonymous(bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
|
||||||
|
|
||||||
let block1 = createBlock(\`<p block-ref=\\"0\\"><block-text-1/><block-child-0/></p>\`);
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
const refs = ctx.__owl__.refs;
|
|
||||||
const ref1 = (el) => refs[\`root\`] = el;
|
|
||||||
let b2;
|
|
||||||
let txt1 = ctx['props'].tree.value;
|
|
||||||
if (ctx['props'].tree.child) {
|
|
||||||
b2 = component(\`Test\`, {tree: ctx['props'].tree.child}, key + \`__1\`, node, ctx);
|
|
||||||
}
|
|
||||||
return block1([ref1, txt1], [b2]);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`refs refs are properly bound in slots 1`] = `
|
exports[`refs refs are properly bound in slots 1`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -75,37 +75,6 @@ exports[`slots can define and call slots 2`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`slots can define and call slots with bound params 1`] = `
|
|
||||||
"function anonymous(bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
|
||||||
let { capture, bind } = helpers;
|
|
||||||
|
|
||||||
function slot1(ctx, node, key = \\"\\") {
|
|
||||||
return text(\`abc\`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
const ctx1 = capture(ctx);
|
|
||||||
return component(\`Child\`, {slots: {'abc': {__render: slot1, __ctx: ctx1, getValue: bind(ctx, ctx['getValue'])}}}, key + \`__1\`, node, ctx);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`slots can define and call slots with bound params 2`] = `
|
|
||||||
"function anonymous(bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
|
||||||
let { callSlot } = helpers;
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
let b2 = callSlot(ctx, node, key, 'abc', false, {});
|
|
||||||
let b3 = text(ctx['props'].slots['abc'].getValue());
|
|
||||||
return multi([b2, b3]);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`slots can define and call slots with params 1`] = `
|
exports[`slots can define and call slots with params 1`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
@@ -455,69 +424,6 @@ exports[`slots default slot next to named slot, with default content 2`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`slots default slot with params with - in it 1`] = `
|
|
||||||
"function anonymous(bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
|
||||||
|
|
||||||
function slot1(ctx, node, key = \\"\\") {
|
|
||||||
return text(ctx['slotScope']['some-value']);
|
|
||||||
}
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
return component(\`Child\`, {slots: {'default': {__render: slot1, __ctx: ctx, __scope: \\"slotScope\\"}}}, key + \`__1\`, node, ctx);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`slots default slot with params with - in it 2`] = `
|
|
||||||
"function anonymous(bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
|
||||||
let { callSlot } = helpers;
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
return callSlot(ctx, node, key, 'default', false, {'some-value': ctx['state'].value});
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`slots default slot with slot scope: shorthand syntax 1`] = `
|
|
||||||
"function anonymous(bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
|
||||||
|
|
||||||
function slot1(ctx, node, key = \\"\\") {
|
|
||||||
let b2,b3;
|
|
||||||
if (ctx['slotScope'].bool) {
|
|
||||||
b2 = text(\`some text\`);
|
|
||||||
} else {
|
|
||||||
b3 = text(\`other text\`);
|
|
||||||
}
|
|
||||||
return multi([b2, b3]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
return component(\`Child\`, {slots: {'default': {__render: slot1, __ctx: ctx, __scope: \\"slotScope\\"}}}, key + \`__1\`, node, ctx);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`slots default slot with slot scope: shorthand syntax 2`] = `
|
|
||||||
"function anonymous(bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
|
||||||
let { callSlot } = helpers;
|
|
||||||
|
|
||||||
let block1 = createBlock(\`<span><block-child-0/></span>\`);
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
let b2 = callSlot(ctx, node, key, 'default', false, {bool: ctx['state'].bool});
|
|
||||||
return block1([], [b2]);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`slots default slot work with text nodes (variation) 1`] = `
|
exports[`slots default slot work with text nodes (variation) 1`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
@@ -1233,6 +1139,7 @@ exports[`slots simple default slot with params 1`] = `
|
|||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { capture } = helpers;
|
||||||
|
|
||||||
function slot1(ctx, node, key = \\"\\") {
|
function slot1(ctx, node, key = \\"\\") {
|
||||||
let b2,b3;
|
let b2,b3;
|
||||||
@@ -1245,7 +1152,8 @@ exports[`slots simple default slot with params 1`] = `
|
|||||||
}
|
}
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
return component(\`Child\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__1\`, node, ctx);
|
const ctx1 = capture(ctx);
|
||||||
|
return component(\`Child\`, {slots: {'default': {__render: slot1, __ctx: ctx1, __scope: \\"slotScope\\"}}}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -1265,29 +1173,38 @@ exports[`slots simple default slot with params 2`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`slots simple default slot with params and bound function 1`] = `
|
exports[`slots simple default slot with params 3`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
function slot1(ctx, node, key = \\"\\") {
|
function slot1(ctx, node, key = \\"\\") {
|
||||||
return text(ctx['slotScope'].fn());
|
let b2,b3;
|
||||||
|
if (ctx['slotScope'].bool) {
|
||||||
|
b2 = text(\`some text\`);
|
||||||
|
} else {
|
||||||
|
b3 = text(\`other text\`);
|
||||||
|
}
|
||||||
|
return multi([b2, b3]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
return component(\`Child\`, {slots: {'default': {__render: slot1, __ctx: ctx, __scope: \\"slotScope\\"}}}, key + \`__1\`, node, ctx);
|
return component(\`Child\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`slots simple default slot with params and bound function 2`] = `
|
exports[`slots simple default slot with params 4`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
let { callSlot, bind } = helpers;
|
let { callSlot } = helpers;
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<span><block-child-0/></span>\`);
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
return callSlot(ctx, node, key, 'default', false, {fn: bind(ctx, ctx['getValue'])});
|
let b2 = callSlot(ctx, node, key, 'default', false, {bool: ctx['state'].bool});
|
||||||
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -1319,44 +1236,6 @@ exports[`slots simple default slot, variation 2`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`slots simple slot with slot scope 1`] = `
|
|
||||||
"function anonymous(bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
|
||||||
let { capture } = helpers;
|
|
||||||
|
|
||||||
function slot1(ctx, node, key = \\"\\") {
|
|
||||||
let b2,b3;
|
|
||||||
if (ctx['slotScope'].bool) {
|
|
||||||
b2 = text(\`some text\`);
|
|
||||||
} else {
|
|
||||||
b3 = text(\`other text\`);
|
|
||||||
}
|
|
||||||
return multi([b2, b3]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
const ctx1 = capture(ctx);
|
|
||||||
return component(\`Child\`, {slots: {'slotName': {__render: slot1, __ctx: ctx1, __scope: \\"slotScope\\"}}}, key + \`__1\`, node, ctx);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`slots simple slot with slot scope 2`] = `
|
|
||||||
"function anonymous(bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
|
||||||
let { callSlot } = helpers;
|
|
||||||
|
|
||||||
let block1 = createBlock(\`<span><block-child-0/></span>\`);
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
let b2 = callSlot(ctx, node, key, 'slotName', false, {bool: ctx['state'].bool});
|
|
||||||
return block1([], [b2]);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`slots slot and (inline) t-call 1`] = `
|
exports[`slots slot and (inline) t-call 1`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ exports[`style and class handling class on sub component, which is switched to a
|
|||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
return component(\`Child\`, {class: 'someclass', child: ctx['state'].child}, key + \`__1\`, node, ctx);
|
return component(\`Child\`, {class: 'someclass',child: ctx['state'].child}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ exports[`list of components reconciliation alg works for t-foreach in t-foreach,
|
|||||||
for (let i2 = 0; i2 < l_block4; i2++) {
|
for (let i2 = 0; i2 < l_block4; i2++) {
|
||||||
ctx[\`col\`] = v_block4[i2];
|
ctx[\`col\`] = v_block4[i2];
|
||||||
let key2 = ctx['col'];
|
let key2 = ctx['col'];
|
||||||
let b6 = component(\`Child\`, {row: ctx['row'], col: ctx['col']}, key + \`__1__\${key1}__\${key2}\`, node, ctx);
|
let b6 = component(\`Child\`, {row: ctx['row'],col: ctx['col']}, key + \`__1__\${key1}__\${key2}\`, node, ctx);
|
||||||
c_block4[i2] = withKey(block5([], [b6]), key2);
|
c_block4[i2] = withKey(block5([], [b6]), key2);
|
||||||
}
|
}
|
||||||
ctx = ctx.__proto__;
|
ctx = ctx.__proto__;
|
||||||
|
|||||||
@@ -410,24 +410,6 @@ exports[`t-model directive on an textarea 1`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`t-model directive t-model on an input with an undefined value 1`] = `
|
|
||||||
"function anonymous(bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
|
||||||
let { toNumber } = helpers;
|
|
||||||
|
|
||||||
let block1 = createBlock(\`<input block-attribute-0=\\"value\\" block-handler-1=\\"input\\"/>\`);
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
const bExpr1 = ctx['state'];
|
|
||||||
const expr1 = 'text';
|
|
||||||
let attr1 = bExpr1[expr1];
|
|
||||||
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
|
|
||||||
return block1([attr1, hdlr1]);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`t-model directive t-model on select with static options 1`] = `
|
exports[`t-model directive t-model on select with static options 1`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ exports[`t-props t-props with props 1`] = `
|
|||||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2 = component(\`Child\`, Object.assign({}, ctx['childProps'], {a: 1, b: 2}), key + \`__1\`, node, ctx);
|
let b2 = component(\`Child\`, Object.assign({}, ctx['childProps'], {a: 1,b: 2}), key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
|
|||||||
@@ -171,27 +171,6 @@ describe("basics", () => {
|
|||||||
expect(error!.message).toBe("Cannot mount a component on a detached dom node");
|
expect(error!.message).toBe("Cannot mount a component on a detached dom node");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("a component cannot be mounted in a detached node (even if node is detached later)", async () => {
|
|
||||||
const warn = console.warn;
|
|
||||||
console.warn = jest.fn();
|
|
||||||
class Test extends Component {
|
|
||||||
static template = xml`<div/>`;
|
|
||||||
}
|
|
||||||
let error: Error;
|
|
||||||
const prom = mount(Test, fixture);
|
|
||||||
await Promise.resolve();
|
|
||||||
fixture.remove();
|
|
||||||
try {
|
|
||||||
await prom;
|
|
||||||
} catch (e) {
|
|
||||||
error = e as Error;
|
|
||||||
}
|
|
||||||
expect(error!).toBeDefined();
|
|
||||||
expect(error!.message).toBe("Cannot mount a component on a detached dom node");
|
|
||||||
expect(console.warn).toBeCalledTimes(1);
|
|
||||||
console.warn = warn;
|
|
||||||
});
|
|
||||||
|
|
||||||
test("crashes if it cannot find a template", async () => {
|
test("crashes if it cannot find a template", async () => {
|
||||||
class Test extends Component {
|
class Test extends Component {
|
||||||
static template = "wrongtemplate";
|
static template = "wrongtemplate";
|
||||||
|
|||||||
@@ -82,6 +82,8 @@ describe("basics", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("display a nice error if it cannot find component (in dev mode)", async () => {
|
test("display a nice error if it cannot find component (in dev mode)", async () => {
|
||||||
|
const info = console.info;
|
||||||
|
console.info = jest.fn(() => {}); // dev mode message
|
||||||
class SomeComponent extends Component {}
|
class SomeComponent extends Component {}
|
||||||
class Parent extends Component {
|
class Parent extends Component {
|
||||||
static template = xml`<SomeMispelledComponent />`;
|
static template = xml`<SomeMispelledComponent />`;
|
||||||
@@ -89,7 +91,7 @@ describe("basics", () => {
|
|||||||
}
|
}
|
||||||
let error: Error;
|
let error: Error;
|
||||||
try {
|
try {
|
||||||
await mount(Parent, fixture, { test: true });
|
await mount(Parent, fixture, { dev: true });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = e as Error;
|
error = e as Error;
|
||||||
}
|
}
|
||||||
@@ -98,6 +100,8 @@ describe("basics", () => {
|
|||||||
expect(console.error).toBeCalledTimes(0);
|
expect(console.error).toBeCalledTimes(0);
|
||||||
expect(mockConsoleError).toBeCalledTimes(0);
|
expect(mockConsoleError).toBeCalledTimes(0);
|
||||||
expect(mockConsoleWarn).toBeCalledTimes(1);
|
expect(mockConsoleWarn).toBeCalledTimes(1);
|
||||||
|
expect(console.info).toBeCalledTimes(1);
|
||||||
|
console.info = info;
|
||||||
});
|
});
|
||||||
|
|
||||||
test("simple catchError", async () => {
|
test("simple catchError", async () => {
|
||||||
@@ -175,30 +179,6 @@ describe("errors and promises", () => {
|
|||||||
expect(mockConsoleWarn).toBeCalledTimes(1);
|
expect(mockConsoleWarn).toBeCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("an error in onMounted callback will have the component's setup in its stack trace", async () => {
|
|
||||||
class App extends Component {
|
|
||||||
static template = xml`<div>abc</div>`;
|
|
||||||
setup() {
|
|
||||||
onMounted(() => {
|
|
||||||
throw new Error("boom");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let error: Error;
|
|
||||||
try {
|
|
||||||
await mount(App, fixture, { test: true });
|
|
||||||
} catch (e) {
|
|
||||||
error = e as Error;
|
|
||||||
}
|
|
||||||
expect(error!).toBeDefined();
|
|
||||||
expect(error!.stack).toContain("App.setup");
|
|
||||||
expect(error!.stack).toContain("error_handling.test.ts");
|
|
||||||
expect(fixture.innerHTML).toBe("");
|
|
||||||
expect(mockConsoleError).toBeCalledTimes(0);
|
|
||||||
expect(mockConsoleWarn).toBeCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("an error in willPatch call will reject the render promise", async () => {
|
test("an error in willPatch call will reject the render promise", async () => {
|
||||||
class Root extends Component {
|
class Root extends Component {
|
||||||
static template = xml`<div><t t-esc="val"/></div>`;
|
static template = xml`<div><t t-esc="val"/></div>`;
|
||||||
@@ -211,13 +191,13 @@ describe("errors and promises", () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const root = await mount(Root, fixture, { test: true });
|
const root = await mount(Root, fixture);
|
||||||
root.val = 4;
|
root.val = 4;
|
||||||
let error: Error;
|
let error: Error;
|
||||||
root.render();
|
root.render();
|
||||||
await nextTick();
|
await nextTick();
|
||||||
expect(error!).toBeDefined();
|
expect(error!).toBeDefined();
|
||||||
expect(error!.message).toBe(`The following error occurred in onWillPatch: "boom"`);
|
expect(error!.message).toBe("boom");
|
||||||
expect(mockConsoleError).toBeCalledTimes(0);
|
expect(mockConsoleError).toBeCalledTimes(0);
|
||||||
expect(mockConsoleWarn).toBeCalledTimes(0);
|
expect(mockConsoleWarn).toBeCalledTimes(0);
|
||||||
});
|
});
|
||||||
@@ -234,13 +214,13 @@ describe("errors and promises", () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const root = await mount(Root, fixture, { test: true });
|
const root = await mount(Root, fixture);
|
||||||
root.val = 4;
|
root.val = 4;
|
||||||
let error: Error;
|
let error: Error;
|
||||||
root.render();
|
root.render();
|
||||||
await nextTick();
|
await nextTick();
|
||||||
expect(error!).toBeDefined();
|
expect(error!).toBeDefined();
|
||||||
expect(error!.message).toBe(`The following error occurred in onPatched: "boom"`);
|
expect(error!.message).toBe("boom");
|
||||||
expect(mockConsoleError).toBeCalledTimes(0);
|
expect(mockConsoleError).toBeCalledTimes(0);
|
||||||
expect(mockConsoleWarn).toBeCalledTimes(0);
|
expect(mockConsoleWarn).toBeCalledTimes(0);
|
||||||
});
|
});
|
||||||
@@ -316,6 +296,7 @@ describe("errors and promises", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("errors in mounted and in willUnmount", async () => {
|
test("errors in mounted and in willUnmount", async () => {
|
||||||
|
expect.assertions(4);
|
||||||
class Example extends Component {
|
class Example extends Component {
|
||||||
static template = xml`<div/>`;
|
static template = xml`<div/>`;
|
||||||
val: any;
|
val: any;
|
||||||
@@ -332,11 +313,9 @@ describe("errors and promises", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await mount(Example, fixture, { test: true });
|
await mount(Example, fixture);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
expect((e as Error).message).toBe(
|
expect((e as Error).message).toBe("Error in mounted");
|
||||||
`The following error occurred in onMounted: "Error in mounted"`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
// 1 additional error is logged because the destruction of the app causes
|
// 1 additional error is logged because the destruction of the app causes
|
||||||
// the onWillUnmount hook to be called and to fail
|
// the onWillUnmount hook to be called and to fail
|
||||||
@@ -395,28 +374,6 @@ describe("can catch errors", () => {
|
|||||||
expect(mockConsoleWarn).toBeCalledTimes(0);
|
expect(mockConsoleWarn).toBeCalledTimes(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("calling a hook outside setup should crash", async () => {
|
|
||||||
class Root extends Component {
|
|
||||||
static template = xml`<t t-esc="state.value"/>`;
|
|
||||||
state = useState({ value: 1 });
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
onWillStart(() => {
|
|
||||||
this.state = useState({ value: 2 });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let e: Error;
|
|
||||||
try {
|
|
||||||
await mount(Root, fixture, { test: true });
|
|
||||||
} catch (error) {
|
|
||||||
e = error as Error;
|
|
||||||
}
|
|
||||||
expect(e!.message).toBe(
|
|
||||||
`The following error occurred in onWillStart: "No active component (a hook function should only be called in 'setup')"`
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("can catch an error in the initial call of a component render function (parent mounted)", async () => {
|
test("can catch an error in the initial call of a component render function (parent mounted)", async () => {
|
||||||
class ErrorComponent extends Component {
|
class ErrorComponent extends Component {
|
||||||
static template = xml`<div>hey<t t-esc="state.this.will.crash"/></div>`;
|
static template = xml`<div>hey<t t-esc="state.this.will.crash"/></div>`;
|
||||||
|
|||||||
@@ -322,24 +322,6 @@ describe("hooks", () => {
|
|||||||
expect(fixture.innerHTML).toBe("3<div>5</div>");
|
expect(fixture.innerHTML).toBe("3<div>5</div>");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("parent and child env (with useChildSubEnv then useSubEnv)", async () => {
|
|
||||||
class Child extends Component {
|
|
||||||
static template = xml`<div t-if="env.hasParent"><t t-esc="env.val"/></div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
class Parent extends Component {
|
|
||||||
static template = xml`<t t-esc="env.val"/><Child/>`;
|
|
||||||
static components = { Child };
|
|
||||||
setup() {
|
|
||||||
useChildSubEnv({ hasParent: true });
|
|
||||||
useSubEnv({ val: 5 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const env = { val: 3 };
|
|
||||||
await mount(Parent, fixture, { env });
|
|
||||||
expect(fixture.innerHTML).toBe("5<div>5</div>");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("can use onWillStart, onWillUpdateProps", async () => {
|
test("can use onWillStart, onWillUpdateProps", async () => {
|
||||||
const steps: string[] = [];
|
const steps: string[] = [];
|
||||||
async function slow(): Promise<string> {
|
async function slow(): Promise<string> {
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
import { Component, mount, useState, xml } from "../../src";
|
|
||||||
import { makeTestFixture, nextTick, snapshotEverything } from "../helpers";
|
|
||||||
|
|
||||||
let fixture: HTMLElement;
|
|
||||||
|
|
||||||
snapshotEverything();
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
fixture = makeTestFixture();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("hydration", () => {
|
|
||||||
test("can hydrate a simple static component", async () => {
|
|
||||||
fixture.innerHTML = "<div>giuoco pianissimo</div>";
|
|
||||||
const target = fixture.firstChild as any;
|
|
||||||
|
|
||||||
class Test extends Component {
|
|
||||||
static template = xml`<div>giuoco pianissimo</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
await mount(Test, target, { hydrate: true });
|
|
||||||
|
|
||||||
expect(fixture.innerHTML).toBe("<div>giuoco pianissimo</div>");
|
|
||||||
expect(fixture.firstChild).toBe(target);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("can hydrate a component with a handler", async () => {
|
|
||||||
fixture.innerHTML = "<div>0</div>";
|
|
||||||
const target = fixture.firstChild as any;
|
|
||||||
|
|
||||||
class Counter extends Component {
|
|
||||||
static template = xml`<div t-on-click="inc"><t t-esc="state.value"/></div>`;
|
|
||||||
|
|
||||||
state = useState({ value: 0 });
|
|
||||||
|
|
||||||
inc() {
|
|
||||||
this.state.value++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await mount(Counter, target, { hydrate: true });
|
|
||||||
|
|
||||||
expect(fixture.innerHTML).toBe("<div>0</div>");
|
|
||||||
expect(fixture.firstChild).toBe(target);
|
|
||||||
|
|
||||||
target.click();
|
|
||||||
await nextTick();
|
|
||||||
expect(fixture.innerHTML).toBe("<div>1</div>");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("can hydrate a component with a sub component", async () => {
|
|
||||||
fixture.innerHTML = "<p><button>0</button></p>";
|
|
||||||
const target = fixture.firstChild as any;
|
|
||||||
|
|
||||||
class Counter extends Component {
|
|
||||||
static template = xml`<button t-on-click="inc"><t t-esc="state.value"/></button>`;
|
|
||||||
|
|
||||||
state = useState({ value: 0 });
|
|
||||||
|
|
||||||
inc() {
|
|
||||||
this.state.value++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Parent extends Component {
|
|
||||||
static template = xml`<p><Counter/></p>`;
|
|
||||||
static components = { Counter };
|
|
||||||
}
|
|
||||||
|
|
||||||
await mount(Parent, target, { hydrate: true });
|
|
||||||
|
|
||||||
expect(fixture.innerHTML).toBe("<p><button>0</button></p>");
|
|
||||||
expect(fixture.firstChild).toBe(target);
|
|
||||||
|
|
||||||
target.querySelector("button")!.click();
|
|
||||||
await nextTick();
|
|
||||||
expect(fixture.innerHTML).toBe("<p><button>1</button></p>");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -12,7 +12,7 @@ let mockConsoleWarn: any;
|
|||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
console.info = (message: any) => {
|
console.info = (message: any) => {
|
||||||
if (message === DEV_MSG()) {
|
if (message === DEV_MSG) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
info(message);
|
info(message);
|
||||||
@@ -684,7 +684,7 @@ describe("props validation", () => {
|
|||||||
test("default values are applied before validating props at update", async () => {
|
test("default values are applied before validating props at update", async () => {
|
||||||
// need to do something about errors catched in render
|
// need to do something about errors catched in render
|
||||||
class SubComp extends Component {
|
class SubComp extends Component {
|
||||||
static props = { p: { type: Number, optional: true } };
|
static props = { p: { type: Number } };
|
||||||
static template = xml`<div><t t-esc="props.p"/></div>`;
|
static template = xml`<div><t t-esc="props.p"/></div>`;
|
||||||
static defaultProps = { p: 4 };
|
static defaultProps = { p: 4 };
|
||||||
}
|
}
|
||||||
@@ -791,9 +791,9 @@ describe("default props", () => {
|
|||||||
expect(fixture.innerHTML).toBe("<div><div>4</div></div>");
|
expect(fixture.innerHTML).toBe("<div><div>4</div></div>");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("can set default boolean values", async () => {
|
test("can set default required boolean values", async () => {
|
||||||
class SubComp extends Component {
|
class SubComp extends Component {
|
||||||
static props = ["p?", "q?"];
|
static props = ["p", "q"];
|
||||||
static defaultProps = { p: true, q: false };
|
static defaultProps = { p: true, q: false };
|
||||||
static template = xml`<span><t t-if="props.p">hey</t><t t-if="!props.q">hey</t></span>`;
|
static template = xml`<span><t t-if="props.p">hey</t><t t-if="!props.q">hey</t></span>`;
|
||||||
}
|
}
|
||||||
@@ -804,28 +804,4 @@ describe("default props", () => {
|
|||||||
await mount(Parent, fixture, { dev: true });
|
await mount(Parent, fixture, { dev: true });
|
||||||
expect(fixture.innerHTML).toBe("<div><span>heyhey</span></div>");
|
expect(fixture.innerHTML).toBe("<div><span>heyhey</span></div>");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("a default prop cannot be defined on a mandatory prop", async () => {
|
|
||||||
class Child extends Component {
|
|
||||||
static props = {
|
|
||||||
mandatory: Number,
|
|
||||||
};
|
|
||||||
static defaultProps = { mandatory: 3 };
|
|
||||||
static template = xml` <div><t t-esc="props.mandatory"/></div>`;
|
|
||||||
}
|
|
||||||
class Parent extends Component {
|
|
||||||
static components = { Child };
|
|
||||||
static template = xml`<Child/>`;
|
|
||||||
}
|
|
||||||
let error: Error;
|
|
||||||
try {
|
|
||||||
await mount(Parent, fixture, { dev: true });
|
|
||||||
} catch (e) {
|
|
||||||
error = e as Error;
|
|
||||||
}
|
|
||||||
expect(error!).toBeDefined();
|
|
||||||
expect(error!.message).toBe(
|
|
||||||
"A default value cannot be defined for a mandatory prop (name: 'mandatory', component: Child)"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Component, mount, onMounted, useRef, useState } from "../../src/index";
|
import { Component, mount, useRef, useState } from "../../src/index";
|
||||||
import { logStep, makeTestFixture, nextTick, snapshotEverything } from "../helpers";
|
import { makeTestFixture, nextTick, snapshotEverything } from "../helpers";
|
||||||
import { xml } from "../../src/index";
|
import { xml } from "../../src/index";
|
||||||
|
|
||||||
snapshotEverything();
|
snapshotEverything();
|
||||||
@@ -100,27 +100,4 @@ describe("refs", () => {
|
|||||||
expect(console.warn).toBeCalledTimes(1);
|
expect(console.warn).toBeCalledTimes(1);
|
||||||
console.warn = consoleWarn;
|
console.warn = consoleWarn;
|
||||||
});
|
});
|
||||||
|
|
||||||
test("refs and recursive templates", async () => {
|
|
||||||
class Test extends Component {
|
|
||||||
static components = {};
|
|
||||||
static template = xml`
|
|
||||||
<p t-ref="root">
|
|
||||||
<t t-esc="props.tree.value"/>
|
|
||||||
<t t-if="props.tree.child"><Test tree="props.tree.child"/></t>
|
|
||||||
</p>`;
|
|
||||||
root = useRef("root");
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
onMounted(() => logStep(this.root.el!.outerHTML));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Test.components = { Test };
|
|
||||||
|
|
||||||
const tree = { value: "a", child: { value: "b", child: null } };
|
|
||||||
|
|
||||||
await mount(Test, fixture, { props: { tree } });
|
|
||||||
expect(fixture.innerHTML).toBe("<p>a<p>b</p></p>");
|
|
||||||
expect(["<p>b</p>", "<p>a<p>b</p></p>"]).toBeLogged();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -45,36 +45,7 @@ describe("slots", () => {
|
|||||||
expect(fixture.innerHTML).toBe("some text");
|
expect(fixture.innerHTML).toBe("some text");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("simple slot with slot scope", async () => {
|
test("simple default slot with params", async () => {
|
||||||
let child: any;
|
|
||||||
class Child extends Component {
|
|
||||||
static template = xml`<span><t t-slot="slotName" bool="state.bool"/></span>`;
|
|
||||||
state = useState({ bool: true });
|
|
||||||
setup() {
|
|
||||||
child = this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Parent extends Component {
|
|
||||||
static template = xml`
|
|
||||||
<Child>
|
|
||||||
<t t-set-slot="slotName" t-slot-scope="slotScope">
|
|
||||||
<t t-if="slotScope.bool">some text</t>
|
|
||||||
<t t-else="slotScope.bool">other text</t>
|
|
||||||
</t>
|
|
||||||
</Child>`;
|
|
||||||
static components = { Child };
|
|
||||||
}
|
|
||||||
|
|
||||||
await mount(Parent, fixture);
|
|
||||||
expect(fixture.innerHTML).toBe("<span>some text</span>");
|
|
||||||
|
|
||||||
child.state.bool = false;
|
|
||||||
await nextTick();
|
|
||||||
expect(fixture.innerHTML).toBe("<span>other text</span>");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("default slot with slot scope: shorthand syntax", async () => {
|
|
||||||
let child: any;
|
let child: any;
|
||||||
class Child extends Component {
|
class Child extends Component {
|
||||||
static template = xml`<span><t t-slot="default" bool="state.bool"/></span>`;
|
static template = xml`<span><t t-slot="default" bool="state.bool"/></span>`;
|
||||||
@@ -86,9 +57,11 @@ describe("slots", () => {
|
|||||||
|
|
||||||
class Parent extends Component {
|
class Parent extends Component {
|
||||||
static template = xml`
|
static template = xml`
|
||||||
<Child t-slot-scope="slotScope">
|
<Child>
|
||||||
<t t-if="slotScope.bool">some text</t>
|
<t t-set-slot="default" t-slot-scope="slotScope">
|
||||||
<t t-else="slotScope.bool">other text</t>
|
<t t-if="slotScope.bool">some text</t>
|
||||||
|
<t t-else="slotScope.bool">other text</t>
|
||||||
|
</t>
|
||||||
</Child>`;
|
</Child>`;
|
||||||
static components = { Child };
|
static components = { Child };
|
||||||
}
|
}
|
||||||
@@ -126,41 +99,6 @@ describe("slots", () => {
|
|||||||
expect(mockConsoleWarn).toBeCalledTimes(1);
|
expect(mockConsoleWarn).toBeCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("simple default slot with params and bound function", async () => {
|
|
||||||
class Child extends Component {
|
|
||||||
static template = xml`<t t-slot="default" fn.bind="getValue"/>`;
|
|
||||||
state = useState({ value: 123 });
|
|
||||||
getValue() {
|
|
||||||
return this.state.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Parent extends Component {
|
|
||||||
static template = xml`
|
|
||||||
<Child t-slot-scope="slotScope"><t t-esc="slotScope.fn()"/></Child>`;
|
|
||||||
static components = { Child };
|
|
||||||
}
|
|
||||||
|
|
||||||
await mount(Parent, fixture);
|
|
||||||
expect(fixture.innerHTML).toBe("123");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("default slot with params with - in it", async () => {
|
|
||||||
class Child extends Component {
|
|
||||||
static template = xml`<t t-slot="default" some-value="state.value"/>`;
|
|
||||||
state = useState({ value: 123 });
|
|
||||||
}
|
|
||||||
|
|
||||||
class Parent extends Component {
|
|
||||||
static template = xml`
|
|
||||||
<Child t-slot-scope="slotScope"><t t-esc="slotScope['some-value']"/></Child>`;
|
|
||||||
static components = { Child };
|
|
||||||
}
|
|
||||||
|
|
||||||
await mount(Parent, fixture);
|
|
||||||
expect(fixture.innerHTML).toBe("123");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("fun: two calls to the same slot", async () => {
|
test("fun: two calls to the same slot", async () => {
|
||||||
class Child extends Component {
|
class Child extends Component {
|
||||||
static template = xml`<t t-slot="default"/><t t-slot="default"/>`;
|
static template = xml`<t t-slot="default"/><t t-slot="default"/>`;
|
||||||
@@ -277,30 +215,6 @@ describe("slots", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("can define and call slots with bound params", async () => {
|
|
||||||
class Child extends Component {
|
|
||||||
static template = xml`
|
|
||||||
<t t-slot="abc"/>
|
|
||||||
<t t-esc="props.slots['abc'].getValue()"/>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
class Parent extends Component {
|
|
||||||
static components = { Child };
|
|
||||||
static template = xml`
|
|
||||||
<Child>
|
|
||||||
<t t-set-slot="abc" getValue.bind="getValue">abc</t>
|
|
||||||
</Child>`;
|
|
||||||
state = useState({ value: 444 });
|
|
||||||
getValue() {
|
|
||||||
return this.state.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await mount(Parent, fixture);
|
|
||||||
|
|
||||||
expect(fixture.innerHTML).toBe("abc444");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("no named slot content => just no children", async () => {
|
test("no named slot content => just no children", async () => {
|
||||||
class Dialog extends Component {
|
class Dialog extends Component {
|
||||||
static template = xml`<span><t t-slot="header"/></span>`;
|
static template = xml`<span><t t-slot="header"/></span>`;
|
||||||
|
|||||||
@@ -30,19 +30,6 @@ describe("t-model directive", () => {
|
|||||||
expect(fixture.innerHTML).toBe("<div><input><span>test</span></div>");
|
expect(fixture.innerHTML).toBe("<div><input><span>test</span></div>");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("t-model on an input with an undefined value", async () => {
|
|
||||||
class SomeComponent extends Component {
|
|
||||||
static template = xml`<input t-model="state.text"/>`;
|
|
||||||
state = useState({ text: undefined });
|
|
||||||
}
|
|
||||||
await mount(SomeComponent, fixture);
|
|
||||||
|
|
||||||
expect(fixture.innerHTML).toBe("<input>");
|
|
||||||
|
|
||||||
const input = fixture.querySelector("input")!;
|
|
||||||
expect(input.value).toBe("");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("basic use, on an input with bracket expression", async () => {
|
test("basic use, on an input with bracket expression", async () => {
|
||||||
class SomeComponent extends Component {
|
class SomeComponent extends Component {
|
||||||
static template = xml`
|
static template = xml`
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ exports[`Memo if no prop change, prevent renderings from above 1`] = `
|
|||||||
let b2 = text(ctx['state'].a);
|
let b2 = text(ctx['state'].a);
|
||||||
let b3 = text(ctx['state'].b);
|
let b3 = text(ctx['state'].b);
|
||||||
let b4 = text(ctx['state'].c);
|
let b4 = text(ctx['state'].c);
|
||||||
let b9 = component(\`Memo\`, {a: ctx['state'].a, b: ctx['state'].b,slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__1\`, node, ctx);
|
let b9 = component(\`Memo\`, {a: ctx['state'].a,b: ctx['state'].b,slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__1\`, node, ctx);
|
||||||
return multi([b2, b3, b4, b9]);
|
return multi([b2, b3, b4, b9]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ snapshotEverything();
|
|||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
console.info = (message: any) => {
|
console.info = (message: any) => {
|
||||||
if (message === DEV_MSG()) {
|
if (message === DEV_MSG) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
info(message);
|
info(message);
|
||||||
|
|||||||
+2
-192
@@ -24,8 +24,8 @@ class Root extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// dev=false for benchmarking. we don't want to benchmark dev code!
|
// Application setup
|
||||||
mount(Root, document.body, { templates: TEMPLATES, dev: false });`;
|
mount(Root, document.body, { templates: TEMPLATES, dev: true });`;
|
||||||
|
|
||||||
const COMPONENTS_XML = /*xml*/`
|
const COMPONENTS_XML = /*xml*/`
|
||||||
<templates>
|
<templates>
|
||||||
@@ -1573,191 +1573,6 @@ class Root extends Component {
|
|||||||
mount(Root, document.body, { templates: TEMPLATES, dev: true});
|
mount(Root, document.body, { templates: TEMPLATES, dev: true});
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const BENCHMARK_JS = `
|
|
||||||
const { Component, mount, xml, useState, onWillRender, onPatched} = owl;
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
// Data generation
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
let idCounter = 1;
|
|
||||||
const adjectives = [
|
|
||||||
"pretty", "large", "big", "small", "tall", "short", "long", "handsome", "plain",
|
|
||||||
"quaint", "clean", "elegant", "easy", "angry", "crazy", "helpful", "mushy", "odd",
|
|
||||||
"unsightly", "adorable", "important", "inexpensive", "cheap", "expensive", "fancy"];
|
|
||||||
const colours = ["red", "yellow", "blue", "green", "pink", "brown", "purple", "brown", "white", "black", "orange"];
|
|
||||||
const nouns = ["table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", "sandwich", "burger", "pizza", "mouse", "keyboard"];
|
|
||||||
|
|
||||||
function _random (max) { return Math.round(Math.random() * 1000) % max; };
|
|
||||||
|
|
||||||
function buildData(count) {
|
|
||||||
const data = new Array(count);
|
|
||||||
for (let i = 0; i < count; i++) {
|
|
||||||
const label = \`\${adjectives[_random(adjectives.length)]} \${colours[_random(colours.length)]} \${nouns[_random(nouns.length)]}\`;
|
|
||||||
data[i] = {
|
|
||||||
id: idCounter++,
|
|
||||||
label,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
// Components
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
class Button extends Component {
|
|
||||||
static template = xml\`
|
|
||||||
<div class='col-sm-6 smallpad'>
|
|
||||||
<button t-att-id="props.id" class='btn btn-primary btn-block' type='button' t-on-click="props.onClick">
|
|
||||||
<t t-esc="props.text"/>
|
|
||||||
</button>
|
|
||||||
</div>\`;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class Row extends Component {
|
|
||||||
static template = xml\`
|
|
||||||
<tr t-att-class="props.isSelected ? 'danger' : ''">
|
|
||||||
<td class="col-md-1" t-esc="props.row.id" />
|
|
||||||
<td class="col-md-4">
|
|
||||||
<a t-on-click="() => props.onSelect(props.row.id)" t-esc="props.row.label" />
|
|
||||||
</td>
|
|
||||||
<td class="col-md-1">
|
|
||||||
<a t-on-click="() => props.onRemove(props.row.id)" class="remove">[x]
|
|
||||||
<span class='glyphicon glyphicon-remove' aria-hidden="true" />
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td class='col-md-6'/>
|
|
||||||
</tr>\`
|
|
||||||
}
|
|
||||||
|
|
||||||
class Root extends Component {
|
|
||||||
static template = xml\`
|
|
||||||
<div class='container'>
|
|
||||||
<div class='jumbotron'>
|
|
||||||
<div class='row'>
|
|
||||||
<div class='col-md-6'>
|
|
||||||
<h1>Owl Keyed</h1>
|
|
||||||
</div>
|
|
||||||
<div class='col-md-6'>
|
|
||||||
<div class='row'>
|
|
||||||
<Button id="'run'" onClick.bind="run" text="'Create 1,000 rows'" />
|
|
||||||
<Button id="'runlots'" onClick.bind="runLots" text="'Create 10,000 rows'" />
|
|
||||||
<Button id="'add'" onClick.bind="add" text="'Append 1,000 rows'" />
|
|
||||||
<Button id="'update'" onClick.bind="update" text="'Update every 10th row'" />
|
|
||||||
<Button id="'clear'" onClick.bind="clear" text="'Clear'" />
|
|
||||||
<Button id="'swaprows'" onClick.bind="swapRows" text="'Swap Rows'" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<table class='table table-hover table-striped test-data'>
|
|
||||||
<tbody>
|
|
||||||
<t t-foreach="state.rows" t-as="row" t-key="row.id">
|
|
||||||
<Row row="row" isSelected="row.id === state.selectedRowId" onSelect.bind="selectRow" onRemove.bind="removeRow"/>
|
|
||||||
</t>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<span class='preloadicon glyphicon glyphicon-remove' aria-hidden="true" />
|
|
||||||
</div>\`;
|
|
||||||
|
|
||||||
static components = { Button, Row };
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
this.state = useState({
|
|
||||||
rows: [],
|
|
||||||
selectedRowId: null
|
|
||||||
});
|
|
||||||
this.benchmarking = false;
|
|
||||||
onPatched(() => {
|
|
||||||
if (this.benchmarking) {
|
|
||||||
this.stop();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
start(descr) {
|
|
||||||
this.benchmarking = \`[\${descr}]\`;
|
|
||||||
console.time(this.benchmarking);
|
|
||||||
}
|
|
||||||
stop() {
|
|
||||||
console.timeEnd(this.benchmarking);
|
|
||||||
this.benchmarking = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
run() {
|
|
||||||
this.start('add1000');
|
|
||||||
this.state.rows = buildData(1000);
|
|
||||||
this.state.selectedRowId = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
runLots() {
|
|
||||||
this.start('add10_000');
|
|
||||||
this.state.rows = buildData(10_000);
|
|
||||||
this.state.selectedRowId = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
add() {
|
|
||||||
this.start('append1000');
|
|
||||||
this.state.rows = this.state.rows.concat(buildData(1000));
|
|
||||||
}
|
|
||||||
|
|
||||||
update() {
|
|
||||||
this.start('update1/10th');
|
|
||||||
let index = 0;
|
|
||||||
const rows = this.state.rows;
|
|
||||||
while (index < rows.length) {
|
|
||||||
rows[index].label = rows[index].label + " !!!";
|
|
||||||
index += 10;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
clear() {
|
|
||||||
this.start('clear');
|
|
||||||
this.state.rows = [];
|
|
||||||
this.state.selectedRowId = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
swapRows() {
|
|
||||||
this.start('swap');
|
|
||||||
const rows = this.state.rows;
|
|
||||||
if (rows.length > 998) {
|
|
||||||
let tmp = rows[1];
|
|
||||||
rows[1] = rows[998];
|
|
||||||
rows[998] = tmp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
selectRow(id) {
|
|
||||||
this.start('select');
|
|
||||||
this.state.selectedRowId = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
removeRow(id) {
|
|
||||||
this.start('remove1');
|
|
||||||
const rows = this.state.rows;
|
|
||||||
rows.splice(rows.findIndex(row => row.id === id), 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
mount(Root, document.body, { templates: TEMPLATES, dev: true });
|
|
||||||
|
|
||||||
`;
|
|
||||||
|
|
||||||
const BENCHMARK_CSS = `
|
|
||||||
tr.danger {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.remove:hover {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
.remove {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const SAMPLES = [
|
export const SAMPLES = [
|
||||||
{
|
{
|
||||||
description: "Components",
|
description: "Components",
|
||||||
@@ -1810,9 +1625,4 @@ export const SAMPLES = [
|
|||||||
xml: WMS_XML,
|
xml: WMS_XML,
|
||||||
css: WMS_CSS,
|
css: WMS_CSS,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
description: "Benchmark example",
|
|
||||||
code: BENCHMARK_JS,
|
|
||||||
css: BENCHMARK_CSS,
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user