mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
Compare commits
244 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 076b0d774e | |||
| f405fe9323 | |||
| 1ae9d514b9 | |||
| 592d9a458e | |||
| 0dbd2bd463 | |||
| 8ec7a6f9bf | |||
| 12b8ce963e | |||
| bb9d65e95b | |||
| 73c339fff1 | |||
| 406be446a5 | |||
| bd2aa8a72f | |||
| 3536f41f00 | |||
| 804ad3c35e | |||
| 4a922ed82d | |||
| a6f0985d43 | |||
| 6aee1355c8 | |||
| 921ced7c90 | |||
| 1da930cb25 | |||
| cc8e11c9c9 | |||
| 3196b585fd | |||
| ea2ccc5a03 | |||
| 960808aeb2 | |||
| 1fb1d37e32 | |||
| 24ce8613c5 | |||
| 2c1226d737 | |||
| 140818b5f9 | |||
| 83de53d283 | |||
| 50aac42bdc | |||
| bd98d4d0d0 | |||
| 4d68dac24d | |||
| 722abd6d5f | |||
| a7305a5cdb | |||
| ff734c706c | |||
| add5fdd737 | |||
| a221411938 | |||
| bb6479f44f | |||
| aa95149997 | |||
| e4b4ee471f | |||
| 3af5e57825 | |||
| 979712f84e | |||
| 93f2c1d766 | |||
| 0728c8333d | |||
| 6639d361c3 | |||
| 72962f1dd1 | |||
| a4d9aae9a7 | |||
| 1e8576ad40 | |||
| 753d82149e | |||
| 700030cc7d | |||
| d828f39a2d | |||
| 0a73154985 | |||
| d88eb34d4f | |||
| a8d88d4009 | |||
| 6d9ed0d62f | |||
| b33471e819 | |||
| 3fb65b3a89 | |||
| 09d192999a | |||
| 466cf50b73 | |||
| 96620d3e8e | |||
| 176c89b278 | |||
| 38941bc26f | |||
| ce8ddd1cbf | |||
| 81f44ee5d3 | |||
| 99b5e9ec55 | |||
| 4f35f03986 | |||
| eab0caa6cb | |||
| 5a2c769eab | |||
| 89d63ff29a | |||
| a2e8abc243 | |||
| dfd0dcedb8 | |||
| ad743c205c | |||
| 7c78442e43 | |||
| 0d13c362d3 | |||
| 374dbb2fd9 | |||
| 2a5f37cf4b | |||
| cfb6b9f958 | |||
| 82f6923a21 | |||
| cccb379377 | |||
| 82c7c24438 | |||
| 41ad5db2e3 | |||
| 42a140a8e3 | |||
| 7711733a23 | |||
| 281b32965e | |||
| cec451fd15 | |||
| 6a7703ea82 | |||
| 4a03a60084 | |||
| a6bdca082a | |||
| 90167c5436 | |||
| c221721d7f | |||
| 38f39b6755 | |||
| 1775467434 | |||
| 52d0526ddd | |||
| 4b170b9b45 | |||
| 3e1fe07ba7 | |||
| 5bf47500d5 | |||
| 75ad0835e9 | |||
| 3d6a5eb828 | |||
| aceaeef8cc | |||
| 06fc3a2c77 | |||
| 92cc4375f8 | |||
| 9f2e2bcc66 | |||
| c7af885f43 | |||
| bd5637c0a3 | |||
| 3c98ef8cb1 | |||
| aad6b806ba | |||
| 772c275bd4 | |||
| 416deeb865 | |||
| 7e40fa300a | |||
| cc1eea0945 | |||
| bf9cceb56f | |||
| 7eaecac0b5 | |||
| ddc358f48a | |||
| e2819323ee | |||
| 983b9f996d | |||
| fd295b3be3 | |||
| e675f7ff5b | |||
| 702fb3b253 | |||
| 63fbcf99fd | |||
| 14a6289f60 | |||
| 894deed13b | |||
| 6f435c36d8 | |||
| 5dddf8f9a3 | |||
| 463eb4bb86 | |||
| b66d5231d3 | |||
| 3c12519277 | |||
| c1a973a4d8 | |||
| e91e50a812 | |||
| 2e176f135d | |||
| 49c7585998 | |||
| f32b1deb2c | |||
| 1da3ecdbee | |||
| a1c619f094 | |||
| eceb3e6280 | |||
| 8a1ac13975 | |||
| 779003e715 | |||
| 8c600fa539 | |||
| bcc4fe2a27 | |||
| bb4948f3dc | |||
| cc4480e001 | |||
| 7143c2e39b | |||
| a8d8310b8e | |||
| 0bbea351a6 | |||
| 2601a176c4 | |||
| 05a57d6da5 | |||
| e6e6c31632 | |||
| 3f66d9fe6c | |||
| 5d8141a67c | |||
| 6459d8d289 | |||
| 2943ca3921 | |||
| 4866ed8e8a | |||
| 93b88cad8d | |||
| b90180a9e0 | |||
| 8239a5d2cd | |||
| a073568667 | |||
| 7143dd3ff5 | |||
| c0cf2c9e3d | |||
| db9658c140 | |||
| eb2c41aa91 | |||
| a45ca98dac | |||
| ced777f0be | |||
| 7df0a4e93f | |||
| f3555cfae0 | |||
| 629b379ea9 | |||
| a400fc5e69 | |||
| 5d4a38ad0f | |||
| 093218a067 | |||
| ed3e6dcbb6 | |||
| cb107cef7d | |||
| aecc320c29 | |||
| e580ec00fe | |||
| ee5f6c7569 | |||
| c627b0add8 | |||
| b902edc1be | |||
| 1761af9c24 | |||
| db93ef08ff | |||
| 03787cfb39 | |||
| 10745c52d0 | |||
| 2b90e3a688 | |||
| b2ea241270 | |||
| 81e5b24f2f | |||
| 717fd3b6ab | |||
| 3fa1bb62f6 | |||
| 201f06c187 | |||
| ae30d9db7d | |||
| af80cefa76 | |||
| 1f6e84d141 | |||
| 1658d15b87 | |||
| c1439814bf | |||
| d5fbaff9f7 | |||
| 8c16790471 | |||
| 9b8c582b32 | |||
| 1700a6fba3 | |||
| 7513b1e507 | |||
| e0c0306acd | |||
| bca6afeb90 | |||
| 756d32daa0 | |||
| 8169f05edc | |||
| 153f4379f4 | |||
| 7ffb9afbd9 | |||
| c03042b44d | |||
| df2d6b6a0e | |||
| 7c04cc425e | |||
| 16f1e2c237 | |||
| 8a84b5be56 | |||
| 219923d752 | |||
| 348b505e5f | |||
| d3745e4e5f | |||
| 80cb6b7a91 | |||
| 4cceb239dd | |||
| 2ae0149adb | |||
| 3eb63452e7 | |||
| 1296964ae2 | |||
| c71db28bc6 | |||
| a0b2551e4a | |||
| ebd2e4324f | |||
| aa3148eddf | |||
| 42811344da | |||
| a0e1af83ac | |||
| c16d8ed6de | |||
| 15e4c856da | |||
| ced5d0f69f | |||
| b6eb4d009e | |||
| 8c71d99e5f | |||
| d7f3f4defe | |||
| 0f2192604c | |||
| 10df0b5f4a | |||
| d569ea1c28 | |||
| 52fa81c510 | |||
| d6668e3439 | |||
| ee1ef20ce1 | |||
| 9d5ffe11c7 | |||
| 900a3ee501 | |||
| 0a544bd7e8 | |||
| 4415cc8932 | |||
| efa147fdab | |||
| e746574a1d | |||
| c06049076a | |||
| bc04f727ac | |||
| 0bc9573a8a | |||
| 73f94fba3f | |||
| 7a16449724 | |||
| 718c765e3b | |||
| 150d620b8e | |||
| 307b936d01 | |||
| 6950f8e628 |
@@ -5,7 +5,7 @@ name: Node.js CI
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ master, owl-next ]
|
branches: [ master ]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -25,3 +25,4 @@ jobs:
|
|||||||
- run: npm install
|
- run: npm install
|
||||||
- run: npm run test
|
- run: npm run test
|
||||||
- run: npm run check-formatting
|
- run: npm run check-formatting
|
||||||
|
- run: npm run build
|
||||||
|
|||||||
+245
-31
@@ -8,13 +8,16 @@ patching the `setup` method of `Component` to auto register all the lifecycle
|
|||||||
methods as hooks). This will be done for the transition period, but will be
|
methods as hooks). This will be done for the transition period, but will be
|
||||||
removed after.
|
removed after.
|
||||||
|
|
||||||
## Changes
|
## From Owl 1.x to Owl 2.0
|
||||||
|
|
||||||
|
All changes are documented here in no particular order.
|
||||||
|
|
||||||
**Components**
|
**Components**
|
||||||
|
|
||||||
- components can now have empty content or multiple root nodes (htmlelement or text) ([details](#31-components-can-now-have-arbitrary-content))
|
- components can now have empty content or multiple root nodes (htmlelement or text) ([details](#31-components-can-now-have-arbitrary-content))
|
||||||
- new `useEffect` hook
|
- breaking: component.el is removed ([details](#9-componentel-is-removed))
|
||||||
- new `onDestroyed`, `onWillRender` and `onRendered` hooks
|
- new `useEffect` hook ([doc](doc/reference/hooks.md#useeffect))
|
||||||
|
- new `onWillDestroy`, `onWillRender` and `onRendered` hooks ([doc](doc/reference/component.md#lifecycle))
|
||||||
- breaking: lifecycle methods are removed ([details](#1-component-lifecycle-methods-are-removed))
|
- breaking: lifecycle methods are removed ([details](#1-component-lifecycle-methods-are-removed))
|
||||||
- breaking: can no longer be mounted on detached DOM ([details](#2-components-can-no-longer-be-mounted-in-a-detached-dom-element))
|
- breaking: can no longer be mounted on detached DOM ([details](#2-components-can-no-longer-be-mounted-in-a-detached-dom-element))
|
||||||
- breaking: standalone `mount` method API is simpler ([details](#4-mount-method-api-is-simpler))
|
- breaking: standalone `mount` method API is simpler ([details](#4-mount-method-api-is-simpler))
|
||||||
@@ -22,47 +25,79 @@ removed after.
|
|||||||
- breaking: components can no longer be unmounted/remounted ([details](#6-components-can-no-longer-be-unmountedremounted))
|
- breaking: components can no longer be unmounted/remounted ([details](#6-components-can-no-longer-be-unmountedremounted))
|
||||||
- breaking: template name is no longer inferred from the class name ([details](#7-template-name-is-no-longer-inferred-from-the-class-name))
|
- breaking: template name is no longer inferred from the class name ([details](#7-template-name-is-no-longer-inferred-from-the-class-name))
|
||||||
- breaking: components no longer have a `shouldUpdate` method ([details](#8-components-no-longer-have-a-shouldupdate-method))
|
- breaking: components no longer have a `shouldUpdate` method ([details](#8-components-no-longer-have-a-shouldupdate-method))
|
||||||
- breaking: component.el may be a text node, and is no longer `null` ([details](#9-componentel-may-be-a-text-node-and-is-no-longer-null))
|
|
||||||
- breaking: style/class on components are now regular props ([details](#10-styleclass-on-components-are-now-regular-props))
|
|
||||||
- breaking: components can no longer be mounted with position=self ([details](#11-components-can-no-longer-be-mounted-with-positionself))
|
- breaking: components can no longer be mounted with position=self ([details](#11-components-can-no-longer-be-mounted-with-positionself))
|
||||||
|
- breaking: `render` method does not return a promise anymore ([details](#35-render-method-does-not-return-a-promise-anymore))
|
||||||
|
- 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))
|
||||||
|
- 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))
|
||||||
|
- breaking: rendering a component does not necessarily render child components ([details](#40-rendering-a-component-does-not-necessarily-render-child-components))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**Templates**
|
||||||
|
|
||||||
|
- 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-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))
|
||||||
|
- 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))
|
||||||
|
- 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
|
||||||
|
|
||||||
**Portal**
|
|
||||||
|
|
||||||
- portals can now have arbitrary content (no longer restricted to one single child)
|
**Reactivity**
|
||||||
- breaking: does no longer transfer dom events ([details](#13-portal-does-no-longer-transfer-dom-events))
|
|
||||||
- breaking: does render as an empty text node instead of `<portal/>` ([details](#14-portal-does-render-as-an-empty-text-node-instead-of-portal))
|
- 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**
|
||||||
|
|
||||||
- breaking: `t-set` does not define a slot any more ([details](#3-t-set-will-no-longer-work-to-define-a-slot))
|
- breaking: `t-set` does not define a slot any more ([details](#3-t-set-will-no-longer-work-to-define-a-slot))
|
||||||
|
- slots capabilities have been improved ([doc](doc/reference/slots.md))
|
||||||
|
- params can be give to slot content (to pass information from slot owner to slot user)
|
||||||
|
- slots are given as a `prop` (and can be manipulated/propagated to sub components )
|
||||||
|
- slots can define scopes (to pass information from slot user to slot owner)
|
||||||
|
|
||||||
|
|
||||||
|
**Portal**
|
||||||
|
|
||||||
|
- Portal are now defined with `t-portal` ([details](#33-portal-are-now-defined-with-t-portal))
|
||||||
|
- portals can now have arbitrary content (no longer restricted to one single child)
|
||||||
|
- breaking: does no longer transfer dom events ([details](#13-portal-does-no-longer-transfer-dom-events))
|
||||||
|
- breaking: does render as an empty text node instead of `<portal/>` ([details](#14-portal-does-render-as-an-empty-text-node-instead-of-portal))
|
||||||
|
|
||||||
|
|
||||||
**Miscellaneous**
|
**Miscellaneous**
|
||||||
|
|
||||||
- improved performance
|
- improved performance
|
||||||
- much simpler code
|
- much simpler code
|
||||||
- finer grained reactivity: owl 2 tracks change per key/component
|
- new App class to encapsulate a root Owl component (with the config for that application) ([doc](doc/reference/app.md))
|
||||||
- finer grained reactivity: sub components can reobserve state
|
|
||||||
- new App class to encapsulate a root Owl component (with the config for that application)
|
|
||||||
- new `Memo` component
|
- new `Memo` component
|
||||||
|
- new `useEffect` hook ([doc](doc/reference/hooks.md#useeffect))
|
||||||
- breaking: `Context` is removed ([details](#15-context-is-removed))
|
- breaking: `Context` is removed ([details](#15-context-is-removed))
|
||||||
- breaking: `env` is now totally empty ([details](#16-env-is-now-totally-empty))
|
- breaking: `env` is now totally empty ([details](#16-env-is-now-totally-empty))
|
||||||
|
- breaking: `env` is now frozen ([details](#28-env-is-now-frozen))
|
||||||
|
- new hook: `useChildSubEnv` (only applies to child components) ([details](#27-usechildsubenv-only-applies-to-child-components))
|
||||||
- breaking: most exports are exported at top level ([details](#18-most-exports-are-exported-at-top-level))
|
- breaking: most exports are exported at top level ([details](#18-most-exports-are-exported-at-top-level))
|
||||||
- breaking: properties are no longer set as attributes ([details](#19-properties-are-no-longer-set-as-attributes))
|
- breaking: properties are no longer set as attributes ([details](#19-properties-are-no-longer-set-as-attributes))
|
||||||
- breaking: `t-foreach` should always have a corresponding `t-key` ([details](#20-t-foreach-should-always-have-a-corresponding-t-key))
|
|
||||||
- breaking: `EventBus` api changed: it is now an `EventTarget` ([details](#21-eventbus-api-changed-it-is-now-an-eventtarget))
|
- breaking: `EventBus` api changed: it is now an `EventTarget` ([details](#21-eventbus-api-changed-it-is-now-an-eventtarget))
|
||||||
- breaking: `Store` is removed ([details](#22-store-is-removed))
|
- breaking: `Store` is removed ([details](#22-store-is-removed))
|
||||||
- breaking: `Router` is removed ([details](#23-router-is-removed))
|
- breaking: `Router` is removed ([details](#23-router-is-removed))
|
||||||
- breaking: transition system is removed ([details](#24-transition-system-is-removed))
|
- breaking: transition system is removed ([details](#24-transition-system-is-removed))
|
||||||
- breaking: no more global components or templates ([details](#25-no-more-global-components-or-templates))
|
- breaking: no more global components or templates ([details](#25-no-more-global-components-or-templates))
|
||||||
- breaking: `AsyncRoot` utility component is removed ([details](#26-asyncroot-utility-component-is-removed))
|
- breaking: `AsyncRoot` utility component is removed ([details](#26-asyncroot-utility-component-is-removed))
|
||||||
- breaking: `useSubEnv` only applies to child components ([details](#27-usesubenv-only-applies-to-child-components))
|
|
||||||
- breaking: `env` is now frozen ([details](#28-env-is-now-frozen))
|
|
||||||
- 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: `renderToString` function on qweb has been removed ([details](#32-rendertostring-on-qweb-has-been-removed))
|
- breaking: `renderToString` function on qweb has been removed ([details](#32-rendertostring-on-qweb-has-been-removed))
|
||||||
|
- breaking: `debounce` utility function has been removed ([details](#34-debounce-utility-function-has-been-removed))
|
||||||
|
- breaking: `browser` object has been removed ([details](#39-browser-object-has-been-removed))
|
||||||
|
|
||||||
## Details/Rationale/Migration
|
## Details/Rationale/Migration
|
||||||
|
|
||||||
@@ -96,6 +131,8 @@ class MyComponent extends Component {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Documentation: [Component Lifecycle](doc/reference/component.md#lifecycle)
|
||||||
|
|
||||||
### 2. components can no longer be mounted in a detached dom element
|
### 2. components can no longer be mounted in a detached dom element
|
||||||
|
|
||||||
Nor document fragment.
|
Nor document fragment.
|
||||||
@@ -147,6 +184,8 @@ because real applications will need to configure the templates, the translations
|
|||||||
and other stuff. All complex usecases need to go through the new `App` class,
|
and other stuff. All complex usecases need to go through the new `App` class,
|
||||||
that encapsulates the root of an owl application.
|
that encapsulates the root of an owl application.
|
||||||
|
|
||||||
|
Documentation: [Mounting a component](doc/reference/app.md#mount-helper)
|
||||||
|
|
||||||
### 5. components can no longer be instantiated and mounted by hand
|
### 5. components can no longer be instantiated and mounted by hand
|
||||||
|
|
||||||
In Owl 1, it was possible to instantiate a component by hand:
|
In Owl 1, it was possible to instantiate a component by hand:
|
||||||
@@ -240,10 +279,14 @@ ideas may help:
|
|||||||
</Memo>
|
</Memo>
|
||||||
```
|
```
|
||||||
|
|
||||||
### 9. component.el may be a text node, and is no longer `null`
|
### 9. component.el is removed
|
||||||
|
|
||||||
This comes from the fact that Owl 2 supports fragments (arbitrary content). When
|
This comes from the fact that Owl 2 supports fragments (arbitrary content).
|
||||||
it is not defined, it was `null` in Owl 1 and is `undefined` in owl 2.
|
|
||||||
|
Migration: if one need a reference to the root htmlelement of a template, it is
|
||||||
|
suggested to simply add a `ref` on it, and access the reference as needed.
|
||||||
|
|
||||||
|
Documentation: [Refs](doc/reference/refs.md)
|
||||||
|
|
||||||
### 10. style/class on components are now regular props
|
### 10. style/class on components are now regular props
|
||||||
|
|
||||||
@@ -286,7 +329,13 @@ compatible with the fact that a component can have a root `<div>` then later,
|
|||||||
change it to something else, or even a text node.
|
change it to something else, or even a text node.
|
||||||
|
|
||||||
Migration: no real way to do the same. Owl application needs to be appended or
|
Migration: no real way to do the same. Owl application needs to be appended or
|
||||||
prepended in something, maybe a `div`.
|
prepended in something, maybe a `div`. Remember that you the root component
|
||||||
|
can have multiple roots
|
||||||
|
|
||||||
|
Documentation:
|
||||||
|
- [Fragments](doc/reference/templates.md#fragments)
|
||||||
|
- [Mounting a component](doc/reference/app.md#mount-helper)
|
||||||
|
|
||||||
|
|
||||||
### 12. `t-on` does not work on components any more
|
### 12. `t-on` does not work on components any more
|
||||||
|
|
||||||
@@ -313,8 +362,16 @@ the component API to accept explicitely a callback as props.
|
|||||||
|
|
||||||
```xml
|
```xml
|
||||||
<SomeComponent onSomeEvent="doSomething"/>
|
<SomeComponent onSomeEvent="doSomething"/>
|
||||||
|
<!-- or alternatively: -->
|
||||||
|
<SomeComponent onSomeEvent.bind="doSomething"/>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Note that one of the example above uses the `.bind` suffix, to bind the function
|
||||||
|
prop to the component. Most of the time, binding the function is necessary, and
|
||||||
|
using the `.bind` suffix is very helpful in that case.
|
||||||
|
|
||||||
|
Documentation: [Binding function props](doc/reference/props.md#binding-function-props)
|
||||||
|
|
||||||
### 13. Portal does no longer transfer DOM events
|
### 13. Portal does no longer transfer DOM events
|
||||||
|
|
||||||
In Owl 1, a Portal component would listen to events emitted on its portalled
|
In Owl 1, a Portal component would listen to events emitted on its portalled
|
||||||
@@ -372,6 +429,8 @@ Migration: there is no proper way to get an equivalent. The closest is to get
|
|||||||
a reference to the root App using `this.__owl__.app`. If you need to do this,
|
a reference to the root App using `this.__owl__.app`. If you need to do this,
|
||||||
let us know. If this is a legitimate usecase, we may add a `useApp` hook.
|
let us know. If this is a legitimate usecase, we may add a `useApp` hook.
|
||||||
|
|
||||||
|
Documentation: [Environment](doc/reference/environment.md)
|
||||||
|
|
||||||
### 17. `t-component` no longer accepts strings
|
### 17. `t-component` no longer accepts strings
|
||||||
|
|
||||||
In owl 1, we could write this:
|
In owl 1, we could write this:
|
||||||
@@ -397,6 +456,8 @@ the implementation is slightly simpler.
|
|||||||
Migration: simply using `constructor.components.Coucou` instead of `Coucou` will
|
Migration: simply using `constructor.components.Coucou` instead of `Coucou` will
|
||||||
do the trick.
|
do the trick.
|
||||||
|
|
||||||
|
Documentation: [Component](doc/reference/component.md#dynamic-sub-components)
|
||||||
|
|
||||||
### 18. most exports are exported at top level
|
### 18. most exports are exported at top level
|
||||||
|
|
||||||
Most exports are flattened: for ex, `onMounted` is in owl, not in `owl.hooks`.
|
Most exports are flattened: for ex, `onMounted` is in owl, not in `owl.hooks`.
|
||||||
@@ -453,6 +514,8 @@ rewritten like this: `bus.addEventListener("event-type", (({detail: info}) => {.
|
|||||||
|
|
||||||
Do not forget to similarly replace `bus.off(...)` by `bus.removeEventListener(...)`
|
Do not forget to similarly replace `bus.off(...)` by `bus.removeEventListener(...)`
|
||||||
|
|
||||||
|
Documentation: [EventBus](doc/reference/utils.md#eventbus)
|
||||||
|
|
||||||
### 22. `Store` is removed
|
### 22. `Store` is removed
|
||||||
|
|
||||||
The Store system had been abandoned in owl 2.
|
The Store system had been abandoned in owl 2.
|
||||||
@@ -514,14 +577,12 @@ either a fallback when the data is not ready, or the actual component with data
|
|||||||
as props. If there is no escape, and `AsyncRoot` is needed, please reach out to
|
as props. If there is no escape, and `AsyncRoot` is needed, please reach out to
|
||||||
us so we can study this usecase.
|
us so we can study this usecase.
|
||||||
|
|
||||||
### 27. `useSubEnv` only applies to child components
|
### 27. `useChildSubEnv` (only applies to child components)
|
||||||
|
|
||||||
In Owl 1, a call to `useSubEnv` would define a new environment for the children
|
|
||||||
AND the component. It now only defines an environment for the children.
|
|
||||||
|
|
||||||
Rationale: This was a subtle cause for bugs: some code had to be rrun
|
|
||||||
before the call to `useSubEnv`, otherwise it could interfere with the sub environment.
|
|
||||||
|
|
||||||
|
In Owl, a call to `useSubEnv` would define a new environment for the children
|
||||||
|
AND the component. It is very useful, but in some cases, one only need to update
|
||||||
|
the children component environment. This can now be done with a new hook:
|
||||||
|
[`useChildSubEnv`](doc/reference/hooks.md#usesubenv-and-usechildsubenv)
|
||||||
|
|
||||||
### 28. `env` is now frozen
|
### 28. `env` is now frozen
|
||||||
|
|
||||||
@@ -536,6 +597,8 @@ components. This use case still works with `useSubEnv`.
|
|||||||
Migration: use `useSubEnv` instead of writing directly to the env. Also, note
|
Migration: use `useSubEnv` instead of writing directly to the env. Also, note
|
||||||
that the environment given to the App can initially contain anything.
|
that the environment given to the App can initially contain anything.
|
||||||
|
|
||||||
|
Documentation: [Environment](doc/reference/environment.md)
|
||||||
|
|
||||||
### 29. `t-ref` does not work on component
|
### 29. `t-ref` does not work on component
|
||||||
|
|
||||||
Before, `t-ref` could be used to get a reference to a child component. It no
|
Before, `t-ref` could be used to get a reference to a child component. It no
|
||||||
@@ -576,6 +639,8 @@ adapted like this:
|
|||||||
<button t-on-click="() => this.someFunction(someVar)">blabla</button>
|
<button t-on-click="() => this.someFunction(someVar)">blabla</button>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Documentation: [Event Handling](doc/reference/event_handling.md)
|
||||||
|
|
||||||
### 31. components can now have arbitrary content
|
### 31. components can now have arbitrary content
|
||||||
|
|
||||||
Before Owl 2, components had to limit themselves to one single htmlelement as
|
Before Owl 2, components had to limit themselves to one single htmlelement as
|
||||||
@@ -588,6 +653,8 @@ So, the following template works for components:
|
|||||||
hello
|
hello
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Documentation: [Fragments](doc/reference/templates.md#fragments)
|
||||||
|
|
||||||
### 32. `renderToString` on QWeb has been removed
|
### 32. `renderToString` on QWeb has been removed
|
||||||
|
|
||||||
Rationale: the `renderToString` function was a qweb method, which made sense because
|
Rationale: the `renderToString` function was a qweb method, which made sense because
|
||||||
@@ -601,12 +668,159 @@ Also, this can easily be done in userspace, by mounting a component in a div. F
|
|||||||
export async function renderToString(template, context) {
|
export async function renderToString(template, context) {
|
||||||
class C extends Component {
|
class C extends Component {
|
||||||
static template = template;
|
static template = template;
|
||||||
|
setup () {
|
||||||
|
Object.assign(this, context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
document.body.appendChild(div);
|
document.body.appendChild(div);
|
||||||
const component = await mount(C, div);
|
const app = new App(C);
|
||||||
|
await app.mount(div);
|
||||||
const result = div.innerHTML;
|
const result = div.innerHTML;
|
||||||
app.destroy();
|
app.destroy();
|
||||||
div.remove();
|
div.remove();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The function above works for most cases, but is asynchronous. An alternative
|
||||||
|
function could look like this:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const { App, blockDom } = owl;
|
||||||
|
const app = new App(Component); // act as a template repository
|
||||||
|
|
||||||
|
function renderToString(template, context = {}) {
|
||||||
|
app.addTemplate(template, template, { allowDuplicate: true });
|
||||||
|
const templateFn = app.getTemplate(template);
|
||||||
|
const bdom = templateFn(context, {});
|
||||||
|
const div = document.createElement('div')
|
||||||
|
blockDom.mount(bdom, div);
|
||||||
|
return div.innerHTML;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This is a synchronous function, so it will not work with components, but it should
|
||||||
|
be useful for most simple templates.
|
||||||
|
|
||||||
|
Also note that these two examples do not translate their templates. To do that,
|
||||||
|
they need to be modified to pass the proper translate function to the `App`
|
||||||
|
configuration.
|
||||||
|
|
||||||
|
### 33. Portal are now defined with `t-portal`
|
||||||
|
|
||||||
|
Before Owl 2, one could use the `Portal` component by importing it and using it.
|
||||||
|
Now, it is no longer available. Instead, we can simply use the `t-portal` directive:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<div>
|
||||||
|
some content
|
||||||
|
<span t-portal="'body'">
|
||||||
|
portalled content
|
||||||
|
</span>
|
||||||
|
<div>
|
||||||
|
```
|
||||||
|
|
||||||
|
Rationale: it makes it slightly simpler to use (just need the directive, instead
|
||||||
|
of having to import and use a sub component), it makes the implementation slightly
|
||||||
|
simpler as well. Also, it prevents subclassing the Portal component, which could
|
||||||
|
be dangerous, since it is really doing weird stuff under the hood, and could
|
||||||
|
easily be broken inadvertendly.
|
||||||
|
|
||||||
|
### 34. `debounce` utility function has been removed
|
||||||
|
|
||||||
|
Rationale: it did not really help that much, is available as utility function
|
||||||
|
elsewhere, so, we decided to have a smaller footprint by focusing Owl on what
|
||||||
|
it does best.
|
||||||
|
|
||||||
|
### 35. `render` method does not return a promise anymore
|
||||||
|
|
||||||
|
Rationale: using the `render` method directly and waiting for it to complete
|
||||||
|
was slightly un-declarative. Also, it can be done using the lifecycle hooks
|
||||||
|
any way.
|
||||||
|
|
||||||
|
Migration: if necessary, one can use the lifecycle hooks to execute code after
|
||||||
|
the next mounted/patched operation.
|
||||||
|
|
||||||
|
### 36. `catchError` method is replaced by `onError` hook
|
||||||
|
|
||||||
|
The `catchError` method was used to provide a way to components to handle errors
|
||||||
|
occurring during the component lifecycle. This has been replaced by a `onError`
|
||||||
|
hook, with a similar API.
|
||||||
|
|
||||||
|
Rationale: `catchError` felt a little big awkward, when most of the way we
|
||||||
|
interact with componentss is via hooks. Using hooks felt more natural and
|
||||||
|
consistent.
|
||||||
|
|
||||||
|
Migration: mostly replace all `catchError` methods by `onError` hooks in the
|
||||||
|
`setup` method.
|
||||||
|
|
||||||
|
Documentation: [Error Handling](doc/reference/error_handling.md)
|
||||||
|
|
||||||
|
|
||||||
|
## 37. Support for inline css (`css` tag and static `style`) has been removed
|
||||||
|
|
||||||
|
Rationale: Owl tries to focus on what it does best, and supporting inline css
|
||||||
|
was not a priority. It used to support some simplified scss language, but it
|
||||||
|
was feared that it would cause more trouble than it was worth. Also, it seems
|
||||||
|
like it can be done in userspace.
|
||||||
|
|
||||||
|
Migration: it seems possible to implement an equivalent solution using hooks. A
|
||||||
|
simple implementation could look like this:
|
||||||
|
|
||||||
|
```js
|
||||||
|
let cache = {};
|
||||||
|
|
||||||
|
function useStyle(css) {
|
||||||
|
if (!css in cache) {
|
||||||
|
const sheet = document.createElement("style");
|
||||||
|
sheet.innerHTML = css;
|
||||||
|
cache[css] = sheet;
|
||||||
|
document.head.appendChild(sheet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 38. `t-raw` directive has been removed (replaced by `t-out`)
|
||||||
|
|
||||||
|
To match the Odoo qweb server implementation, Owl does no longer implement `t-raw`.
|
||||||
|
It is replaced by the `t-out` directive, which is safer: it requires the data
|
||||||
|
to be marked explicitely as markup if it is to be inserted without escaping.
|
||||||
|
Otherwise, it will be escaped (just like `t-esc`).
|
||||||
|
|
||||||
|
Migration: replace all `t-raw` uses by `t-out`, and uses the `markup` function
|
||||||
|
to mark all the js values.
|
||||||
|
|
||||||
|
Documentation: [Outputting data](doc/reference/templates.md#outputting-data)
|
||||||
|
|
||||||
|
## 39. `browser` object has been removed
|
||||||
|
|
||||||
|
Rationale: the `browser` object caused more trouble than it was worth. Also, it
|
||||||
|
seems like this should be done in user space, not at the framework level.
|
||||||
|
|
||||||
|
Migration: code should just be adapted to either use another browser object,
|
||||||
|
or to use native browser function (and then, just mock them directly).
|
||||||
|
|
||||||
|
## 40. Rendering a component does not necessarily render child components
|
||||||
|
|
||||||
|
Before, if one had the following component tree:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD;
|
||||||
|
A-->B;
|
||||||
|
A-->C;
|
||||||
|
```
|
||||||
|
|
||||||
|
when `A` would render, it would also render `B` and `C`. Now, in Owl 2, it will
|
||||||
|
(shallow) compare the before and after props, and `B` or `C` will only be rerendered
|
||||||
|
if their props have changed.
|
||||||
|
|
||||||
|
Now, the question is what happens if the props have changed, but in a deeper way?
|
||||||
|
In that case, Owl will know, because each props are now reactive. So, if some
|
||||||
|
inner value read by `B` was changed, then only `B` will be updated.
|
||||||
|
|
||||||
|
Rationale: This was just not possible in Owl 1, but it now possible. This is
|
||||||
|
due to the rewriteof the underlying rendering engine and the reactivity
|
||||||
|
system. The goal is to have a big performance boost in large screen with many
|
||||||
|
components: now Owl only rerender what is strictly useful.
|
||||||
|
|
||||||
|
|||||||
@@ -16,8 +16,10 @@ framework, written in Typescript, taking the best ideas from React and Vue in a
|
|||||||
simple and consistent way. Owl's main features are:
|
simple and consistent way. Owl's main features are:
|
||||||
|
|
||||||
- a declarative component system,
|
- a declarative component system,
|
||||||
- a reactivity system based on hooks,
|
- a fine grained reactivity system similar to Vue,
|
||||||
- concurrent mode by default,
|
- hooks
|
||||||
|
- fragments
|
||||||
|
- asynchronous rendering
|
||||||
|
|
||||||
Owl components are defined with ES6 classes and xml templates, uses an
|
Owl components are defined with ES6 classes and xml templates, uses an
|
||||||
underlying virtual DOM, integrates beautifully with hooks, and the rendering is
|
underlying virtual DOM, integrates beautifully with hooks, and the rendering is
|
||||||
@@ -38,28 +40,26 @@ const { Component, useState, mount, xml } = owl;
|
|||||||
|
|
||||||
class Counter extends Component {
|
class Counter extends Component {
|
||||||
static template = xml`
|
static template = xml`
|
||||||
<button t-on-click="() => state.value++">
|
<button t-on-click="() => state.value = state.value + props.increment">
|
||||||
Click Me! [<t t-esc="state.value"/>]
|
Click Me! [<t t-esc="state.value"/>]
|
||||||
</button>`;
|
</button>`;
|
||||||
|
|
||||||
state = useState({ value: 0 });
|
state = useState({ value: 0 });
|
||||||
}
|
}
|
||||||
|
|
||||||
class App extends Component {
|
class Root extends Component {
|
||||||
static template = xml`
|
static template = xml`
|
||||||
<div>
|
<span>Hello Owl</span>
|
||||||
<span>Hello Owl</span>
|
<Counter increment="2"/>`;
|
||||||
<Counter />
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
static components = { Counter };
|
static components = { Counter };
|
||||||
}
|
}
|
||||||
|
|
||||||
mount(App, document.body);
|
mount(Root, document.body);
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that the counter component is made reactive with the [`useState` hook](doc/reference/hooks.md#usestate).
|
Note that the counter component is made reactive with the [`useState` hook](doc/reference/hooks.md#usestate).
|
||||||
Also, all examples here uses the [`xml` helper](doc/reference/tags.md#xml-tag) to define inline templates.
|
Also, all examples here uses the [`xml` helper](doc/reference/templates.md#inline-templates) to define inline templates.
|
||||||
But this is not mandatory, many applications will load templates separately.
|
But this is not mandatory, many applications will load templates separately.
|
||||||
|
|
||||||
More interesting examples can be found on the
|
More interesting examples can be found on the
|
||||||
@@ -74,47 +74,42 @@ Are you new to Owl? This is the place to start!
|
|||||||
- [Tutorial: create a TodoList application](doc/learning/tutorial_todoapp.md)
|
- [Tutorial: create a TodoList application](doc/learning/tutorial_todoapp.md)
|
||||||
- [How to start an Owl project](doc/learning/quick_start.md)
|
- [How to start an Owl project](doc/learning/quick_start.md)
|
||||||
- [How to test Components](doc/learning/how_to_test.md)
|
- [How to test Components](doc/learning/how_to_test.md)
|
||||||
- [How to write Single File Components](doc/learning/how_to_write_sfc.md)
|
|
||||||
|
|
||||||
### Reference
|
### Reference
|
||||||
|
|
||||||
You will find here a complete reference of every feature, class or object
|
- [Overview](doc/readme.md)
|
||||||
provided by Owl.
|
- [App](doc/reference/app.md)
|
||||||
|
|
||||||
- [Animations](doc/reference/animations.md)
|
|
||||||
- [Browser](doc/reference/browser.md)
|
|
||||||
- [Component](doc/reference/component.md)
|
- [Component](doc/reference/component.md)
|
||||||
- [Content](doc/reference/content.md)
|
- [Component Lifecycle](doc/reference/component.md#lifecycle)
|
||||||
- [Concurrency Model](doc/reference/concurrency_model.md)
|
- [Concurrency Model](doc/reference/concurrency_model.md)
|
||||||
- [Configuration](doc/reference/config.md)
|
- [Dynamic sub components](doc/reference/component.md#dynamic-sub-components)
|
||||||
- [Context](doc/reference/context.md)
|
|
||||||
- [Environment](doc/reference/environment.md)
|
- [Environment](doc/reference/environment.md)
|
||||||
- [Event Bus](doc/reference/event_bus.md)
|
|
||||||
- [Event Handling](doc/reference/event_handling.md)
|
|
||||||
- [Error Handling](doc/reference/error_handling.md)
|
- [Error Handling](doc/reference/error_handling.md)
|
||||||
|
- [Event Handling](doc/reference/event_handling.md)
|
||||||
|
- [Form Input Bindings](doc/reference/input_bindings.md)
|
||||||
|
- [Fragments](doc/reference/templates.md#fragments)
|
||||||
- [Hooks](doc/reference/hooks.md)
|
- [Hooks](doc/reference/hooks.md)
|
||||||
- [Mounting a component](doc/reference/mounting.md)
|
- [Loading Templates](doc/reference/app.md#loading-templates)
|
||||||
- [Miscellaneous Components](doc/reference/misc.md)
|
- [Mounting a component](doc/reference/app.md#mount-helper)
|
||||||
- [Observer](doc/reference/observer.md)
|
- [Portal](doc/reference/portal.md)
|
||||||
- [Props](doc/reference/props.md)
|
- [Props](doc/reference/props.md)
|
||||||
- [Props Validation](doc/reference/props_validation.md)
|
- [Props Validation](doc/reference/props.md#props-validation)
|
||||||
- [QWeb Templating Language](doc/reference/qweb_templating_language.md)
|
- [Reactivity](doc/reference/reactivity.md)
|
||||||
- [QWeb Engine](doc/reference/qweb_engine.md)
|
- [Rendering SVG](doc/reference/templates.md#rendering-svg)
|
||||||
|
- [Refs](doc/reference/refs.md)
|
||||||
- [Slots](doc/reference/slots.md)
|
- [Slots](doc/reference/slots.md)
|
||||||
- [Tags](doc/reference/tags.md)
|
- [Sub components](doc/reference/component.md#sub-components)
|
||||||
|
- [Sub templates](doc/reference/templates.md#sub-templates)
|
||||||
|
- [Templates (Qweb)](doc/reference/templates.md)
|
||||||
|
- [Translations](doc/reference/translations.md)
|
||||||
- [Utils](doc/reference/utils.md)
|
- [Utils](doc/reference/utils.md)
|
||||||
|
|
||||||
### Other Topics
|
### Other Topics
|
||||||
|
|
||||||
This section provides miscellaneous document that explains some topics
|
- [Notes On Owl Architecture](doc/miscellaneous/architecture.md)
|
||||||
which cannot be considered either a tutorial, or reference documentation.
|
|
||||||
|
|
||||||
- [Owl architecture: the Virtual DOM](doc/miscellaneous/vdom.md)
|
|
||||||
- [Owl architecture: the rendering pipeline](doc/miscellaneous/rendering.md)
|
|
||||||
- [Comparison with React/Vue](doc/miscellaneous/comparison.md)
|
- [Comparison with React/Vue](doc/miscellaneous/comparison.md)
|
||||||
- [Why did Odoo built Owl?](doc/miscellaneous/why_owl.md)
|
- [Why did Odoo build Owl?](doc/miscellaneous/why_owl.md)
|
||||||
|
- [Changelog (from owl 1.x to 2.x)](CHANGELOG.md)
|
||||||
|
|
||||||
|
|
||||||
## Installing Owl
|
## Installing Owl
|
||||||
|
|
||||||
@@ -126,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.7](https://github.com/odoo/owl/releases/tag/v1.4.7)
|
- [owl-1.4.10](https://github.com/odoo/owl/releases/tag/v1.4.10)
|
||||||
|
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
# 🦉 How to write Single File Components 🦉
|
|
||||||
|
|
||||||
It is very useful to group code by feature instead of by type of file. It makes
|
|
||||||
it easier to scale application to larger size.
|
|
||||||
|
|
||||||
To do so, Owl has two small helpers that make it easy to define a
|
|
||||||
template or a stylesheet inside a javascript (or typescript) file: the
|
|
||||||
[`xml`](../reference/tags.md#xml-tag) and [`css`](../reference/tags.md#css-tag)
|
|
||||||
helper.
|
|
||||||
|
|
||||||
This means that the template, the style and the javascript code can be defined in
|
|
||||||
the same file. For example:
|
|
||||||
|
|
||||||
```js
|
|
||||||
const { Component } = owl;
|
|
||||||
const { xml, css } = owl.tags;
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
// TEMPLATE
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
const TEMPLATE = xml/* xml */ `
|
|
||||||
<div class="main">
|
|
||||||
<Sidebar/>
|
|
||||||
<Content />
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
// STYLE
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
const STYLE = css/* css */ `
|
|
||||||
.main {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 200px auto;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
// CODE
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
class Main extends Component {
|
|
||||||
static template = TEMPLATE;
|
|
||||||
static style = STYLE;
|
|
||||||
static components = { Sidebar, Content };
|
|
||||||
|
|
||||||
// rest of component...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that the above example has an inline xml comment, just after the `xml` call.
|
|
||||||
This is useful for some editor plugins, such as the VS Code addon
|
|
||||||
`Comment tagged template`, which, if installed, add syntax highlighting to the
|
|
||||||
content of the template string.
|
|
||||||
@@ -116,7 +116,7 @@ class App extends Component {}
|
|||||||
App.template = xml`<div>todo app</div>`;
|
App.template = xml`<div>todo app</div>`;
|
||||||
```
|
```
|
||||||
|
|
||||||
Note 3: writing inline templates with the [`xml` helper](../reference/tags.md#xml-tag)
|
Note 3: writing inline templates with the [`xml` helper](../reference/templates.md#inline-templates)
|
||||||
is nice, but there is no syntax highlighting, and this makes it very easy to
|
is nice, but there is no syntax highlighting, and this makes it very easy to
|
||||||
have malformed xml. Some editors support syntax highlighting for this situation.
|
have malformed xml. Some editors support syntax highlighting for this situation.
|
||||||
For example, VS Code has an addon `Comment tagged template`, which, if installed,
|
For example, VS Code has an addon `Comment tagged template`, which, if installed,
|
||||||
@@ -173,14 +173,14 @@ class Root extends Component {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The template contains a [`t-foreach`](../reference/qweb_templating_language.md#loops) loop to iterate
|
The template contains a [`t-foreach`](../reference/templates.md#loops) loop to iterate
|
||||||
through the tasks. It can find the `tasks` list from the component, since the
|
through the tasks. It can find the `tasks` list from the component, since the
|
||||||
component is the rendering context. Note that we use the `id` of each task as a
|
component is the rendering context. Note that we use the `id` of each task as a
|
||||||
`t-key`, which is very common. There are two css classes: `task-list` and `task`,
|
`t-key`, which is very common. There are two css classes: `task-list` and `task`,
|
||||||
that we will use in the next section.
|
that we will use in the next section.
|
||||||
|
|
||||||
Finally, notice the use of the `t-att-checked` attribute:
|
Finally, notice the use of the `t-att-checked` attribute:
|
||||||
prefixing an attribute by [`t-att`](../reference/qweb_templating_language.md#dynamic-attributes) makes
|
prefixing an attribute by [`t-att`](../reference/templates.md#dynamic-attributes) makes
|
||||||
it dynamic. Owl will evaluate the expression and set it as the value of the
|
it dynamic. Owl will evaluate the expression and set it as the value of the
|
||||||
attribute.
|
attribute.
|
||||||
|
|
||||||
@@ -277,10 +277,10 @@ A lot of stuff happened here:
|
|||||||
- the `Task` component has a `props` key: this is only useful for validation
|
- the `Task` component has a `props` key: this is only useful for validation
|
||||||
purpose. It says that each `Task` should be given exactly one prop, named
|
purpose. It says that each `Task` should be given exactly one prop, named
|
||||||
`task`. If this is not the case, Owl will throw an
|
`task`. If this is not the case, Owl will throw an
|
||||||
[error](../reference/props_validation.md). This is extremely
|
[error](../reference/props.md#props-validation). This is extremely
|
||||||
useful when refactoring components
|
useful when refactoring components
|
||||||
- finally, to activate the props validation, we need to set Owl's
|
- finally, to activate the props validation, we need to set Owl's
|
||||||
[mode](../reference/config.md#mode) to `dev`. This is done in the last argument
|
[mode](../reference/app.md#configuration) to `dev`. This is done in the last argument
|
||||||
of the `mount` function. Note that this should be removed when an app is used in a real
|
of the `mount` function. Note that this should be removed when an app is used in a real
|
||||||
production environment, since `dev` mode is slightly slower, due to extra
|
production environment, since `dev` mode is slightly slower, due to extra
|
||||||
checks and validations.
|
checks and validations.
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
# 🦉 Notes On Owl Architecture 🦉
|
||||||
|
|
||||||
|
We explain here how Owl is designed
|
||||||
|
|
||||||
|
Warning: these notes are technical by nature, and intended for people working
|
||||||
|
on Owl (or interested in understanding its design).
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Roughly speaking, Owl has 5 main parts:
|
||||||
|
|
||||||
|
- a virtual dom system (in `src/blockdom`)
|
||||||
|
- a component system (in `src/component`)
|
||||||
|
- a template compiler (located in the `src/compiler` folder)
|
||||||
|
- a small runtime code to tie them together (in `src/app`)
|
||||||
|
- a reactivity system (in `src/reactivity.ts`)
|
||||||
|
|
||||||
|
There are some other files, but the core of Owl can be understood with these
|
||||||
|
five main parts.
|
||||||
|
|
||||||
|
The virtual dom is an optimized virtual dom based on blocks, which supports
|
||||||
|
multi blocks (for fragments). Everything that owl renders is internally
|
||||||
|
represented by a virtual node. The job of the virtual dom is to efficiently
|
||||||
|
represent the current state of the application, and to build an actual DOM
|
||||||
|
representation when needed, or update the DOM whenever it is needed.
|
||||||
|
|
||||||
|
- some other helpers/smaller scale stuff
|
||||||
|
A rendering occurs in two phases:
|
||||||
|
|
||||||
|
- virtual rendering: this generates the virtual dom in memory, asynchronously
|
||||||
|
- patch: applies a virtual tree to the screen (synchronously)
|
||||||
|
|
||||||
|
There are several classes involved in a rendering:
|
||||||
|
|
||||||
|
- components
|
||||||
|
- a scheduler
|
||||||
|
- fibers: small objects containing some metadata, associated with a rendering of
|
||||||
|
a specific component
|
||||||
|
|
||||||
|
Components are organized in a dynamic component tree, visible in the user
|
||||||
|
interface. Whenever a rendering is initiated in a component `C`:
|
||||||
|
|
||||||
|
- a fiber is created on `C` with the rendering props information
|
||||||
|
- the virtual rendering phase starts on C (will asynchronously render all the
|
||||||
|
child components)
|
||||||
|
- the fiber is added to the scheduler, which will poll continuously, every
|
||||||
|
animation frame, if the fiber is done
|
||||||
|
- once it is done, the scheduler will call the task callback, which will apply
|
||||||
|
the patch (if it was not cancelled in the meantime).
|
||||||
|
|
||||||
|
# 🦉 VDom 🦉
|
||||||
|
|
||||||
|
Owl is a declarative component system: we declare the structure of the component
|
||||||
|
tree, and Owl will translate that to a list of imperative operations. This
|
||||||
|
translation is done by a virtual dom. This is the low level layer of Owl, most
|
||||||
|
developer will not need to call directly the virtual dom functions.
|
||||||
|
|
||||||
|
The main idea behind a virtual dom is to keep a in-memory representation of the
|
||||||
|
DOM (called a virtual node), and whenever some change is needed, to regenerate
|
||||||
|
a new representation, compute the difference between the old and the new, then
|
||||||
|
apply the changes.
|
||||||
|
|
||||||
|
`vdom` exports two functions:
|
||||||
|
|
||||||
|
- `h`: create a new virtual node
|
||||||
|
- `patch`: compare two virtual nodes, and apply the difference.
|
||||||
|
|
||||||
|
Note: Owl's virtual dom is a fork of [snabbdom](https://github.com/snabbdom/snabbdom).
|
||||||
@@ -78,7 +78,7 @@ additional tools, we made a lot of effort to make the most of the web platform.
|
|||||||
|
|
||||||
For example, Owl uses the standard `xml` parser that comes with every browser.
|
For example, Owl uses the standard `xml` parser that comes with every browser.
|
||||||
Because of that, Owl did not have to write its own template parser. Another
|
Because of that, Owl did not have to write its own template parser. Another
|
||||||
example is the [`xml`](../reference/tags.md#xml-tag) tag helper function, which makes use of
|
example is the [`xml`](../reference/templates.md#inline-templates) tag helper function, which makes use of
|
||||||
native template literals to allow in a natural way to write `xml` templates
|
native template literals to allow in a natural way to write `xml` templates
|
||||||
directly in the javascript code. This can be easily integrated with editor
|
directly in the javascript code. This can be easily integrated with editor
|
||||||
plugins to have autocompletion inside the template.
|
plugins to have autocompletion inside the template.
|
||||||
@@ -126,7 +126,7 @@ structured than a template language. Note that the tooling is quite impressive:
|
|||||||
there is a syntax highlighter for jsx here on github!
|
there is a syntax highlighter for jsx here on github!
|
||||||
|
|
||||||
By comparison, here is the equivalent Owl component, written with the
|
By comparison, here is the equivalent Owl component, written with the
|
||||||
[`xml`](../reference/tags.md#xml-tag) tag helper:
|
[`xml`](../reference/templates.md#inline-templates) tag helper:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
class Clock extends Component {
|
class Clock extends Component {
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
# 🦉 Rendering Pipeline 🦉
|
|
||||||
|
|
||||||
We explain here how Owl is designed, from the perspective of its rendering
|
|
||||||
pipeline.
|
|
||||||
|
|
||||||
Warning: these notes are technical by nature, and intended for people working
|
|
||||||
on Owl (or interested in understanding its design).
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
A rendering occurs in two phases:
|
|
||||||
|
|
||||||
- virtual rendering: this generates the virtual dom in memory, asynchronously
|
|
||||||
- patch: applies a virtual tree to the screen (synchronously)
|
|
||||||
|
|
||||||
There are several classes involved in a rendering:
|
|
||||||
|
|
||||||
- components
|
|
||||||
- a scheduler
|
|
||||||
- fibers: small objects containing some metadata, associated with a rendering of
|
|
||||||
a specific component
|
|
||||||
|
|
||||||
Components are organized in a dynamic component tree, visible in the user
|
|
||||||
interface. Whenever a rendering is initiated in a component `C`:
|
|
||||||
|
|
||||||
- a fiber is created on `C` with the rendering props information
|
|
||||||
- the virtual rendering phase starts on C (will asynchronously render all the
|
|
||||||
child components)
|
|
||||||
- the fiber is added to the scheduler, which will poll continuously, every
|
|
||||||
animation frame, if the fiber is done
|
|
||||||
- once it is done, the scheduler will call the task callback, which will apply
|
|
||||||
the patch (if it was not cancelled in the meantime).
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# 🦉 VDom 🦉
|
|
||||||
|
|
||||||
Owl is a declarative component system: we declare the structure of the component
|
|
||||||
tree, and Owl will translate that to a list of imperative operations. This
|
|
||||||
translation is done by a virtual dom. This is the low level layer of Owl, most
|
|
||||||
developer will not need to call directly the virtual dom functions.
|
|
||||||
|
|
||||||
The main idea behind a virtual dom is to keep a in-memory representation of the
|
|
||||||
DOM (called a virtual node), and whenever some change is needed, to regenerate
|
|
||||||
a new representation, compute the difference between the old and the new, then
|
|
||||||
apply the changes.
|
|
||||||
|
|
||||||
`vdom` exports two functions:
|
|
||||||
|
|
||||||
- `h`: create a new virtual node
|
|
||||||
- `patch`: compare two virtual nodes, and apply the difference.
|
|
||||||
|
|
||||||
Note: Owl's virtual dom is a fork of [snabbdom](https://github.com/snabbdom/snabbdom).
|
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
# 🦉 Owl overview 🦉
|
||||||
|
|
||||||
|
Here is a list of everything exported by the Owl library:
|
||||||
|
|
||||||
|
Main entities:
|
||||||
|
|
||||||
|
- [`App`](reference/app.md): represent an Owl application (mainly a root component,a set of templates, and a config)
|
||||||
|
- [`Component`](reference/component.md): the main class to define a concrete Owl component
|
||||||
|
- [`mount`](reference/app.md#mount-helper): main entry point for most application: mount a component to a target
|
||||||
|
- [`xml`](reference/templates.md#inline-templates): helper to define an inline template
|
||||||
|
|
||||||
|
Reactivity
|
||||||
|
|
||||||
|
- [`useState`](reference/reactivity.md#usestate): create a reactive object (hook, linked to a specific component)
|
||||||
|
- [`reactive`](reference/reactivity.md#reactive): create a reactive object (not linked to any component)
|
||||||
|
- [`markRaw`](reference/reactivity.md#markraw): mark an object or array so that it is ignored by the reactivity system
|
||||||
|
- [`toRaw`](reference/reactivity.md#toraw): given a reactive objet, return the raw (non reactive) underlying object
|
||||||
|
|
||||||
|
Lifecycle hooks:
|
||||||
|
|
||||||
|
- [`onWillStart`](reference/component.md#willstart): hook to define asynchronous code that should be executed before component is rendered
|
||||||
|
- [`onMounted`](reference/component.md#mounted): hook to define code that should be executed when component is mounted
|
||||||
|
- [`onWillPatch`](reference/component.md#willpatch): hook to define code that should be executed before component is patched
|
||||||
|
- [`onWillUpdateProps`](reference/component.md#willupdateprops): hook to define code that should be executed before component is updated
|
||||||
|
- [`onPatched`](reference/component.md#patched): hook to define code that should be executed when component is patched
|
||||||
|
- [`onWillRender`](reference/component.md#willrender): hook to define code that should be executed before component is rendered
|
||||||
|
- [`onRendered`](reference/component.md#rendered): hook to define code that should be executed after component is rendered
|
||||||
|
- [`onWillUnmount`](reference/component.md#willunmount): hook to define code that should be executed before component is unmounted
|
||||||
|
- [`onWillDestroy`](reference/component.md#willdestroy): hook to define code that should be executed before component is destroyed
|
||||||
|
- [`onError`](reference/component.md#onerror): hook to define a Owl error handler
|
||||||
|
|
||||||
|
Other hooks:
|
||||||
|
|
||||||
|
- [`useComponent`](reference/hooks.md#usecomponent): return a reference to the current component (useful to create derived hooks)
|
||||||
|
- [`useEffect`](reference/hooks.md#useeffect): define an effect with its dependencies
|
||||||
|
- [`useEnv`](reference/hooks.md#useenv): return a reference to the current env
|
||||||
|
- [`useExternalListener`](reference/hooks.md#useexternallistener): add a listener outside of a component DOM
|
||||||
|
- [`useRef`](reference/hooks.md#useref): get an object representing a reference (`t-ref`)
|
||||||
|
- [`useChildSubEnv`](reference/hooks.md#usesubenv-and-usechildsubenv): extend the current env with additional information (for child components)
|
||||||
|
- [`useSubEnv`](reference/hooks.md#usesubenv-and-usechildsubenv): extend the current env with additional information (for current component and child components)
|
||||||
|
|
||||||
|
Utility/helpers:
|
||||||
|
|
||||||
|
- [`EventBus`](reference/utils.md#eventbus): a simple event bus
|
||||||
|
- [`loadFile`](reference/utils.md#loadfile): an helper to load a file from the server
|
||||||
|
- [`markup`](reference/templates.md#outputting-data): utility function to define strings that represent html (should not be escaped)
|
||||||
|
- [`status`](reference/component.md#status-helper): utility function to get the status of a component (new, mounted or destroyed)
|
||||||
|
- [`whenReady`](reference/utils.md#whenready): utility function to execute code when DOM is ready
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
# 🦉 Animations 🦉
|
|
||||||
|
|
||||||
Animation is a complex topic. There are many different use cases, and many
|
|
||||||
solutions and technologies. Owl only supports some basic use cases.
|
|
||||||
|
|
||||||
## Simple CSS effects
|
|
||||||
|
|
||||||
Sometimes, using pure CSS is enough. For these use cases, Owl is not really
|
|
||||||
necessary: it just needs to render a DOM element with a specific class. For
|
|
||||||
example:
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<a class="btn flash" t-on-click="doSomething">Click</a>
|
|
||||||
```
|
|
||||||
|
|
||||||
with the following CSS:
|
|
||||||
|
|
||||||
```css
|
|
||||||
btn {
|
|
||||||
background-color: gray;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flash {
|
|
||||||
transition: background 0.5s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flash:active {
|
|
||||||
background-color: #41454a;
|
|
||||||
transition: background 0s;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
will produce a nice flash effect whenever the user clicks (or activates with the
|
|
||||||
keyboard) the button.
|
|
||||||
|
|
||||||
## CSS Transitions
|
|
||||||
|
|
||||||
A more complex situation occurs when we want to transition an element in or out
|
|
||||||
of the page. For example, we may want a fade-in and fade-out effect.
|
|
||||||
|
|
||||||
The `t-transition` directive is here to help us. It works on html elements and
|
|
||||||
on components, by adding and removing some css classes.
|
|
||||||
|
|
||||||
To perform useful transition effects, whenever an element appears or disappears,
|
|
||||||
it is necessary to add/remove some css style or class at some precise moment in
|
|
||||||
the lifetime of a node. Since this is not easy to do by hand, Owl `t-transition`
|
|
||||||
directive is there to help.
|
|
||||||
|
|
||||||
Whenever a node has a `t-transition` directive, with a `name` value, the following
|
|
||||||
sequence of events will happen:
|
|
||||||
|
|
||||||
At node insertion:
|
|
||||||
|
|
||||||
- the css classes `name-enter` and `name-enter-active` will be added directly
|
|
||||||
when the node is inserted into the DOM.
|
|
||||||
- on the next animation frame: the css class `name-enter` will be removed and the
|
|
||||||
class `name-enter-to` will be added (so they can be used to trigger css
|
|
||||||
transition effects).
|
|
||||||
- at the end of the transition, `name-enter-to` and `name-enter-active` will be removed.
|
|
||||||
|
|
||||||
At node destruction:
|
|
||||||
|
|
||||||
- the css classes `name-leave` and `name-leave-active` will be added before the
|
|
||||||
node is removed to the DOM.
|
|
||||||
- on the next animation frame: the css class `name-leave` will be removed and the
|
|
||||||
class `name-leave-to` will be added (so they can be used to trigger css
|
|
||||||
transition effects).
|
|
||||||
- at the end of the transition, `name-leave-to` and `name-leave-active` will be removed.
|
|
||||||
|
|
||||||
For example, a simple fade in/out effect can be done with this:
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<div>
|
|
||||||
<div t-if="state.flag" class="square" t-transition="fade">Hello</div>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
```css
|
|
||||||
.fade-enter-active,
|
|
||||||
.fade-leave-active {
|
|
||||||
transition: opacity 0.5s;
|
|
||||||
}
|
|
||||||
.fade-enter,
|
|
||||||
.fade-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The `t-transition` directive can be applied on a node element or on a component.
|
|
||||||
|
|
||||||
Notes:
|
|
||||||
|
|
||||||
Owl does not support more than one transition on a single node, so the
|
|
||||||
`t-transition` expression must be a single value (i.e. no space allowed).
|
|
||||||
|
|
||||||
## SCSS Mixins
|
|
||||||
|
|
||||||
If you use SCSS, you can use mixins to make generic animations. Here is an exemple with a fade in / fade out animation:
|
|
||||||
|
|
||||||
```scss
|
|
||||||
@mixin animation-fade($time, $name) {
|
|
||||||
.#{$name}_fade-enter-active,
|
|
||||||
.#{$name}_fade-active {
|
|
||||||
transition: all $time;
|
|
||||||
}
|
|
||||||
|
|
||||||
.#{$name}_fade-enter {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.#{$name}_fade-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
|
|
||||||
```scss
|
|
||||||
@include animation-fade(0.5s, "o_notification");
|
|
||||||
```
|
|
||||||
|
|
||||||
You can now have in your template:
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<SomeTag t-transition="o_notification_fade"/>
|
|
||||||
```
|
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
# 🦉 App 🦉
|
||||||
|
|
||||||
|
## Content
|
||||||
|
|
||||||
|
- [Overview](#overview)
|
||||||
|
- [API](#api)
|
||||||
|
- [Configuration](#configuration)
|
||||||
|
- [`mount` helper](#mount-helper)
|
||||||
|
- [Loading templates](#loading-templates)
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Every Owl application has a root element, a set of templates, an environment and
|
||||||
|
possibly a few other settings. The `App` class is a simple class that represents
|
||||||
|
all of these elements. Here is an example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const {Component, App } = owl;
|
||||||
|
|
||||||
|
class MyComponent extends Component { ... }
|
||||||
|
|
||||||
|
const app = new App(MyComponent, { props: {...}, templates: "..."});
|
||||||
|
app.mount(document.body);
|
||||||
|
```
|
||||||
|
|
||||||
|
The basic workflow is: create an `App` instance configured with the root
|
||||||
|
component, the templates, and possibly other settings. Then, we mount that
|
||||||
|
instance somewhere in the DOM.
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
- **`constructor(Root[, config])`**: first argument should be a component class (not
|
||||||
|
an instance), and the optional second argument is a configuration object (see below).
|
||||||
|
|
||||||
|
- **`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
|
||||||
|
to a target in the DOM. Note that this is an asynchronous operation: the `mount`
|
||||||
|
method returns a promise that resolves to the component instance whenever it
|
||||||
|
is complete.
|
||||||
|
|
||||||
|
The `option` object is an object with the following keys:
|
||||||
|
|
||||||
|
- **`position (string)`**: either `first-child` or `last-child`. This option determines
|
||||||
|
the position of the application in the target: either first or last child.
|
||||||
|
|
||||||
|
- **`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
|
||||||
|
|
||||||
|
Note that there is a `mount` helper to do that in just a line:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const { mount, Component } = owl;
|
||||||
|
|
||||||
|
class MyComponent extends Component {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
mount(MyComponent, document.body, { props: {...}, templates: "..."});
|
||||||
|
```
|
||||||
|
|
||||||
|
Here is the `mount` function signature:
|
||||||
|
|
||||||
|
**`mount(Component, target, config)`** with the following arguments:
|
||||||
|
|
||||||
|
- **`Component`**: a component class (Root component of the app)
|
||||||
|
- **`target`**: an html element, where the component will be mounted as last child
|
||||||
|
- **`config (optional)`**: a config object (the same as the App config object)
|
||||||
|
|
||||||
|
Most of the time, the `mount` helper is more convenient, but whenever one needs
|
||||||
|
a reference to the actual Owl App, then using the `App` class directly is
|
||||||
|
possible.
|
||||||
|
|
||||||
|
## Loading templates
|
||||||
|
|
||||||
|
Most applications will need to load templates whenever they start. Here is
|
||||||
|
what it could look like in practice:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// in the main js file:
|
||||||
|
const { loadFile, mount } = owl;
|
||||||
|
|
||||||
|
// async, so we can use async/await
|
||||||
|
(async function setup() {
|
||||||
|
const templates = await loadFile(`/some/endpoint/that/return/templates`);
|
||||||
|
const env = {
|
||||||
|
_t: someTranslateFn,
|
||||||
|
templates,
|
||||||
|
// possibly other stuff
|
||||||
|
};
|
||||||
|
|
||||||
|
mount(Root, document.body, { env });
|
||||||
|
})();
|
||||||
|
```
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
# 🦉 Browser 🦉
|
|
||||||
|
|
||||||
## Content
|
|
||||||
|
|
||||||
- [Overview](#overview)
|
|
||||||
- [Browser Content](#browser-content)
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
The browser object contains some browser native APIs, such as `setTimeout`, that
|
|
||||||
are used by Owl and its utility functions. They are exposed with the intent of
|
|
||||||
making them mockable if necessary.
|
|
||||||
|
|
||||||
```js
|
|
||||||
owl.browser.setTimeout === window.setTimeout; // return true
|
|
||||||
```
|
|
||||||
|
|
||||||
For now, this object contains some functions that are not used by Owl. They
|
|
||||||
will eventually be removed in Owl 2.0.
|
|
||||||
|
|
||||||
## Browser Content
|
|
||||||
|
|
||||||
More specifically, the `browser` object contains the following methods and objects:
|
|
||||||
|
|
||||||
- `setTimeout`
|
|
||||||
- `clearTimeout`
|
|
||||||
- `setInterval`
|
|
||||||
- `clearInterval`
|
|
||||||
- `requestAnimationFrame`
|
|
||||||
- `random`
|
|
||||||
- `Date`
|
|
||||||
- `fetch`
|
|
||||||
- `localStorage`
|
|
||||||
+271
-672
File diff suppressed because it is too large
Load Diff
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
Owl was designed from the very beginning with asynchronous components. This comes
|
Owl was designed from the very beginning with asynchronous components. This comes
|
||||||
from the `willStart` and the `willUpdateProps` lifecycle hooks. With these
|
from the `willStart` and the `willUpdateProps` lifecycle hooks. With these
|
||||||
methods, it is possible to build complex highly concurrent applications.
|
asynchronous hooks, it is possible to build complex highly concurrent applications.
|
||||||
|
|
||||||
Owl concurrent mode has several benefits: it makes it possible to delay the
|
Owl concurrent mode has several benefits: it makes it possible to delay the
|
||||||
rendering until some asynchronous operation is complete, it makes it possible
|
rendering until some asynchronous operation is complete, it makes it possible
|
||||||
@@ -35,7 +35,8 @@ two phases: _virtual rendering_ and _patching_.
|
|||||||
|
|
||||||
### Virtual rendering
|
### Virtual rendering
|
||||||
|
|
||||||
This phase represent the process of rendering a template, in memory, which create a virtual representation of the desired component html). The output of this phase is a
|
This phase represent the process of rendering a template, in memory, which creates
|
||||||
|
a virtual representation of the desired component html). The output of this phase is a
|
||||||
virtual DOM.
|
virtual DOM.
|
||||||
|
|
||||||
It is asynchronous: each subcomponents needs to either be created (so, `willStart`
|
It is asynchronous: each subcomponents needs to either be created (so, `willStart`
|
||||||
@@ -94,7 +95,7 @@ component (with some code like `app.mount(document.body)`).
|
|||||||
5. The method `mounted` is called recursively on all components in the following
|
5. The method `mounted` is called recursively on all components in the following
|
||||||
order: `E`, `D`, `C`, `B`, `A`.
|
order: `E`, `D`, `C`, `B`, `A`.
|
||||||
|
|
||||||
**Scenario 2: rerendering a component**. Now, let's assume that the user clicked on some
|
**Scenario 2: updating a component**. Now, let's assume that the user clicked on some
|
||||||
button in `C`, and this results in a state update, which is supposed to:
|
button in `C`, and this results in a state update, which is supposed to:
|
||||||
|
|
||||||
- update `D`,
|
- update `D`,
|
||||||
@@ -136,8 +137,7 @@ Here is what Owl will do:
|
|||||||
6. `mounted` hook is called on `F`, `patched` hooks are called on `D`, `C`
|
6. `mounted` hook is called on `F`, `patched` hooks are called on `D`, `C`
|
||||||
|
|
||||||
Tags are very small helpers to make it easy to write inline templates. There is
|
Tags are very small helpers to make it easy to write inline templates. There is
|
||||||
only one currently available tag: `xml`, but we plan to add other tags later,
|
only one currently available tag: `xml`.
|
||||||
such as a `css` tag, which will be used to write [single file components](../learning/how_to_write_sfc.md).
|
|
||||||
|
|
||||||
### Asynchronous Rendering
|
### Asynchronous Rendering
|
||||||
|
|
||||||
@@ -160,22 +160,4 @@ Here are a few tips on how to work with asynchronous components:
|
|||||||
1. Minimize the use of asynchronous components!
|
1. Minimize the use of asynchronous components!
|
||||||
2. Lazy loading external libraries is a good use case for async rendering. This
|
2. Lazy loading external libraries is a good use case for async rendering. This
|
||||||
is mostly fine, because we can assume that it will only takes a fraction of a
|
is mostly fine, because we can assume that it will only takes a fraction of a
|
||||||
second, and only once (see [`owl.utils.loadJS`](utils.md#loadjs))
|
second, and only once.
|
||||||
3. For all the other cases, the [`AsyncRoot`](misc.md#asyncroot) component is there to help you. When
|
|
||||||
this component is met, a new rendering
|
|
||||||
sub tree is created, such that the rendering of that component (and its
|
|
||||||
children) is not tied to the rendering of the rest of the interface. It can
|
|
||||||
be used on an asynchronous component, to prevent it from delaying the
|
|
||||||
rendering of the whole interface, or on a synchronous one, such that its
|
|
||||||
rendering isn't delayed by other (asynchronous) components. Note that this
|
|
||||||
directive has no effect on the first rendering, but only on subsequent ones
|
|
||||||
(triggered by state or props changes).
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<div t-name="ParentComponent">
|
|
||||||
<SyncChild />
|
|
||||||
<AsyncRoot>
|
|
||||||
<AsyncChild/>
|
|
||||||
</AsyncRoot>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
# 🦉 Config 🦉
|
|
||||||
|
|
||||||
The Owl framework is designed to work in many situations. However, it is
|
|
||||||
sometimes necessary to customize some behaviour. This is done by using the
|
|
||||||
global `config` object. It provides two settings:
|
|
||||||
|
|
||||||
- [`mode`](#mode) (default value: `prod`),
|
|
||||||
- [`enableTransitions`](#enabletransitions) (default value: `true`).
|
|
||||||
|
|
||||||
## Mode
|
|
||||||
|
|
||||||
By default, Owl is in _production_ mode, this means that it will try to do its
|
|
||||||
job fast, and skip some expensive operations. However, it is sometimes necessary
|
|
||||||
to have better information on what is going on, this is the purpose
|
|
||||||
of the `dev` mode.
|
|
||||||
|
|
||||||
Owl has a mode flag, in `owl.config.mode`. Its default value is `prod`, but
|
|
||||||
it can be set to `dev`:
|
|
||||||
|
|
||||||
```js
|
|
||||||
owl.config.mode = "dev";
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that templates compiled with the `prod` settings will not be recompiled.
|
|
||||||
So, changing this setting is best done at startup.
|
|
||||||
|
|
||||||
An important job done by the `dev` mode is to validate props for each component
|
|
||||||
creation and update. Also, extra props will cause an error.
|
|
||||||
|
|
||||||
## `enableTransitions`
|
|
||||||
|
|
||||||
Transitions are usually nice, but they can cause issues in some specific cases,
|
|
||||||
such as automated tests. It is uncomfortable having to wait for a transition
|
|
||||||
to end before moving to the next step.
|
|
||||||
|
|
||||||
To solve this issue, Owl can be configured to ignore the `t-transition` directive.
|
|
||||||
To do that, one only needs to set the `enableTransitions` flag to false:
|
|
||||||
|
|
||||||
```js
|
|
||||||
owl.config.enableTransitions = false;
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that it suffers from the same drawback as the "dev" mode: all compiled
|
|
||||||
templates, if any, will keep their current behaviours.
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
# 🦉 Owl Content 🦉
|
|
||||||
|
|
||||||
Here is a complete visual representation of everything exported by the `owl`
|
|
||||||
global object.
|
|
||||||
|
|
||||||
For example, `Component` is available at `owl.Component` and `EventBus` is
|
|
||||||
exported as `owl.core.EventBus`.
|
|
||||||
|
|
||||||
```
|
|
||||||
browser
|
|
||||||
Component misc
|
|
||||||
Context AsyncRoot
|
|
||||||
QWeb Portal
|
|
||||||
mount
|
|
||||||
useState tags
|
|
||||||
config css
|
|
||||||
mode xml
|
|
||||||
core utils
|
|
||||||
EventBus debounce
|
|
||||||
Observer escape
|
|
||||||
hooks loadJS
|
|
||||||
onWillStart loadFile
|
|
||||||
onMounted shallowEqual
|
|
||||||
onWillUpdateProps whenReady
|
|
||||||
onWillPatch
|
|
||||||
onPatched
|
|
||||||
onWillUnmount
|
|
||||||
useContext
|
|
||||||
useState
|
|
||||||
useRef
|
|
||||||
useComponent
|
|
||||||
useEnv
|
|
||||||
useSubEnv
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that for convenience, the `useState` hook is also exported at the root of the `owl` object.
|
|
||||||
@@ -6,15 +6,15 @@
|
|||||||
- [Setting an Environment](#setting-an-environment)
|
- [Setting an Environment](#setting-an-environment)
|
||||||
- [Using a sub environment](#using-a-sub-environment)
|
- [Using a sub environment](#using-a-sub-environment)
|
||||||
- [Content of an Environment](#content-of-an-environment)
|
- [Content of an Environment](#content-of-an-environment)
|
||||||
- [Special keys](#special-keys)
|
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
An environment is an object which contains a [`QWeb` instance](qweb_engine.md). Whenever
|
An environment is a shared object given to all components in a tree. It is not
|
||||||
a root component is created, it is assigned an environment (see
|
used by Owl itself, but it is useful for application developers to provide a
|
||||||
[below](#setting-an-environment) for more info on this). This environment is
|
simple communication channel between components (in addition to the props).
|
||||||
then automatically given to each sub component (and accessible in the `this.env`
|
|
||||||
property).
|
The `env` given to the [`App`](app.md) is assigned to the `env` component
|
||||||
|
property.
|
||||||
|
|
||||||
```
|
```
|
||||||
Root
|
Root
|
||||||
@@ -22,31 +22,14 @@ property).
|
|||||||
A B
|
A B
|
||||||
```
|
```
|
||||||
|
|
||||||
This way, all components share the same `QWeb` instance. Owl internally requires
|
Also, the `env` object is frozen when the application is started. This is done
|
||||||
that the environment has a `qweb` key which maps to a
|
to ensure a simpler mental model of what's happening in runtime. Note that it
|
||||||
[`QWeb`](qweb_engine.md) instance. This is the QWeb instance that will be used to
|
is only shallowly frozen, so sub objects can be modified.
|
||||||
render each templates in this specific component tree. Note that if no `QWeb`
|
|
||||||
instance is provided, Owl will simply generate it on the fly.
|
|
||||||
|
|
||||||
The environment is mostly static. Each application is free to add anything to
|
|
||||||
the environment, which is very useful, since this can be accessed by each sub
|
|
||||||
component.
|
|
||||||
|
|
||||||
## Setting an environment
|
## Setting an environment
|
||||||
|
|
||||||
An Owl application needs an [environment](environment.md) to be executed. The
|
The correct way to customize an environment is to simply give it to the `App`,
|
||||||
environment has an important key: the [QWeb](qweb_engine.md) instance, which will render
|
whenever it is created.
|
||||||
all templates.
|
|
||||||
|
|
||||||
Whenever a root component `App` is mounted, Owl will setup a valid environment by
|
|
||||||
following the next steps:
|
|
||||||
|
|
||||||
- take the `env` object defined on `App.env` (if no `env` was explicitly setup,
|
|
||||||
this will return the empty `env` object defined on `Component`)
|
|
||||||
- if `env.qweb` is not set, then Owl will create a `QWeb` instance.
|
|
||||||
|
|
||||||
The correct way to customize an environment is to simply set it up on the root
|
|
||||||
component class, before the first component is created:
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const env = {
|
const env = {
|
||||||
@@ -56,81 +39,38 @@ const env = {
|
|||||||
...
|
...
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
mount(App, { target: document.body, env });
|
|
||||||
|
new App(Root, { env }).mount(document.body);
|
||||||
|
|
||||||
|
// or alternatively
|
||||||
|
mount(App, document.body, { env });
|
||||||
```
|
```
|
||||||
|
|
||||||
It is also possible to simply share an environment between all root components,
|
|
||||||
by simply doing this:
|
|
||||||
|
|
||||||
```js
|
|
||||||
Component.env = myEnv; // will be the default env for all components
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that this environment is the global owl environment for an application. The
|
|
||||||
next section explains how to extend an environment for a specific sub component
|
|
||||||
and its children.
|
|
||||||
|
|
||||||
## Using a sub environment
|
## Using a sub environment
|
||||||
|
|
||||||
It is sometimes useful to add one (or more) specific keys to the environment,
|
It is sometimes useful to add one (or more) specific keys to the environment,
|
||||||
from the perspective of a specific component and its children. In that case, the
|
from the perspective of a specific component and its children. In that case, the
|
||||||
solution presented above will not work, since it sets the global environment.
|
solution presented above will not work, since it sets the global environment.
|
||||||
|
|
||||||
There is a hook for this situation: [`useSubEnv`](hooks.md#usesubenv).
|
There are two hooks for this situation: [`useSubEnv` and `useChildSubEnv`](hooks.md#usesubenv-and-usechildsubenv).
|
||||||
|
|
||||||
```js
|
```js
|
||||||
class FormComponent extends Component {
|
class SomeComponent extends Component {
|
||||||
constructor(parent, props) {
|
setup() {
|
||||||
super(parent, props);
|
useSubEnv({ myKey: someValue }); // myKey is now available for all child components
|
||||||
useSubEnv({ myKey: someValue });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Content of an Environment
|
## Content of an Environment
|
||||||
|
|
||||||
Some good use cases for additional keys in the environment are:
|
The `env` object content is totally up to the application developer. However,
|
||||||
|
some good use cases for additional keys in the environment are:
|
||||||
|
|
||||||
- some configuration keys,
|
- some configuration keys,
|
||||||
- session information,
|
- session information,
|
||||||
- generic services (such as doing rpcs).
|
- generic services (such as doing rpcs).
|
||||||
|
- other utility functions that one want to inject, such as a translation function.
|
||||||
|
|
||||||
Doing it this way means that components are easily testable: we can simply
|
Doing it this way means that components are easily testable: we can simply
|
||||||
create a test environment with mock services.
|
create a test environment with mock services.
|
||||||
|
|
||||||
For example:
|
|
||||||
|
|
||||||
```js
|
|
||||||
async function myEnv() {
|
|
||||||
const templates = await loadTemplates();
|
|
||||||
const qweb = new QWeb({ templates });
|
|
||||||
const session = getSession();
|
|
||||||
|
|
||||||
return {
|
|
||||||
_t: myTranslateFunction,
|
|
||||||
session: session,
|
|
||||||
qweb: qweb,
|
|
||||||
services: {
|
|
||||||
localStorage: localStorage,
|
|
||||||
rpc: rpc,
|
|
||||||
},
|
|
||||||
debug: false,
|
|
||||||
inMobileMode: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function start() {
|
|
||||||
const env = await myEnv();
|
|
||||||
mount(App, { target: document.body, env });
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Special Keys
|
|
||||||
|
|
||||||
There are two special key/value added by Owl if not provided in the environment:
|
|
||||||
the `QWeb` instance and a `browser` object:
|
|
||||||
|
|
||||||
- `qweb` will be set to an empty `QWeb` instance. This is absolutely necessary
|
|
||||||
for Owl to be able to render anything
|
|
||||||
- `browser`: this is an object that contains some common access points to the
|
|
||||||
browser methods with a side effect. See [browser](browser.md) for more information. Note that the browser object will be removed from the environment in Owl 2.0.
|
|
||||||
|
|||||||
@@ -3,60 +3,30 @@
|
|||||||
## Content
|
## Content
|
||||||
|
|
||||||
- [Overview](#overview)
|
- [Overview](#overview)
|
||||||
|
- [Managing Errors](#managing-errors)
|
||||||
- [Example](#example)
|
- [Example](#example)
|
||||||
- [Reference](#reference)
|
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
By default, whenever an error occurs in the rendering of an Owl application, we
|
By default, whenever an error occurs in the rendering of an Owl application, we
|
||||||
destroy the whole application. Otherwise, we cannot offer any guarantee on the
|
destroy the whole application. Otherwise, we cannot offer any guarantee on the
|
||||||
state of the resulting component tree. It might be hopelessly corrupted, but
|
state of the resulting component tree. It might be hopelessly corrupted, but
|
||||||
without any user-visible state.
|
without any user-visible feedback.
|
||||||
|
|
||||||
Clearly, it sometimes is a little bit extreme to destroy the application. This
|
Clearly, it is usually a little bit extreme to destroy the application. This
|
||||||
is why we have a builtin mechanism to handle rendering errors (and errors coming
|
is why we need a mechanism to handle rendering errors (and errors coming
|
||||||
from lifecycle hooks): the `catchError` hook.
|
from lifecycle hooks): the `onError` hook.
|
||||||
|
|
||||||
## Example
|
The main idea is that the `onError` hook register a function that will be called
|
||||||
|
with the error. This function need to handle the situation, most of the time by
|
||||||
|
updating some state and rerendering itself, so the application can return to a
|
||||||
|
normal state.
|
||||||
|
|
||||||
For example, here is how we could implement an `ErrorBoundary` component:
|
## Managing Errors
|
||||||
|
|
||||||
```xml
|
Whenever the `onError` lifecycle hook is used, all errors coming from
|
||||||
<div t-name="ErrorBoundary">
|
|
||||||
<t t-if="state.error">
|
|
||||||
Error handled
|
|
||||||
</t>
|
|
||||||
<t t-else="">
|
|
||||||
<t t-slot="default" />
|
|
||||||
</t>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
```js
|
|
||||||
class ErrorBoundary extends Component {
|
|
||||||
state = useState({ error: false });
|
|
||||||
|
|
||||||
catchError() {
|
|
||||||
this.state.error = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Using the `ErrorBoundary` is then extremely simple:
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<ErrorBoundary><SomeOtherComponent/></ErrorBoundary>
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that we need to be careful here: the fallback UI should not throw any
|
|
||||||
error, otherwise we risk going into an infinite loop (also, see the page on
|
|
||||||
[slots](slots.md) for more information on the `t-slot` directive).
|
|
||||||
|
|
||||||
## Reference
|
|
||||||
|
|
||||||
Whenever the `catchError` lifecycle hook is implemented, all errors coming from
|
|
||||||
sub components rendering and/or lifecycle method calls will be caught and given
|
sub components rendering and/or lifecycle method calls will be caught and given
|
||||||
to the `catchError` method. This allows us to properly handle the error, and to
|
to the `onError` method. This allows us to properly handle the error, and to
|
||||||
not break the application.
|
not break the application.
|
||||||
|
|
||||||
There are important things to know:
|
There are important things to know:
|
||||||
@@ -65,17 +35,41 @@ There are important things to know:
|
|||||||
Owl will destroy the full application. This is done on purpose, because Owl
|
Owl will destroy the full application. This is done on purpose, because Owl
|
||||||
cannot guarantee that the state is not corrupted from this point on.
|
cannot guarantee that the state is not corrupted from this point on.
|
||||||
|
|
||||||
- errors coming from event handlers are NOT managed by `catchError` or any other
|
- errors coming from event handlers are NOT managed by `onError` or any other
|
||||||
owl mechanism. This is up to the application developer to properly recover
|
owl mechanism. This is up to the application developer to properly recover
|
||||||
from an error
|
from an error
|
||||||
|
|
||||||
Also, it may be useful to know that whenever an error is caught, it is then
|
- if an error handler is unable to properly handle an error, it can just rethrow
|
||||||
broadcasted to the application by an event on the `qweb` instance. It may be
|
an error, and Owl will try looking for another error handler up the component
|
||||||
useful, for example, to log the error somewhere.
|
tree.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
For example, here is how we could implement a generic component `ErrorBoundary`
|
||||||
|
that render its content, and a fallback if an error happened.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
env.qweb.on("error", null, function (error) {
|
class ErrorBoundary extends Component {
|
||||||
// do something
|
static template = xml`
|
||||||
// react to the error
|
<t t-if="error" t-slot="fallback">An error occurred</t>
|
||||||
});
|
<t t-else="" t-slot="content"`;
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
this.state = useState({ error: false });
|
||||||
|
onError(() => (this.state.error = true));
|
||||||
|
}
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Using the `ErrorBoundary` is then simple simple:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<ErrorBoundary>
|
||||||
|
<SomeOtherComponent/>
|
||||||
|
<t t-set-slot="fallback">Some specific error message</t>
|
||||||
|
</ErrorBoundary>
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that we need to be careful here: the fallback UI should not throw any
|
||||||
|
error, otherwise we risk going into an infinite loop (also, see the page on
|
||||||
|
[slots](slots.md) for more information on the `t-slot` directive).
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
# 🦉 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 |
|
|
||||||
@@ -3,22 +3,14 @@
|
|||||||
## Content
|
## Content
|
||||||
|
|
||||||
- [Event Handling](#event-handling)
|
- [Event Handling](#event-handling)
|
||||||
- [Business DOM Events](#business-dom-events)
|
|
||||||
- [Inline Event Handlers](#inline-event-handlers)
|
|
||||||
- [Modifiers](#modifiers)
|
- [Modifiers](#modifiers)
|
||||||
|
- [Synthetic Events](#synthetic-events)
|
||||||
|
|
||||||
## Event Handling
|
## Event Handling
|
||||||
|
|
||||||
In a component's template, it is useful to be able to register handlers on DOM
|
In a component's template, it is useful to be able to register handlers on DOM
|
||||||
elements to some specific events. This is what makes a template _alive_. There
|
elements to some specific events. This is what makes a template _alive_. This
|
||||||
are four different use cases.
|
is done with the `t-on` directive. For example:
|
||||||
|
|
||||||
1. Register an event handler on a DOM node (_pure_ DOM event)
|
|
||||||
2. Register an event handler on a component (_pure_ DOM event)
|
|
||||||
3. Register an event handler on a DOM node (_business_ DOM event)
|
|
||||||
4. Register an event handler on a component (_business_ DOM event)
|
|
||||||
|
|
||||||
A _pure_ DOM event is directly triggered by a user interaction (e.g. a `click`).
|
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<button t-on-click="someMethod">Do something</button>
|
<button t-on-click="someMethod">Do something</button>
|
||||||
@@ -31,93 +23,28 @@ button.addEventListener("click", component.someMethod.bind(component));
|
|||||||
```
|
```
|
||||||
|
|
||||||
The suffix (`click` in this example) is simply the name of the actual DOM
|
The suffix (`click` in this example) is simply the name of the actual DOM
|
||||||
event.
|
event. The value of the `t-on` expression should be a valid javascript expression
|
||||||
|
that evaluates to a function in the context of the current component. So, one
|
||||||
## Business DOM Events
|
can get a reference to the event, or pass some additional arguments. For example,
|
||||||
|
all the following expressions are valid:
|
||||||
A _business_ DOM event is triggered by a call to `trigger` on a component.
|
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<MyComponent t-on-menu-loaded="someMethod" />
|
<button t-on-click="someMethod">Do something</button>
|
||||||
|
<button t-on-click="() => this.increment(3)">Add 3</button>
|
||||||
|
<button t-on-click="ev => this.doStuff(ev, 'value')">Do something</button>
|
||||||
```
|
```
|
||||||
|
|
||||||
```js
|
Notice the use of the `this` keyword in the lambda function: this is the
|
||||||
class MyComponent {
|
correct way to call a method on the component in a lambda function.
|
||||||
someWhere() {
|
|
||||||
const payload = ...;
|
|
||||||
this.trigger('menu-loaded', payload);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The call to `trigger` generates an `OwlEvent`, a subclass of [_CustomEvent_](https://developer.mozilla.org/docs/Web/Guide/Events/Creating_and_triggering_events)
|
One could use the following expression:
|
||||||
with an additional attribute `originalComponent` (the component that triggered
|
|
||||||
the event). The generated event is of type `menu-loaded` and dispatches it on
|
|
||||||
the component's DOM element (`this.el`). The event bubbles and is cancelable.
|
|
||||||
The parent component listening to event `menu-loaded` will receive the payload
|
|
||||||
in its `someMethod` handler (in the `detail` property of the event), whenever
|
|
||||||
the event is triggered.
|
|
||||||
|
|
||||||
```js
|
|
||||||
class ParentComponent {
|
|
||||||
someMethod(ev) {
|
|
||||||
const payload = ev.detail;
|
|
||||||
...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
By convention, we use KebabCase for the name of _business_ events.
|
|
||||||
|
|
||||||
The `t-on` directive allows to prebind its arguments. For example,
|
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<button t-on-click="someMethod(expr)">Do something</button>
|
<button t-on-click="() => increment(3)">Add 3</button>
|
||||||
```
|
```
|
||||||
|
|
||||||
Here, `expr` is a valid Owl expression, so it could be `true` or some variable
|
But then, the increment function may be unbound (unless the component binds it
|
||||||
from the rendering context.
|
in its setup function, for example).
|
||||||
|
|
||||||
### Type Hinting
|
|
||||||
|
|
||||||
Note that if you work with Typescript, the `trigger` method is generic on the type of the payload.
|
|
||||||
|
|
||||||
You can then describe the type of the event, so you will see typing errors...
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
this.trigger<MyCustomPayload>("my-custom-event", payload);
|
|
||||||
```
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
myCustomEventHandler(ev: OwlEvent<MyCustomPayload>) { ... }
|
|
||||||
```
|
|
||||||
|
|
||||||
## Inline Event Handlers
|
|
||||||
|
|
||||||
One can also directly specify inline statements. For example,
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<button t-on-click="state.counter++">Increment counter</button>
|
|
||||||
```
|
|
||||||
|
|
||||||
Here, `state` must be defined in the rendering context (typically the component)
|
|
||||||
as it will be translated to:
|
|
||||||
|
|
||||||
```js
|
|
||||||
button.addEventListener("click", () => {
|
|
||||||
context.state.counter++;
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
Warning: inline expressions are evaluated in the context of the template. This
|
|
||||||
means that they can access the component methods and properties. But if they set
|
|
||||||
a key, the inline statement will actually not modify the component, but a key in
|
|
||||||
a sub scope.
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<button t-on-click="value = 1">Set value to 1 (does not work!!!)</button>
|
|
||||||
<button t-on-click="state.value = 1">Set state.value to 1 (work as expected)</button>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Modifiers
|
## Modifiers
|
||||||
|
|
||||||
@@ -125,12 +52,13 @@ In order to remove the DOM event details from the event handlers (like calls to
|
|||||||
`event.preventDefault`) and let them focus on data logic, _modifiers_ can be
|
`event.preventDefault`) and let them focus on data logic, _modifiers_ can be
|
||||||
specified as additional suffixes of the `t-on` directive.
|
specified as additional suffixes of the `t-on` directive.
|
||||||
|
|
||||||
| Modifier | Description |
|
| Modifier | Description |
|
||||||
| ---------- | ------------------------------------------------------------------------------------------------------------------------ |
|
| ------------ | ------------------------------------------------------------------------------------------------------------------------ |
|
||||||
| `.stop` | calls `event.stopPropagation()` before calling the method |
|
| `.stop` | calls `event.stopPropagation()` before calling the method |
|
||||||
| `.prevent` | calls `event.preventDefault()` before calling the method |
|
| `.prevent` | calls `event.preventDefault()` before calling the method |
|
||||||
| `.self` | calls the method only if the `event.target` is the element itself |
|
| `.self` | calls the method only if the `event.target` is the element itself |
|
||||||
| `.capture` | bind the event handler in [capture](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) mode. |
|
| `.capture` | bind the event handler in [capture](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) mode. |
|
||||||
|
| `.synthetic` | define a synthetic event handler (see below) |
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<button t-on-click.stop="someMethod">Do something</button>
|
<button t-on-click.stop="someMethod">Do something</button>
|
||||||
@@ -149,3 +77,26 @@ modifiers. For example,
|
|||||||
```
|
```
|
||||||
|
|
||||||
This will simply stop the propagation of the event.
|
This will simply stop the propagation of the event.
|
||||||
|
|
||||||
|
## Synthetic Events
|
||||||
|
|
||||||
|
In some cases, attaching an event handler for each element of large lists has
|
||||||
|
a non trivial cost. Owl provides a way to efficiently improve the performance:
|
||||||
|
with synthetic event, it actually adds only one handler on the document body,
|
||||||
|
and will properly call the handler, just as expected.
|
||||||
|
|
||||||
|
The only difference with regular events is that the event is caught at the document
|
||||||
|
body, so it cannot be stopped before it actually gets there. Since it may be
|
||||||
|
surprising in some cases, it is not enabled by default.
|
||||||
|
|
||||||
|
To enable it, one can just use the `.synthetic` suffix:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<div>
|
||||||
|
<t t-foreach="largeList" t-as="elem" t-key="elem.id">
|
||||||
|
<button t-on-click.synthetic="doSomething" ...>
|
||||||
|
<!-- some content -->
|
||||||
|
</button>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|||||||
+158
-280
@@ -3,24 +3,17 @@
|
|||||||
## Content
|
## Content
|
||||||
|
|
||||||
- [Overview](#overview)
|
- [Overview](#overview)
|
||||||
- [Example: Mouse Position](#example-mouse-position)
|
- [The Hook Rule](#the-hook-rule)
|
||||||
- [Example: Autofocus](#example-autofocus)
|
- [Lifecycle hooks](#lifecycle-hooks)
|
||||||
- [Reference](#reference)
|
- [Other hooks](#other-hooks)
|
||||||
- [One Rule](#one-rule)
|
|
||||||
- [`useState`](#usestate)
|
- [`useState`](#usestate)
|
||||||
- [`onMounted`](#onmounted)
|
|
||||||
- [`onWillUnmount`](#onwillunmount)
|
|
||||||
- [`onWillPatch`](#onwillpatch)
|
|
||||||
- [`onPatched`](#onpatched)
|
|
||||||
- [`onWillStart`](#onwillstart)
|
|
||||||
- [`onWillUpdateProps`](#onwillupdateprops)
|
|
||||||
- [`useContext`](#usecontext)
|
|
||||||
- [`useRef`](#useref)
|
- [`useRef`](#useref)
|
||||||
- [`useSubEnv`](#usesubenv)
|
- [`useSubEnv` and `useChildSubEnv`](#usesubenv-and-usechildsubenv)
|
||||||
- [`useExternalListener`](#useexternallistener)
|
- [`useExternalListener`](#useexternallistener)
|
||||||
- [`useComponent`](#usecomponent)
|
- [`useComponent`](#usecomponent)
|
||||||
- [`useEnv`](#useenv)
|
- [`useEnv`](#useenv)
|
||||||
- [Making customized hooks](#making-customized-hooks)
|
- [`useEffect`](#useeffect)
|
||||||
|
- [Example: Mouse Position](#example-mouse-position)
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
@@ -39,96 +32,9 @@ Hooks work beautifully with Owl components: they solve the problems mentioned
|
|||||||
above, and in particular, they are the perfect way to make your component
|
above, and in particular, they are the perfect way to make your component
|
||||||
reactive.
|
reactive.
|
||||||
|
|
||||||
## Example: mouse position
|
## The Hook Rule
|
||||||
|
|
||||||
Here is the classical example of a non trivial hook to track the mouse position.
|
There is only one rule: every hook for a component has to be called in the _setup_ method, or in class fields:
|
||||||
|
|
||||||
```js
|
|
||||||
const { useState, onMounted, onWillUnmount } = owl.hooks;
|
|
||||||
|
|
||||||
// We define here a custom behaviour: this hook tracks the state of the mouse
|
|
||||||
// position
|
|
||||||
function useMouse() {
|
|
||||||
const position = useState({ x: 0, y: 0 });
|
|
||||||
|
|
||||||
function update(e) {
|
|
||||||
position.x = e.clientX;
|
|
||||||
position.y = e.clientY;
|
|
||||||
}
|
|
||||||
onMounted(() => {
|
|
||||||
window.addEventListener("mousemove", update);
|
|
||||||
});
|
|
||||||
onWillUnmount(() => {
|
|
||||||
window.removeEventListener("mousemove", update);
|
|
||||||
});
|
|
||||||
|
|
||||||
return position;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Main root component
|
|
||||||
class App extends owl.Component {
|
|
||||||
static template = xml`
|
|
||||||
<div t-name="App">
|
|
||||||
<div>Mouse: <t t-esc="mouse.x"/>, <t t-esc="mouse.y"/></div>
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
// this hooks is bound to the 'mouse' property.
|
|
||||||
mouse = useMouse();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that we use the prefix `use` for hooks, just like in React. This is just
|
|
||||||
a convention.
|
|
||||||
|
|
||||||
## Example: autofocus
|
|
||||||
|
|
||||||
Hooks can be combined to create the desired effect. For example, the following
|
|
||||||
hook combines the `useRef` hook with the `onPatched` and `onMounted` functions
|
|
||||||
to create an easy way to focus an input whenever it appears in the DOM:
|
|
||||||
|
|
||||||
```js
|
|
||||||
function useAutofocus(name) {
|
|
||||||
let ref = useRef(name);
|
|
||||||
let isInDom = false;
|
|
||||||
function updateFocus() {
|
|
||||||
if (!isInDom && ref.el) {
|
|
||||||
isInDom = true;
|
|
||||||
ref.el.focus();
|
|
||||||
} else if (isInDom && !ref.el) {
|
|
||||||
isInDom = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onPatched(updateFocus);
|
|
||||||
onMounted(updateFocus);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This hook takes the name of a valid `t-ref` directive, which should be present
|
|
||||||
in the template. It then checks whenever the component is mounted or patched if
|
|
||||||
the reference is not valid, and in this case, it will focus the node element.
|
|
||||||
This hook can be used like this:
|
|
||||||
|
|
||||||
```js
|
|
||||||
class SomeComponent extends Component {
|
|
||||||
static template = xml`
|
|
||||||
<div>
|
|
||||||
<input />
|
|
||||||
<input t-ref="myinput"/>
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
constructor(...args) {
|
|
||||||
super(...args);
|
|
||||||
useAutofocus("myinput");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Reference
|
|
||||||
|
|
||||||
### One rule
|
|
||||||
|
|
||||||
There is only one rule: every hook for a component has to be called in the
|
|
||||||
constructor, in the _setup_ method, or in class fields:
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
// ok
|
// ok
|
||||||
@@ -136,14 +42,6 @@ class SomeComponent extends Component {
|
|||||||
state = useState({ value: 0 });
|
state = useState({ value: 0 });
|
||||||
}
|
}
|
||||||
|
|
||||||
// also ok
|
|
||||||
class SomeComponent extends Component {
|
|
||||||
constructor(...args) {
|
|
||||||
super(...args);
|
|
||||||
this.state = useState({ value: 0 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// also ok
|
// also ok
|
||||||
class SomeComponent extends Component {
|
class SomeComponent extends Component {
|
||||||
setup() {
|
setup() {
|
||||||
@@ -159,15 +57,24 @@ class SomeComponent extends Component {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
As you can see, the `useState` hook does not need to be given a reference to
|
## Lifecycle Hooks
|
||||||
the component. This is possible because there is a way to get a reference to the
|
|
||||||
current component: the `Component.current` static property is the reference to the
|
|
||||||
component instance that is currently being created.
|
|
||||||
|
|
||||||
Hooks need to be called in the constructor to ensure that this reference is
|
All lifecycle hooks are documented in detail in their specific [section](component.md#lifecycle).
|
||||||
properly set. This is also a good thing for performance reasons (Owl can use
|
|
||||||
this to optimize its implementation), and for a clean architecture (this makes
|
| Hook | Description |
|
||||||
it easier for developers to understand what is really happening in a component).
|
| ----------------------------------------------------- | ---------------------------------------------------------------------- |
|
||||||
|
| **[onWillStart](component.md#willstart)** | async, before first rendering |
|
||||||
|
| **[onWillRender](component.md#willrender)** | just before component is rendered |
|
||||||
|
| **[onRendered](component.md#rendered)** | just after component is rendered |
|
||||||
|
| **[onMounted](component.md#mounted)** | just after component is rendered and added to the DOM |
|
||||||
|
| **[onWillUpdateProps](component.md#willupdateprops)** | async, before props update |
|
||||||
|
| **[onWillPatch](component.md#willpatch)** | just before the DOM is patched |
|
||||||
|
| **[onPatched](component.md#patched)** | just after the DOM is patched |
|
||||||
|
| **[onWillUnmount](component.md#willunmount)** | just before removing component from DOM |
|
||||||
|
| **[onWillDestroy](component.md#willdestroy)** | just before component is destroyed |
|
||||||
|
| **[onError](component.md#onerror)** | catch and handle errors (see [error handling page](error_handling.md)) |
|
||||||
|
|
||||||
|
## Other Hooks
|
||||||
|
|
||||||
### `useState`
|
### `useState`
|
||||||
|
|
||||||
@@ -178,9 +85,9 @@ The `useState` hook has to be given an object or an array, and will return
|
|||||||
an observed version of it (using a `Proxy`).
|
an observed version of it (using a `Proxy`).
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const { useState } = owl.hooks;
|
const { useState, Component } = owl;
|
||||||
|
|
||||||
class Counter extends owl.Component {
|
class Counter extends Component {
|
||||||
static template = xml`
|
static template = xml`
|
||||||
<button t-on-click="increment">
|
<button t-on-click="increment">
|
||||||
Click Me! [<t t-esc="state.value"/>]
|
Click Me! [<t t-esc="state.value"/>]
|
||||||
@@ -197,128 +104,38 @@ class Counter extends owl.Component {
|
|||||||
It is important to remember that `useState` only works with objects or arrays. It
|
It is important to remember that `useState` only works with objects or arrays. It
|
||||||
is necessary, since Owl needs to react to a change in state.
|
is necessary, since Owl needs to react to a change in state.
|
||||||
|
|
||||||
### `onMounted`
|
|
||||||
|
|
||||||
`onMounted` is not a user hook, but is a building block designed to help make useful
|
|
||||||
abstractions. `onMounted` registers a callback, which will be called when the component
|
|
||||||
is mounted (see example on top of this page).
|
|
||||||
|
|
||||||
### `onWillUnmount`
|
|
||||||
|
|
||||||
`onWillUnmount` is not a user hook, but is a building block designed to help make useful
|
|
||||||
abstractions. `onWillUnmount` registers a callback, which will be called when the component
|
|
||||||
is unmounted (see example on top of this page).
|
|
||||||
|
|
||||||
### `onWillPatch`
|
|
||||||
|
|
||||||
`onWillPatch` is not a user hook, but is a building block designed to help make useful
|
|
||||||
abstractions. `onWillPatch` registers a callback, which will be called just
|
|
||||||
before the component patched.
|
|
||||||
|
|
||||||
### `onPatched`
|
|
||||||
|
|
||||||
`onPatched` is not a user hook, but is a building block designed to help make useful
|
|
||||||
abstractions. `onPatched` registers a callback, which will be called just
|
|
||||||
after the component patched.
|
|
||||||
|
|
||||||
### `onWillStart`
|
|
||||||
|
|
||||||
`onWillStart` is an asynchronous hook. This means that the function registered
|
|
||||||
in the hook will be run just before the component is first rendered and can return a
|
|
||||||
promise, to express the fact that it is an asynchronous operation.
|
|
||||||
|
|
||||||
Note that if there are more than one `onWillStart` registered callback, then they
|
|
||||||
will all be run in parallel.
|
|
||||||
|
|
||||||
It can be used to load some initial data. For example, the following hook will
|
|
||||||
automatically load some data from the server, and return an object that will
|
|
||||||
be ready whenever the component is rendered:
|
|
||||||
|
|
||||||
```js
|
|
||||||
function useLoader() {
|
|
||||||
const component = Component.current;
|
|
||||||
const record = useState({});
|
|
||||||
onWillStart(async () => {
|
|
||||||
const recordId = component.props.id;
|
|
||||||
Object.assign(record, await fetchSomeRecord(recordId));
|
|
||||||
});
|
|
||||||
return record;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that this example does not update the record value whenever props are
|
|
||||||
updated. For that situation, we need to use the `onWillUpdateProps` hook.
|
|
||||||
|
|
||||||
### `onWillUpdateProps`
|
|
||||||
|
|
||||||
Just like `onWillStart`, `onWillUpdateProps` is an asynchronous hook. It is
|
|
||||||
designed to be run whenever the component props are updated. This could be
|
|
||||||
useful to perform some asynchronous task such as fetching updated data.
|
|
||||||
|
|
||||||
```js
|
|
||||||
function useLoader() {
|
|
||||||
const component = Component.current;
|
|
||||||
const record = useState({});
|
|
||||||
|
|
||||||
async function updateRecord(id) {
|
|
||||||
Object.assign(record, await fetchSomeRecord(id));
|
|
||||||
}
|
|
||||||
|
|
||||||
onWillStart(() => updateRecord(component.props.id));
|
|
||||||
onWillUpdateProps((nextProps) => updateRecord(nextProps.id));
|
|
||||||
|
|
||||||
return record;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that if there are more than one `onWillUpdateProps` registered callback,
|
|
||||||
then they will all be run in parallel.
|
|
||||||
|
|
||||||
### `useContext`
|
|
||||||
|
|
||||||
See [`useContext`](context.md#usecontext) for reference documentation.
|
|
||||||
|
|
||||||
### `useRef`
|
### `useRef`
|
||||||
|
|
||||||
The `useRef` hook is useful when we need a way to interact with some inside part
|
The `useRef` hook is useful when we need a way to interact with some inside part
|
||||||
of a component, rendered by Owl. It can work either on a DOM node, or on a component,
|
of a component, rendered by Owl. It only work on a html element tagged by the
|
||||||
tagged by the `t-ref` directive:
|
`t-ref` directive:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<div>
|
<div>
|
||||||
<div t-ref="someDiv"/>
|
<input t-ref="someDiv"/>
|
||||||
<SubComponent t-ref="someComponent"/>
|
<span>hello</span>
|
||||||
</div>
|
</div>
|
||||||
```
|
```
|
||||||
|
|
||||||
In this example, the component will be able to access the `div` and the component
|
In this example, the component will be able to access the `div` and the component
|
||||||
`SubComponent` using the `useRef` hook:
|
`SubComponent` with the `useRef` hook:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
class Parent extends Component {
|
class Parent extends Component {
|
||||||
subRef = useRef("someComponent");
|
inputRef = useRef("someComponent");
|
||||||
divRef = useRef("someDiv");
|
|
||||||
|
|
||||||
someMethod() {
|
someMethod() {
|
||||||
// here, if component is mounted, refs are active:
|
// here, if component is mounted, refs are active:
|
||||||
// - this.divRef.el is the div HTMLElement
|
// - this.inputRef.el is the input HTMLElement
|
||||||
// - this.subRef.comp is the instance of the sub component
|
|
||||||
// - this.subRef.el is the root HTML node of the sub component (i.e. this.subRef.comp.el)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
As shown by the example above, html elements are accessed by using the `el`
|
As shown by the example above, the actual HTMLElement instance is accessed with
|
||||||
key, and components references are accessed with `comp`.
|
the `el` key.
|
||||||
|
|
||||||
Notes:
|
|
||||||
|
|
||||||
- if used on a component, the reference will be set in the `refs`
|
|
||||||
variable between `willPatch` and `patched`,
|
|
||||||
- on a component, accessing `ref.el` will get the root node of the component.
|
|
||||||
|
|
||||||
The `t-ref` directive also accepts dynamic values with string interpolation
|
The `t-ref` directive also accepts dynamic values with string interpolation
|
||||||
(like the [`t-attf-`](qweb_templating_language.md#dynamic-attributes) and
|
(like the [`t-attf-`](templates.md#dynamic-attributes) and
|
||||||
`t-component` directives). For example,
|
`t-component` directives). For example,
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
@@ -335,31 +152,41 @@ this.ref2 = useRef("component_2");
|
|||||||
References are only guaranteed to be active while the parent component is mounted.
|
References are only guaranteed to be active while the parent component is mounted.
|
||||||
If this is not the case, accessing `el` or `comp` on it will return `null`.
|
If this is not the case, accessing `el` or `comp` on it will return `null`.
|
||||||
|
|
||||||
### `useSubEnv`
|
### `useSubEnv` and `useChildSubEnv`
|
||||||
|
|
||||||
The environment is sometimes useful to share some common information between
|
The environment is sometimes useful to share some common information between
|
||||||
all components. But sometimes, we want to _scope_ that knowledge to a subtree.
|
all components. But sometimes, we want to _scope_ that knowledge to a subtree.
|
||||||
|
|
||||||
For example, if we have a form view component, maybe we would like to make some
|
For example, if we have a form view component, maybe we would like to make some
|
||||||
`model` object available to all sub components, but not to the whole application.
|
`model` object available to all sub components, but not to the whole application.
|
||||||
This is where the `useSubEnv` hook may be useful: it lets a component add some
|
This is where the `useChildSubEnv` hook may be useful: it lets a component add some
|
||||||
information to the environment in a way that only the component and its children
|
information to the environment in a way that only its children
|
||||||
can access it:
|
can access it:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
class FormComponent extends Component {
|
class FormComponent extends Component {
|
||||||
constructor(...args) {
|
setup() {
|
||||||
super(...args);
|
|
||||||
const model = makeModel();
|
const model = makeModel();
|
||||||
|
// model will be available on this.env for this component and all children
|
||||||
useSubEnv({ model });
|
useSubEnv({ model });
|
||||||
|
// someKey will be available on this.env for all children
|
||||||
|
useChildSubEnv({ someKey: "value" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The `useSubEnv` takes one argument: an object which contains some key/value that
|
The `useSubEnv` and `useChildSubEnv` hooks take one argument: an object which
|
||||||
will be added to the parent environment. Note that it will extend, not replace
|
contains some key/value that will be added to the current environment. These hooks
|
||||||
the parent environment. And of course, the parent environment will not be
|
will create a new env object with the new information:
|
||||||
affected.
|
|
||||||
|
- `useSubEnv` will assign this new `env` to itself and to all children components
|
||||||
|
- `useChildSubEnv` will only assign this new `env` to all children components.
|
||||||
|
|
||||||
|
As usual in Owl, [environments](environment.md) created with these two hooks are
|
||||||
|
frozen, to prevent unwanted modifications.
|
||||||
|
|
||||||
|
Note that both these hooks can be called an arbitrary number of times. The `env`
|
||||||
|
will then be updated accordingly.
|
||||||
|
|
||||||
### `useExternalListener`
|
### `useExternalListener`
|
||||||
|
|
||||||
@@ -377,70 +204,121 @@ useExternalListener(window, "click", this.closeMenu);
|
|||||||
The `useComponent` hook is useful as a building block for some customized hooks,
|
The `useComponent` hook is useful as a building block for some customized hooks,
|
||||||
that may need a reference to the component calling them.
|
that may need a reference to the component calling them.
|
||||||
|
|
||||||
|
```js
|
||||||
|
function useSomething() {
|
||||||
|
const component = useComponent();
|
||||||
|
// now, component is bound to the instance of the current component
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### `useEnv`
|
### `useEnv`
|
||||||
|
|
||||||
The `useEnv` hook is useful as a building block for some customized hooks,
|
The `useEnv` hook is useful as a building block for some customized hooks,
|
||||||
that may need a reference to the env of the component calling them.
|
that may need a reference to the env of the component calling them.
|
||||||
|
|
||||||
### Making customized hooks
|
```js
|
||||||
|
function useSomething() {
|
||||||
|
const env = useEnv();
|
||||||
|
// now, env is bound to the env of the current component
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Hooks are a wonderful way to organize the code of a complex component by feature
|
### `useEffect`
|
||||||
instead of by lifecycle methods. They are like mixins, except that they can be
|
|
||||||
easily composed together.
|
|
||||||
|
|
||||||
But, like every good things in life, hooks should be used with moderation. They are
|
This hook will run a callback when a component is mounted and patched, and
|
||||||
not the solution to every problem.
|
will run a cleanup function before patching and before unmounting the
|
||||||
|
the component (only if some dependencies have changed).
|
||||||
|
|
||||||
- they may be overkill: if your component needs to perform some action specific
|
It has almost the same API as the React `useEffect` hook, except that the dependencies
|
||||||
to itself (so, the specific code does not need to be shared), there is nothing
|
are defined by a function instead of just the dependencies.
|
||||||
wrong with a simple class method:
|
|
||||||
|
|
||||||
```js
|
The `useEffect` hook takes two function: the effect function and the dependency
|
||||||
// maybe overkill
|
function. The effect function perform some task and return (optionally) a cleanup
|
||||||
class A extends Component {
|
function. The dependency function returns a list of dependencies. If any of these
|
||||||
constructor(...args) {
|
dependencies changes, then the current effect will be cleaned up and reexecuted.
|
||||||
super(...args);
|
|
||||||
useMySpecificHook();
|
Here is an example without any dependencies:
|
||||||
}
|
|
||||||
|
```js
|
||||||
|
useEffect(
|
||||||
|
() => {
|
||||||
|
window.addEventListener("mousemove", someHandler);
|
||||||
|
return () => window.removeEventListener("mousemove", someHandler);
|
||||||
|
},
|
||||||
|
() => []
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
In the example above, the dependency list is empty, so the effect is only cleaned
|
||||||
|
up when the component is unmounted.
|
||||||
|
|
||||||
|
If the dependency function is skipped, then the effect will be cleaned up and
|
||||||
|
rerun at every patch.
|
||||||
|
|
||||||
|
Here is another example, of how one could implement a `useAutofocus` hook with
|
||||||
|
the `useEffect` hook:
|
||||||
|
|
||||||
|
```js
|
||||||
|
function useAutofocus(name) {
|
||||||
|
let ref = useRef(name);
|
||||||
|
useEffect(
|
||||||
|
(el) => el && el.focus(),
|
||||||
|
() => [ref.el]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This hook takes the name of a valid `t-ref` directive, which should be present
|
||||||
|
in the template. It then checks whenever the component is mounted or patched if
|
||||||
|
the reference is not valid, and in this case, it will focus the node element.
|
||||||
|
This hook can be used like this:
|
||||||
|
|
||||||
|
```js
|
||||||
|
class SomeComponent extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<div>
|
||||||
|
<input />
|
||||||
|
<input t-ref="myinput"/>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
useAutofocus("myinput");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
// ok
|
## Example: mouse position
|
||||||
class B extends Component {
|
|
||||||
constructor(...args) {
|
Here is the classical example of a non trivial hook to track the mouse position.
|
||||||
super(...args);
|
|
||||||
this.performSpecificTask();
|
```js
|
||||||
}
|
const { useState, onWillDestroy, Component } = owl;
|
||||||
|
|
||||||
|
// We define here a custom behaviour: this hook tracks the state of the mouse
|
||||||
|
// position
|
||||||
|
function useMouse() {
|
||||||
|
const position = useState({ x: 0, y: 0 });
|
||||||
|
|
||||||
|
function update(e) {
|
||||||
|
position.x = e.clientX;
|
||||||
|
position.y = e.clientY;
|
||||||
}
|
}
|
||||||
```
|
window.addEventListener("mousemove", update);
|
||||||
|
onWillDestroy(() => {
|
||||||
|
window.removeEventListener("mousemove", update);
|
||||||
|
});
|
||||||
|
|
||||||
Note that the second solution is easier to extend in sub components.
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
- they may be harder to test: if a customized hook injects some external side
|
// Main root component
|
||||||
effect dependency, then it is harder to test without doing some non obvious
|
class Root extends Component {
|
||||||
manipulation. For example, assume that we want to give a reference to a
|
static template = xml`<div>Mouse: <t t-esc="mouse.x"/>, <t t-esc="mouse.y"/></div>`;
|
||||||
router in a `useRouter` hook. We could do this:
|
|
||||||
|
|
||||||
```js
|
// this hooks is bound to the 'mouse' property.
|
||||||
const router = new Router(...);
|
mouse = useMouse();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
function useRouter() {
|
Note that we use the prefix `use` for hooks, just like in React. This is just
|
||||||
return router;
|
a convention.
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
As you can see, this does not _hook_ into the internal of the component. It
|
|
||||||
simply returns a global object, which is difficult to mock.
|
|
||||||
|
|
||||||
A better way would be to do something like this: get the reference from the
|
|
||||||
environment.
|
|
||||||
|
|
||||||
```js
|
|
||||||
function useRouter() {
|
|
||||||
const env = useEnv();
|
|
||||||
return env.router;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This means that we give control to the application developer to create the
|
|
||||||
router, which is good, so they can set it up, subclass it, ... And then, to
|
|
||||||
test our components, we can just add a mock router in the environment.
|
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
# 🦉 Form Input Bindings 🦉
|
||||||
|
|
||||||
|
It is very common to need to be able to read the value out of an html `input` (or
|
||||||
|
`textarea`, or `select`) in order to use it (note: it does not need to be in a
|
||||||
|
form!). A possible way to do this is to do it by hand:
|
||||||
|
|
||||||
|
```js
|
||||||
|
class Form extends owl.Component {
|
||||||
|
state = useState({ text: "" });
|
||||||
|
|
||||||
|
_updateInputValue(event) {
|
||||||
|
this.state.text = event.target.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<div>
|
||||||
|
<input t-on-input="_updateInputValue" />
|
||||||
|
<span t-esc="state.text" />
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
This works. However, this requires a little bit of _plumbing_ code. Also, the
|
||||||
|
plumbing code is slightly different if you need to interact with a checkbox,
|
||||||
|
or with radio buttons, or with select tags.
|
||||||
|
|
||||||
|
To help with this situation, Owl has a builtin directive `t-model`: its value
|
||||||
|
should be an observed value in the component (usually `state.someValue`). With
|
||||||
|
the `t-model` directive, we can write a shorter code, equivalent to the previous
|
||||||
|
example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
class Form extends owl.Component {
|
||||||
|
state = { text: "" };
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<div>
|
||||||
|
<input t-model="state.text" />
|
||||||
|
<span t-esc="state.text" />
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
The `t-model` directive works with `<input>`, `<input type="checkbox">`,
|
||||||
|
`<input type="radio">`, `<textarea>` and `<select>`:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<div>
|
||||||
|
<div>Text in an input: <input t-model="state.someVal"/></div>
|
||||||
|
<div>Textarea: <textarea t-model="state.otherVal"/></div>
|
||||||
|
<div>Boolean value: <input type="checkbox" t-model="state.someFlag"/></div>
|
||||||
|
<div>Selection:
|
||||||
|
<select t-model="state.color">
|
||||||
|
<option value="">Select a color</option>
|
||||||
|
<option value="red">Red</option>
|
||||||
|
<option value="blue">Blue</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Selection with radio buttons:
|
||||||
|
<span>
|
||||||
|
<input type="radio" name="color" id="red" value="red" t-model="state.color"/>
|
||||||
|
<label for="red">Red</label>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<input type="radio" name="color" id="blue" value="blue" t-model="state.color" />
|
||||||
|
<label for="blue">Blue</label>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
Like event handling, the `t-model` directive accepts the following modifiers:
|
||||||
|
|
||||||
|
| Modifier | Description |
|
||||||
|
| --------- | -------------------------------------------------------------------- |
|
||||||
|
| `.lazy` | update the value on the `change` event (default is on `input` event) |
|
||||||
|
| `.number` | try to parse the value to a number (using `parseFloat`) |
|
||||||
|
| `.trim` | trim the resulting value |
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<input t-model.lazy="state.someVal" />
|
||||||
|
```
|
||||||
|
|
||||||
|
These modifiers can be combined. For instance, `t-model.lazy.number` will only
|
||||||
|
update a number whenever the change is done.
|
||||||
|
|
||||||
|
Note: the online playground has an example to show how it works.
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
# 🦉 Miscellaneous 🦉
|
|
||||||
|
|
||||||
## Content
|
|
||||||
|
|
||||||
- [Portal](#portal)
|
|
||||||
- [AsyncRoot](#asyncroot)
|
|
||||||
|
|
||||||
## `Portal`
|
|
||||||
|
|
||||||
### Overview
|
|
||||||
|
|
||||||
The component `Portal` is meant to be used as a transparent way to 'teleport' a piece
|
|
||||||
of DOM to the node represented by its sole `target` props.
|
|
||||||
|
|
||||||
This component aims at helping the implementation of the needed infrastructure
|
|
||||||
for modals (as in `bootstrap-modal`).
|
|
||||||
|
|
||||||
### Usage
|
|
||||||
|
|
||||||
The content it will teleport is defined within the `<Portal>` node and
|
|
||||||
internally uses the `default` [Slot](slots.md).
|
|
||||||
|
|
||||||
This slot must contain only **one** node, which in turn can have as many children as necessary.
|
|
||||||
|
|
||||||
The element under which the content will be teleported is represented as a selector
|
|
||||||
by the `target` props which only accepts a string as value.
|
|
||||||
|
|
||||||
The `target` props only supports static selector, and is not meant to be passed to `Portal`
|
|
||||||
as a variable. Namely, `<Portal target="'body'" />` is the intended use.
|
|
||||||
By contrast, `<Portal target="state.target" />` is not supported.
|
|
||||||
|
|
||||||
The component `Portal` has no particular state, rather it is meant to be a slave to its parent,
|
|
||||||
and ultimately just a way for the parent to teleport a piece of its own DOM elsewhere.
|
|
||||||
|
|
||||||
The `Portal`'s root node is always `<portal/>` and is placed where the teleported content
|
|
||||||
_would have_ been. It is this element that the [teleported events](#expected-behaviors) are re-directed on.
|
|
||||||
|
|
||||||
### Example
|
|
||||||
|
|
||||||
The canonic use-case is to implement a Dialog, where a Component may choose to break the natural
|
|
||||||
workflow to help the user put in some data, which it could use later on.
|
|
||||||
|
|
||||||
JavaScript:
|
|
||||||
|
|
||||||
```js
|
|
||||||
const { Component, mount } = owl;
|
|
||||||
const { Portal } = owl.misc;
|
|
||||||
|
|
||||||
class TeleportedComponent extends Component {}
|
|
||||||
class App extends Component {
|
|
||||||
static components = { Portal, TeleportedComponent };
|
|
||||||
}
|
|
||||||
|
|
||||||
mount(App, { target: document.body });
|
|
||||||
```
|
|
||||||
|
|
||||||
XML:
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<templates>
|
|
||||||
<div t-name="TeleportedComponent">
|
|
||||||
<span>I will move soon enough</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div t-name="App">
|
|
||||||
<span>I am like the rest of us</span>
|
|
||||||
<Portal target="'body'">
|
|
||||||
<TeleportedComponent />
|
|
||||||
</Portal>
|
|
||||||
</div>
|
|
||||||
</templates>
|
|
||||||
```
|
|
||||||
|
|
||||||
In this example, the `Portal` component will teleport the `TeleportedComponent`'s `div` as a child of the `body`.
|
|
||||||
`TeleportedComponent` is acting as a Dialog here.
|
|
||||||
|
|
||||||
The resulting DOM will look like:
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<body>
|
|
||||||
<div>
|
|
||||||
<span>I am like the rest of us</span>
|
|
||||||
<portal></portal>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span>I will move soon enough</span>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Expected Behaviors
|
|
||||||
|
|
||||||
The teleported piece is updated as any other `Component`'s DOM and in the same sequence.
|
|
||||||
Namely the teleported piece will be updated in function of its parents components, and patched as
|
|
||||||
a normal child.
|
|
||||||
|
|
||||||
The [_business_ events](event_handling.md#business-dom-events) triggered by a child component will be stopped
|
|
||||||
to not bubble outside of the `target`. They will, on the other hand, be re-directed onto the
|
|
||||||
`Portal`'s root node and bubble up the DOM as if it were triggered by a regular child component.
|
|
||||||
|
|
||||||
Beware that those re-directed events are copies of the original event.
|
|
||||||
They have:
|
|
||||||
|
|
||||||
- The same payload.
|
|
||||||
- The same `originalComponent` than their original counterpart,
|
|
||||||
that is the actual Component that triggered it.
|
|
||||||
- A **different** `target` property than their original counterpart.
|
|
||||||
The `target` of a re-directed event is necessarily the `Portal`'s root node.
|
|
||||||
|
|
||||||
Pure DOM events do not follow this pattern and are free to bubble their natural, unaltered way
|
|
||||||
up to the `body`.
|
|
||||||
|
|
||||||
## `AsyncRoot`
|
|
||||||
|
|
||||||
When this component is used, a new rendering sub tree is created, such that the
|
|
||||||
rendering of that component (and its children) is not tied to the rendering of
|
|
||||||
the rest of the interface. It can be used on an asynchronous component, to
|
|
||||||
prevent it from delaying the rendering of the whole interface, or on a
|
|
||||||
synchronous one, such that its rendering isn't delayed by other (asynchronous)
|
|
||||||
components. Note that this directive has no effect on the first rendering, but
|
|
||||||
only on subsequent ones (triggered by state or props changes).
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<div t-name="ParentComponent">
|
|
||||||
<SyncChild />
|
|
||||||
<AsyncRoot>
|
|
||||||
<AsyncChild/>
|
|
||||||
</AsyncRoot>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
The `AsyncRoot` assumes that there is exactly one root node inside it. It can
|
|
||||||
be a dom node or a component.
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
# 🦉 Mounting an application 🦉
|
|
||||||
|
|
||||||
## Content
|
|
||||||
|
|
||||||
- [Overview](#overview)
|
|
||||||
- [API](#api)
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
Mounting an Owl application is done by using the `mount` method (available in
|
|
||||||
`owl.mount` if you are using the iife build, or it can be directly imported
|
|
||||||
from `owl` if you are using a module system):
|
|
||||||
|
|
||||||
```js
|
|
||||||
const mount = { owl }; // if owl is available as an object
|
|
||||||
|
|
||||||
const env = { ... };
|
|
||||||
const app = await mount(MyComponent, { target: document.body, env });
|
|
||||||
```
|
|
||||||
|
|
||||||
Another example:
|
|
||||||
|
|
||||||
```js
|
|
||||||
const config = {
|
|
||||||
env: ...,
|
|
||||||
props: ...,
|
|
||||||
target: document.body,
|
|
||||||
position: "self",
|
|
||||||
};
|
|
||||||
const app = await mount(App, config);
|
|
||||||
```
|
|
||||||
|
|
||||||
A common way to initialize an application is to first setup an environment,
|
|
||||||
then to call the `mount` method.
|
|
||||||
|
|
||||||
## API
|
|
||||||
|
|
||||||
Mount takes two parameters:
|
|
||||||
|
|
||||||
- `C`, which should be a component class (NOT instance),
|
|
||||||
- `params`, which is an object with the following keys:
|
|
||||||
- `target (HTMLElement | DocumentFragment)`: the target of the mount operation
|
|
||||||
- `env (optional, Env)` an environment
|
|
||||||
- `position (optional, "first-child" | "last-child" | "self")` the position
|
|
||||||
where it should be mounted (see below for more informations)
|
|
||||||
- `props (optional, any)`: some initial values that are given as props. Useful
|
|
||||||
when the root component is configurable, or when testing sub components
|
|
||||||
|
|
||||||
Here are the various positions supported by Owl:
|
|
||||||
|
|
||||||
- `first-child`: with this option, the component will be prepended inside the target,
|
|
||||||
- `last-child` (default value): with this option, the component will be
|
|
||||||
appended in the target element,
|
|
||||||
- `self`: the target will be used as the root element for the component. This
|
|
||||||
means that the target has to be an HTMLElement (and not a document fragment).
|
|
||||||
In this situation, it is possible that the component cannot be unmounted. For
|
|
||||||
example, if its target is `document.body`.
|
|
||||||
|
|
||||||
The `mount` method returns a promise that resolves to the instance of the created
|
|
||||||
component.
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
# 🦉 Observer 🦉
|
|
||||||
|
|
||||||
Owl needs to be able to react to state changes. For example, whenever the state
|
|
||||||
of a component is changed, Owl needs to rerender it. To help with that, there is
|
|
||||||
an Observer class. Its job is to observe the state of an object (or array), and
|
|
||||||
to react to any change. The observer is implemented with the native `Proxy`
|
|
||||||
object. Note that this means that it will not work on older browsers.
|
|
||||||
|
|
||||||
Note that the `Observer` is used by the `useState` and `useContext` hooks. This
|
|
||||||
is the way most Owl applications will create observers. For the majority of
|
|
||||||
use cases, there is no need to directly instantiate an observer.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
|
|
||||||
For example, this code will display `update` in the console:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const observer = new owl.core.Observer();
|
|
||||||
observer.notifyCB = () => console.log("update");
|
|
||||||
const obj = observer.observe({ a: { b: 1 } });
|
|
||||||
|
|
||||||
obj.a.b = 2;
|
|
||||||
```
|
|
||||||
|
|
||||||
This example shows that an observer can observe nested properties.
|
|
||||||
|
|
||||||
## Reference
|
|
||||||
|
|
||||||
**observe** An observer can observe multiple values with the `observe` method.
|
|
||||||
This method takes an object or an array as its argument and will return a proxy
|
|
||||||
(which is mapped to the initial object/array). With this proxy, the observer
|
|
||||||
can detect whenever any internal value is changed.
|
|
||||||
|
|
||||||
**Registering a callback** Whenever an observer sees a state change, it will
|
|
||||||
call its `notifyCB` method. No additional information is given to the callback.
|
|
||||||
|
|
||||||
**deepRevNumber** Each observed value has an internal revision number, which
|
|
||||||
is incremented every time the value is observed. Sometimes, it can be useful
|
|
||||||
to obtain that number:
|
|
||||||
|
|
||||||
```js
|
|
||||||
const observer = new owl.core.Observer();
|
|
||||||
const obj = observer.observe({ a: { b: 1 } });
|
|
||||||
|
|
||||||
observer.revNumber(obj.a); // 1
|
|
||||||
obj.a.b = 2;
|
|
||||||
|
|
||||||
observer.revNumber(obj.a); // 2
|
|
||||||
```
|
|
||||||
|
|
||||||
The `revNumber` can also return 0, which indicates that the value is not
|
|
||||||
observed.
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# 🦉 Portal 🦉
|
||||||
|
|
||||||
|
It is sometimes useful to be able to render some content outside the boundaries
|
||||||
|
of a component. To do that, Owl provides a special directive: `t-portal`:
|
||||||
|
|
||||||
|
```js
|
||||||
|
class SomeComponent extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<div>this is inside the component</div>
|
||||||
|
<div t-portal="'body'">and this is outside</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `t-portal` directive takes a valid css selector as argument. The content of
|
||||||
|
the portalled template will be mounted at the corresponding location. Note that
|
||||||
|
Owl need to insert an empty text node at the location of the portalled content.
|
||||||
+191
-22
@@ -4,8 +4,11 @@
|
|||||||
|
|
||||||
- [Overview](#overview)
|
- [Overview](#overview)
|
||||||
- [Definition](#definition)
|
- [Definition](#definition)
|
||||||
- [Good Practices](#good-practices)
|
- [Binding function props](#binding-function-props)
|
||||||
- [Dynamic Props](#dynamic-props)
|
- [Dynamic Props](#dynamic-props)
|
||||||
|
- [Default Props](#default-props)
|
||||||
|
- [Props validation](#props-validation)
|
||||||
|
- [Good Practices](#good-practices)
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
@@ -38,8 +41,6 @@ The `props` object is made of every attributes defined on the template, with the
|
|||||||
following exceptions:
|
following exceptions:
|
||||||
|
|
||||||
- every attribute starting with `t-` are not props (they are QWeb directives),
|
- every attribute starting with `t-` are not props (they are QWeb directives),
|
||||||
- `style` and `class` attributes are excluded as well (they are applied by Owl on
|
|
||||||
the root element of the component).
|
|
||||||
|
|
||||||
In the following example:
|
In the following example:
|
||||||
|
|
||||||
@@ -47,7 +48,6 @@ In the following example:
|
|||||||
<div>
|
<div>
|
||||||
<ComponentA a="state.a" b="'string'"/>
|
<ComponentA a="state.a" b="'string'"/>
|
||||||
<ComponentB t-if="state.flag" model="model"/>
|
<ComponentB t-if="state.flag" model="model"/>
|
||||||
<ComponentC style="color:red;" class="left-pane" />
|
|
||||||
</div>
|
</div>
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -55,7 +55,193 @@ the `props` object contains the following keys:
|
|||||||
|
|
||||||
- for `ComponentA`: `a` and `b`,
|
- for `ComponentA`: `a` and `b`,
|
||||||
- for `ComponentB`: `model`,
|
- for `ComponentB`: `model`,
|
||||||
- for `ComponentC`: empty object
|
|
||||||
|
## Binding function props
|
||||||
|
|
||||||
|
It is common to have the need to pass a callback as a prop. Since Owl components
|
||||||
|
are class based, the callback frequently needs to be bound to its owner component.
|
||||||
|
So, one can do this:
|
||||||
|
|
||||||
|
```js
|
||||||
|
class SomeComponent extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<div>
|
||||||
|
<Child callback="doSomething"/>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
this.doSomething = this.doSomething.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
doSomething() {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
However, this is such a common use case that Owl provides a special suffix to do
|
||||||
|
just that: `.bind`. This looks like this:
|
||||||
|
|
||||||
|
```js
|
||||||
|
class SomeComponent extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<div>
|
||||||
|
<Child callback.bind="doSomething"/>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
doSomething() {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dynamic Props
|
||||||
|
|
||||||
|
The `t-props` directive can be used to specify totally dynamic props:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<div t-name="ParentComponent">
|
||||||
|
<Child t-props="some.obj"/>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
class ParentComponent {
|
||||||
|
static components = { Child };
|
||||||
|
some = { obj: { a: 1, b: 2 } };
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Default Props
|
||||||
|
|
||||||
|
If the static `defaultProps` property is defined, it will be used to complete
|
||||||
|
props received by the parent, if missing.
|
||||||
|
|
||||||
|
```js
|
||||||
|
class Counter extends owl.Component {
|
||||||
|
static defaultProps = {
|
||||||
|
initialValue: 0,
|
||||||
|
};
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In the example above, the `initialValue` props is now by default set to 0.
|
||||||
|
|
||||||
|
## Props Validation
|
||||||
|
|
||||||
|
As an application becomes complex, it may be quite unsafe to define props in an informal way. This leads to two issues:
|
||||||
|
|
||||||
|
- hard to tell how a component should be used, by looking at its code.
|
||||||
|
- unsafe, it is easy to send wrong props into a component, either by refactoring a component, or one of its parents.
|
||||||
|
|
||||||
|
A props type system solves both issues, by describing the types and shapes
|
||||||
|
of the props. Here is how it works in Owl:
|
||||||
|
|
||||||
|
- `props` key is a static key (so, different from `this.props` in a component instance)
|
||||||
|
- it is optional: it is ok for a component to not define a `props` key.
|
||||||
|
- props are validated whenever a component is created/updated
|
||||||
|
- props are only validated in `dev` mode (see [how to configure an app](app.md#configuration))
|
||||||
|
- if a key does not match the description, an error is thrown
|
||||||
|
- it validates keys defined in (static) `props`. Additional keys given by the
|
||||||
|
parent will cause an error (unless the special prop `*` is present).
|
||||||
|
- it is an object or a list of strings
|
||||||
|
- a list of strings is a simplified props definition, which only lists the name
|
||||||
|
of the props. Also, if the name ends with `?`, it is considered optional.
|
||||||
|
- all props are by default required, unless they are defined with `optional: true`
|
||||||
|
(in that case, it is only done if there is a value)
|
||||||
|
- valid types are: `Number, String, Boolean, Object, Array, Date, Function`, and all
|
||||||
|
constructor functions (so, if you have a `Person` class, it can be used as a type)
|
||||||
|
- arrays are homogeneous (all elements have the same type/shape)
|
||||||
|
|
||||||
|
For each key, a `prop` definition is either a boolean, a constructor, a list of constructors, or an object:
|
||||||
|
|
||||||
|
- a boolean: indicate that the props exists, and is mandatory.
|
||||||
|
- a constructor: this should describe the type, for example: `id: Number` describe
|
||||||
|
the props `id` as a number
|
||||||
|
- a list of constructors. In that case, this means that we allow more than one
|
||||||
|
type. For example, `id: [Number, String]` means that `id` can be either a string
|
||||||
|
or a number.
|
||||||
|
- an object. This makes it possible to have more expressive definition. The following sub keys are then allowed (but not mandatory):
|
||||||
|
- `type`: the main type of the prop being validated
|
||||||
|
- `element`: if the type was `Array`, then the `element` key describes the type of each element in the array. If it is not set, then we only validate the array, 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
|
||||||
|
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
|
||||||
|
sometimes useful for generic components that will propagate some or all their
|
||||||
|
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:
|
||||||
|
|
||||||
|
```js
|
||||||
|
class ComponentA extends owl.Component {
|
||||||
|
static props = ['id', 'url'];
|
||||||
|
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
class ComponentB extends owl.Component {
|
||||||
|
static props = {
|
||||||
|
count: {type: Number},
|
||||||
|
messages: {
|
||||||
|
type: Array,
|
||||||
|
element: {type: Object, shape: {id: Boolean, text: String }
|
||||||
|
},
|
||||||
|
date: Date,
|
||||||
|
combinedVal: [Number, Boolean],
|
||||||
|
optionalProp: { type: Number, optional: true }
|
||||||
|
};
|
||||||
|
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
// only the existence of those 3 keys is documented
|
||||||
|
static props = ['message', 'id', 'date'];
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
// only the existence of those 3 keys is documented. any other key is allowed.
|
||||||
|
static props = ['message', 'id', 'date', '*'];
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
// size is optional
|
||||||
|
static props = ['message', 'size?'];
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
static props = {
|
||||||
|
messageIds: {type: Array, element: Number}, // list of number
|
||||||
|
otherArr: {type: Array}, // just array. no validation is made on sub elements
|
||||||
|
otherArr2: Array, // same as otherArr
|
||||||
|
someObj: {type: Object}, // just an object, no internal validation
|
||||||
|
someObj2: {
|
||||||
|
type: Object,
|
||||||
|
shape: {
|
||||||
|
id: Number,
|
||||||
|
name: {type: String, optional: true},
|
||||||
|
url: String
|
||||||
|
]}, // object, with keys id (number), name (string, optional) and url (string)
|
||||||
|
someFlag: Boolean, // a boolean, mandatory (even if `false`)
|
||||||
|
someVal: [Boolean, Date], // either a boolean or a date
|
||||||
|
otherValue: true, // indicates that it is a prop
|
||||||
|
kindofsmallnumber: {
|
||||||
|
type: Number,
|
||||||
|
validate: n => (0 <= n && n <= 10)
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
validate: e => ["small", "medium", "large"].includes(e)
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
## Good Practices
|
## Good Practices
|
||||||
|
|
||||||
@@ -78,20 +264,3 @@ sent to the parent (for example, with an event).
|
|||||||
Any value can go in a props. Strings, objects, classes, or even callbacks could
|
Any value can go in a props. Strings, objects, classes, or even callbacks could
|
||||||
be given to a child component (but then, in the case of callbacks, communicating
|
be given to a child component (but then, in the case of callbacks, communicating
|
||||||
with events seems more appropriate).
|
with events seems more appropriate).
|
||||||
|
|
||||||
## Dynamic Props
|
|
||||||
|
|
||||||
The `t-props` directive can be used to specify totally dynamic props:
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<div t-name="ParentComponent">
|
|
||||||
<Child t-props="some.obj"/>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
```js
|
|
||||||
class ParentComponent {
|
|
||||||
static components = { Child };
|
|
||||||
some = { obj: { a: 1, b: 2 } };
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -1,103 +0,0 @@
|
|||||||
# 🦉 Props Validation 🦉
|
|
||||||
|
|
||||||
As an application becomes complex, it may be quite unsafe to define props in an informal way. This leads to two issues:
|
|
||||||
|
|
||||||
- hard to tell how a component should be used, by looking at its code.
|
|
||||||
- unsafe, it is easy to send wrong props into a component, either by refactoring a component, or one of its parents.
|
|
||||||
|
|
||||||
A props type system solves both issues, by describing the types and shapes
|
|
||||||
of the props. Here is how it works in Owl:
|
|
||||||
|
|
||||||
- `props` key is a static key (so, different from `this.props` in a component instance)
|
|
||||||
- it is optional: it is ok for a component to not define a `props` key.
|
|
||||||
- props are validated whenever a component is created/updated
|
|
||||||
- props are only validated in `dev` mode (see [config page](config.md#mode))
|
|
||||||
- if a key does not match the description, an error is thrown
|
|
||||||
- it validates keys defined in (static) `props`. Additional keys given by the
|
|
||||||
parent will cause an error.
|
|
||||||
|
|
||||||
For example:
|
|
||||||
|
|
||||||
```js
|
|
||||||
class ComponentA extends owl.Component {
|
|
||||||
static props = ['id', 'url'];
|
|
||||||
|
|
||||||
...
|
|
||||||
}
|
|
||||||
|
|
||||||
class ComponentB extends owl.Component {
|
|
||||||
static props = {
|
|
||||||
count: {type: Number},
|
|
||||||
messages: {
|
|
||||||
type: Array,
|
|
||||||
element: {type: Object, shape: {id: Boolean, text: String }
|
|
||||||
},
|
|
||||||
date: Date,
|
|
||||||
combinedVal: [Number, Boolean]
|
|
||||||
};
|
|
||||||
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- it is an object or a list of strings
|
|
||||||
- a list of strings is a simplified props definition, which only lists the name
|
|
||||||
of the props. Also, if the name ends with `?`, it is considered optional.
|
|
||||||
- all props are by default required, unless they are defined with `optional: true`
|
|
||||||
(in that case, validation is only done if there is a value)
|
|
||||||
- valid types are: `Number, String, Boolean, Object, Array, Date, Function`, and all
|
|
||||||
constructor functions (so, if you have a `Person` class, it can be used as a type)
|
|
||||||
- arrays are homogeneous (all elements have the same type/shape)
|
|
||||||
|
|
||||||
For each key, a `prop` definition is either a boolean, a constructor, a list of constructors, or an object:
|
|
||||||
|
|
||||||
- a boolean: indicate that the props exists, and is mandatory.
|
|
||||||
- a constructor: this should describe the type, for example: `id: Number` describe
|
|
||||||
the props `id` as a number
|
|
||||||
- a list of constructors. In that case, this means that we allow more than one
|
|
||||||
type. For example, `id: [Number, String]` means that `id` can be either a string
|
|
||||||
or a number.
|
|
||||||
- an object. This makes it possible to have more expressive definition. The following sub keys are then allowed (but not mandatory):
|
|
||||||
- `type`: the main type of the prop being validated
|
|
||||||
- `element`: if the type was `Array`, then the `element` key describes the type of each element in the array. If it is not set, then we only validate the array, 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
|
|
||||||
the value is valid or not. Useful for custom validation logic.
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
|
|
||||||
```js
|
|
||||||
// only the existence of those 3 keys is documented
|
|
||||||
static props = ['message', 'id', 'date'];
|
|
||||||
```
|
|
||||||
|
|
||||||
```js
|
|
||||||
// size is optional
|
|
||||||
static props = ['message', 'size?'];
|
|
||||||
```
|
|
||||||
|
|
||||||
```js
|
|
||||||
static props = {
|
|
||||||
messageIds: {type: Array, element: Number}, // list of number
|
|
||||||
otherArr: {type: Array}, // just array. no validation is made on sub elements
|
|
||||||
otherArr2: Array, // same as otherArr
|
|
||||||
someObj: {type: Object}, // just an object, no internal validation
|
|
||||||
someObj2: {
|
|
||||||
type: Object,
|
|
||||||
shape: {
|
|
||||||
id: Number,
|
|
||||||
name: {type: String, optional: true},
|
|
||||||
url: String
|
|
||||||
]}, // object, with keys id (number), name (string, optional) and url (string)
|
|
||||||
someFlag: Boolean, // a boolean, mandatory (even if `false`)
|
|
||||||
someVal: [Boolean, Date], // either a boolean or a date
|
|
||||||
otherValue: true, // indicates that it is a prop
|
|
||||||
kindofsmallnumber: {
|
|
||||||
type: Number,
|
|
||||||
validate: n => (0 <= n && n <= 10)
|
|
||||||
},
|
|
||||||
size: {
|
|
||||||
validate: e => ["small", "medium", "large"].includes(e)
|
|
||||||
},
|
|
||||||
};
|
|
||||||
```
|
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
# 🦉 QWeb Engine 🦉
|
|
||||||
|
|
||||||
## Content
|
|
||||||
|
|
||||||
- [Overview](#overview)
|
|
||||||
- [Reference](#reference)
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
[QWeb](https://www.odoo.com/documentation/13.0/reference/qweb.html) is the primary
|
|
||||||
templating engine used by Odoo. The QWeb class in the OWL project is an
|
|
||||||
implementation of that specification with a few interesting points:
|
|
||||||
|
|
||||||
- it compiles templates into functions that output a virtual DOM instead of a
|
|
||||||
string. This is necessary for the component system.
|
|
||||||
- it has a few extra directives: `t-component`, `t-on`, ...
|
|
||||||
|
|
||||||
We present in this section the engine, not the templating language.
|
|
||||||
|
|
||||||
## Reference
|
|
||||||
|
|
||||||
This section is about the javascript code that implements the `QWeb` specification.
|
|
||||||
Owl exports a `QWeb` class in `owl.QWeb`. To use it, it just needs to be
|
|
||||||
instantiated:
|
|
||||||
|
|
||||||
```js
|
|
||||||
const qweb = new owl.QWeb();
|
|
||||||
```
|
|
||||||
|
|
||||||
Its API is quite simple:
|
|
||||||
|
|
||||||
- **`constructor(config)`**: constructor. Takes an optional configuration object
|
|
||||||
with an optional `templates` string to add initial
|
|
||||||
templates (see `addTemplates` for more information on format of the string)
|
|
||||||
and an optional `translateFn` translate function (see the section on
|
|
||||||
[translations](#translations)).
|
|
||||||
|
|
||||||
```js
|
|
||||||
const qweb = new owl.QWeb({ templates: TEMPLATES, translateFn: _t });
|
|
||||||
```
|
|
||||||
|
|
||||||
- **`addTemplate(name, xmlStr, allowDuplicate)`**: add a specific template.
|
|
||||||
|
|
||||||
```js
|
|
||||||
qweb.addTemplate("mytemplate", "<div>hello</div>");
|
|
||||||
```
|
|
||||||
|
|
||||||
If the optional `allowDuplicate` is set to `true`, then `QWeb` will simply
|
|
||||||
ignore templates added for a second time. Otherwise, `QWeb` will crash.
|
|
||||||
|
|
||||||
- **`addTemplates(xmlStr)`**: add a list of templates (identified by `t-name`
|
|
||||||
attribute).
|
|
||||||
|
|
||||||
```js
|
|
||||||
const TEMPLATES = `
|
|
||||||
<templates>
|
|
||||||
<div t-name="App" class="main">main</div>
|
|
||||||
<div t-name="OtherComponent">other component</div>
|
|
||||||
</templates>`;
|
|
||||||
qweb.addTemplates(TEMPLATES);
|
|
||||||
```
|
|
||||||
|
|
||||||
- **`render(name, context, extra)`**: renders a template. This returns a `vnode`,
|
|
||||||
which is a virtual representation of the DOM (see [vdom doc](../miscellaneous/vdom.md)).
|
|
||||||
|
|
||||||
```js
|
|
||||||
const vnode = qweb.render("App", component);
|
|
||||||
```
|
|
||||||
|
|
||||||
- **`renderToString(name, context)`**: renders a template, but returns an html
|
|
||||||
string.
|
|
||||||
|
|
||||||
```js
|
|
||||||
const str = qweb.renderToString("someTemplate", somecontext);
|
|
||||||
```
|
|
||||||
|
|
||||||
- **`registerTemplate(name, template)`**: static function to register a global
|
|
||||||
QWeb template. This is useful for commonly used components accross the
|
|
||||||
application, and for making a template available to an application without
|
|
||||||
having a reference to the actual QWeb instance.
|
|
||||||
|
|
||||||
```js
|
|
||||||
QWeb.registerTemplate("mytemplate", `<div>some template</div>`);
|
|
||||||
```
|
|
||||||
|
|
||||||
- **`registerComponent(name, Component)`**: static function to register an OWL Component
|
|
||||||
to QWeb's global registry. Globally registered Components can be used in
|
|
||||||
templates (see the `t-component` directive). This is useful for commonly used
|
|
||||||
components accross the application.
|
|
||||||
|
|
||||||
```js
|
|
||||||
class Dialog extends owl.Component { ... }
|
|
||||||
QWeb.registerComponent("Dialog", Dialog);
|
|
||||||
|
|
||||||
...
|
|
||||||
|
|
||||||
class ParentComponent extends owl.Component { ... }
|
|
||||||
qweb.addTemplate("ParentComponent", "<div><Dialog/></div>");
|
|
||||||
```
|
|
||||||
|
|
||||||
In some way, a `QWeb` instance is the core of an Owl application. It is the only
|
|
||||||
mandatory element of an [environment](environment.md). As such, it
|
|
||||||
has an extra responsibility: it can act as an event bus for internal communication
|
|
||||||
between Owl classes. This is the reason why `QWeb` actually extends [EventBus](event_bus.md).
|
|
||||||
|
|
||||||
### Translations
|
|
||||||
|
|
||||||
take care of this and "cherry-pick" 8464a1b04e7469434f9dcb3d68a543f58cb61b8e
|
|
||||||
|
|
||||||
If properly setup, Owl QWeb engine can translate all rendered templates. To do
|
|
||||||
so, it needs a translate function, which takes a string and returns a string.
|
|
||||||
|
|
||||||
For example:
|
|
||||||
|
|
||||||
```js
|
|
||||||
const translations = {
|
|
||||||
hello: "bonjour",
|
|
||||||
yes: "oui",
|
|
||||||
no: "non",
|
|
||||||
};
|
|
||||||
const translateFn = (str) => translations[str] || str;
|
|
||||||
|
|
||||||
const qweb = new QWeb({ translateFn });
|
|
||||||
```
|
|
||||||
|
|
||||||
Once setup, all rendered templates will be translated using `translateFn`:
|
|
||||||
|
|
||||||
- each text node will be replaced with its translation,
|
|
||||||
- each of the following attribute values will be translated as well: `title`,
|
|
||||||
`placeholder`, `label` and `alt`,
|
|
||||||
- translating text nodes can be disabled with the special attribute `t-translation`,
|
|
||||||
if its value is `off`.
|
|
||||||
|
|
||||||
So, with the above `translateFn`, the following templates:
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<div>hello</div>
|
|
||||||
<div t-translation="off">hello</div>
|
|
||||||
<div>Are you sure?</div>
|
|
||||||
<input placeholder="hello" other="yes"/>
|
|
||||||
```
|
|
||||||
|
|
||||||
will be rendered as:
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<div>bonjour</div>
|
|
||||||
<div>hello</div>
|
|
||||||
<div>Are you sure?</div>
|
|
||||||
<input placeholder="bonjour" other="yes"/>
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that the translation is done during the compilation of the template, not
|
|
||||||
when it is rendered.
|
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
# 🦉 Reactivity 🦉
|
||||||
|
|
||||||
|
## Content
|
||||||
|
|
||||||
|
- [Overview](#overview)
|
||||||
|
- [`useState`](#usestate)
|
||||||
|
- [`reactive`](#reactive)
|
||||||
|
- [`markRaw`](#markraw)
|
||||||
|
- [`toRaw`](#toraw)
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Reactivity is a big topic in javascript frameworks. The goal is to provide a
|
||||||
|
simple way to manipulate state, in such a way that the interface automatically
|
||||||
|
update accordingly to state changes. Also, we obviously want this to happen in
|
||||||
|
a performant way.
|
||||||
|
|
||||||
|
To solve this issue, Owl provides two reactivity primitives:
|
||||||
|
|
||||||
|
- `reactive`, which returns a proxy to its first argument, and tracks all read/update
|
||||||
|
operation going through it,
|
||||||
|
- `useState`: a hook, that internally uses `reactive`, and is linked to its
|
||||||
|
owner component: any read operation will be tracked (key by key), and any
|
||||||
|
updates to these tracked values will cause the component to be rerendered.
|
||||||
|
|
||||||
|
Most of the time, the `useState` hook is the best solution.
|
||||||
|
|
||||||
|
Since version 2.0, Owl applies the fine grained reactivity at the component
|
||||||
|
level: props are automatically turned into reactive object, so Owl can track
|
||||||
|
which part of these props are consumed by each component, and is therefore able
|
||||||
|
to only rerender the impacted components.
|
||||||
|
|
||||||
|
## `useState`
|
||||||
|
|
||||||
|
Let us start by an example of how `useState` could be used:
|
||||||
|
|
||||||
|
```js
|
||||||
|
class Counter extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<div t-on-click="increment">
|
||||||
|
<t t-esc="state.value"/>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
this.state = useState({ value: 0 });
|
||||||
|
}
|
||||||
|
|
||||||
|
increment() {
|
||||||
|
this.state.value++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If one were to use a simple state object, Owl would not be aware that the value
|
||||||
|
was changed and that the component should be rerendered. With the `useState`
|
||||||
|
hook, `this.state` is now a reactive object, so this component works as expected.
|
||||||
|
|
||||||
|
## `reactive`
|
||||||
|
|
||||||
|
The `reactive` function is the basic reactivity primitive. It takes an object
|
||||||
|
or an array as first argument, and optionally, a function as the second argument.
|
||||||
|
The function will be called whenever any tracked value is updated.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const obj = reactive({ a: 1 }, () => console.log("changed"));
|
||||||
|
|
||||||
|
obj.a = 2; // does not log anything: the 'a' key was not read
|
||||||
|
console.log(obj.a); // log 2, and reads the 'a' key => it is now tracked
|
||||||
|
obj.a = 3; // log 'changed' because we updated a tracked value
|
||||||
|
```
|
||||||
|
|
||||||
|
An important property of reactive objects is that they can be reobserved: this
|
||||||
|
will create an independant proxy that tracks another set of keys:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const obj1 = reactive({ a: 1, b: 2 }, () => console.log("observer 1"));
|
||||||
|
const obj2 = reactive(obj1, () => console.log("observer 2"));
|
||||||
|
|
||||||
|
console.log(obj1.a); // log 1, and reads the 'a' key => it is now tracked by observer 1
|
||||||
|
console.log(obj1.b); // log 2, and 'b' is now tracked by observer 1
|
||||||
|
console.log(obj2.b); // log 2, and 'b' is now tracked by observer 1
|
||||||
|
obj2.a = 3; // log 'observer1', because observer2 does not track a
|
||||||
|
obj2.b = 3; // log 'observer1' and 'observer2'
|
||||||
|
```
|
||||||
|
|
||||||
|
Obviously, one can use `reactive` on the result of a `useState` if wanted, this
|
||||||
|
is the proper way to watch for some state changes.
|
||||||
|
|
||||||
|
## `markRaw`
|
||||||
|
|
||||||
|
Marks an object so that it is ignored by the reactivity system. This function returns its argument.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const someObject = markRaw(...);
|
||||||
|
const state = useState({
|
||||||
|
a: 1,
|
||||||
|
obj: someObject
|
||||||
|
});
|
||||||
|
// here, state.obj === someObject
|
||||||
|
```
|
||||||
|
|
||||||
|
This is useful in some rare cases. For example, some complex and large object such
|
||||||
|
that going through the reactivity system may cause a non trivial performance slowdown.
|
||||||
|
|
||||||
|
However, use this function with caution: this is an escape hatch from the reactivity
|
||||||
|
system, and as such, using it may cause subtle and unintended issues!
|
||||||
|
|
||||||
|
## `toRaw`
|
||||||
|
|
||||||
|
Given a reactive object, this function returns the underlying, non-reactive,
|
||||||
|
corresponding object.
|
||||||
|
|
||||||
|
```js
|
||||||
|
// in setup
|
||||||
|
const state = useState({ value: 1 });
|
||||||
|
|
||||||
|
// later:
|
||||||
|
const rawState = toRaw(this.state);
|
||||||
|
rawState.value = 3; // will NOT be picked up by the reactivity system!!!
|
||||||
|
```
|
||||||
|
|
||||||
|
Here again, this is useful in some situations where we want to explicitely bypass
|
||||||
|
Owl, but using this function means that the responsability of coordinating
|
||||||
|
state update is given to the user code, instead of Owl. Subtle bugs may arise!
|
||||||
|
|
||||||
|
Also, normal (non-reactive objects) will be directly returned by `toRaw`:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const obj = { a: 1 };
|
||||||
|
console.log(toRaw(obj) === obj); // true
|
||||||
|
```
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# 🦉 References 🦉
|
||||||
|
|
||||||
|
The `useRef` hook is useful when we need a way to interact with some inside part
|
||||||
|
of a component, rendered by Owl. It can work either on a DOM node, or on a component,
|
||||||
|
targeted by the `t-ref` directive. See the [hooks section](hooks.md#useref) for
|
||||||
|
more detail.
|
||||||
|
|
||||||
|
As a short example, here is how we could set the focus on a given input:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<div>
|
||||||
|
<input t-ref="input"/>
|
||||||
|
<button t-on-click="focusInput">Click</button>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { useRef } from "owl/hooks";
|
||||||
|
|
||||||
|
class SomeComponent extends Component {
|
||||||
|
inputRef = useRef("input");
|
||||||
|
|
||||||
|
focusInput() {
|
||||||
|
this.inputRef.el.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Be aware that the `el` property will only be set when the target of the `t-ref`
|
||||||
|
directive is mounted in the DOM. Otherwise, it will be set to `null`.
|
||||||
|
|
||||||
|
The `useRef` hook cannot be used to get a reference to an instance of a sub
|
||||||
|
component.
|
||||||
|
|
||||||
|
Note that this example uses the suffix `ref` to name the reference. This
|
||||||
|
is not mandatory, but it is a useful convention, so we do not forget that it is
|
||||||
|
a reference object.
|
||||||
+197
-54
@@ -3,71 +3,92 @@
|
|||||||
## Content
|
## Content
|
||||||
|
|
||||||
- [Overview](#overview)
|
- [Overview](#overview)
|
||||||
- [Example](#example)
|
- [Named slots](#named-slots)
|
||||||
- [Reference](#reference)
|
- [Rendering Context](#rendering-context)
|
||||||
|
- [Default Slot](#default-slot)
|
||||||
|
- [Default Content](#default-content)
|
||||||
|
- [Dynamic slots](#dynamic-slots)
|
||||||
|
- [Slots and props](#slots-and-props)
|
||||||
|
- [Slot params](#slot-params)
|
||||||
|
- [Slot scopes](#slot-scopes)
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
Owl is a template based component system. There is therefore a need to be able
|
Owl is a template based component system. There is therefore a need to be able
|
||||||
to make generic components. For example, imagine a generic `Dialog`
|
to make generic components. For example, imagine a generic `Navbar`
|
||||||
component, which is able to display some arbitrary content.
|
component, which displays a navbar, but with some customizable content. Since
|
||||||
|
the specific content is only known to the user of the `Navbar`, it would be nice
|
||||||
Obviously, we want to use this component everywhere in our application, to
|
to specify it in the template where `Navbar` is used:
|
||||||
display various different content. The `Dialog` component is technically the
|
|
||||||
owner of its content, but is only a container. The user of the `Dialog` is
|
|
||||||
the component that want to _inject_ something inside the `Dialog`. This is
|
|
||||||
exactly what slots are for.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
|
|
||||||
To make generic components, it is useful to be able for a parent component to _inject_
|
|
||||||
some sub template, but still be the owner. For example, a generic dialog component
|
|
||||||
will need to render some content, some footer, but with the parent as the
|
|
||||||
rendering context.
|
|
||||||
|
|
||||||
Slots are inserted with the `t-slot` directive:
|
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<div t-name="Dialog" class="modal">
|
<div>
|
||||||
<div class="modal-title"><t t-esc="props.title"/></div>
|
<Navbar>
|
||||||
<div class="modal-content">
|
<span>Hello Owl</span>
|
||||||
|
</Navbar>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
This is exactly the way slots work! In the example above, the user of the `Navbar`
|
||||||
|
component specify some content (here, in the default slot). The `Navbar`
|
||||||
|
component can insert that content in its own template at the appropriate location.
|
||||||
|
An important information to notice is that the content of the slot is rendered in
|
||||||
|
the parent context, not in the navbar. As such, it can access values and methods
|
||||||
|
from the parent component.
|
||||||
|
|
||||||
|
Here is how the `Navbar` component could be defined, with the `t-slot` directive:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<div class="navbar">
|
||||||
|
<t t-slot="default"/>
|
||||||
|
<ul>
|
||||||
|
<!-- rest of the navbar here -->
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Named slots
|
||||||
|
|
||||||
|
Default slots are very useful, but sometimes, we may need more than one slot.
|
||||||
|
This is what named slots are for! For example, suppose we implement a component
|
||||||
|
`InfoBox` that display a title and some specific content. Its template could look
|
||||||
|
like this:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<div class="info-box">
|
||||||
|
<div class="info-box-title">
|
||||||
|
<t t-slot="title"/>
|
||||||
|
<span class="info-box-close-button" t-on-click="close">X</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-box-content">
|
||||||
<t t-slot="content"/>
|
<t t-slot="content"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
|
||||||
<t t-slot="footer"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
```
|
```
|
||||||
|
|
||||||
Slots are defined by the caller, with the `t-set-slot` directive:
|
And one could use it with the `t-set-slot` directive:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<div t-name="SomeComponent">
|
<InfoBox>
|
||||||
<div>some component</div>
|
<t t-set-slot="title">
|
||||||
<Dialog title="'Some Dialog'">
|
Specific Title. It could be html also.
|
||||||
<t t-set-slot="content">
|
</t>
|
||||||
<div>hey</div>
|
<t t-set-slot="content">
|
||||||
</t>
|
<!-- some template here, with html, events, whatever -->
|
||||||
<t t-set-slot="footer">
|
</t>
|
||||||
<button t-on-click="doSomething">ok</button>
|
</InfoBox>
|
||||||
</t>
|
|
||||||
</Dialog>
|
|
||||||
</div>
|
|
||||||
```
|
```
|
||||||
|
|
||||||
In this example, the component `Dialog` will render the slots `content` and `footer`
|
## Rendering context
|
||||||
with its parent as rendering context. This means that clicking on the button
|
|
||||||
will execute the `doSomething` method on the parent, not on the dialog.
|
|
||||||
|
|
||||||
Note: Owl previously used the `t-set` directive to define the content of a slot.
|
The content of the slots is actually rendered with the rendering context corresponding
|
||||||
This is deprecated and should no longer be used in new code.
|
to where it was defined, not where it is positioned. This allows the user to define
|
||||||
|
event handlers that will be bound to the correct component (usually, the
|
||||||
|
grandparent of the slot content).
|
||||||
|
|
||||||
## Reference
|
## Default Slot
|
||||||
|
|
||||||
### Default Slot
|
All elements inside the component which are not a named slot will be treated as
|
||||||
|
part of the content of the `default` slot. For example:
|
||||||
The first element inside the component which is not a named slot will
|
|
||||||
be considered the `default` slot. For example:
|
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<div t-name="Parent">
|
<div t-name="Parent">
|
||||||
@@ -81,7 +102,20 @@ be considered the `default` slot. For example:
|
|||||||
</div>
|
</div>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Default content
|
One can mix default slot and named slots:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<div>
|
||||||
|
<Child>
|
||||||
|
default content
|
||||||
|
<t t-set-slot="footer">
|
||||||
|
content for footer slot here
|
||||||
|
</t>
|
||||||
|
</Child>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Default content
|
||||||
|
|
||||||
Slots can define a default content, in case the parent did not define them:
|
Slots can define a default content, in case the parent did not define them:
|
||||||
|
|
||||||
@@ -96,12 +130,7 @@ Slots can define a default content, in case the parent did not define them:
|
|||||||
<!-- will be rendered as: <div><span>default content</span></div> -->
|
<!-- will be rendered as: <div><span>default content</span></div> -->
|
||||||
```
|
```
|
||||||
|
|
||||||
Rendering context: the content of the slots is actually rendered with the
|
## Dynamic Slots
|
||||||
rendering context corresponding to where it was defined, not where it is
|
|
||||||
positioned. This allows the user to define event handlers that will be bound
|
|
||||||
to the correct component (usually, the grandparent of the slot content).
|
|
||||||
|
|
||||||
### Dynamic Slots
|
|
||||||
|
|
||||||
The `t-slot` directive is actually able to use any expressions, using string
|
The `t-slot` directive is actually able to use any expressions, using string
|
||||||
interplolation:
|
interplolation:
|
||||||
@@ -109,3 +138,117 @@ interplolation:
|
|||||||
```xml
|
```xml
|
||||||
<t t-slot="{{current}}" />
|
<t t-slot="{{current}}" />
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This will evaluate the `current` expression, and insert the corresponding slot
|
||||||
|
at the place of the `t-slot` directive.
|
||||||
|
|
||||||
|
## Slots and props
|
||||||
|
|
||||||
|
In a sense, slots are almost the same as a prop: they define some information
|
||||||
|
to pass to the child component. To make it possible to use it, and to pass it
|
||||||
|
down to sub component, Owl actually define a special prop `slots` that contains
|
||||||
|
all slot information given to the component. It looks like this:
|
||||||
|
|
||||||
|
```js
|
||||||
|
{ slotName_1: slotInfo_1, ..., slotName_m: slotInfo_m }
|
||||||
|
```
|
||||||
|
|
||||||
|
So, a component can pass its slots to a subcomponent like this:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<Child slots="props.slots"/>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Slot params
|
||||||
|
|
||||||
|
For advanced usecases, it may be necessary to pass additional information to a
|
||||||
|
slot. This can be done by providing extra key/value pairs to the `t-set-slot`
|
||||||
|
directive. Then, the generic component can read them in its prop `slots`.
|
||||||
|
|
||||||
|
For example, here is how a Notebook component could be implemented (a component
|
||||||
|
with multiple page, and a tab bar, which only render the current active page,
|
||||||
|
and each page has a title).
|
||||||
|
|
||||||
|
```js
|
||||||
|
class Notebook extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<div class="notebook">
|
||||||
|
<div class="tabs">
|
||||||
|
<t t-foreach="tabNames" t-as="tab" t-key="tab_index">
|
||||||
|
<span t-att-class="{active:tab_index === activeTab}" t-on-click="() => state.activeTab=tab">
|
||||||
|
<t t-esc="props.slots[tab].title"/>
|
||||||
|
</span>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
<div class="page">
|
||||||
|
<t t-slot="{{currentSlot}}"/>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
this.state = useState({ activeTab: 0 });
|
||||||
|
this.tabNames = Object.keys(this.props.slots);
|
||||||
|
}
|
||||||
|
|
||||||
|
get currentSlot() {
|
||||||
|
return this.tabNames[this.state.activeTab];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Notice how one can read the `title` value for each slots. Here is how one could
|
||||||
|
use this `Notebook` component:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<Notebook>
|
||||||
|
<t t-set-slot="page1" title="'Page 1'">
|
||||||
|
<div>this is in the page 1</div>
|
||||||
|
</t>
|
||||||
|
<t t-set-slot="page2" title="'Page 2'" hidden="somevalue">
|
||||||
|
<div>this is in the page 2</div>
|
||||||
|
</t>
|
||||||
|
</Notebook>
|
||||||
|
```
|
||||||
|
|
||||||
|
Slot params works like normal props, so one can use the `.bind` suffix to
|
||||||
|
bind a function if needed.
|
||||||
|
|
||||||
|
## Slot scopes
|
||||||
|
|
||||||
|
For other kinds 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
|
||||||
|
params.
|
||||||
|
|
||||||
|
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
|
||||||
|
everything given by the child component:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<MyComponent>
|
||||||
|
<t t-set-slot="foo" t-slot-scope="scope">
|
||||||
|
content
|
||||||
|
<t t-esc="scope.bool"/>
|
||||||
|
<t t-esc="scope.num"/>
|
||||||
|
</t>
|
||||||
|
</MyComponent>
|
||||||
|
```
|
||||||
|
|
||||||
|
And the child component that includes the slot can provide values like this:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<t t-slot="foo" bool="other_var" num="5">
|
||||||
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
|
|||||||
@@ -1,184 +0,0 @@
|
|||||||
# 🦉 Tags 🦉
|
|
||||||
|
|
||||||
## Content
|
|
||||||
|
|
||||||
- [Overview](#overview)
|
|
||||||
- [`xml` tag](#xml-tag)
|
|
||||||
- [`css` tag](#css-tag)
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
Tags are very small helpers intended to make it easy to write inline templates
|
|
||||||
or styles. There are currently two tags: `css` and `xml`. With these functions,
|
|
||||||
it is possible to write [single file components](../learning/how_to_write_sfc.md).
|
|
||||||
|
|
||||||
## XML tag
|
|
||||||
|
|
||||||
The `xml` tag is certainly the most useful tag. It is used to define an inline
|
|
||||||
QWeb template for a component. Without tags, creating a standalone component
|
|
||||||
would look like this:
|
|
||||||
|
|
||||||
```js
|
|
||||||
import { Component } from 'owl'
|
|
||||||
|
|
||||||
const name = 'some-unique-name';
|
|
||||||
const template = `
|
|
||||||
<div>
|
|
||||||
<span t-if="somecondition">text</span>
|
|
||||||
<button t-on-click="someMethod">Click</button>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
QWeb.registerTemplate(name, template);
|
|
||||||
|
|
||||||
class MyComponent extends Component {
|
|
||||||
static template = name;
|
|
||||||
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
With tags, this process is slightly simplified. The name is uniquely generated,
|
|
||||||
and the template is automatically registered:
|
|
||||||
|
|
||||||
```js
|
|
||||||
const { Component } = owl;
|
|
||||||
const { xml } = owl.tags;
|
|
||||||
|
|
||||||
class MyComponent extends Component {
|
|
||||||
static template = xml`
|
|
||||||
<div>
|
|
||||||
<span t-if="somecondition">text</span>
|
|
||||||
<button t-on-click="someMethod">Click</button>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## CSS tag
|
|
||||||
|
|
||||||
The CSS tag is useful to define a css stylesheet in the javascript file:
|
|
||||||
|
|
||||||
```js
|
|
||||||
class MyComponent extends Component {
|
|
||||||
static template = xml`
|
|
||||||
<div class="my-component">some template</div>
|
|
||||||
`;
|
|
||||||
static style = css`
|
|
||||||
.my-component {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The `css` tag registers internally the css information. Then, whenever the first
|
|
||||||
instance of the component is created, will add a `<style>` tag to the document
|
|
||||||
`<head>`.
|
|
||||||
|
|
||||||
Note that to make it more useful, like other css preprocessors, the `css` tag
|
|
||||||
accepts a small extension of the css specification: css scopes can be nested,
|
|
||||||
and the rules will then be expanded by the `css` helper:
|
|
||||||
|
|
||||||
```scss
|
|
||||||
.my-component {
|
|
||||||
display: block;
|
|
||||||
.sub-component h {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
will be formatted as:
|
|
||||||
|
|
||||||
```css
|
|
||||||
.my-component {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.my-component .sub-component h {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This extension brings another useful feature: the `&` selector which refers to
|
|
||||||
the parent selector. For example, we want our component to be red when hovered.
|
|
||||||
We would like to write something like:
|
|
||||||
|
|
||||||
```scss
|
|
||||||
.my-component {
|
|
||||||
display: block;
|
|
||||||
:hover {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
but it will be formatted as:
|
|
||||||
|
|
||||||
```css
|
|
||||||
.my-component {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.my-component :hover {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The `&` selector can be used to solve this problem:
|
|
||||||
|
|
||||||
```scss
|
|
||||||
.my-component {
|
|
||||||
display: block;
|
|
||||||
&:hover {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
will be formatted as:
|
|
||||||
|
|
||||||
```css
|
|
||||||
.my-component {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.my-component:hover {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Now, there is no additional processing done by the `css` tag. However, since it
|
|
||||||
is done in javascript at runtime, we actually have more power. For example:
|
|
||||||
|
|
||||||
1. sharing values between javascript and css:
|
|
||||||
|
|
||||||
```js
|
|
||||||
import { theme } from "./theme";
|
|
||||||
|
|
||||||
class MyComponent extends Component {
|
|
||||||
static template = xml`<div class="my-component">...</div>`;
|
|
||||||
static style = css`
|
|
||||||
.my-component {
|
|
||||||
color: ${theme.MAIN_COLOR};
|
|
||||||
background-color: ${theme.SECONDARY_color};
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
2. scoping rules to the current component:
|
|
||||||
|
|
||||||
```js
|
|
||||||
import { generateUUID } from "./utils";
|
|
||||||
|
|
||||||
const uuid = generateUUID();
|
|
||||||
|
|
||||||
class MyComponent extends Component {
|
|
||||||
static template = xml`<div data-o-${uuid}="">...</div>`;
|
|
||||||
static style = css`
|
|
||||||
[data-o-${uuid}] {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
# 🦉 QWeb Templating Language🦉
|
# 🦉 Templates 🦉
|
||||||
|
|
||||||
## Content
|
## Content
|
||||||
|
|
||||||
- [Overview](#overview)
|
- [Overview](#overview)
|
||||||
- [Directives](#directives)
|
- [Directives](#directives)
|
||||||
- [Reference](#reference)
|
- [QWeb Template reference](#qweb-template-reference)
|
||||||
- [White Spaces](#white-spaces)
|
- [White Spaces](#white-spaces)
|
||||||
- [Root Nodes](#root-nodes)
|
|
||||||
- [Expression Evaluation](#expression-evaluation)
|
- [Expression Evaluation](#expression-evaluation)
|
||||||
- [Static html Nodes](#static-html-nodes)
|
- [Static html Nodes](#static-html-nodes)
|
||||||
- [Outputting Data](#outputting-data)
|
- [Outputting Data](#outputting-data)
|
||||||
@@ -16,17 +15,20 @@
|
|||||||
- [Dynamic Class Attribute](#dynamic-class-attribute)
|
- [Dynamic Class Attribute](#dynamic-class-attribute)
|
||||||
- [Dynamic Tag Names](#dynamic-tag-names)
|
- [Dynamic Tag Names](#dynamic-tag-names)
|
||||||
- [Loops](#loops)
|
- [Loops](#loops)
|
||||||
- [Rendering Sub Templates](#rendering-sub-templates)
|
- [Sub Templates](#sub-templates)
|
||||||
- [Dynamic Sub Templates](#dynamic-sub-templates)
|
- [Dynamic Sub Templates](#dynamic-sub-templates)
|
||||||
- [Translations](#translations)
|
|
||||||
- [Debugging](#debugging)
|
- [Debugging](#debugging)
|
||||||
|
- [Fragments](#fragments)
|
||||||
|
- [Inline templates](#inline-templates)
|
||||||
|
- [Rendering svg](#rendering-svg)
|
||||||
|
- [Restrictions](#restrictions)
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
[QWeb](https://www.odoo.com/documentation/13.0/reference/qweb.html) is the primary
|
Owl templates are describe using the [QWeb](https://www.odoo.com/documentation/13.0/reference/qweb.html) specification. It is based on the XML format, and used
|
||||||
templating engine used by Odoo. It is based on the XML format, and used
|
|
||||||
mostly to generate HTML. In OWL, QWeb templates are compiled into functions that
|
mostly to generate HTML. In OWL, QWeb templates are compiled into functions that
|
||||||
generate a virtual dom representation of the HTML.
|
generate a virtual dom representation of the HTML. Also, since Owl is a live
|
||||||
|
component system, there are additional directives specific to Owl (such as `t-on`).
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<div>
|
<div>
|
||||||
@@ -53,34 +55,33 @@ extensions.
|
|||||||
|
|
||||||
For reference, here is a list of all standard QWeb directives:
|
For reference, here is a list of all standard QWeb directives:
|
||||||
|
|
||||||
| Name | Description |
|
| Name | Description |
|
||||||
| ------------------------------ | -------------------------------------------------------------- |
|
| ------------------------------ | --------------------------------------------------------------- |
|
||||||
| `t-esc` | [Outputting safely a value](#outputting-data) |
|
| `t-esc` | [Outputting safely a value](#outputting-data) |
|
||||||
| `t-raw` | [Outputting value, without escaping](#outputting-data) |
|
| `t-out` | [Outputting value, possibly without escaping](#outputting-data) |
|
||||||
| `t-set`, `t-value` | [Setting variables](#setting-variables) |
|
| `t-set`, `t-value` | [Setting variables](#setting-variables) |
|
||||||
| `t-if`, `t-elif`, `t-else`, | [conditionally rendering](#conditionals) |
|
| `t-if`, `t-elif`, `t-else`, | [conditionally rendering](#conditionals) |
|
||||||
| `t-foreach`, `t-as` | [Loops](#loops) |
|
| `t-foreach`, `t-as` | [Loops](#loops) |
|
||||||
| `t-att`, `t-attf-*`, `t-att-*` | [Dynamic attributes](#dynamic-attributes) |
|
| `t-att`, `t-attf-*`, `t-att-*` | [Dynamic attributes](#dynamic-attributes) |
|
||||||
| `t-call` | [Rendering sub templates](#rendering-sub-templates) |
|
| `t-call` | [Rendering sub templates](#sub-templates) |
|
||||||
| `t-debug`, `t-log` | [Debugging](#debugging) |
|
| `t-debug`, `t-log` | [Debugging](#debugging) |
|
||||||
| `t-translation` | [Disabling the translation of a node](#translations) |
|
| `t-translation` | [Disabling the translation of a node](translations.md) |
|
||||||
| `t-name` | [Defining a template (not really a directive)](qweb_engine.md) |
|
|
||||||
|
|
||||||
The component system in Owl requires additional directives, to express various
|
The component system in Owl requires additional directives, to express various
|
||||||
needs. Here is a list of all Owl specific directives:
|
needs. Here is a list of all Owl specific directives:
|
||||||
|
|
||||||
| Name | Description |
|
| Name | Description |
|
||||||
| ------------------------ | ------------------------------------------------------------------------------- |
|
| -------------------------------------- | --------------------------------------------------------------- |
|
||||||
| `t-component`, `t-props` | [Defining a sub component](component.md#composition) |
|
| `t-component`, `t-props` | [Defining a sub component](component.md#sub-components) |
|
||||||
| `t-ref` | [Setting a reference to a dom node or a sub component](component.md#references) |
|
| `t-ref` | [Setting a reference to a dom node or a sub component](refs.md) |
|
||||||
| `t-key` | [Defining a key (to help virtual dom reconciliation)](#loops) |
|
| `t-key` | [Defining a key (to help virtual dom reconciliation)](#loops) |
|
||||||
| `t-on-*` | [Event handling](event_handling.md) |
|
| `t-on-*` | [Event handling](event_handling.md) |
|
||||||
| `t-transition` | [Defining an animation](animations.md#css-transitions) |
|
| `t-portal` | [Portal](portal.md) |
|
||||||
| `t-slot` | [Rendering a slot](slots.md) |
|
| `t-slot`, `t-set-slot`, `t-slot-scope` | [Rendering a slot](slots.md) |
|
||||||
| `t-model` | [Form input bindings](component.md#form-input-bindings) |
|
| `t-model` | [Form input bindings](input_bindings.md) |
|
||||||
| `t-tag` | [Rendering nodes with dynamic tag name](#dynamic-tag-names) |
|
| `t-tag` | [Rendering nodes with dynamic tag name](#dynamic-tag-names) |
|
||||||
|
|
||||||
## Reference
|
## QWeb Template Reference
|
||||||
|
|
||||||
### White Spaces
|
### White Spaces
|
||||||
|
|
||||||
@@ -90,32 +91,6 @@ White spaces in a template are handled in a special way:
|
|||||||
- if a whitespace-only text node contains a linebreak, it is ignored
|
- if a whitespace-only text node contains a linebreak, it is ignored
|
||||||
- the previous rules do not apply if we are in a `<pre>` tag
|
- the previous rules do not apply if we are in a `<pre>` tag
|
||||||
|
|
||||||
### Root Nodes
|
|
||||||
|
|
||||||
For many reasons, Owl QWeb templates should have a single root node. More
|
|
||||||
precisely, the result of a template rendering should have a single root node:
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<!–– not ok: two root nodes ––>
|
|
||||||
<t>
|
|
||||||
<div>foo</div>
|
|
||||||
<div>bar</div>
|
|
||||||
</t>
|
|
||||||
|
|
||||||
<!–– ok: result has one single root node ––>
|
|
||||||
<t>
|
|
||||||
<div t-if="someCondition">foo</div>
|
|
||||||
<span t-else="">bar</span>
|
|
||||||
</t>
|
|
||||||
```
|
|
||||||
|
|
||||||
Extra root nodes will actually be ignored (even though they will be rendered
|
|
||||||
in memory).
|
|
||||||
|
|
||||||
Note: this does not apply to subtemplates (see the `t-call` directive). In that
|
|
||||||
case, they will be inlined in the main template, and can actually have many
|
|
||||||
root nodes.
|
|
||||||
|
|
||||||
### Expression Evaluation
|
### Expression Evaluation
|
||||||
|
|
||||||
QWeb expressions are strings that will be processed at compile time. Each variable in
|
QWeb expressions are strings that will be processed at compile time. Each variable in
|
||||||
@@ -190,24 +165,29 @@ rendered with the value `value` set to `42` in the rendering context yields:
|
|||||||
<p>42</p>
|
<p>42</p>
|
||||||
```
|
```
|
||||||
|
|
||||||
The `t-raw` directive is almost the same as `t-esc`, but without the escaping.
|
The `t-out` directive is almost the same as `t-esc`, but possibly without the
|
||||||
This is mostly useful to inject a raw html string somewhere. Obviously, this
|
escaping. The difference is that the value received by the `t-out` directive
|
||||||
is unsafe to do in general, and should only be used for strings known to be safe.
|
will only be not-escaped if it has been marked as such, using the `markup`
|
||||||
|
utility function:
|
||||||
|
|
||||||
```xml
|
For example, in the following component:
|
||||||
<p><t t-raw="value"/></p>
|
|
||||||
|
```js
|
||||||
|
const { markup, Component, xml } = owl;
|
||||||
|
|
||||||
|
class SomeComponent extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<t t-out="value1"/>
|
||||||
|
<t t-out="value2"/>`;
|
||||||
|
|
||||||
|
value1 = "<div>some text 1</div>";
|
||||||
|
value2 = markup("<div>some text 2</div>");
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
rendered with the value `value` set to `<span>foo</span>` in the rendering context yields:
|
The first `t-out` will act as a `t-esc` directive, which means that the content
|
||||||
|
of `value1` will be escaped. However, since `value2` has been tagged as a markup,
|
||||||
```html
|
this will be injected as html.
|
||||||
<p><span>foo</span></p>
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that since the content of the expression is not known beforehand, the `t-raw`
|
|
||||||
directive has to parse the html (and convert it to a virtual dom structure) for
|
|
||||||
each rendering. So, it will be much slower than a regular template. It is
|
|
||||||
therefore advised to limit the use of `t-raw` whenever possible.
|
|
||||||
|
|
||||||
### Setting Variables
|
### Setting Variables
|
||||||
|
|
||||||
@@ -368,7 +348,7 @@ collection to iterate on, and a second parameter `t-as` providing the name to us
|
|||||||
for the current item of the iteration:
|
for the current item of the iteration:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<t t-foreach="[1, 2, 3]" t-as="i">
|
<t t-foreach="[1, 2, 3]" t-as="i" t-key="i">
|
||||||
<p><t t-esc="i"/></p>
|
<p><t t-esc="i"/></p>
|
||||||
</t>
|
</t>
|
||||||
```
|
```
|
||||||
@@ -384,13 +364,17 @@ will be rendered as:
|
|||||||
Like conditions, `t-foreach` applies to the element bearing the directive’s attribute, and
|
Like conditions, `t-foreach` applies to the element bearing the directive’s attribute, and
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<p t-foreach="[1, 2, 3]" t-as="i">
|
<p t-foreach="[1, 2, 3]" t-as="i" t-key="i">
|
||||||
<t t-esc="i"/>
|
<t t-esc="i"/>
|
||||||
</p>
|
</p>
|
||||||
```
|
```
|
||||||
|
|
||||||
is equivalent to the previous example.
|
is equivalent to the previous example.
|
||||||
|
|
||||||
|
An important difference should be made with the usual `QWeb` behaviour: Owl
|
||||||
|
requires the presence of a `t-key` directive, to be able to properly reconcile
|
||||||
|
renderings.
|
||||||
|
|
||||||
`t-foreach` can iterate on an array (the current item will be the current value)
|
`t-foreach` can iterate on an array (the current item will be the current value)
|
||||||
or an object (the current item will be the current key).
|
or an object (the current item will be the current key).
|
||||||
|
|
||||||
@@ -416,7 +400,7 @@ into the global context.
|
|||||||
<t t-set="existing_variable" t-value="false"/>
|
<t t-set="existing_variable" t-value="false"/>
|
||||||
<!-- existing_variable now False -->
|
<!-- existing_variable now False -->
|
||||||
|
|
||||||
<p t-foreach="Array(3)" t-as="i">
|
<p t-foreach="Array(3)" t-as="i" t-key="i">
|
||||||
<t t-set="existing_variable" t-value="true"/>
|
<t t-set="existing_variable" t-value="true"/>
|
||||||
<t t-set="new_variable" t-value="true"/>
|
<t t-set="new_variable" t-value="true"/>
|
||||||
<!-- existing_variable and new_variable now true -->
|
<!-- existing_variable and new_variable now true -->
|
||||||
@@ -430,17 +414,14 @@ Even though Owl tries to be as declarative as possible, the DOM does not fully
|
|||||||
expose its state declaratively in the DOM tree. For example, the scrolling state,
|
expose its state declaratively in the DOM tree. For example, the scrolling state,
|
||||||
the current user selection, the focused element or the state of an input are not
|
the current user selection, the focused element or the state of an input are not
|
||||||
set as attribute in the DOM tree. This is why we use a virtual dom
|
set as attribute in the DOM tree. This is why we use a virtual dom
|
||||||
algorithm to keep the actual DOM node as much as possible.
|
algorithm to make sure we keep the actual DOM node instead of replacing it with
|
||||||
|
a new one.
|
||||||
However, in some situations, this is not enough, and we need to help Owl decide
|
|
||||||
if an element is actually the same, or is a different element with the same
|
|
||||||
properties.
|
|
||||||
|
|
||||||
Consider the following situation: we have a list of two items `[{text: "a"}, {text: "b"}]`
|
Consider the following situation: we have a list of two items `[{text: "a"}, {text: "b"}]`
|
||||||
and we render them in this template:
|
and we render them in this template:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<p t-foreach="items" t-as="item"><t t-esc="item.text"/></p>
|
<p t-foreach="items" t-as="item" t-key="item_index"><t t-esc="item.text"/></p>
|
||||||
```
|
```
|
||||||
|
|
||||||
The result will be two `<p>` tags with text `a` and `b`. Now, if we swap them,
|
The result will be two `<p>` tags with text `a` and `b`. Now, if we swap them,
|
||||||
@@ -501,7 +482,7 @@ using the `...` javascript operator. For example:
|
|||||||
The `...` operator will convert the `Set` (or any other iterables) into a list,
|
The `...` operator will convert the `Set` (or any other iterables) into a list,
|
||||||
which will work with Owl QWeb.
|
which will work with Owl QWeb.
|
||||||
|
|
||||||
### Rendering Sub Templates
|
### Sub Templates
|
||||||
|
|
||||||
QWeb templates can be used for top level rendering, but they can also be used
|
QWeb templates can be used for top level rendering, but they can also be used
|
||||||
from within another template (to avoid duplication or give names to parts of
|
from within another template (to avoid duplication or give names to parts of
|
||||||
@@ -574,21 +555,6 @@ using string interpolation. For example:
|
|||||||
Here, the name of the template is obtained from the `template` value in the
|
Here, the name of the template is obtained from the `template` value in the
|
||||||
template rendering context.
|
template rendering context.
|
||||||
|
|
||||||
### Translations
|
|
||||||
|
|
||||||
By default, QWeb specify that templates should be translated. If this behaviour
|
|
||||||
is not wanted, there is a `t-translation` directive which can turn off
|
|
||||||
translations (if it is set to the `off` value), with the following rules:
|
|
||||||
|
|
||||||
- each text node will be replaced with its translation,
|
|
||||||
- each of the following attribute values will be translated as well: `title`,
|
|
||||||
`placeholder`, `label` and `alt`,
|
|
||||||
- translating text nodes can be disabled with the special attribute `t-translation`,
|
|
||||||
if its value is `off`.
|
|
||||||
|
|
||||||
See [here](qweb_engine.md#translations) for more information on how to setup a
|
|
||||||
translate function in Owl QWeb.
|
|
||||||
|
|
||||||
### Debugging
|
### Debugging
|
||||||
|
|
||||||
The javascript QWeb implementation provides two useful debugging directives:
|
The javascript QWeb implementation provides two useful debugging directives:
|
||||||
@@ -611,3 +577,117 @@ will stop execution if the browser dev tools are open.
|
|||||||
```
|
```
|
||||||
|
|
||||||
will print 42 to the console.
|
will print 42 to the console.
|
||||||
|
|
||||||
|
## Fragments
|
||||||
|
|
||||||
|
Owl 2 supports templates with an arbitrary number of root elements, or even just
|
||||||
|
a text node. So, the following templates are all valid:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
hello owl. This is just a text node!
|
||||||
|
```
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<div>hello</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<div>hello</div>
|
||||||
|
<div>ola</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<div t-if="someCondition"><SomeChildComponent/></div>
|
||||||
|
```
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<t t-if="someCondition"><SomeChildComponent/></t>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Inline templates
|
||||||
|
|
||||||
|
Most real applications will define their templates in a XML file, to benefit
|
||||||
|
from the XML ecosystem, and to do some additional processing, such as translating
|
||||||
|
them. However, in some cases, it is convenient to be able to define a template
|
||||||
|
inline. To do so, one can use the `xml` helper function:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const { Component, xml } = owl;
|
||||||
|
|
||||||
|
class MyComponent extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<div>
|
||||||
|
<span t-if="somecondition">text</span>
|
||||||
|
<button t-on-click="someMethod">Click</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
mount(MyComponent, document.body);
|
||||||
|
```
|
||||||
|
|
||||||
|
This function simply generates an unique string id, and register the template
|
||||||
|
under that id in the internals of Owl, then return the id.
|
||||||
|
|
||||||
|
## Rendering svg
|
||||||
|
|
||||||
|
Owl components can be used to generate dynamic SVG graphs:
|
||||||
|
|
||||||
|
```js
|
||||||
|
class Node extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<g>
|
||||||
|
<circle t-att-cx="props.x" t-att-cy="props.y" r="4" fill="black"/>
|
||||||
|
<text t-att-x="props.x - 5" t-att-y="props.y + 18"><t t-esc="props.node.label"/></text>
|
||||||
|
<t t-set="childx" t-value="props.x + 100"/>
|
||||||
|
<t t-set="height" t-value="props.height/(props.node.children || []).length"/>
|
||||||
|
<t t-foreach="props.node.children || []" t-as="child">
|
||||||
|
<t t-set="childy" t-value="props.y + child_index*height"/>
|
||||||
|
<line t-att-x1="props.x" t-att-y1="props.y" t-att-x2="childx" t-att-y2="childy" stroke="black" />
|
||||||
|
<Node x="childx" y="childy" node="child" height="height"/>
|
||||||
|
</t>
|
||||||
|
</g>
|
||||||
|
`;
|
||||||
|
static components = { Node };
|
||||||
|
}
|
||||||
|
|
||||||
|
class RootNode extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<svg height="180">
|
||||||
|
<Node node="graph" x="10" y="20" height="180"/>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
static components = { Node };
|
||||||
|
graph = {
|
||||||
|
label: "a",
|
||||||
|
children: [
|
||||||
|
{ label: "b" },
|
||||||
|
{ label: "c", children: [{ label: "d" }, { label: "e" }] },
|
||||||
|
{ label: "f", children: [{ label: "g" }] },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This `RootNode` component will then display a live SVG representation of the
|
||||||
|
graph described by the `graph` property. Note that there is a recursive structure
|
||||||
|
here: the `Node` component uses itself as a subcomponent.
|
||||||
|
|
||||||
|
**Important note:** Owl needs to properly set the namespace for each svg elements.
|
||||||
|
Since Owl compile each template separately, it is not able to determine easily
|
||||||
|
if a template is supposed to be included in a svg namespace or not. Therefore,
|
||||||
|
Owl depends on a heuristic: if a tag is either `svg`, `g` or `path`, then it will
|
||||||
|
be considered as svg. In practice, this means that each component or each sub
|
||||||
|
templates (included with `t-call`) should have one of these tag as root tag.
|
||||||
|
|
||||||
|
## Restrictions
|
||||||
|
|
||||||
|
Note that Owl templates forbid the use of tag and or attributes starting with
|
||||||
|
the `block-` string. This restriction prevents name collision with the internal
|
||||||
|
code of Owl.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<div><block-1>this will not be accepted by Owl</block-1></div>
|
||||||
|
```
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
# 🦉 Translations 🦉
|
||||||
|
|
||||||
|
If properly setup, Owl can translate all rendered templates. To do
|
||||||
|
so, it needs a translate function, which takes a string and returns a string.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const translations = {
|
||||||
|
hello: "bonjour",
|
||||||
|
yes: "oui",
|
||||||
|
no: "non",
|
||||||
|
};
|
||||||
|
const translateFn = (str) => translations[str] || str;
|
||||||
|
|
||||||
|
const app = new App(Root, { templates, tranaslateFn });
|
||||||
|
// ...
|
||||||
|
```
|
||||||
|
|
||||||
|
See the [app configuration page](app.md#configuration) for more info on how to
|
||||||
|
configure an Owl application.
|
||||||
|
|
||||||
|
Once setup, all rendered templates will be translated using `translateFn`:
|
||||||
|
|
||||||
|
- each text node will be replaced with its translation,
|
||||||
|
- each of the following attribute values will be translated as well: `title`,
|
||||||
|
`placeholder`, `label` and `alt`,
|
||||||
|
- translating text nodes can be disabled with the special attribute `t-translation`,
|
||||||
|
if its value is `off`.
|
||||||
|
|
||||||
|
So, with the above `translateFn`, the following templates:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<div>hello</div>
|
||||||
|
<div t-translation="off">hello</div>
|
||||||
|
<div>Are you sure?</div>
|
||||||
|
<input placeholder="hello" other="yes"/>
|
||||||
|
```
|
||||||
|
|
||||||
|
will be rendered as:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<div>bonjour</div>
|
||||||
|
<div>hello</div>
|
||||||
|
<div>Are you sure?</div>
|
||||||
|
<input placeholder="bonjour" other="yes"/>
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that the translation is done during the compilation of the template, not
|
||||||
|
when it is rendered.
|
||||||
|
|
||||||
|
In some case, it is useful to be able to extend the list of translatable attributes.
|
||||||
|
For example, one may want to also translate `data-title` attributes. To do that,
|
||||||
|
we can define additional attributes with the `translatableAttributes` option:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const app = new App(Root, { templates, tranaslateFn, translatableAttributes: ["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...
|
||||||
|
```
|
||||||
+17
-107
@@ -6,11 +6,8 @@ functions are all available in the `owl.utils` namespace.
|
|||||||
## Content
|
## Content
|
||||||
|
|
||||||
- [`whenReady`](#whenready): executing code when DOM is ready
|
- [`whenReady`](#whenready): executing code when DOM is ready
|
||||||
- [`loadJS`](#loadjs): loading script files
|
|
||||||
- [`loadFile`](#loadfile): loading a file (useful for templates)
|
- [`loadFile`](#loadfile): loading a file (useful for templates)
|
||||||
- [`escape`](#escape): sanitizing strings
|
- [`EventBus`](#eventbus): a simple EventBus
|
||||||
- [`debounce`](#debounce): limiting rate of function calls
|
|
||||||
- [`shallowEqual`](#shallowequal): shallow object comparison
|
|
||||||
|
|
||||||
## `whenReady`
|
## `whenReady`
|
||||||
|
|
||||||
@@ -19,40 +16,20 @@ not ready yet, resolved directly otherwise). If called with a callback as
|
|||||||
argument, it executes it as soon as the DOM ready (or directly).
|
argument, it executes it as soon as the DOM ready (or directly).
|
||||||
|
|
||||||
```js
|
```js
|
||||||
Promise.all([loadFile("templates.xml"), owl.utils.whenReady()]).then(function ([templates]) {
|
const { whenReady } = owl;
|
||||||
const qweb = new owl.QWeb({ templates });
|
|
||||||
const env = { qweb };
|
await whenReady();
|
||||||
await mount(App, { env, target: document.body });
|
// do something
|
||||||
});
|
|
||||||
```
|
```
|
||||||
|
|
||||||
or alternatively:
|
or alternatively:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
owl.utils.whenReady(function () {
|
whenReady(function () {
|
||||||
const qweb = new owl.QWeb();
|
// do something
|
||||||
const env = { qweb };
|
|
||||||
await mount(App, { env, target: document.body });
|
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
## `loadJS`
|
|
||||||
|
|
||||||
`loadJS` takes a url (string) for a javascript resource, and loads it (by adding
|
|
||||||
a script tag in the document head). It returns a promise, so the caller can
|
|
||||||
properly reacts when it is ready. Also, it is smart: it maintains a list of urls
|
|
||||||
previously loaded (or currently being loaded), and prevent doing twice the work.
|
|
||||||
|
|
||||||
For example, it is useful for lazy loading external libraries:
|
|
||||||
|
|
||||||
```js
|
|
||||||
class MyComponent extends owl.Component {
|
|
||||||
willStart() {
|
|
||||||
return owl.utils.loadJS("/static/libs/someLib.js");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## `loadFile`
|
## `loadFile`
|
||||||
|
|
||||||
`loadFile` is a helper function to fetch a file. It simply
|
`loadFile` is a helper function to fetch a file. It simply
|
||||||
@@ -60,89 +37,22 @@ performs a `GET` request and returns the resulting string in a promise. The
|
|||||||
initial usecase for this function is to load a template file. For example:
|
initial usecase for this function is to load a template file. For example:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
const { loadFile } = owl;
|
||||||
|
|
||||||
async function makeEnv() {
|
async function makeEnv() {
|
||||||
const templates = await owl.utils.loadFile("templates.xml");
|
const templates = await loadFile("templates.xml");
|
||||||
const qweb = new owl.QWeb({ templates });
|
// do something
|
||||||
return { qweb };
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that unlike `loadJS`, this function returns the content of the file as a
|
## `EventBus`
|
||||||
string. It does not add a `script` tag or any other side effect.
|
|
||||||
|
|
||||||
## `escape`
|
It is a simple `EventBus`, with the same API as usual DOM elements, and an
|
||||||
|
additional `trigger` method to dispatch events:
|
||||||
Sometimes, we need to display dynamic data (for example user-generated data) in
|
|
||||||
the user interface. If this is done by a `QWeb` template, it is not an issue:
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<div><t t-esc="user.data"/></div>
|
|
||||||
```
|
|
||||||
|
|
||||||
The `QWeb` engine will create a `div` node and add the content of the `user.data`
|
|
||||||
string as a text node, so the web browser will not parse it as html. However,
|
|
||||||
it may be a problem if this is done with some javascript code like this:
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
class BadComponent extends Component {
|
const bus = new EventBus();
|
||||||
// some template with a ref to a div
|
bus.addEventListener("event", () => console.log("something happened"));
|
||||||
// some code ...
|
|
||||||
|
|
||||||
mounted() {
|
bus.trigger("event"); // 'something happened' is logged
|
||||||
this.divRef.el.innerHTML = this.state.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
In this case, the content of the `div` will be parsed as html, which may inject
|
|
||||||
unwanted behaviour. To fix this, the `escape` function will simply transform a
|
|
||||||
string into an escaped version of the same string, which will be properly displayed
|
|
||||||
by the browser, but which will not be parsed as html (for example, `"<ok>"` is
|
|
||||||
escaped to the string: `"<ok>"`). So, the bad example above can be fixed
|
|
||||||
with the following change:
|
|
||||||
|
|
||||||
```js
|
|
||||||
this.divRef.el.innerHTML = owl.utils.escape(this.state.value);
|
|
||||||
```
|
|
||||||
|
|
||||||
## `debounce`
|
|
||||||
|
|
||||||
The `debounce` function is useful when we want to limit the number of times some
|
|
||||||
function/action is perfomed. For example, this may be useful to prevent issue
|
|
||||||
with people double clicking on a button.
|
|
||||||
|
|
||||||
It takes three arguments:
|
|
||||||
|
|
||||||
- `func` (function): this is the function that will be rate limited
|
|
||||||
- `wait` (number): this is the number of milliseconds that we want to use to
|
|
||||||
rate limit the function `func`
|
|
||||||
- `immediate` (optional, boolean, default=false): if `immediate` is true, the
|
|
||||||
function will be triggered immediately (leading edge of the interval). If false,
|
|
||||||
the function will be triggered at the end (trailing edge).
|
|
||||||
|
|
||||||
It returns a function. For example:
|
|
||||||
|
|
||||||
```js
|
|
||||||
const debounce = owl.utils.debounce;
|
|
||||||
window.addEventListener("mousemove", debounce(doSomething, 100));
|
|
||||||
```
|
|
||||||
|
|
||||||
As this example shows, it is usualy useful for event handlers which are triggered
|
|
||||||
very quickly, such as `scroll` or `mousemove` events.
|
|
||||||
|
|
||||||
## `shallowEqual`
|
|
||||||
|
|
||||||
This function checks if two objects have the same values assigned to each keys:
|
|
||||||
|
|
||||||
```js
|
|
||||||
shallowEqual({ a: 1, b: 2 }, { a: 1, b: 2 }); // true
|
|
||||||
shallowEqual({ a: 1, b: 2 }, { a: 1, b: 3 }); // false
|
|
||||||
```
|
|
||||||
|
|
||||||
However, for performance reasons, it assumes that the two objects have the same
|
|
||||||
keys. If we are in a situation where this is not guaranteed, the following code
|
|
||||||
will work:
|
|
||||||
|
|
||||||
```js
|
|
||||||
const completeShallowEqual = (a, b) => shallowEqual(a, b) && shallowEqual(b, a);
|
|
||||||
```
|
```
|
||||||
|
|||||||
+3
-3
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@odoo/owl",
|
"name": "@odoo/owl",
|
||||||
"version": "2.0.0-alpha1",
|
"version": "2.0.0-alpha.3",
|
||||||
"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",
|
||||||
@@ -13,10 +13,10 @@
|
|||||||
"node": ">=12.18.3"
|
"node": ">=12.18.3"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:bundle": "rollup -c",
|
"build:bundle": "rollup -c --failAfterWarnings",
|
||||||
"build": "npm run build:bundle",
|
"build": "npm run build:bundle",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand --watch",
|
"test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand --watch --testTimeout=5000000",
|
||||||
"test:watch": "jest --watch",
|
"test:watch": "jest --watch",
|
||||||
"playground:serve": "python3 tools/server.py || python tools/server.py",
|
"playground:serve": "python3 tools/server.py || python tools/server.py",
|
||||||
"playground": "npm run build && npm run playground:serve",
|
"playground": "npm run build && npm run playground:serve",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
# 🦉 OWL Roadmap 🦉
|
# 🦉 OWL Roadmap 🦉
|
||||||
|
|
||||||
- Current version: 1.4.7
|
- Current version: 1.4.10
|
||||||
- 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
|
||||||
|
|||||||
+43
-33
@@ -1,9 +1,10 @@
|
|||||||
import { Component } from "../component/component";
|
import { Component, ComponentConstructor } from "../component/component";
|
||||||
import { ComponentNode } from "../component/component_node";
|
import { ComponentNode } from "../component/component_node";
|
||||||
import { MountOptions } from "../component/fibers";
|
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
|
||||||
|
|
||||||
@@ -11,52 +12,57 @@ export interface Env {
|
|||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AppConfig extends TemplateSetConfig {
|
export interface AppConfig<P, E> extends TemplateSetConfig {
|
||||||
env?: Env;
|
props?: P;
|
||||||
props?: any;
|
env?: E;
|
||||||
|
test?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DEV_MSG = `Owl is running in 'dev' mode.
|
export const DEV_MSG = () => {
|
||||||
|
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/master/doc/reference/config.md#mode for more information.`;
|
See https://github.com/odoo/owl/blob/${hash}/doc/reference/app.md#configuration for more information.`;
|
||||||
|
};
|
||||||
|
|
||||||
export class App<T extends typeof Component = any> extends TemplateSet {
|
export class App<
|
||||||
Root: T;
|
T extends abstract new (...args: any) => any = any,
|
||||||
props: any;
|
P extends object = any,
|
||||||
env: Env;
|
E = any
|
||||||
scheduler = new Scheduler(window.requestAnimationFrame.bind(window));
|
> extends TemplateSet {
|
||||||
root: ComponentNode | null = null;
|
static validateTarget = validateTarget;
|
||||||
|
|
||||||
constructor(Root: T, config: AppConfig = {}) {
|
Root: ComponentConstructor<P, E>;
|
||||||
|
props: P;
|
||||||
|
env: E;
|
||||||
|
scheduler = new Scheduler();
|
||||||
|
root: ComponentNode<P, E> | null = null;
|
||||||
|
|
||||||
|
constructor(Root: ComponentConstructor<P, E>, config: AppConfig<P, E> = {}) {
|
||||||
super(config);
|
super(config);
|
||||||
this.Root = Root;
|
this.Root = Root;
|
||||||
if (config.dev) {
|
if (config.test) {
|
||||||
console.info(DEV_MSG);
|
this.dev = true;
|
||||||
|
}
|
||||||
|
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));
|
this.env = Object.freeze(Object.defineProperties({}, descrs)) as E;
|
||||||
this.props = config.props || {};
|
this.props = config.props || ({} as P);
|
||||||
}
|
}
|
||||||
|
|
||||||
mount(target: HTMLElement, options?: MountOptions): Promise<InstanceType<T>> {
|
mount(target: HTMLElement, options?: MountOptions): Promise<Component<P, E> & InstanceType<T>> {
|
||||||
this.checkTarget(target);
|
App.validateTarget(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) {
|
makeNode(Component: ComponentConstructor, props: any): ComponentNode {
|
||||||
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: T, props: any): ComponentNode {
|
|
||||||
return new ComponentNode(Component, props, this);
|
return new ComponentNode(Component, props, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,10 +102,14 @@ export class App<T extends typeof Component = any> extends TemplateSet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function mount<T extends typeof Component>(
|
export async function mount<
|
||||||
C: T,
|
T extends abstract new (...args: any) => any = any,
|
||||||
|
P extends object = any,
|
||||||
|
E = any
|
||||||
|
>(
|
||||||
|
C: T & ComponentConstructor<P, E>,
|
||||||
target: HTMLElement,
|
target: HTMLElement,
|
||||||
config: AppConfig & MountOptions = {}
|
config: AppConfig<P, E> & MountOptions = {}
|
||||||
): Promise<InstanceType<T>> {
|
): Promise<Component<P, E> & InstanceType<T>> {
|
||||||
return new App(C, config).mount(target, config);
|
return new App(C, config).mount(target, config);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { BDom, multi, text, toggler } from "../blockdom";
|
|||||||
import { validateProps } from "../component/props_validation";
|
import { validateProps } from "../component/props_validation";
|
||||||
import { Markup } from "../utils";
|
import { Markup } from "../utils";
|
||||||
import { html } from "../blockdom/index";
|
import { html } from "../blockdom/index";
|
||||||
|
import { TARGET } from "../reactivity";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This file contains utility functions that will be injected in each template,
|
* This file contains utility functions that will be injected in each template,
|
||||||
@@ -21,7 +22,8 @@ function callSlot(
|
|||||||
extra: any,
|
extra: any,
|
||||||
defaultContent?: (ctx: any, node: any, key: string) => BDom
|
defaultContent?: (ctx: any, node: any, key: string) => BDom
|
||||||
): BDom {
|
): BDom {
|
||||||
const slots = (ctx.props && ctx.props.slots) || {};
|
key = key + "__slot_" + name;
|
||||||
|
const slots = ctx.props[TARGET].slots || {};
|
||||||
const { __render, __ctx, __scope } = slots[name] || {};
|
const { __render, __ctx, __scope } = slots[name] || {};
|
||||||
const slotScope = Object.create(__ctx || {});
|
const slotScope = Object.create(__ctx || {});
|
||||||
if (__scope) {
|
if (__scope) {
|
||||||
@@ -135,7 +137,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 (typeof value === "string") {
|
} else if (value instanceof String || typeof value === "string") {
|
||||||
safeKey = "string_unsafe";
|
safeKey = "string_unsafe";
|
||||||
block = text(value);
|
block = text(value);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+18
-7
@@ -1,11 +1,11 @@
|
|||||||
import { createBlock, html, list, multi, text, toggler, comment } from "../blockdom";
|
import { createBlock, html, list, multi, text, toggler, comment } from "../blockdom";
|
||||||
import { compile, Template } from "../compiler";
|
import { compile, Template } from "../compiler";
|
||||||
import { component } from "../component/component_node";
|
import { component, getCurrent } from "../component/component_node";
|
||||||
import { UTILS } from "./template_helpers";
|
import { UTILS } from "./template_helpers";
|
||||||
|
|
||||||
const bdom = { text, createBlock, list, multi, html, toggler, component, comment };
|
const bdom = { text, createBlock, list, multi, html, toggler, component, comment };
|
||||||
|
|
||||||
export const globalTemplates: { [key: string]: string | Node } = {};
|
export const globalTemplates: { [key: string]: string | Element } = {};
|
||||||
|
|
||||||
function parseXML(xml: string): Document {
|
function parseXML(xml: string): Document {
|
||||||
const parser = new DOMParser();
|
const parser = new DOMParser();
|
||||||
@@ -67,7 +67,11 @@ export class TemplateSet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addTemplate(name: string, template: string | Node, options: { allowDuplicate?: boolean } = {}) {
|
addTemplate(
|
||||||
|
name: string,
|
||||||
|
template: string | Element,
|
||||||
|
options: { allowDuplicate?: boolean } = {}
|
||||||
|
) {
|
||||||
if (name in this.rawTemplates && !options.allowDuplicate) {
|
if (name in this.rawTemplates && !options.allowDuplicate) {
|
||||||
throw new Error(`Template ${name} already defined`);
|
throw new Error(`Template ${name} already defined`);
|
||||||
}
|
}
|
||||||
@@ -82,7 +86,6 @@ export class TemplateSet {
|
|||||||
xml = xml instanceof Document ? xml : parseXML(xml);
|
xml = xml instanceof Document ? xml : parseXML(xml);
|
||||||
for (const template of xml.querySelectorAll("[t-name]")) {
|
for (const template of xml.querySelectorAll("[t-name]")) {
|
||||||
const name = template.getAttribute("t-name")!;
|
const name = template.getAttribute("t-name")!;
|
||||||
template.removeAttribute("t-name");
|
|
||||||
this.addTemplate(name, template, options);
|
this.addTemplate(name, template, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -91,19 +94,27 @@ export class TemplateSet {
|
|||||||
if (!(name in this.templates)) {
|
if (!(name in this.templates)) {
|
||||||
const rawTemplate = this.rawTemplates[name];
|
const rawTemplate = this.rawTemplates[name];
|
||||||
if (rawTemplate === undefined) {
|
if (rawTemplate === undefined) {
|
||||||
throw new Error(`Missing template: "${name}"`);
|
let extraInfo = "";
|
||||||
|
try {
|
||||||
|
const componentName = getCurrent().component.constructor.name;
|
||||||
|
extraInfo = ` (for component "${componentName}")`;
|
||||||
|
} catch {}
|
||||||
|
throw new Error(`Missing template: "${name}"${extraInfo}`);
|
||||||
}
|
}
|
||||||
const templateFn = this._compileTemplate(name, rawTemplate);
|
const templateFn = this._compileTemplate(name, rawTemplate);
|
||||||
// first add a function to lazily get the template, in case there is a
|
// first add a function to lazily get the template, in case there is a
|
||||||
// recursive call to the template name
|
// recursive call to the template name
|
||||||
this.templates[name] = (context, parent) => this.templates[name](context, parent);
|
const templates = this.templates;
|
||||||
|
this.templates[name] = function (context, parent) {
|
||||||
|
return templates[name].call(this, context, parent);
|
||||||
|
};
|
||||||
const template = templateFn(bdom, this.utils);
|
const template = templateFn(bdom, this.utils);
|
||||||
this.templates[name] = template;
|
this.templates[name] = template;
|
||||||
}
|
}
|
||||||
return this.templates[name];
|
return this.templates[name];
|
||||||
}
|
}
|
||||||
|
|
||||||
_compileTemplate(name: string, template: string | Node) {
|
_compileTemplate(name: string, template: string | Element) {
|
||||||
return compile(template, {
|
return compile(template, {
|
||||||
name,
|
name,
|
||||||
dev: this.dev,
|
dev: this.dev,
|
||||||
|
|||||||
@@ -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 || "";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -299,12 +299,13 @@ 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 };
|
ctx = { collectors: [], locations: [], children, cbRefs: [], refN: tree.refN, refList: [] };
|
||||||
fromIdx = 0;
|
fromIdx = 0;
|
||||||
}
|
}
|
||||||
if (tree.refN) {
|
if (tree.refN) {
|
||||||
@@ -408,11 +409,11 @@ function updateCtx(ctx: BlockCtx, tree: IntermediateTree) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "ref":
|
case "ref":
|
||||||
ctx.cbRefs.push(info.idx);
|
const index = ctx.cbRefs.push(info.idx) - 1;
|
||||||
ctx.locations.push({
|
ctx.locations.push({
|
||||||
idx: info.idx,
|
idx: info.idx,
|
||||||
refIdx: info.refIdx!,
|
refIdx: info.refIdx!,
|
||||||
setData: setRef,
|
setData: makeRefSetter(index, ctx.refList),
|
||||||
updateData: NO_OP,
|
updateData: NO_OP,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -426,12 +427,21 @@ 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 refs = ctx.cbRefs;
|
const cbRefs = 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 ref of refs) {
|
for (let cbRef of cbRefs) {
|
||||||
let fn = (this as any).data[ref];
|
let fn = (this as any).data[cbRef];
|
||||||
fn(null);
|
fn(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -476,12 +486,12 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass {
|
|||||||
const nodeInsertBefore = nodeProto.insertBefore;
|
const nodeInsertBefore = nodeProto.insertBefore;
|
||||||
const elementRemove = elementProto.remove;
|
const elementRemove = elementProto.remove;
|
||||||
|
|
||||||
return class Block {
|
class Block {
|
||||||
el: HTMLElement | undefined;
|
el: HTMLElement | undefined;
|
||||||
refs: Node[] | undefined;
|
|
||||||
data: any[] | undefined;
|
|
||||||
parentEl?: HTMLElement | undefined;
|
parentEl?: HTMLElement | undefined;
|
||||||
|
data: any[] | undefined;
|
||||||
children?: (VNode | undefined)[];
|
children?: (VNode | undefined)[];
|
||||||
|
refs: Node[] | undefined;
|
||||||
|
|
||||||
constructor(data?: any[]) {
|
constructor(data?: any[]) {
|
||||||
this.data = data;
|
this.data = data;
|
||||||
@@ -502,46 +512,62 @@ 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);
|
const el = nodeCloneNode.call(template, true) as HTMLElement;
|
||||||
nodeInsertBefore.call(parent, el, afterNode);
|
nodeInsertBefore.call(parent, el, afterNode);
|
||||||
if (isDynamic) {
|
this.el = el;
|
||||||
// collecting references
|
this.parentEl = parent;
|
||||||
const refs: Node[] = new Array(refN);
|
}
|
||||||
this.refs = refs;
|
patch(other: Block, withBeforeRemove: boolean) {}
|
||||||
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 (isDynamic) {
|
||||||
if (locN) {
|
Block.prototype.mount = function mount(parent: HTMLElement, afterNode: Node | null) {
|
||||||
const data = this.data!;
|
const el = nodeCloneNode.call(template, true);
|
||||||
for (let i = 0; i < locN; i++) {
|
// collecting references
|
||||||
const loc = locations[i];
|
const refs: Node[] = new Array(refN);
|
||||||
loc.setData.call(refs[loc.refIdx], data[i]);
|
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]);
|
||||||
|
}
|
||||||
|
|
||||||
// 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];
|
}
|
||||||
const afterNode = loc.afterRefIdx ? refs[loc.afterRefIdx] : null;
|
}
|
||||||
child.isOnlyChild = loc.isOnlyChild;
|
|
||||||
child.mount(refs[loc.parentRefIdx] as any, afterNode);
|
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;
|
||||||
}
|
}
|
||||||
@@ -586,19 +612,17 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
toString() {
|
}
|
||||||
const div = document.createElement("div");
|
return Block;
|
||||||
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 setRef(this: HTMLElement, fn: any) {
|
function makeRefSetter(index: number, refs: (() => void)[][]): Setter<HTMLElement> {
|
||||||
fn(this);
|
return function setRef(this: HTMLElement, fn: any) {
|
||||||
|
refs[refs.length - 1][index] = () => fn(this);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ class VHtml {
|
|||||||
// remove current content
|
// remove current content
|
||||||
this.remove();
|
this.remove();
|
||||||
this.content = content;
|
this.content = content;
|
||||||
|
this.html = other.html;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -98,7 +98,6 @@ class VList {
|
|||||||
let endVn2 = ch2[endIdx2];
|
let endVn2 = ch2[endIdx2];
|
||||||
|
|
||||||
let mapping: any = undefined;
|
let mapping: any = undefined;
|
||||||
// let noFullRemove = this.hasNoComponent;
|
|
||||||
|
|
||||||
while (startIdx1 <= endIdx1 && startIdx2 <= endIdx2) {
|
while (startIdx1 <= endIdx1 && startIdx2 <= endIdx2) {
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ export class VMulti {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toString(): string {
|
toString(): string {
|
||||||
return this.children.map((c) => c!.toString()).join("");
|
return this.children.map((c) => (c ? c!.toString() : "")).join("");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
text: string | String;
|
||||||
parentEl?: HTMLElement | undefined;
|
parentEl?: HTMLElement | undefined;
|
||||||
el?: any;
|
el?: any;
|
||||||
|
|
||||||
constructor(text: string) {
|
constructor(text: string | String) {
|
||||||
this.text = text;
|
this.text = text;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ class VComment extends VSimpleNode {
|
|||||||
patch() {}
|
patch() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function text(str: string): VNode<VText> {
|
export function text(str: string | String): VNode<VText> {
|
||||||
return new VText(str);
|
return new VText(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+141
-60
@@ -19,6 +19,7 @@ import {
|
|||||||
ASTTSet,
|
ASTTSet,
|
||||||
ASTTranslation,
|
ASTTranslation,
|
||||||
ASTType,
|
ASTType,
|
||||||
|
ASTTPortal,
|
||||||
} from "./parser";
|
} from "./parser";
|
||||||
|
|
||||||
type BlockType = "block" | "text" | "multi" | "list" | "html" | "comment";
|
type BlockType = "block" | "text" | "multi" | "list" | "html" | "comment";
|
||||||
@@ -38,6 +39,7 @@ export interface CodeGenOptions extends Config {
|
|||||||
// of HTML (as we will parse it as xml later)
|
// of HTML (as we will parse it as xml later)
|
||||||
const xmlDoc = document.implementation.createDocument(null, null, null);
|
const xmlDoc = document.implementation.createDocument(null, null, null);
|
||||||
|
|
||||||
|
const MODS = new Set(["stop", "capture", "prevent", "self", "synthetic"]);
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// BlockDescription
|
// BlockDescription
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
@@ -125,6 +127,8 @@ interface Context {
|
|||||||
isLast?: boolean;
|
isLast?: boolean;
|
||||||
translate: boolean;
|
translate: boolean;
|
||||||
tKeyExpr: string | null;
|
tKeyExpr: string | null;
|
||||||
|
nameSpace?: string;
|
||||||
|
tModelSelectedExpr?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createContext(parentCtx: Context, params?: Partial<Context>) {
|
function createContext(parentCtx: Context, params?: Partial<Context>) {
|
||||||
@@ -135,6 +139,8 @@ function createContext(parentCtx: Context, params?: Partial<Context>) {
|
|||||||
forceNewBlock: true,
|
forceNewBlock: true,
|
||||||
translate: parentCtx.translate,
|
translate: parentCtx.translate,
|
||||||
tKeyExpr: null,
|
tKeyExpr: null,
|
||||||
|
nameSpace: parentCtx.nameSpace,
|
||||||
|
tModelSelectedExpr: parentCtx.tModelSelectedExpr,
|
||||||
},
|
},
|
||||||
params
|
params
|
||||||
);
|
);
|
||||||
@@ -208,14 +214,24 @@ export class CodeGenerator {
|
|||||||
templateName?: string;
|
templateName?: string;
|
||||||
dev: boolean;
|
dev: boolean;
|
||||||
translateFn: (s: string) => string;
|
translateFn: (s: string) => string;
|
||||||
translatableAttributes: string[];
|
translatableAttributes: string[] = TRANSLATABLE_ATTRS;
|
||||||
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);
|
||||||
this.translatableAttributes = options.translatableAttributes || TRANSLATABLE_ATTRS;
|
if (options.translatableAttributes) {
|
||||||
|
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;
|
||||||
@@ -445,6 +461,8 @@ export class CodeGenerator {
|
|||||||
case ASTType.TTranslation:
|
case ASTType.TTranslation:
|
||||||
this.compileTTranslation(ast, ctx);
|
this.compileTTranslation(ast, ctx);
|
||||||
break;
|
break;
|
||||||
|
case ASTType.TPortal:
|
||||||
|
this.compileTPortal(ast, ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -501,7 +519,12 @@ export class CodeGenerator {
|
|||||||
const modifiers = rawEvent
|
const modifiers = rawEvent
|
||||||
.split(".")
|
.split(".")
|
||||||
.slice(1)
|
.slice(1)
|
||||||
.map((m) => `"${m}"`);
|
.map((m) => {
|
||||||
|
if (!MODS.has(m)) {
|
||||||
|
throw new Error(`Unknown event modifier: '${m}'`);
|
||||||
|
}
|
||||||
|
return `"${m}"`;
|
||||||
|
});
|
||||||
let modifiersCode = "";
|
let modifiersCode = "";
|
||||||
if (modifiers.length) {
|
if (modifiers.length) {
|
||||||
modifiersCode = `${modifiers.join(",")}, `;
|
modifiersCode = `${modifiers.join(",")}, `;
|
||||||
@@ -511,10 +534,10 @@ export class CodeGenerator {
|
|||||||
|
|
||||||
compileTDomNode(ast: ASTDomNode, ctx: Context) {
|
compileTDomNode(ast: ASTDomNode, ctx: Context) {
|
||||||
let { block, forceNewBlock } = ctx;
|
let { block, forceNewBlock } = ctx;
|
||||||
const isNewBlock = !block || forceNewBlock || ast.dynamicTag !== null;
|
const isNewBlock = !block || forceNewBlock || ast.dynamicTag !== null || ast.ns;
|
||||||
let codeIdx = this.target.code.length;
|
let codeIdx = this.target.code.length;
|
||||||
if (isNewBlock) {
|
if (isNewBlock) {
|
||||||
if (ast.dynamicTag && ctx.block) {
|
if ((ast.dynamicTag || ctx.tKeyExpr || ast.ns) && ctx.block) {
|
||||||
this.insertAnchor(ctx.block!);
|
this.insertAnchor(ctx.block!);
|
||||||
}
|
}
|
||||||
block = this.createBlock(block, "block", ctx);
|
block = this.createBlock(block, "block", ctx);
|
||||||
@@ -527,28 +550,41 @@ export class CodeGenerator {
|
|||||||
}
|
}
|
||||||
// attributes
|
// attributes
|
||||||
const attrs: { [key: string]: string } = {};
|
const attrs: { [key: string]: string } = {};
|
||||||
if (ast.ns) {
|
const nameSpace = ast.ns || ctx.nameSpace;
|
||||||
|
if (nameSpace && isNewBlock) {
|
||||||
// specific namespace uri
|
// specific namespace uri
|
||||||
attrs["block-ns"] = ast.ns;
|
attrs["block-ns"] = nameSpace;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let key in ast.attrs) {
|
for (let key in ast.attrs) {
|
||||||
|
let expr, attrName;
|
||||||
if (key.startsWith("t-attf")) {
|
if (key.startsWith("t-attf")) {
|
||||||
let expr = interpolate(ast.attrs[key]);
|
expr = interpolate(ast.attrs[key]);
|
||||||
const idx = block!.insertData(expr, "attr");
|
const idx = block!.insertData(expr, "attr");
|
||||||
attrs["block-attribute-" + idx] = key.slice(7);
|
|
||||||
|
attrName = key.slice(7);
|
||||||
|
attrs["block-attribute-" + idx] = attrName;
|
||||||
} else if (key.startsWith("t-att")) {
|
} else if (key.startsWith("t-att")) {
|
||||||
let expr = compileExpr(ast.attrs[key]);
|
expr = compileExpr(ast.attrs[key]);
|
||||||
const idx = block!.insertData(expr, "attr");
|
const idx = block!.insertData(expr, "attr");
|
||||||
if (key === "t-att") {
|
if (key === "t-att") {
|
||||||
attrs[`block-attributes`] = String(idx);
|
attrs[`block-attributes`] = String(idx);
|
||||||
} else {
|
} else {
|
||||||
attrs[`block-attribute-${idx}`] = key.slice(6);
|
attrName = key.slice(6);
|
||||||
|
attrs[`block-attribute-${idx}`] = attrName;
|
||||||
}
|
}
|
||||||
} else if (this.translatableAttributes.includes(key)) {
|
} else if (this.translatableAttributes.includes(key)) {
|
||||||
attrs[key] = this.translateFn(ast.attrs[key]);
|
attrs[key] = this.translateFn(ast.attrs[key]);
|
||||||
} else {
|
} else {
|
||||||
|
expr = `"${ast.attrs[key]}"`;
|
||||||
|
attrName = key;
|
||||||
attrs[key] = ast.attrs[key];
|
attrs[key] = ast.attrs[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (attrName === "value" && ctx.tModelSelectedExpr) {
|
||||||
|
let selectedId = block!.insertData(`${ctx.tModelSelectedExpr} === ${expr}`, "attr");
|
||||||
|
attrs[`block-attribute-${selectedId}`] = "selected";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// event handlers
|
// event handlers
|
||||||
@@ -588,8 +624,10 @@ export class CodeGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// t-model
|
// t-model
|
||||||
|
let tModelSelectedExpr;
|
||||||
if (ast.model) {
|
if (ast.model) {
|
||||||
const {
|
const {
|
||||||
|
hasDynamicChildren,
|
||||||
baseExpr,
|
baseExpr,
|
||||||
expr,
|
expr,
|
||||||
eventType,
|
eventType,
|
||||||
@@ -600,20 +638,25 @@ export class CodeGenerator {
|
|||||||
} = ast.model;
|
} = ast.model;
|
||||||
|
|
||||||
const baseExpression = compileExpr(baseExpr);
|
const baseExpression = compileExpr(baseExpr);
|
||||||
const id = this.generateId();
|
const bExprId = this.generateId("bExpr");
|
||||||
this.addLine(`const bExpr${id} = ${baseExpression};`);
|
this.addLine(`const ${bExprId} = ${baseExpression};`);
|
||||||
|
|
||||||
const expression = compileExpr(expr);
|
const expression = compileExpr(expr);
|
||||||
|
const exprId = this.generateId("expr");
|
||||||
|
this.addLine(`const ${exprId} = ${expression};`);
|
||||||
|
|
||||||
|
const fullExpression = `${bExprId}[${exprId}]`;
|
||||||
|
|
||||||
let idx: number;
|
let idx: number;
|
||||||
if (specialInitTargetAttr) {
|
if (specialInitTargetAttr) {
|
||||||
idx = block!.insertData(
|
idx = block!.insertData(`${fullExpression} === '${attrs[targetAttr]}'`, "attr");
|
||||||
`${baseExpression}[${expression}] === '${attrs[targetAttr]}'`,
|
|
||||||
"attr"
|
|
||||||
);
|
|
||||||
attrs[`block-attribute-${idx}`] = specialInitTargetAttr;
|
attrs[`block-attribute-${idx}`] = specialInitTargetAttr;
|
||||||
|
} else if (hasDynamicChildren) {
|
||||||
|
const bValueId = this.generateId("bValue");
|
||||||
|
tModelSelectedExpr = `${bValueId}`;
|
||||||
|
this.addLine(`let ${tModelSelectedExpr} = ${fullExpression}`);
|
||||||
} else {
|
} else {
|
||||||
idx = block!.insertData(`${baseExpression}[${expression}]`, "attr");
|
idx = block!.insertData(`${fullExpression}`, "attr");
|
||||||
attrs[`block-attribute-${idx}`] = targetAttr;
|
attrs[`block-attribute-${idx}`] = targetAttr;
|
||||||
}
|
}
|
||||||
this.helpers.add("toNumber");
|
this.helpers.add("toNumber");
|
||||||
@@ -621,7 +664,7 @@ export class CodeGenerator {
|
|||||||
valueCode = shouldTrim ? `${valueCode}.trim()` : valueCode;
|
valueCode = shouldTrim ? `${valueCode}.trim()` : valueCode;
|
||||||
valueCode = shouldNumberize ? `toNumber(${valueCode})` : valueCode;
|
valueCode = shouldNumberize ? `toNumber(${valueCode})` : valueCode;
|
||||||
|
|
||||||
const handler = `[(ev) => { bExpr${id}[${expression}] = ${valueCode}; }]`;
|
const handler = `[(ev) => { ${fullExpression} = ${valueCode}; }]`;
|
||||||
idx = block!.insertData(handler, "hdlr");
|
idx = block!.insertData(handler, "hdlr");
|
||||||
attrs[`block-handler-${idx}`] = eventType;
|
attrs[`block-handler-${idx}`] = eventType;
|
||||||
}
|
}
|
||||||
@@ -644,6 +687,9 @@ export class CodeGenerator {
|
|||||||
index: block!.childNumber,
|
index: block!.childNumber,
|
||||||
forceNewBlock: false,
|
forceNewBlock: false,
|
||||||
isLast: ctx.isLast && i === children.length - 1,
|
isLast: ctx.isLast && i === children.length - 1,
|
||||||
|
tKeyExpr: ctx.tKeyExpr,
|
||||||
|
nameSpace,
|
||||||
|
tModelSelectedExpr,
|
||||||
});
|
});
|
||||||
this.compileAST(child, subCtx);
|
this.compileAST(child, subCtx);
|
||||||
}
|
}
|
||||||
@@ -658,7 +704,7 @@ export class CodeGenerator {
|
|||||||
const children = block!.children.slice();
|
const children = block!.children.slice();
|
||||||
let current = children.shift();
|
let current = children.shift();
|
||||||
for (let i = codeIdx; i < code.length; i++) {
|
for (let i = codeIdx; i < code.length; i++) {
|
||||||
if (code[i].trimStart().startsWith(`let ${current!.varName}`)) {
|
if (code[i].trimStart().startsWith(`let ${current!.varName} `)) {
|
||||||
code[i] = code[i].replace(`let ${current!.varName}`, current!.varName);
|
code[i] = code[i].replace(`let ${current!.varName}`, current!.varName);
|
||||||
current = children.shift();
|
current = children.shift();
|
||||||
if (!current) break;
|
if (!current) break;
|
||||||
@@ -753,7 +799,7 @@ export class CodeGenerator {
|
|||||||
const children = block!.children.slice();
|
const children = block!.children.slice();
|
||||||
let current = children.shift();
|
let current = children.shift();
|
||||||
for (let i = codeIdx; i < code.length; i++) {
|
for (let i = codeIdx; i < code.length; i++) {
|
||||||
if (code[i].trimStart().startsWith(`let ${current!.varName}`)) {
|
if (code[i].trimStart().startsWith(`let ${current!.varName} `)) {
|
||||||
code[i] = code[i].replace(`let ${current!.varName}`, current!.varName);
|
code[i] = code[i].replace(`let ${current!.varName}`, current!.varName);
|
||||||
current = children.shift();
|
current = children.shift();
|
||||||
if (!current) break;
|
if (!current) break;
|
||||||
@@ -897,7 +943,7 @@ export class CodeGenerator {
|
|||||||
const children = block!.children.slice();
|
const children = block!.children.slice();
|
||||||
let current = children.shift();
|
let current = children.shift();
|
||||||
for (let i = codeIdx; i < code.length; i++) {
|
for (let i = codeIdx; i < code.length; i++) {
|
||||||
if (code[i].trimStart().startsWith(`let ${current!.varName}`)) {
|
if (code[i].trimStart().startsWith(`let ${current!.varName} `)) {
|
||||||
code[i] = code[i].replace(`let ${current!.varName}`, current!.varName);
|
code[i] = code[i].replace(`let ${current!.varName}`, current!.varName);
|
||||||
current = children.shift();
|
current = children.shift();
|
||||||
if (!current) break;
|
if (!current) break;
|
||||||
@@ -1004,27 +1050,50 @@ 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[] = [];
|
||||||
let hasSlotsProp = false;
|
const propExpr = this.formatPropObject(ast.props);
|
||||||
for (let propName in ast.props) {
|
if (propExpr) {
|
||||||
let propValue = this.captureExpression(ast.props[propName]) || undefined;
|
props.push(propExpr);
|
||||||
if (propName.includes(".")) {
|
|
||||||
let [name, suffix] = propName.split(".");
|
|
||||||
if (suffix === "bind") {
|
|
||||||
this.helpers.add("bind");
|
|
||||||
propName = name;
|
|
||||||
propValue = `bind(ctx, ${propValue})`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
propName = /^[a-z_]+$/i.test(propName) ? propName : `'${propName}'`;
|
|
||||||
props.push(`${propName}: ${propValue}`);
|
|
||||||
if (propName === "slots") {
|
|
||||||
hasSlotsProp = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// slots
|
// slots
|
||||||
@@ -1047,9 +1116,7 @@ export class CodeGenerator {
|
|||||||
params.push(`__scope: "${scope}"`);
|
params.push(`__scope: "${scope}"`);
|
||||||
}
|
}
|
||||||
if (ast.slots[slotName].attrs) {
|
if (ast.slots[slotName].attrs) {
|
||||||
for (const [n, v] of Object.entries(ast.slots[slotName].attrs!)) {
|
params.push(this.formatPropObject(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}`);
|
||||||
@@ -1058,29 +1125,29 @@ export class CodeGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (slotDef && !(ast.dynamicProps || hasSlotsProp)) {
|
if (slotDef && !(ast.dynamicProps || hasSlotsProp)) {
|
||||||
props.push(`slots: ${slotDef}`);
|
this.helpers.add("markRaw");
|
||||||
|
props.push(`slots: markRaw(${slotDef})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const propStr = `{${props.join(",")}}`;
|
const propStr = `{${props.join(",")}}`;
|
||||||
|
|
||||||
let propString = propStr;
|
let propString = propStr;
|
||||||
if (ast.dynamicProps) {
|
if (ast.dynamicProps) {
|
||||||
if (!props.length) {
|
propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)}${
|
||||||
propString = `${compileExpr(ast.dynamicProps)}`;
|
props.length ? ", " + propStr : ""
|
||||||
} else {
|
})`;
|
||||||
propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)}, ${propStr})`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let propVar: string;
|
let propVar: string;
|
||||||
if ((slotDef && (ast.dynamicProps || hasSlotsProp)) || this.dev) {
|
if ((slotDef && (ast.dynamicProps || hasSlotsProp)) || this.dev) {
|
||||||
propVar = this.generateId("props");
|
propVar = this.generateId("props");
|
||||||
this.addLine(`const ${propVar!} = ${propString}`);
|
this.addLine(`const ${propVar!} = ${propString};`);
|
||||||
propString = propVar!;
|
propString = propVar!;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (slotDef && (ast.dynamicProps || hasSlotsProp)) {
|
if (slotDef && (ast.dynamicProps || hasSlotsProp)) {
|
||||||
this.addLine(`${propVar!}.slots = Object.assign(${slotDef}, ${propVar!}.slots)`);
|
this.helpers.add("markRaw");
|
||||||
|
this.addLine(`${propVar!}.slots = markRaw(Object.assign(${slotDef}, ${propVar!}.slots))`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// cmap key
|
// cmap key
|
||||||
@@ -1094,7 +1161,7 @@ export class CodeGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.dev) {
|
if (this.dev) {
|
||||||
this.addLine(`helpers.validateProps(${expr}, ${propVar!}, ctx)`);
|
this.addLine(`helpers.validateProps(${expr}, ${propVar!}, ctx);`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (block && (ctx.forceNewBlock === false || ctx.tKeyExpr)) {
|
if (block && (ctx.forceNewBlock === false || ctx.tKeyExpr)) {
|
||||||
@@ -1102,7 +1169,10 @@ export class CodeGenerator {
|
|||||||
this.insertAnchor(block);
|
this.insertAnchor(block);
|
||||||
}
|
}
|
||||||
|
|
||||||
const keyArg = `key+\`${key}\`,${ctx.tKeyExpr}`;
|
let keyArg = `key + \`${key}\``;
|
||||||
|
if (ctx.tKeyExpr) {
|
||||||
|
keyArg = `${ctx.tKeyExpr} + ${keyArg}`;
|
||||||
|
}
|
||||||
const blockArgs = `${expr}, ${propString}, ${keyArg}, node, ctx`;
|
const blockArgs = `${expr}, ${propString}, ${keyArg}, node, ctx`;
|
||||||
let blockExpr = `component(${blockArgs})`;
|
let blockExpr = `component(${blockArgs})`;
|
||||||
if (ast.isDynamic) {
|
if (ast.isDynamic) {
|
||||||
@@ -1125,15 +1195,7 @@ export class CodeGenerator {
|
|||||||
slotName = "'" + ast.name + "'";
|
slotName = "'" + ast.name + "'";
|
||||||
}
|
}
|
||||||
|
|
||||||
let scope = null;
|
const scope = ast.attrs ? `{${this.formatPropObject(ast.attrs)}}` : 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})`;
|
||||||
@@ -1158,4 +1220,23 @@ export class CodeGenerator {
|
|||||||
this.compileAST(ast.content, Object.assign({}, ctx, { translate: false }));
|
this.compileAST(ast.content, Object.assign({}, ctx, { translate: false }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
compileTPortal(ast: ASTTPortal, ctx: Context) {
|
||||||
|
this.helpers.add("Portal");
|
||||||
|
|
||||||
|
let { block } = ctx;
|
||||||
|
const name = this.compileInNewTarget("slot", ast.content, ctx);
|
||||||
|
const key = this.generateComponentKey();
|
||||||
|
let ctxStr = "ctx";
|
||||||
|
if (this.target.loopLevel || !this.hasSafeContext) {
|
||||||
|
ctxStr = this.generateId("ctx");
|
||||||
|
this.helpers.add("capture");
|
||||||
|
this.addLine(`const ${ctxStr} = capture(ctx);`);
|
||||||
|
}
|
||||||
|
const blockString = `component(Portal, {target: ${ast.target},slots: {'default': {__render: ${name}, __ctx: ${ctxStr}}}}, key + \`${key}\`, node, ctx)`;
|
||||||
|
if (block) {
|
||||||
|
this.insertAnchor(block);
|
||||||
|
}
|
||||||
|
block = this.createBlock(block, "multi", ctx);
|
||||||
|
this.insertBlock(blockString, block, { ...ctx, forceNewBlock: false });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ export type TemplateFunction = (blocks: any, utils: any) => Template;
|
|||||||
interface CompileOptions extends Config {
|
interface CompileOptions extends Config {
|
||||||
name?: string;
|
name?: string;
|
||||||
}
|
}
|
||||||
export function compile(template: string | Node, options: CompileOptions = {}): TemplateFunction {
|
export function compile(
|
||||||
|
template: string | Element,
|
||||||
|
options: CompileOptions = {}
|
||||||
|
): TemplateFunction {
|
||||||
// parsing
|
// parsing
|
||||||
const ast = parse(template);
|
const ast = parse(template);
|
||||||
|
|
||||||
|
|||||||
@@ -329,7 +329,9 @@ export function compileExprToArray(expr: string): Token[] {
|
|||||||
// Mark all variables that have been used locally.
|
// Mark all variables that have been used locally.
|
||||||
// This assumes the expression has only one scope (incorrect but "good enough for now")
|
// This assumes the expression has only one scope (incorrect but "good enough for now")
|
||||||
for (const token of tokens) {
|
for (const token of tokens) {
|
||||||
if (token.type === "SYMBOL" && localVars.has(token.value)) {
|
if (token.type === "SYMBOL" && token.varName && localVars.has(token.value)) {
|
||||||
|
token.originalValue = token.value;
|
||||||
|
token.value = `_${token.value}`;
|
||||||
token.isLocal = true;
|
token.isLocal = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+99
-88
@@ -20,6 +20,7 @@ export const enum ASTType {
|
|||||||
TSlot,
|
TSlot,
|
||||||
TCallBlock,
|
TCallBlock,
|
||||||
TTranslation,
|
TTranslation,
|
||||||
|
TPortal,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ASTText {
|
export interface ASTText {
|
||||||
@@ -32,6 +33,17 @@ export interface ASTComment {
|
|||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface TModelInfo {
|
||||||
|
hasDynamicChildren?: boolean;
|
||||||
|
baseExpr: string;
|
||||||
|
expr: string;
|
||||||
|
targetAttr: string;
|
||||||
|
specialInitTargetAttr: string | null;
|
||||||
|
eventType: "change" | "click" | "input";
|
||||||
|
shouldTrim: boolean;
|
||||||
|
shouldNumberize: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ASTDomNode {
|
export interface ASTDomNode {
|
||||||
type: ASTType.DomNode;
|
type: ASTType.DomNode;
|
||||||
tag: string;
|
tag: string;
|
||||||
@@ -40,15 +52,7 @@ export interface ASTDomNode {
|
|||||||
content: AST[];
|
content: AST[];
|
||||||
ref: string | null;
|
ref: string | null;
|
||||||
on: { [key: string]: string };
|
on: { [key: string]: string };
|
||||||
model: {
|
model?: TModelInfo | null;
|
||||||
baseExpr: string;
|
|
||||||
expr: string;
|
|
||||||
targetAttr: string;
|
|
||||||
specialInitTargetAttr: string | null;
|
|
||||||
eventType: "change" | "click" | "input";
|
|
||||||
shouldTrim: boolean;
|
|
||||||
shouldNumberize: boolean;
|
|
||||||
} | null;
|
|
||||||
ns: string | null;
|
ns: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,8 +96,6 @@ export interface ASTTForEach {
|
|||||||
key: string | null;
|
key: string | null;
|
||||||
body: AST;
|
body: AST;
|
||||||
memo: string;
|
memo: string;
|
||||||
isOnlyChild: boolean;
|
|
||||||
hasNoComponent: boolean;
|
|
||||||
hasNoFirst: boolean;
|
hasNoFirst: boolean;
|
||||||
hasNoLast: boolean;
|
hasNoLast: boolean;
|
||||||
hasNoIndex: boolean;
|
hasNoIndex: boolean;
|
||||||
@@ -149,6 +151,12 @@ export interface ASTTranslation {
|
|||||||
content: AST | null;
|
content: AST | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ASTTPortal {
|
||||||
|
type: ASTType.TPortal;
|
||||||
|
target: string;
|
||||||
|
content: AST;
|
||||||
|
}
|
||||||
|
|
||||||
export type AST =
|
export type AST =
|
||||||
| ASTText
|
| ASTText
|
||||||
| ASTComment
|
| ASTComment
|
||||||
@@ -166,28 +174,41 @@ export type AST =
|
|||||||
| ASTTCallBlock
|
| ASTTCallBlock
|
||||||
| ASTLog
|
| ASTLog
|
||||||
| ASTDebug
|
| ASTDebug
|
||||||
| ASTTranslation;
|
| ASTTranslation
|
||||||
|
| ASTTPortal;
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// Parser
|
// Parser
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
interface ParsingContext {
|
const cache: WeakMap<Element, AST> = new WeakMap();
|
||||||
inPreTag: boolean;
|
|
||||||
inSVG: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parse(xml: string | Node): AST {
|
export function parse(xml: string | Element): AST {
|
||||||
const node = xml instanceof Element ? xml : (parseXML(`<t>${xml}</t>`).firstChild! as Element);
|
if (typeof xml === "string") {
|
||||||
normalizeXML(node);
|
const elem = parseXML(`<t>${xml}</t>`).firstChild as Element;
|
||||||
const ctx = { inPreTag: false, inSVG: false };
|
return _parse(elem);
|
||||||
const ast = parseNode(node, ctx);
|
}
|
||||||
|
let ast = cache.get(xml);
|
||||||
if (!ast) {
|
if (!ast) {
|
||||||
return { type: ASTType.Text, value: "" };
|
// we clone here the xml to prevent modifying it in place
|
||||||
|
ast = _parse(xml.cloneNode(true) as Element);
|
||||||
|
cache.set(xml, ast);
|
||||||
}
|
}
|
||||||
return ast;
|
return ast;
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseNode(node: ChildNode, ctx: ParsingContext): AST | null {
|
function _parse(xml: Element): AST {
|
||||||
|
normalizeXML(xml);
|
||||||
|
const ctx = { inPreTag: false, inSVG: false };
|
||||||
|
return parseNode(xml, ctx) || { type: ASTType.Text, value: "" };
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ParsingContext {
|
||||||
|
tModelInfo?: TModelInfo | null;
|
||||||
|
inPreTag: boolean;
|
||||||
|
inSVG: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseNode(node: Node, ctx: ParsingContext): AST | null {
|
||||||
if (!(node instanceof Element)) {
|
if (!(node instanceof Element)) {
|
||||||
return parseTextCommentNode(node, ctx);
|
return parseTextCommentNode(node, ctx);
|
||||||
}
|
}
|
||||||
@@ -195,6 +216,7 @@ function parseNode(node: ChildNode, ctx: ParsingContext): AST | null {
|
|||||||
parseTDebugLog(node, ctx) ||
|
parseTDebugLog(node, ctx) ||
|
||||||
parseTForEach(node, ctx) ||
|
parseTForEach(node, ctx) ||
|
||||||
parseTIf(node, ctx) ||
|
parseTIf(node, ctx) ||
|
||||||
|
parseTPortal(node, ctx) ||
|
||||||
parseTCall(node, ctx) ||
|
parseTCall(node, ctx) ||
|
||||||
parseTCallBlock(node, ctx) ||
|
parseTCallBlock(node, ctx) ||
|
||||||
parseTEscNode(node, ctx) ||
|
parseTEscNode(node, ctx) ||
|
||||||
@@ -226,7 +248,7 @@ function parseTNode(node: Element, ctx: ParsingContext): AST | null {
|
|||||||
const lineBreakRE = /[\r\n]/;
|
const lineBreakRE = /[\r\n]/;
|
||||||
const whitespaceRE = /\s+/g;
|
const whitespaceRE = /\s+/g;
|
||||||
|
|
||||||
function parseTextCommentNode(node: ChildNode, ctx: ParsingContext): AST | null {
|
function parseTextCommentNode(node: Node, ctx: ParsingContext): AST | null {
|
||||||
if (node.nodeType === Node.TEXT_NODE) {
|
if (node.nodeType === Node.TEXT_NODE) {
|
||||||
let value = node.textContent || "";
|
let value = node.textContent || "";
|
||||||
if (!ctx.inPreTag) {
|
if (!ctx.inPreTag) {
|
||||||
@@ -274,6 +296,8 @@ function parseTDebugLog(node: Element, ctx: ParsingContext): AST | null {
|
|||||||
const hasDotAtTheEnd = /\.[\w_]+\s*$/;
|
const hasDotAtTheEnd = /\.[\w_]+\s*$/;
|
||||||
const hasBracketsAtTheEnd = /\[[^\[]+\]\s*$/;
|
const hasBracketsAtTheEnd = /\[[^\[]+\]\s*$/;
|
||||||
|
|
||||||
|
const ROOT_SVG_TAGS = new Set(["svg", "g", "path"]);
|
||||||
|
|
||||||
function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
|
function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
|
||||||
const { tagName } = node;
|
const { tagName } = node;
|
||||||
const dynamicTag = node.getAttribute("t-tag");
|
const dynamicTag = node.getAttribute("t-tag");
|
||||||
@@ -281,22 +305,23 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
|
|||||||
if (tagName === "t" && !dynamicTag) {
|
if (tagName === "t" && !dynamicTag) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (tagName.startsWith("block-")) {
|
||||||
|
throw new Error(`Invalid tag name: '${tagName}'`);
|
||||||
|
}
|
||||||
ctx = Object.assign({}, ctx);
|
ctx = Object.assign({}, ctx);
|
||||||
if (tagName === "pre") {
|
if (tagName === "pre") {
|
||||||
ctx.inPreTag = true;
|
ctx.inPreTag = true;
|
||||||
}
|
}
|
||||||
const shouldAddSVGNS = tagName === "svg" || (tagName === "g" && !ctx.inSVG);
|
const shouldAddSVGNS = ROOT_SVG_TAGS.has(tagName) && !ctx.inSVG;
|
||||||
ctx.inSVG = ctx.inSVG || shouldAddSVGNS;
|
ctx.inSVG = ctx.inSVG || shouldAddSVGNS;
|
||||||
const ns = shouldAddSVGNS ? "http://www.w3.org/2000/svg" : null;
|
const ns = shouldAddSVGNS ? "http://www.w3.org/2000/svg" : null;
|
||||||
const ref = node.getAttribute("t-ref");
|
const ref = node.getAttribute("t-ref");
|
||||||
node.removeAttribute("t-ref");
|
node.removeAttribute("t-ref");
|
||||||
|
|
||||||
const children = parseChildren(node, ctx);
|
|
||||||
|
|
||||||
const nodeAttrsNames = node.getAttributeNames();
|
const nodeAttrsNames = node.getAttributeNames();
|
||||||
const attrs: ASTDomNode["attrs"] = {};
|
const attrs: ASTDomNode["attrs"] = {};
|
||||||
const on: ASTDomNode["on"] = {};
|
const on: ASTDomNode["on"] = {};
|
||||||
let model: ASTDomNode["model"] = null;
|
let model: TModelInfo | null = null;
|
||||||
|
|
||||||
for (let attr of nodeAttrsNames) {
|
for (let attr of nodeAttrsNames) {
|
||||||
const value = node.getAttribute(attr)!;
|
const value = node.getAttribute(attr)!;
|
||||||
@@ -344,16 +369,26 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
|
|||||||
shouldTrim: hasTrimMod && (isOtherInput || isTextarea),
|
shouldTrim: hasTrimMod && (isOtherInput || isTextarea),
|
||||||
shouldNumberize: hasNumberMod && (isOtherInput || isTextarea),
|
shouldNumberize: hasNumberMod && (isOtherInput || isTextarea),
|
||||||
};
|
};
|
||||||
} else {
|
if (isSelect) {
|
||||||
|
// don't pollute the original ctx
|
||||||
|
ctx = Object.assign({}, ctx);
|
||||||
|
ctx.tModelInfo = model;
|
||||||
|
}
|
||||||
|
} else if (attr.startsWith("block-")) {
|
||||||
|
throw new Error(`Invalid attribute: '${attr}'`);
|
||||||
|
} else if (attr !== "t-name") {
|
||||||
if (attr.startsWith("t-") && !attr.startsWith("t-att")) {
|
if (attr.startsWith("t-") && !attr.startsWith("t-att")) {
|
||||||
throw new Error(`Unknown QWeb directive: '${attr}'`);
|
throw new Error(`Unknown QWeb directive: '${attr}'`);
|
||||||
}
|
}
|
||||||
|
const tModel = ctx.tModelInfo;
|
||||||
|
if (tModel && ["t-att-value", "t-attf-value"].includes(attr)) {
|
||||||
|
tModel.hasDynamicChildren = true;
|
||||||
|
}
|
||||||
attrs[attr] = value;
|
attrs[attr] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (children.length === 1 && children[0].type === ASTType.TForEach) {
|
|
||||||
children[0].isOnlyChild = true;
|
const children = parseChildren(node, ctx);
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
type: ASTType.DomNode,
|
type: ASTType.DomNode,
|
||||||
tag: tagName,
|
tag: tagName,
|
||||||
@@ -478,8 +513,6 @@ function parseTForEach(node: Element, ctx: ParsingContext): AST | null {
|
|||||||
body,
|
body,
|
||||||
memo,
|
memo,
|
||||||
key,
|
key,
|
||||||
isOnlyChild: false,
|
|
||||||
hasNoComponent: hasNoComponent(body),
|
|
||||||
hasNoFirst,
|
hasNoFirst,
|
||||||
hasNoLast,
|
hasNoLast,
|
||||||
hasNoIndex,
|
hasNoIndex,
|
||||||
@@ -487,58 +520,6 @@ function parseTForEach(node: Element, ctx: ParsingContext): AST | null {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns true if we are sure the ast does not contain any component
|
|
||||||
*/
|
|
||||||
function hasNoComponent(ast: AST): boolean {
|
|
||||||
switch (ast.type) {
|
|
||||||
case ASTType.TComponent:
|
|
||||||
case ASTType.TOut:
|
|
||||||
case ASTType.TCall:
|
|
||||||
case ASTType.TCallBlock:
|
|
||||||
case ASTType.TSlot:
|
|
||||||
return false;
|
|
||||||
case ASTType.TSet:
|
|
||||||
case ASTType.Text:
|
|
||||||
case ASTType.Comment:
|
|
||||||
case ASTType.TEsc:
|
|
||||||
return true;
|
|
||||||
case ASTType.TKey:
|
|
||||||
return hasNoComponent(ast.content);
|
|
||||||
case ASTType.TDebug:
|
|
||||||
case ASTType.TLog:
|
|
||||||
case ASTType.TTranslation:
|
|
||||||
return ast.content ? hasNoComponent(ast.content) : true;
|
|
||||||
case ASTType.TForEach:
|
|
||||||
return ast.hasNoComponent;
|
|
||||||
case ASTType.Multi:
|
|
||||||
case ASTType.DomNode: {
|
|
||||||
for (let elem of ast.content) {
|
|
||||||
if (!hasNoComponent(elem)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
case ASTType.TIf: {
|
|
||||||
if (!hasNoComponent(ast.content)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (ast.tElif) {
|
|
||||||
for (let elem of ast.tElif) {
|
|
||||||
if (!hasNoComponent(elem.content)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ast.tElse && !hasNoComponent(ast.tElse)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseTKey(node: Element, ctx: ParsingContext): AST | null {
|
function parseTKey(node: Element, ctx: ParsingContext): AST | null {
|
||||||
if (!node.hasAttribute("t-key")) {
|
if (!node.hasAttribute("t-key")) {
|
||||||
return null;
|
return null;
|
||||||
@@ -701,6 +682,9 @@ 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)!;
|
||||||
@@ -766,6 +750,9 @@ 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 };
|
||||||
@@ -805,6 +792,30 @@ function parseTTranslation(node: Element, ctx: ParsingContext): AST | null {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Portal
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function parseTPortal(node: Element, ctx: ParsingContext): AST | null {
|
||||||
|
if (!node.hasAttribute("t-portal")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const target = node.getAttribute("t-portal")!;
|
||||||
|
node.removeAttribute("t-portal");
|
||||||
|
const content = parseNode(node, ctx);
|
||||||
|
if (!content) {
|
||||||
|
return {
|
||||||
|
type: ASTType.Text,
|
||||||
|
value: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type: ASTType.TPortal,
|
||||||
|
target,
|
||||||
|
content,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// helpers
|
// helpers
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
@@ -812,7 +823,7 @@ function parseTTranslation(node: Element, ctx: ParsingContext): AST | null {
|
|||||||
/**
|
/**
|
||||||
* Parse all the child nodes of a given node and return a list of ast elements
|
* Parse all the child nodes of a given node and return a list of ast elements
|
||||||
*/
|
*/
|
||||||
function parseChildren(node: Node, ctx: ParsingContext): AST[] {
|
function parseChildren(node: Element, ctx: ParsingContext): AST[] {
|
||||||
const children: AST[] = [];
|
const children: AST[] = [];
|
||||||
for (let child of node.childNodes) {
|
for (let child of node.childNodes) {
|
||||||
const childAst = parseNode(child, ctx);
|
const childAst = parseNode(child, ctx);
|
||||||
@@ -831,7 +842,7 @@ function parseChildren(node: Node, ctx: ParsingContext): AST[] {
|
|||||||
* Parse all the child nodes of a given node and return an ast if possible.
|
* Parse all the child nodes of a given node and return an ast if possible.
|
||||||
* In the case there are multiple children, they are wrapped in a astmulti.
|
* In the case there are multiple children, they are wrapped in a astmulti.
|
||||||
*/
|
*/
|
||||||
function parseChildNodes(node: Node, ctx: ParsingContext): AST | null {
|
function parseChildNodes(node: Element, ctx: ParsingContext): AST | null {
|
||||||
const children = parseChildren(node, ctx);
|
const children = parseChildren(node, ctx);
|
||||||
switch (children.length) {
|
switch (children.length) {
|
||||||
case 0:
|
case 0:
|
||||||
|
|||||||
@@ -1,19 +1,34 @@
|
|||||||
import type { Env } from "../app/app";
|
|
||||||
import type { ComponentNode } from "./component_node";
|
import type { ComponentNode } from "./component_node";
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// Component Class
|
// Component Class
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
export class Component {
|
type Props = { [key: string]: any };
|
||||||
|
|
||||||
|
interface StaticComponentProperties {
|
||||||
|
template: string;
|
||||||
|
defaultProps?: any;
|
||||||
|
props?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ComponentConstructor<P extends Props = any, E = any> = (new (
|
||||||
|
props: P,
|
||||||
|
env: E,
|
||||||
|
node: ComponentNode
|
||||||
|
) => Component<P, E>) &
|
||||||
|
StaticComponentProperties;
|
||||||
|
|
||||||
|
export class Component<Props = any, Env = any> {
|
||||||
static template: string = "";
|
static template: string = "";
|
||||||
static props?: any;
|
static props?: any;
|
||||||
|
static defaultProps?: any;
|
||||||
|
|
||||||
props: any;
|
props: Props;
|
||||||
env: Env;
|
env: Env;
|
||||||
__owl__: ComponentNode;
|
__owl__: ComponentNode;
|
||||||
|
|
||||||
constructor(props: any, env: Env, node: ComponentNode) {
|
constructor(props: Props, env: Env, node: ComponentNode) {
|
||||||
this.props = props;
|
this.props = props;
|
||||||
this.env = env;
|
this.env = env;
|
||||||
this.__owl__ = node;
|
this.__owl__ = node;
|
||||||
@@ -21,7 +36,7 @@ export class Component {
|
|||||||
|
|
||||||
setup() {}
|
setup() {}
|
||||||
|
|
||||||
render() {
|
render(deep: boolean = false) {
|
||||||
this.__owl__.render();
|
this.__owl__.render(deep);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+116
-50
@@ -1,6 +1,9 @@
|
|||||||
import type { App, Env } from "../app/app";
|
import type { App, Env } from "../app/app";
|
||||||
import { BDom, VNode } from "../blockdom";
|
import { BDom, VNode } from "../blockdom";
|
||||||
import { Component } from "./component";
|
import { clearReactivesForCallback, Reactive, reactive, TARGET, NonReactive } from "../reactivity";
|
||||||
|
import { batched, Callback } from "../utils";
|
||||||
|
import { Component, ComponentConstructor } from "./component";
|
||||||
|
import { fibersInError, handleError } from "./error_handling";
|
||||||
import {
|
import {
|
||||||
Fiber,
|
Fiber,
|
||||||
makeChildFiber,
|
makeChildFiber,
|
||||||
@@ -9,13 +12,15 @@ import {
|
|||||||
MountOptions,
|
MountOptions,
|
||||||
RootFiber,
|
RootFiber,
|
||||||
} from "./fibers";
|
} from "./fibers";
|
||||||
import { handleError, fibersInError } from "./error_handling";
|
|
||||||
import { applyDefaultProps } from "./props_validation";
|
import { applyDefaultProps } from "./props_validation";
|
||||||
import { STATUS } from "./status";
|
import { STATUS } from "./status";
|
||||||
|
|
||||||
let currentNode: ComponentNode | null = null;
|
let currentNode: ComponentNode | null = null;
|
||||||
|
|
||||||
export function getCurrent(): ComponentNode | null {
|
export function getCurrent(): ComponentNode {
|
||||||
|
if (!currentNode) {
|
||||||
|
throw new Error("No active component (a hook function should only be called in 'setup')");
|
||||||
|
}
|
||||||
return currentNode;
|
return currentNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,38 +28,60 @@ export function useComponent(): Component {
|
|||||||
return currentNode!.component;
|
return currentNode!.component;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function component(
|
// -----------------------------------------------------------------------------
|
||||||
name: string | typeof Component,
|
// Integration with reactivity system (useState)
|
||||||
props: any,
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
const batchedRenderFunctions = new WeakMap<ComponentNode, Callback>();
|
||||||
|
/**
|
||||||
|
* Creates a reactive object that will be observed by the current component.
|
||||||
|
* Reading data from the returned object (eg during rendering) will cause the
|
||||||
|
* component to subscribe to that data and be rerendered when it changes.
|
||||||
|
*
|
||||||
|
* @param state the state to observe
|
||||||
|
* @returns a reactive object that will cause the component to re-render on
|
||||||
|
* relevant changes
|
||||||
|
* @see reactive
|
||||||
|
*/
|
||||||
|
export function useState<T extends object>(state: T): Reactive<T> | NonReactive<T> {
|
||||||
|
const node = getCurrent();
|
||||||
|
let render = batchedRenderFunctions.get(node)!;
|
||||||
|
if (!render) {
|
||||||
|
render = batched(node.render.bind(node));
|
||||||
|
batchedRenderFunctions.set(node, render);
|
||||||
|
// manual implementation of onWillDestroy to break cyclic dependency
|
||||||
|
node.willDestroy.push(clearReactivesForCallback.bind(null, render));
|
||||||
|
}
|
||||||
|
return reactive(state, render);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// component function (used in compiled template code)
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
type Props = { [key: string]: any };
|
||||||
|
|
||||||
|
function arePropsDifferent(props1: Props, props2: Props): boolean {
|
||||||
|
for (let k in props1) {
|
||||||
|
if (props1[k] !== props2[k]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Object.keys(props1).length !== Object.keys(props2).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function component<P extends object>(
|
||||||
|
name: string | ComponentConstructor<P>,
|
||||||
|
props: P,
|
||||||
key: string,
|
key: string,
|
||||||
tKey: null | string,
|
|
||||||
ctx: ComponentNode,
|
ctx: ComponentNode,
|
||||||
parent: any
|
parent: any
|
||||||
): ComponentNode {
|
): ComponentNode<P> {
|
||||||
const parentChildren = ctx.children;
|
let node: any = ctx.children[key];
|
||||||
const destroy = ComponentNode.prototype.destroy;
|
|
||||||
|
|
||||||
if (tKey) {
|
|
||||||
const parentMap = ctx.keyToTkey;
|
|
||||||
const oldTkey = parentMap[key];
|
|
||||||
if (oldTkey && oldTkey !== tKey) {
|
|
||||||
const oldKey = key + oldTkey;
|
|
||||||
const node = parentChildren[oldKey];
|
|
||||||
if (node && node.status < STATUS.MOUNTED) {
|
|
||||||
destroy.call(node);
|
|
||||||
delete parentChildren[oldKey];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
parentMap[key] = tKey;
|
|
||||||
key = key + tKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
let node: any = parentChildren[key];
|
|
||||||
let isDynamic = typeof name !== "string";
|
let isDynamic = typeof name !== "string";
|
||||||
|
|
||||||
if (node) {
|
if (node) {
|
||||||
if (node.status < STATUS.MOUNTED) {
|
if (node.status < STATUS.MOUNTED) {
|
||||||
destroy.call(node);
|
node.destroy();
|
||||||
node = undefined;
|
node = undefined;
|
||||||
} else if (node.status === STATUS.DESTROYED) {
|
} else if (node.status === STATUS.DESTROYED) {
|
||||||
node = undefined;
|
node = undefined;
|
||||||
@@ -66,7 +93,10 @@ export function component(
|
|||||||
|
|
||||||
const parentFiber = ctx.fiber!;
|
const parentFiber = ctx.fiber!;
|
||||||
if (node) {
|
if (node) {
|
||||||
node.updateAndRender(props, parentFiber);
|
const currentProps = node.component.props[TARGET];
|
||||||
|
if (parentFiber.deep || arePropsDifferent(currentProps, props)) {
|
||||||
|
node.updateAndRender(props, parentFiber);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// new component
|
// new component
|
||||||
let C;
|
let C;
|
||||||
@@ -79,27 +109,24 @@ export function component(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
node = new ComponentNode(C, props, ctx.app, ctx);
|
node = new ComponentNode(C, props, ctx.app, ctx);
|
||||||
parentChildren[key] = node;
|
ctx.children[key] = node;
|
||||||
|
|
||||||
const fiber = makeChildFiber(node, parentFiber);
|
node.initiateRender(new Fiber(node, parentFiber));
|
||||||
node.initiateRender(fiber);
|
|
||||||
}
|
}
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// Component VNode
|
// Component VNode class
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
type LifecycleHook = Function;
|
type LifecycleHook = Function;
|
||||||
|
|
||||||
export class ComponentNode<T extends typeof Component = typeof Component>
|
export class ComponentNode<P extends object = any, E = any> implements VNode<ComponentNode<P, E>> {
|
||||||
implements VNode<ComponentNode>
|
|
||||||
{
|
|
||||||
el?: HTMLElement | Text | undefined;
|
el?: HTMLElement | Text | undefined;
|
||||||
app: App;
|
app: App;
|
||||||
fiber: Fiber | null = null;
|
fiber: Fiber | null = null;
|
||||||
component: InstanceType<T>;
|
component: Component<P, E>;
|
||||||
bdom: BDom | null = null;
|
bdom: BDom | null = null;
|
||||||
status: STATUS = STATUS.NEW;
|
status: STATUS = STATUS.NEW;
|
||||||
|
|
||||||
@@ -109,7 +136,6 @@ export class ComponentNode<T extends typeof Component = typeof Component>
|
|||||||
childEnv: Env;
|
childEnv: Env;
|
||||||
children: { [key: string]: ComponentNode } = Object.create(null);
|
children: { [key: string]: ComponentNode } = Object.create(null);
|
||||||
refs: any = {};
|
refs: any = {};
|
||||||
keyToTkey: any = {};
|
|
||||||
|
|
||||||
willStart: LifecycleHook[] = [];
|
willStart: LifecycleHook[] = [];
|
||||||
willUpdateProps: LifecycleHook[] = [];
|
willUpdateProps: LifecycleHook[] = [];
|
||||||
@@ -119,7 +145,7 @@ export class ComponentNode<T extends typeof Component = typeof Component>
|
|||||||
patched: LifecycleHook[] = [];
|
patched: LifecycleHook[] = [];
|
||||||
willDestroy: LifecycleHook[] = [];
|
willDestroy: LifecycleHook[] = [];
|
||||||
|
|
||||||
constructor(C: T, props: any, app: App, parent?: ComponentNode) {
|
constructor(C: ComponentConstructor<P, E>, props: P, app: App, parent?: ComponentNode) {
|
||||||
currentNode = this;
|
currentNode = this;
|
||||||
this.app = app;
|
this.app = app;
|
||||||
this.parent = parent || null;
|
this.parent = parent || null;
|
||||||
@@ -127,9 +153,11 @@ export class ComponentNode<T extends typeof Component = typeof Component>
|
|||||||
applyDefaultProps(props, C);
|
applyDefaultProps(props, C);
|
||||||
const env = (parent && parent.childEnv) || app.env;
|
const env = (parent && parent.childEnv) || app.env;
|
||||||
this.childEnv = env;
|
this.childEnv = env;
|
||||||
|
props = useState(props);
|
||||||
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) {
|
||||||
@@ -141,7 +169,7 @@ export class ComponentNode<T extends typeof Component = typeof Component>
|
|||||||
async initiateRender(fiber: Fiber | MountFiber) {
|
async initiateRender(fiber: Fiber | MountFiber) {
|
||||||
this.fiber = fiber;
|
this.fiber = fiber;
|
||||||
if (this.mounted.length) {
|
if (this.mounted.length) {
|
||||||
fiber.root.mounted.push(fiber);
|
fiber.root!.mounted.push(fiber);
|
||||||
}
|
}
|
||||||
const component = this.component;
|
const component = this.component;
|
||||||
try {
|
try {
|
||||||
@@ -155,21 +183,29 @@ export class ComponentNode<T extends typeof Component = typeof Component>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async render() {
|
async render(deep: boolean = false) {
|
||||||
let current = this.fiber;
|
let current = this.fiber;
|
||||||
if (current && current.root.locked) {
|
if (current && current.root!.locked) {
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
// situation may have changed after the microtask tick
|
// situation may have changed after the microtask tick
|
||||||
current = this.fiber;
|
current = this.fiber;
|
||||||
}
|
}
|
||||||
if (current && !current.bdom && !fibersInError.has(current)) {
|
if (current) {
|
||||||
return;
|
if (!current.bdom && !fibersInError.has(current)) {
|
||||||
}
|
if (deep) {
|
||||||
if (!this.bdom && !current) {
|
// we want the render from this point on to be with deep=true
|
||||||
|
current.deep = deep;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// if current rendering was with deep=true, we want this one to be the same
|
||||||
|
deep = deep || current.deep;
|
||||||
|
} else if (!this.bdom) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fiber = makeRootFiber(this);
|
const fiber = makeRootFiber(this);
|
||||||
|
fiber.deep = deep;
|
||||||
this.fiber = fiber;
|
this.fiber = fiber;
|
||||||
this.app.scheduler.addFiber(fiber);
|
this.app.scheduler.addFiber(fiber);
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
@@ -187,7 +223,7 @@ export class ComponentNode<T extends typeof Component = typeof Component>
|
|||||||
// a root fiber to a child fiber in the previous microtick, because it was
|
// a root fiber to a child fiber in the previous microtick, because it was
|
||||||
// embedded in a rendering coming from above, so the fiber will be rendered
|
// embedded in a rendering coming from above, so the fiber will be rendered
|
||||||
// in the next microtick anyway, so we should not render it again.
|
// in the next microtick anyway, so we should not render it again.
|
||||||
if (this.fiber && (current || !fiber.parent)) {
|
if (this.fiber === fiber && (current || !fiber.parent)) {
|
||||||
this._render(fiber);
|
this._render(fiber);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -195,7 +231,7 @@ export class ComponentNode<T extends typeof Component = typeof Component>
|
|||||||
_render(fiber: Fiber | RootFiber) {
|
_render(fiber: Fiber | RootFiber) {
|
||||||
try {
|
try {
|
||||||
fiber.bdom = this.renderFn();
|
fiber.bdom = this.renderFn();
|
||||||
fiber.root.counter--;
|
fiber.root!.counter--;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
handleError({ node: this, error: e });
|
handleError({ node: this, error: e });
|
||||||
}
|
}
|
||||||
@@ -231,6 +267,10 @@ export class ComponentNode<T extends typeof Component = typeof Component>
|
|||||||
this.fiber = fiber;
|
this.fiber = fiber;
|
||||||
const component = this.component;
|
const component = this.component;
|
||||||
applyDefaultProps(props, component.constructor as any);
|
applyDefaultProps(props, component.constructor as any);
|
||||||
|
|
||||||
|
currentNode = this;
|
||||||
|
props = useState(props);
|
||||||
|
currentNode = null;
|
||||||
const prom = Promise.all(this.willUpdateProps.map((f) => f.call(component, props)));
|
const prom = Promise.all(this.willUpdateProps.map((f) => f.call(component, props)));
|
||||||
await prom;
|
await prom;
|
||||||
if (fiber !== this.fiber) {
|
if (fiber !== this.fiber) {
|
||||||
@@ -238,7 +278,7 @@ export class ComponentNode<T extends typeof Component = typeof Component>
|
|||||||
}
|
}
|
||||||
component.props = props;
|
component.props = props;
|
||||||
this._render(fiber);
|
this._render(fiber);
|
||||||
const parentRoot = parentFiber.root;
|
const parentRoot = parentFiber.root!;
|
||||||
if (this.willPatch.length) {
|
if (this.willPatch.length) {
|
||||||
parentRoot.willPatch.push(fiber);
|
parentRoot.willPatch.push(fiber);
|
||||||
}
|
}
|
||||||
@@ -295,7 +335,19 @@ export class ComponentNode<T extends typeof Component = typeof Component>
|
|||||||
}
|
}
|
||||||
|
|
||||||
patch() {
|
patch() {
|
||||||
this.bdom!.patch(this!.fiber!.bdom!, false);
|
if (this.fiber && this.fiber.parent) {
|
||||||
|
// we only patch here renderings coming from above. renderings initiated
|
||||||
|
// by the component will be patched independently in the appropriate
|
||||||
|
// fiber.complete
|
||||||
|
this._patch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_patch() {
|
||||||
|
const hasChildren = Object.keys(this.children).length > 0;
|
||||||
|
this.bdom!.patch(this!.fiber!.bdom!, hasChildren);
|
||||||
|
if (hasChildren) {
|
||||||
|
this.cleanOutdatedChildren();
|
||||||
|
}
|
||||||
this.fiber!.appliedToDom = true;
|
this.fiber!.appliedToDom = true;
|
||||||
this.fiber = null;
|
this.fiber = null;
|
||||||
}
|
}
|
||||||
@@ -307,4 +359,18 @@ export class ComponentNode<T extends typeof Component = typeof Component>
|
|||||||
remove() {
|
remove() {
|
||||||
this.bdom!.remove();
|
this.bdom!.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cleanOutdatedChildren() {
|
||||||
|
const children = this.children;
|
||||||
|
for (const key in children) {
|
||||||
|
const node = children[key];
|
||||||
|
const status = node.status;
|
||||||
|
if (status !== STATUS.MOUNTED) {
|
||||||
|
delete children[key];
|
||||||
|
if (status !== STATUS.DESTROYED) {
|
||||||
|
node.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ function _handleError(node: ComponentNode | null, error: any, isFirstRound = fal
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (stopped) {
|
if (stopped) {
|
||||||
if (isFirstRound && fiber) {
|
if (isFirstRound && fiber && fiber.node.fiber) {
|
||||||
fiber.root.counter--;
|
fiber.root!.counter--;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -52,7 +52,7 @@ export function handleError(params: ErrorParams) {
|
|||||||
current = current.parent;
|
current = current.parent;
|
||||||
} while (current);
|
} while (current);
|
||||||
|
|
||||||
fibersInError.set(fiber.root, error);
|
fibersInError.set(fiber.root!, error);
|
||||||
|
|
||||||
const handled = _handleError(node, error, true);
|
const handled = _handleError(node, error, true);
|
||||||
if (!handled) {
|
if (!handled) {
|
||||||
|
|||||||
+12
-52
@@ -3,46 +3,11 @@ 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";
|
||||||
|
|
||||||
/**
|
|
||||||
* Cleans on the root fiber the patch and willPatch fiber lists
|
|
||||||
* It is typically needed when the same root fiber needs to recycle on
|
|
||||||
* of its children or grandchildren's fiber.
|
|
||||||
*/
|
|
||||||
function cleanPatchableFiber(child: Fiber, root: RootFiber) {
|
|
||||||
const { willPatch, patched } = root;
|
|
||||||
let i = willPatch.indexOf(child);
|
|
||||||
if (i > -1) {
|
|
||||||
willPatch.splice(i, 1);
|
|
||||||
}
|
|
||||||
i = patched.indexOf(child);
|
|
||||||
if (i > -1) {
|
|
||||||
patched.splice(i, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function makeChildFiber(node: ComponentNode, parent: Fiber): Fiber {
|
export function makeChildFiber(node: ComponentNode, parent: Fiber): Fiber {
|
||||||
let current = node.fiber;
|
let current = node.fiber;
|
||||||
if (current) {
|
if (current) {
|
||||||
// current is necessarily a rootfiber here
|
cancelFibers(current.children);
|
||||||
let root = parent.root;
|
current.root = null;
|
||||||
const isSameRoot = current.root === root;
|
|
||||||
cancelFibers(root, current.children);
|
|
||||||
current.children = [];
|
|
||||||
current.parent = parent;
|
|
||||||
// only increment our rendering if we were not
|
|
||||||
// already accounted for, or that we have been rendered
|
|
||||||
// already (in which case our fiber was removed from the root rendering)
|
|
||||||
if (!isSameRoot || current.bdom) {
|
|
||||||
root.counter++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isSameRoot) {
|
|
||||||
cleanPatchableFiber(current, root);
|
|
||||||
}
|
|
||||||
|
|
||||||
current.bdom = null;
|
|
||||||
current.root = root;
|
|
||||||
return current;
|
|
||||||
}
|
}
|
||||||
return new Fiber(node, parent);
|
return new Fiber(node, parent);
|
||||||
}
|
}
|
||||||
@@ -50,10 +15,9 @@ export function makeChildFiber(node: ComponentNode, parent: Fiber): Fiber {
|
|||||||
export function makeRootFiber(node: ComponentNode): Fiber {
|
export function makeRootFiber(node: ComponentNode): Fiber {
|
||||||
let current = node.fiber;
|
let current = node.fiber;
|
||||||
if (current) {
|
if (current) {
|
||||||
let root = current.root;
|
let root = current.root!;
|
||||||
root.counter -= cancelFibers(root, current.children);
|
root.counter = root.counter + 1 - cancelFibers(current.children);
|
||||||
current.children = [];
|
current.children = [];
|
||||||
root.counter++;
|
|
||||||
current.bdom = null;
|
current.bdom = null;
|
||||||
if (fibersInError.has(current)) {
|
if (fibersInError.has(current)) {
|
||||||
fibersInError.delete(current);
|
fibersInError.delete(current);
|
||||||
@@ -69,22 +33,20 @@ export function makeRootFiber(node: ComponentNode): Fiber {
|
|||||||
if (node.patched.length) {
|
if (node.patched.length) {
|
||||||
fiber.patched.push(fiber);
|
fiber.patched.push(fiber);
|
||||||
}
|
}
|
||||||
|
|
||||||
return fiber;
|
return fiber;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns number of not-yet rendered fibers cancelled
|
* @returns number of not-yet rendered fibers cancelled
|
||||||
*/
|
*/
|
||||||
function cancelFibers(root: any, fibers: Fiber[]): number {
|
function cancelFibers(fibers: Fiber[]): number {
|
||||||
let result = 0;
|
let result = 0;
|
||||||
for (let fiber of fibers) {
|
for (let fiber of fibers) {
|
||||||
fiber.node.fiber = null;
|
fiber.node.fiber = null;
|
||||||
fiber.root = root;
|
|
||||||
if (!fiber.bdom) {
|
if (!fiber.bdom) {
|
||||||
result++;
|
result++;
|
||||||
}
|
}
|
||||||
result += cancelFibers(root, fiber.children);
|
result += cancelFibers(fiber.children);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -92,16 +54,18 @@ function cancelFibers(root: any, fibers: Fiber[]): number {
|
|||||||
export class Fiber {
|
export class Fiber {
|
||||||
node: ComponentNode;
|
node: ComponentNode;
|
||||||
bdom: BDom | null = null;
|
bdom: BDom | null = null;
|
||||||
root: RootFiber;
|
root: RootFiber | null; // A Fiber that has been replaced by another has no root
|
||||||
parent: Fiber | null;
|
parent: Fiber | null;
|
||||||
children: Fiber[] = [];
|
children: Fiber[] = [];
|
||||||
appliedToDom = false;
|
appliedToDom = false;
|
||||||
|
deep: boolean = false;
|
||||||
|
|
||||||
constructor(node: ComponentNode, parent: Fiber | null) {
|
constructor(node: ComponentNode, parent: Fiber | null) {
|
||||||
this.node = node;
|
this.node = node;
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
if (parent) {
|
if (parent) {
|
||||||
const root = parent.root;
|
this.deep = parent.deep;
|
||||||
|
const root = parent.root!;
|
||||||
root.counter++;
|
root.counter++;
|
||||||
this.root = root;
|
this.root = root;
|
||||||
parent.children.push(this);
|
parent.children.push(this);
|
||||||
@@ -143,13 +107,8 @@ export class RootFiber extends Fiber {
|
|||||||
current = undefined;
|
current = undefined;
|
||||||
|
|
||||||
// Step 2: patching the dom
|
// Step 2: patching the dom
|
||||||
node.bdom!.patch(this.bdom!, Object.keys(node.children).length > 0);
|
node._patch();
|
||||||
this.appliedToDom = true;
|
|
||||||
|
|
||||||
this.locked = false;
|
this.locked = false;
|
||||||
// unregistering the fiber before mounted since it can do another render
|
|
||||||
// and that the current rendering is obviously completed
|
|
||||||
node.fiber = null;
|
|
||||||
|
|
||||||
// Step 4: calling all mounted lifecycle hooks
|
// Step 4: calling all mounted lifecycle hooks
|
||||||
let mountedFibers = this.mounted;
|
let mountedFibers = this.mounted;
|
||||||
@@ -198,6 +157,7 @@ export class MountFiber extends RootFiber {
|
|||||||
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,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { filterOutModifiersFromData } from "../blockdom/config";
|
import { filterOutModifiersFromData } from "../blockdom/config";
|
||||||
|
import { STATUS } from "./status";
|
||||||
|
|
||||||
export const mainEventHandler = (data: any, ev: Event, currentTarget?: EventTarget | null) => {
|
export const mainEventHandler = (data: any, ev: Event, currentTarget?: EventTarget | null) => {
|
||||||
const { data: _data, modifiers } = filterOutModifiersFromData(data);
|
const { data: _data, modifiers } = filterOutModifiersFromData(data);
|
||||||
@@ -30,7 +31,14 @@ export const mainEventHandler = (data: any, ev: Event, currentTarget?: EventTarg
|
|||||||
// We check this rather than data[0] being truthy (or typeof function) so that it crashes
|
// We check this rather than data[0] being truthy (or typeof function) so that it crashes
|
||||||
// as expected when there is a handler expression that evaluates to a falsy value
|
// as expected when there is a handler expression that evaluates to a falsy value
|
||||||
if (Object.hasOwnProperty.call(data, 0)) {
|
if (Object.hasOwnProperty.call(data, 0)) {
|
||||||
data[0].call(data[1] ? data[1].__owl__.component : null, ev);
|
const handler = data[0];
|
||||||
|
if (typeof handler !== "function") {
|
||||||
|
throw new Error(`Invalid handler (expected a function, received: '${handler}')`);
|
||||||
|
}
|
||||||
|
let node = data[1] ? data[1].__owl__ : null;
|
||||||
|
if (node ? node.status === STATUS.MOUNTED : true) {
|
||||||
|
handler.call(node ? node.component : null, ev);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return stopped;
|
return stopped;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,71 +1,106 @@
|
|||||||
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();
|
||||||
node.willStart.push(fn);
|
const decorate = node.app.dev ? wrapError : (fn: any) => fn;
|
||||||
|
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();
|
||||||
node.willUpdateProps.push(fn);
|
const decorate = node.app.dev ? wrapError : (fn: any) => fn;
|
||||||
|
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();
|
||||||
node.mounted.push(fn);
|
const decorate = node.app.dev ? wrapError : (fn: any) => fn;
|
||||||
|
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();
|
||||||
node.willPatch.unshift(fn);
|
const decorate = node.app.dev ? wrapError : (fn: any) => fn;
|
||||||
|
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();
|
||||||
node.patched.push(fn);
|
const decorate = node.app.dev ? wrapError : (fn: any) => fn;
|
||||||
|
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();
|
||||||
node.willUnmount.unshift(fn);
|
const decorate = node.app.dev ? wrapError : (fn: any) => fn;
|
||||||
|
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();
|
||||||
node.willDestroy.push(fn);
|
const decorate = node.app.dev ? wrapError : (fn: any) => fn;
|
||||||
|
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;
|
||||||
node.renderFn = () => {
|
const decorate = node.app.dev ? wrapError : (fn: any) => fn;
|
||||||
fn();
|
node.renderFn = decorate(() => {
|
||||||
|
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;
|
||||||
node.renderFn = () => {
|
const decorate = node.app.dev ? wrapError : (fn: any) => fn;
|
||||||
|
node.renderFn = decorate(() => {
|
||||||
const result = renderFn();
|
const result = renderFn();
|
||||||
fn();
|
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 = [];
|
||||||
nodeErrorHandlers.set(node, handlers);
|
nodeErrorHandlers.set(node, handlers);
|
||||||
}
|
}
|
||||||
handlers.push(callback);
|
handlers.push(callback.bind(node.component));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { Component } from "./component";
|
import { ComponentConstructor } from "./component";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply default props (only top level).
|
* Apply default props (only top level).
|
||||||
*
|
*
|
||||||
* Note that this method does modify in place the props
|
* Note that this method does modify in place the props
|
||||||
*/
|
*/
|
||||||
export function applyDefaultProps(props: { [key: string]: any }, ComponentClass: typeof Component) {
|
export function applyDefaultProps<P>(props: P, ComponentClass: ComponentConstructor<P>) {
|
||||||
const defaultProps = (ComponentClass as any).defaultProps;
|
const defaultProps = ComponentClass.defaultProps;
|
||||||
if (defaultProps) {
|
if (defaultProps) {
|
||||||
for (let propName in defaultProps) {
|
for (let propName in defaultProps) {
|
||||||
if (props![propName] === undefined) {
|
if ((props as any)[propName] === undefined) {
|
||||||
props![propName] = defaultProps[propName];
|
(props as any)[propName] = defaultProps[propName];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -34,13 +34,20 @@ function getPropDescription(staticProps: any) {
|
|||||||
* visit recursively the props and all the children to check if they are valid.
|
* visit recursively the props and all the children to check if they are valid.
|
||||||
* This is why it is only done in 'dev' mode.
|
* This is why it is only done in 'dev' mode.
|
||||||
*/
|
*/
|
||||||
export const validateProps = function (name: string | typeof Component, props: any, parent?: any) {
|
export function validateProps<P>(name: string | ComponentConstructor<P>, props: P, parent?: any) {
|
||||||
const ComponentClass = (
|
const ComponentClass =
|
||||||
typeof name !== "string" ? name : parent.constructor.components[name]
|
typeof name !== "string"
|
||||||
) as typeof Component;
|
? name
|
||||||
|
: (parent.constructor.components[name] as ComponentConstructor<P> | undefined);
|
||||||
|
|
||||||
|
if (!ComponentClass) {
|
||||||
|
// this is an error, wrong component. We silently return here instead so the
|
||||||
|
// error is triggered by the usual path ('component' function)
|
||||||
|
return;
|
||||||
|
}
|
||||||
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;
|
||||||
|
|
||||||
@@ -48,8 +55,18 @@ export const validateProps = function (name: string | typeof Component, props: a
|
|||||||
if (propName === "*") {
|
if (propName === "*") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (props[propName] === undefined) {
|
const propDef = propsDef[propName];
|
||||||
if (propsDef[propName] && !propsDef[propName].optional) {
|
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 (isMandatory) {
|
||||||
throw new Error(`Missing props '${propName}' (component '${ComponentClass.name}')`);
|
throw new Error(`Missing props '${propName}' (component '${ComponentClass.name}')`);
|
||||||
} else {
|
} else {
|
||||||
continue;
|
continue;
|
||||||
@@ -57,7 +74,7 @@ export const validateProps = function (name: string | typeof Component, props: a
|
|||||||
}
|
}
|
||||||
let isValid;
|
let isValid;
|
||||||
try {
|
try {
|
||||||
isValid = isValidProp(props[propName], propsDef[propName]);
|
isValid = isValidProp((props as any)[propName], propDef);
|
||||||
} 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
|
||||||
@@ -75,7 +92,7 @@ export const validateProps = function (name: string | typeof Component, props: a
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if an invidual prop value matches its (static) prop definition
|
* Check if an invidual prop value matches its (static) prop definition
|
||||||
|
|||||||
@@ -7,12 +7,15 @@ import { STATUS } from "./status";
|
|||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
export class Scheduler {
|
export class Scheduler {
|
||||||
|
// capture the value of requestAnimationFrame as soon as possible, to avoid
|
||||||
|
// interactions with other code, such as test frameworks that override them
|
||||||
|
static requestAnimationFrame = window.requestAnimationFrame.bind(window);
|
||||||
tasks: Set<RootFiber> = new Set();
|
tasks: Set<RootFiber> = new Set();
|
||||||
isRunning: boolean = false;
|
isRunning: boolean = false;
|
||||||
requestAnimationFrame: Window["requestAnimationFrame"];
|
requestAnimationFrame: Window["requestAnimationFrame"];
|
||||||
|
|
||||||
constructor(requestAnimationFrame: Window["requestAnimationFrame"]) {
|
constructor() {
|
||||||
this.requestAnimationFrame = requestAnimationFrame;
|
this.requestAnimationFrame = Scheduler.requestAnimationFrame;
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
@@ -25,7 +28,7 @@ export class Scheduler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addFiber(fiber: Fiber) {
|
addFiber(fiber: Fiber) {
|
||||||
this.tasks.add(fiber.root);
|
this.tasks.add(fiber.root!);
|
||||||
if (!this.isRunning) {
|
if (!this.isRunning) {
|
||||||
this.start();
|
this.start();
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-7
@@ -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,13 @@ 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 {
|
||||||
|
const env = Object.create(currentEnv);
|
||||||
|
const descrs = Object.getOwnPropertyDescriptors(extension);
|
||||||
|
return Object.freeze(Object.defineProperties(env, descrs));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -38,12 +44,15 @@ export function useEnv<E extends Env>(): E {
|
|||||||
* constructor method.
|
* constructor method.
|
||||||
*/
|
*/
|
||||||
export function useSubEnv(envExtension: Env) {
|
export function useSubEnv(envExtension: Env) {
|
||||||
const node = getCurrent()!;
|
const node = getCurrent();
|
||||||
const env = Object.create(node.childEnv);
|
node.component.env = extendEnv(node.component.env as any, envExtension);
|
||||||
const descrs = Object.getOwnPropertyDescriptors(envExtension);
|
useChildSubEnv(envExtension);
|
||||||
node.childEnv = Object.freeze(Object.defineProperties(env, descrs));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useChildSubEnv(envExtension: Env) {
|
||||||
|
const node = getCurrent();
|
||||||
|
node.childEnv = extendEnv(node.childEnv, envExtension);
|
||||||
|
}
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// useEffect
|
// useEffect
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
@@ -112,7 +121,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));
|
||||||
|
|||||||
+9
-4
@@ -1,3 +1,4 @@
|
|||||||
|
import { UTILS } from "./app/template_helpers";
|
||||||
import {
|
import {
|
||||||
config,
|
config,
|
||||||
createBlock,
|
createBlock,
|
||||||
@@ -12,9 +13,14 @@ import {
|
|||||||
comment,
|
comment,
|
||||||
} from "./blockdom";
|
} from "./blockdom";
|
||||||
import { mainEventHandler } from "./component/handler";
|
import { mainEventHandler } from "./component/handler";
|
||||||
|
import { Portal } from "./portal";
|
||||||
|
import { markRaw } from "./reactivity";
|
||||||
|
export type { Reactive } from "./reactivity";
|
||||||
|
|
||||||
config.shouldNormalizeDom = false;
|
config.shouldNormalizeDom = false;
|
||||||
config.mainEventHandler = mainEventHandler;
|
config.mainEventHandler = mainEventHandler;
|
||||||
|
(UTILS as any).Portal = Portal;
|
||||||
|
(UTILS as any).markRaw = markRaw;
|
||||||
|
|
||||||
export const blockDom = {
|
export const blockDom = {
|
||||||
config,
|
config,
|
||||||
@@ -34,13 +40,12 @@ export const blockDom = {
|
|||||||
|
|
||||||
export { App, mount } from "./app/app";
|
export { App, mount } from "./app/app";
|
||||||
export { Component } from "./component/component";
|
export { Component } from "./component/component";
|
||||||
export { useComponent } from "./component/component_node";
|
export { useComponent, useState } from "./component/component_node";
|
||||||
export { status } from "./component/status";
|
export { status } from "./component/status";
|
||||||
export { Portal } from "./portal";
|
|
||||||
export { Memo } from "./memo";
|
export { Memo } from "./memo";
|
||||||
export { xml } from "./app/template_set";
|
export { xml } from "./app/template_set";
|
||||||
export { useState, reactive } from "./reactivity";
|
export { reactive, markRaw, toRaw } from "./reactivity";
|
||||||
export { useEffect, useEnv, useExternalListener, useRef, useSubEnv } from "./hooks";
|
export { useEffect, useEnv, useExternalListener, useRef, useChildSubEnv, useSubEnv } from "./hooks";
|
||||||
export { EventBus, whenReady, loadFile, markup } from "./utils";
|
export { EventBus, whenReady, loadFile, markup } from "./utils";
|
||||||
export {
|
export {
|
||||||
onWillStart,
|
onWillStart,
|
||||||
|
|||||||
+16
-12
@@ -1,7 +1,7 @@
|
|||||||
import type { ComponentNode } from "./component/component_node";
|
import { onWillUnmount } from "./component/lifecycle_hooks";
|
||||||
import { Component } from "./component/component";
|
|
||||||
import { xml } from "./app/template_set";
|
import { xml } from "./app/template_set";
|
||||||
import { BDom, text, VNode } from "./blockdom";
|
import { BDom, text, VNode } from "./blockdom";
|
||||||
|
import { Component } from "./component/component";
|
||||||
|
|
||||||
const VText: any = text("").constructor;
|
const VText: any = text("").constructor;
|
||||||
|
|
||||||
@@ -35,9 +35,11 @@ class VPortal extends VText implements Partial<VNode<VPortal>> {
|
|||||||
this.realBDom!.beforeRemove();
|
this.realBDom!.beforeRemove();
|
||||||
}
|
}
|
||||||
remove() {
|
remove() {
|
||||||
super.remove();
|
if (this.realBDom) {
|
||||||
this.realBDom!.remove();
|
super.remove();
|
||||||
this.realBDom = null;
|
this.realBDom!.remove();
|
||||||
|
this.realBDom = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
patch(other: VPortal) {
|
patch(other: VPortal) {
|
||||||
@@ -60,12 +62,14 @@ export class Portal extends Component {
|
|||||||
slots: true,
|
slots: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props: any, env: any, node: ComponentNode) {
|
setup() {
|
||||||
super(props, env, node);
|
const node = this.__owl__;
|
||||||
node._render = function (fiber: any) {
|
const renderFn = node.renderFn;
|
||||||
const bdom = new VPortal(props.target, this.renderFn());
|
node.renderFn = () => new VPortal(this.props.target, renderFn());
|
||||||
fiber.bdom = bdom;
|
onWillUnmount(() => {
|
||||||
fiber.root.counter--;
|
if (node.bdom) {
|
||||||
};
|
node.bdom.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+49
-64
@@ -1,18 +1,25 @@
|
|||||||
import { onWillUnmount } from "./component/lifecycle_hooks";
|
import { Callback } from "./utils";
|
||||||
import { ComponentNode, getCurrent } from "./component/component_node";
|
|
||||||
|
|
||||||
// Allows to get the target of a Reactive (used for making a new Reactive from the underlying object)
|
// Allows to get the target of a Reactive (used for making a new Reactive from the underlying object)
|
||||||
const TARGET = Symbol("Target");
|
export const TARGET = Symbol("Target");
|
||||||
|
// Escape hatch to prevent reactivity system to turn something into a reactive
|
||||||
|
const SKIP = Symbol("Skip");
|
||||||
// Special key to subscribe to, to be notified of key creation/deletion
|
// Special key to subscribe to, to be notified of key creation/deletion
|
||||||
const KEYCHANGES = Symbol("Key changes");
|
const KEYCHANGES = Symbol("Key changes");
|
||||||
|
|
||||||
type ObjectKey = string | number | symbol;
|
type ObjectKey = string | number | symbol;
|
||||||
|
|
||||||
type Target = object;
|
type Target = object;
|
||||||
type Callback = () => void;
|
|
||||||
type Reactive<T extends Target = Target> = T & {
|
export type Reactive<T extends Target = Target> = T & {
|
||||||
[TARGET]: any;
|
[TARGET]: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type NonReactive<T extends Target = Target> = T & {
|
||||||
|
[SKIP]: any;
|
||||||
|
};
|
||||||
|
const objectToString = Object.prototype.toString;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether a given value can be made into a reactive object.
|
* Checks whether a given value can be made into a reactive object.
|
||||||
*
|
*
|
||||||
@@ -20,14 +27,35 @@ type Reactive<T extends Target = Target> = T & {
|
|||||||
* @returns whether the value can be made reactive
|
* @returns whether the value can be made reactive
|
||||||
*/
|
*/
|
||||||
function canBeMadeReactive(value: any): boolean {
|
function canBeMadeReactive(value: any): boolean {
|
||||||
return (
|
if (typeof value !== "object") {
|
||||||
typeof value === "object" &&
|
return false;
|
||||||
value !== null &&
|
}
|
||||||
!(value instanceof Date) &&
|
// extract "RawType" from strings like "[object RawType]" => this lets us
|
||||||
!(value instanceof Promise) &&
|
// ignore many native objects such as Promise (whose toString is [object Promise])
|
||||||
!(value instanceof String) &&
|
// or Date ([object Date]).
|
||||||
!(value instanceof Number)
|
const rawType = objectToString.call(value).slice(8, -1);
|
||||||
);
|
return rawType === "Object" || rawType === "Array";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark an object or array so that it is ignored by the reactivity system
|
||||||
|
*
|
||||||
|
* @param value the value to mark
|
||||||
|
* @returns the object itself
|
||||||
|
*/
|
||||||
|
export function markRaw<T extends Target>(value: T): NonReactive<T> {
|
||||||
|
(value as any)[SKIP] = true;
|
||||||
|
return value as NonReactive<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a reactive objet, return the raw (non reactive) underlying object
|
||||||
|
*
|
||||||
|
* @param value a reactive value
|
||||||
|
* @returns the underlying value
|
||||||
|
*/
|
||||||
|
export function toRaw<T extends object>(value: Reactive<T>): T {
|
||||||
|
return value[TARGET] || value;
|
||||||
}
|
}
|
||||||
|
|
||||||
const targetToKeysToCallbacks = new WeakMap<Target, Map<ObjectKey, Set<Callback>>>();
|
const targetToKeysToCallbacks = new WeakMap<Target, Map<ObjectKey, Set<Callback>>>();
|
||||||
@@ -85,7 +113,7 @@ const callbacksToTargets = new WeakMap<Callback, Set<Target>>();
|
|||||||
*
|
*
|
||||||
* @param callback the callback for which the reactives need to be cleared
|
* @param callback the callback for which the reactives need to be cleared
|
||||||
*/
|
*/
|
||||||
function clearReactivesForCallback(callback: Callback): void {
|
export function clearReactivesForCallback(callback: Callback): void {
|
||||||
const targetsToClear = callbacksToTargets.get(callback);
|
const targetsToClear = callbacksToTargets.get(callback);
|
||||||
if (!targetsToClear) {
|
if (!targetsToClear) {
|
||||||
return;
|
return;
|
||||||
@@ -130,10 +158,16 @@ const reactiveCache = new WeakMap<Target, WeakMap<Callback, Reactive>>();
|
|||||||
* reactive has changed
|
* reactive has changed
|
||||||
* @returns a proxy that tracks changes to it
|
* @returns a proxy that tracks changes to it
|
||||||
*/
|
*/
|
||||||
export function reactive<T extends Target>(target: T, callback: Callback = () => {}): Reactive<T> {
|
export function reactive<T extends Target>(
|
||||||
|
target: T,
|
||||||
|
callback: Callback = () => {}
|
||||||
|
): Reactive<T> | NonReactive<T> {
|
||||||
if (!canBeMadeReactive(target)) {
|
if (!canBeMadeReactive(target)) {
|
||||||
throw new Error(`Cannot make the given value reactive`);
|
throw new Error(`Cannot make the given value reactive`);
|
||||||
}
|
}
|
||||||
|
if (SKIP in target) {
|
||||||
|
return target as NonReactive<T>;
|
||||||
|
}
|
||||||
const originalTarget = (target as Reactive)[TARGET];
|
const originalTarget = (target as Reactive)[TARGET];
|
||||||
if (originalTarget) {
|
if (originalTarget) {
|
||||||
return reactive(originalTarget, callback);
|
return reactive(originalTarget, callback);
|
||||||
@@ -190,52 +224,3 @@ export function reactive<T extends Target>(target: T, callback: Callback = () =>
|
|||||||
}
|
}
|
||||||
return reactivesForTarget.get(callback) as Reactive<T>;
|
return reactivesForTarget.get(callback) as Reactive<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a batched version of a callback so that all calls to it in the same
|
|
||||||
* microtick will only call the original callback once.
|
|
||||||
*
|
|
||||||
* @param callback the callback to batch
|
|
||||||
* @returns a batched version of the original callback
|
|
||||||
*/
|
|
||||||
export function batched(callback: Callback): Callback {
|
|
||||||
let called = false;
|
|
||||||
return async () => {
|
|
||||||
// This await blocks all calls to the callback here, then releases them sequentially
|
|
||||||
// in the next microtick. This line decides the granularity of the batch.
|
|
||||||
await Promise.resolve();
|
|
||||||
if (!called) {
|
|
||||||
called = true;
|
|
||||||
callback();
|
|
||||||
// wait for all calls in this microtick to fall through before resetting "called"
|
|
||||||
// so that only the first call to the batched function calls the original callback
|
|
||||||
await Promise.resolve();
|
|
||||||
called = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const batchedRenderFunctions = new WeakMap<ComponentNode, Callback>();
|
|
||||||
/**
|
|
||||||
* Creates a reactive object that will be observed by the current component.
|
|
||||||
* Reading data from the returned object (eg during rendering) will cause the
|
|
||||||
* component to subscribe to that data and be rerendered when it changes.
|
|
||||||
*
|
|
||||||
* @param state the state to observe
|
|
||||||
* @returns a reactive object that will cause the component to re-render on
|
|
||||||
* relevant changes
|
|
||||||
* @see reactive
|
|
||||||
*/
|
|
||||||
export function useState<T extends object>(state: T): Reactive<T> {
|
|
||||||
const node = getCurrent()!;
|
|
||||||
if (!batchedRenderFunctions.has(node)) {
|
|
||||||
batchedRenderFunctions.set(
|
|
||||||
node,
|
|
||||||
batched(() => node.render())
|
|
||||||
);
|
|
||||||
onWillUnmount(() => clearReactivesForCallback(render));
|
|
||||||
}
|
|
||||||
const render = batchedRenderFunctions.get(node)!;
|
|
||||||
const reactiveState = reactive(state, render);
|
|
||||||
return reactiveState;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,3 +1,38 @@
|
|||||||
|
export type Callback = () => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a batched version of a callback so that all calls to it in the same
|
||||||
|
* microtick will only call the original callback once.
|
||||||
|
*
|
||||||
|
* @param callback the callback to batch
|
||||||
|
* @returns a batched version of the original callback
|
||||||
|
*/
|
||||||
|
export function batched(callback: Callback): Callback {
|
||||||
|
let called = false;
|
||||||
|
return async () => {
|
||||||
|
// This await blocks all calls to the callback here, then releases them sequentially
|
||||||
|
// in the next microtick. This line decides the granularity of the batch.
|
||||||
|
await Promise.resolve();
|
||||||
|
if (!called) {
|
||||||
|
called = true;
|
||||||
|
callback();
|
||||||
|
// wait for all calls in this microtick to fall through before resetting "called"
|
||||||
|
// so that only the first call to the batched function calls the original callback
|
||||||
|
await Promise.resolve();
|
||||||
|
called = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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 }));
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ exports[`Reactivity: useState destroyed component before being mounted is inacti
|
|||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2;
|
let b2;
|
||||||
if (ctx['state'].flag) {
|
if (ctx['state'].flag) {
|
||||||
b2 = component(\`Child\`, {}, key+\`__1\`,null, node, ctx);
|
b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
@@ -87,7 +87,7 @@ exports[`Reactivity: useState destroyed component is inactive 1`] = `
|
|||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2;
|
let b2;
|
||||||
if (ctx['state'].flag) {
|
if (ctx['state'].flag) {
|
||||||
b2 = component(\`Child\`, {}, key+\`__1\`,null, node, ctx);
|
b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
@@ -131,7 +131,7 @@ exports[`Reactivity: useState parent and children subscribed to same context 1`]
|
|||||||
let block1 = createBlock(\`<div><block-child-0/><block-text-0/></div>\`);
|
let block1 = createBlock(\`<div><block-child-0/><block-text-0/></div>\`);
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2 = component(\`Child\`, {}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
let txt1 = ctx['contextObj'].b;
|
let txt1 = ctx['contextObj'].b;
|
||||||
return block1([txt1], [b2]);
|
return block1([txt1], [b2]);
|
||||||
}
|
}
|
||||||
@@ -223,8 +223,8 @@ exports[`Reactivity: useState two components are updated in parallel 1`] = `
|
|||||||
let block1 = createBlock(\`<div><block-child-0/><block-child-1/></div>\`);
|
let block1 = createBlock(\`<div><block-child-0/><block-child-1/></div>\`);
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2 = component(\`Child\`, {}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
let b3 = component(\`Child\`, {}, key+\`__2\`,null, node, ctx);
|
let b3 = component(\`Child\`, {}, key + \`__2\`, node, ctx);
|
||||||
return block1([], [b2, b3]);
|
return block1([], [b2, b3]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -252,8 +252,8 @@ exports[`Reactivity: useState two components can subscribe to same context 1`] =
|
|||||||
let block1 = createBlock(\`<div><block-child-0/><block-child-1/></div>\`);
|
let block1 = createBlock(\`<div><block-child-0/><block-child-1/></div>\`);
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2 = component(\`Child\`, {}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
let b3 = component(\`Child\`, {}, key+\`__2\`,null, node, ctx);
|
let b3 = component(\`Child\`, {}, key + \`__2\`, node, ctx);
|
||||||
return block1([], [b2, b3]);
|
return block1([], [b2, b3]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -281,8 +281,8 @@ exports[`Reactivity: useState two independent components on different levels are
|
|||||||
let block1 = createBlock(\`<div><block-child-0/><block-child-1/></div>\`);
|
let block1 = createBlock(\`<div><block-child-0/><block-child-1/></div>\`);
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2 = component(\`Child\`, {}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
let b3 = component(\`Parent\`, {}, key+\`__2\`,null, node, ctx);
|
let b3 = component(\`Parent\`, {}, key + \`__2\`, node, ctx);
|
||||||
return block1([], [b2, b3]);
|
return block1([], [b2, b3]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -310,7 +310,7 @@ exports[`Reactivity: useState two independent components on different levels are
|
|||||||
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\`, {}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -344,7 +344,7 @@ exports[`Reactivity: useState useless atoms should be deleted 1`] = `
|
|||||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||||
ctx[\`id\`] = v_block2[i1];
|
ctx[\`id\`] = v_block2[i1];
|
||||||
let key1 = ctx['id'];
|
let key1 = ctx['id'];
|
||||||
c_block2[i1] = withKey(component(\`Quantity\`, {id: ctx['id']}, key+\`__1__\${key1}\`,null, node, ctx), key1);
|
c_block2[i1] = withKey(component(\`Quantity\`, {id: ctx['id']}, key + \`__1__\${key1}\`, node, ctx), key1);
|
||||||
}
|
}
|
||||||
ctx = ctx.__proto__;
|
ctx = ctx.__proto__;
|
||||||
let b2 = list(c_block2);
|
let b2 = list(c_block2);
|
||||||
|
|||||||
@@ -169,6 +169,15 @@ 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,4 +1,5 @@
|
|||||||
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";
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -55,3 +56,15 @@ 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();
|
||||||
|
});
|
||||||
|
|||||||
@@ -77,6 +77,11 @@ describe("multi blocks", () => {
|
|||||||
expect(fixture.innerHTML).toBe("ab");
|
expect(fixture.innerHTML).toBe("ab");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("multi vnode can be used as text", () => {
|
||||||
|
mount(text(multi([text("a"), undefined]) as any), fixture);
|
||||||
|
expect(fixture.innerHTML).toBe("a");
|
||||||
|
});
|
||||||
|
|
||||||
test("multi inside a block", async () => {
|
test("multi inside a block", async () => {
|
||||||
const block = createBlock("<div><block-child-0/></div>");
|
const block = createBlock("<div><block-child-0/></div>");
|
||||||
const tree = block([], [multi([text("foo"), text("bar")])]);
|
const tree = block([], [multi([text("foo"), text("bar")])]);
|
||||||
|
|||||||
@@ -353,7 +353,7 @@ exports[`t-on t-on modifiers (native listener) t-on with prevent modifier in t-f
|
|||||||
let key1 = ctx['project'];
|
let key1 = ctx['project'];
|
||||||
const v1 = ctx['onEdit'];
|
const v1 = ctx['onEdit'];
|
||||||
const v2 = ctx['project'];
|
const v2 = ctx['project'];
|
||||||
let hdlr1 = [\\"prevent\\", ev=>v1(v2.id,ev), ctx];
|
let hdlr1 = [\\"prevent\\", _ev=>v1(v2.id,_ev), ctx];
|
||||||
let txt1 = ctx['project'].name;
|
let txt1 = ctx['project'].name;
|
||||||
c_block2[i1] = withKey(block3([hdlr1, txt1]), key1);
|
c_block2[i1] = withKey(block3([hdlr1, txt1]), key1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,11 +30,11 @@ exports[`misc complex template 1`] = `
|
|||||||
b3 = block3();
|
b3 = block3();
|
||||||
}
|
}
|
||||||
ctx = Object.create(ctx);
|
ctx = Object.create(ctx);
|
||||||
const [k_block4, v_block4, l_block4, c_block4] = prepareList(ctx['batch'].slot_ids.filter(slot=>slot.build_id.id&&!slot.trigger_id.manual&&(ctx['options'].trigger_display[slot.trigger_id.id])));
|
const [k_block4, v_block4, l_block4, c_block4] = prepareList(ctx['batch'].slot_ids.filter(_slot=>_slot.build_id.id&&!_slot.trigger_id.manual&&(ctx['options'].trigger_display[_slot.trigger_id.id])));
|
||||||
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}\`,null, 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\`,null, 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\`,null, 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]);
|
||||||
|
|||||||
@@ -44,10 +44,92 @@ exports[`properly support svg namespace to svg tags added even if already in svg
|
|||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
let block1 = createBlock(\`<svg block-ns=\\"http://www.w3.org/2000/svg\\"><svg block-ns=\\"http://www.w3.org/2000/svg\\"/></svg>\`);
|
let block1 = createBlock(\`<svg block-ns=\\"http://www.w3.org/2000/svg\\"><svg/></svg>\`);
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
return block1();
|
return block1();
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`properly support svg svg creates new block if it is within html -- 2 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||||
|
let block2 = createBlock(\`<svg block-ns=\\"http://www.w3.org/2000/svg\\"><polygon fill=\\"#000000\\" points=\\"0 0 4 4 8 0\\" transform=\\"translate(5 7)\\"/><block-child-0/></svg>\`);
|
||||||
|
let block3 = createBlock(\`<path block-ns=\\"http://www.w3.org/2000/svg\\"/>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let b3;
|
||||||
|
if (ctx['hasPath']) {
|
||||||
|
b3 = block3();
|
||||||
|
}
|
||||||
|
let b2 = block2([], [b3]);
|
||||||
|
return block1([], [b2]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`properly support svg svg creates new block if it is within html 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||||
|
let block2 = createBlock(\`<svg block-ns=\\"http://www.w3.org/2000/svg\\"><polygon fill=\\"#000000\\" points=\\"0 0 4 4 8 0\\" transform=\\"translate(5 7)\\"/></svg>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let b2 = block2();
|
||||||
|
return block1([], [b2]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`properly support svg svg namespace added to sub templates if root tag is path 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { getTemplate } = helpers;
|
||||||
|
const callTemplate_1 = getTemplate(\`path\`);
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<svg block-ns=\\"http://www.w3.org/2000/svg\\"><block-child-0/></svg>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||||
|
return block1([], [b2]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`properly support svg svg namespace added to sub templates if root tag is path 2`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<path block-ns=\\"http://www.w3.org/2000/svg\\"/>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return block1();
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`properly support svg svg namespace added to sub-blocks 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<svg block-ns=\\"http://www.w3.org/2000/svg\\"><block-child-0/></svg>\`);
|
||||||
|
let block2 = createBlock(\`<path block-ns=\\"http://www.w3.org/2000/svg\\"/>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let b2;
|
||||||
|
if (ctx['path']) {
|
||||||
|
b2 = block2();
|
||||||
|
}
|
||||||
|
return block1([], [b2]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|||||||
@@ -68,3 +68,38 @@ exports[`t-key t-key directive in a list 1`] = `
|
|||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`t-key t-key on sub dom node pushes a child block in its parent 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<div><block-child-0/><block-child-1/></div>\`);
|
||||||
|
let block2 = createBlock(\`<span/>\`);
|
||||||
|
let block3 = createBlock(\`<div><h1/></div>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let b2,b3;
|
||||||
|
if (ctx['hasSpan']) {
|
||||||
|
b2 = block2();
|
||||||
|
}
|
||||||
|
const tKey_1 = ctx['key'];
|
||||||
|
b3 = toggler(tKey_1, block3());
|
||||||
|
return block1([], [b2, b3]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`t-key t-key on sub dom node pushes a child block in its parent 2`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<div><h1/></div>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
const tKey_1 = ctx['key'];
|
||||||
|
return toggler(tKey_1, block1());
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|||||||
@@ -407,6 +407,36 @@ 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
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -87,6 +87,31 @@ exports[`t-set set from attribute lookup 1`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`t-set set from body literal (with t-if/t-else 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { isBoundary, withDefault, LazyValue } = helpers;
|
||||||
|
|
||||||
|
function value1(ctx, node, key = \\"\\") {
|
||||||
|
let b2,b3;
|
||||||
|
if (ctx['condition']) {
|
||||||
|
b2 = text(\`true\`);
|
||||||
|
} else {
|
||||||
|
b3 = text(\`false\`);
|
||||||
|
}
|
||||||
|
return multi([b2, b3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
ctx = Object.create(ctx);
|
||||||
|
ctx[isBoundary] = 1
|
||||||
|
ctx[\`value\`] = new LazyValue(value1, ctx, node);
|
||||||
|
return text(ctx['value']);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`t-set set from body literal 1`] = `
|
exports[`t-set set from body literal 1`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -1,5 +1,19 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`loading templates addTemplates does not modify its xml document in place 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<div><block-text-0/></div>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let txt1 = ctx['value'];
|
||||||
|
return block1([txt1]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`loading templates can initialize qweb with a string 1`] = `
|
exports[`loading templates can initialize qweb with a string 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 translatable attributes 1`] = `
|
exports[`translation support can set and remove 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=\\"word\\">text</div>\`);
|
let block1 = createBlock(\`<div tomato=\\"word\\" potato=\\"mot\\" title=\\"mot\\" label=\\"word\\">text</div>\`);
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
return block1();
|
return block1();
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { renderToString } from "../helpers";
|
||||||
|
|
||||||
|
describe("blacklisted tags and attributes", () => {
|
||||||
|
test("template with block-text tag", () => {
|
||||||
|
const template = `<div><block-text-0/>hello</div>`;
|
||||||
|
expect(() => renderToString(template)).toThrow("Invalid tag name: 'block-text-0'");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("template with block-handler tag", () => {
|
||||||
|
const template = `<div block-handler-0="click">hello</div>`;
|
||||||
|
expect(() => renderToString(template)).toThrow("Invalid attribute: 'block-handler-0'");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -2,6 +2,7 @@ import { TemplateSet } from "../../src/app/template_set";
|
|||||||
import { mount } from "../../src/blockdom";
|
import { mount } from "../../src/blockdom";
|
||||||
import { makeTestFixture, renderToBdom, renderToString, snapshotEverything } from "../helpers";
|
import { makeTestFixture, renderToBdom, renderToString, snapshotEverything } from "../helpers";
|
||||||
import { markup } from "../../src/utils";
|
import { markup } from "../../src/utils";
|
||||||
|
import { STATUS } from "../../src/component/status";
|
||||||
|
|
||||||
snapshotEverything();
|
snapshotEverything();
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
@@ -11,7 +12,7 @@ snapshotEverything();
|
|||||||
describe("t-on", () => {
|
describe("t-on", () => {
|
||||||
function mountToFixture(template: string, ctx: any = {}, node?: any): HTMLDivElement {
|
function mountToFixture(template: string, ctx: any = {}, node?: any): HTMLDivElement {
|
||||||
if (!node) {
|
if (!node) {
|
||||||
node = { component: ctx };
|
node = { component: ctx, status: STATUS.MOUNTED };
|
||||||
ctx.__owl__ = node;
|
ctx.__owl__ = node;
|
||||||
}
|
}
|
||||||
const block = renderToBdom(template, ctx, node);
|
const block = renderToBdom(template, ctx, node);
|
||||||
@@ -156,7 +157,7 @@ describe("t-on", () => {
|
|||||||
expect(this).toBe(owner);
|
expect(this).toBe(owner);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const node = { component: owner };
|
const node = { component: owner, status: STATUS.MOUNTED };
|
||||||
owner.__owl__ = node;
|
owner.__owl__ = node;
|
||||||
const fixture = makeTestFixture();
|
const fixture = makeTestFixture();
|
||||||
const render = context.getTemplate("main");
|
const render = context.getTemplate("main");
|
||||||
@@ -181,7 +182,7 @@ describe("t-on", () => {
|
|||||||
expect(this).toBe(owner);
|
expect(this).toBe(owner);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const node = { component: owner };
|
const node = { component: owner, status: STATUS.MOUNTED };
|
||||||
owner.__owl__ = node;
|
owner.__owl__ = node;
|
||||||
const fixture = makeTestFixture();
|
const fixture = makeTestFixture();
|
||||||
const render = context.getTemplate("main");
|
const render = context.getTemplate("main");
|
||||||
@@ -260,7 +261,7 @@ describe("t-on", () => {
|
|||||||
expect(this).toBe(owner);
|
expect(this).toBe(owner);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const node = { component: owner };
|
const node = { component: owner, status: STATUS.MOUNTED };
|
||||||
owner.__owl__ = node;
|
owner.__owl__ = node;
|
||||||
|
|
||||||
const fixture = makeTestFixture();
|
const fixture = makeTestFixture();
|
||||||
@@ -286,7 +287,7 @@ describe("t-on", () => {
|
|||||||
value: 444,
|
value: 444,
|
||||||
};
|
};
|
||||||
|
|
||||||
const node = { component: owner };
|
const node = { component: owner, status: STATUS.MOUNTED };
|
||||||
owner.__owl__ = node;
|
owner.__owl__ = node;
|
||||||
|
|
||||||
const fixture = makeTestFixture();
|
const fixture = makeTestFixture();
|
||||||
@@ -479,6 +480,14 @@ describe("t-on", () => {
|
|||||||
button.click();
|
button.click();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("t-on crashes when used with unknown modifier", async () => {
|
||||||
|
const template = `<div t-on-click.somemodifier="onClick" />`;
|
||||||
|
|
||||||
|
let owner = { onClick(e: Event) {} };
|
||||||
|
|
||||||
|
expect(() => mountToFixture(template, owner)).toThrowError("Unknown event modifier");
|
||||||
|
});
|
||||||
|
|
||||||
test("t-on combined with t-esc", async () => {
|
test("t-on combined with t-esc", async () => {
|
||||||
expect.assertions(3);
|
expect.assertions(3);
|
||||||
const template = `<div><button t-on-click="onClick" t-esc="text"/></div>`;
|
const template = `<div><button t-on-click="onClick" t-esc="text"/></div>`;
|
||||||
|
|||||||
@@ -163,17 +163,21 @@ describe("expression evaluation", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("arrow functions", () => {
|
test("arrow functions", () => {
|
||||||
expect(compileExpr("list.map(e => e.val)")).toBe("ctx['list'].map(e=>e.val)");
|
expect(compileExpr("list.map(e => e.val)")).toBe("ctx['list'].map(_e=>_e.val)");
|
||||||
expect(compileExpr("list.map(e => a + e)")).toBe("ctx['list'].map(e=>ctx['a']+e)");
|
expect(compileExpr("list.map(e => a + e)")).toBe("ctx['list'].map(_e=>ctx['a']+_e)");
|
||||||
expect(compileExpr("list.map((e) => e)")).toBe("ctx['list'].map((e)=>e)");
|
expect(compileExpr("list.map((e) => e)")).toBe("ctx['list'].map((_e)=>_e)");
|
||||||
expect(compileExpr("list.map((elem, index) => elem + index)")).toBe(
|
expect(compileExpr("list.map((elem, index) => elem + index)")).toBe(
|
||||||
"ctx['list'].map((elem,index)=>elem+index)"
|
"ctx['list'].map((_elem,_index)=>_elem+_index)"
|
||||||
|
);
|
||||||
|
expect(compileExpr("(ev => ev)(e)")).toBe("(_ev=>_ev)(ctx['e'])");
|
||||||
|
expect(compileExpr("(v1) => myFunc(v1)")).toBe("(_v1)=>ctx['myFunc'](_v1)");
|
||||||
|
expect(compileExpr("list.data.map((data) => data)")).toBe(
|
||||||
|
"ctx['list'].data.map((_data)=>_data)"
|
||||||
);
|
);
|
||||||
expect(compileExpr("(ev => ev)(e)")).toBe("(ev=>ev)(ctx['e'])");
|
|
||||||
});
|
});
|
||||||
test.skip("arrow functions: not yet supported", () => {
|
test.skip("arrow functions: not yet supported", () => {
|
||||||
// e is added to localvars in inline_expression but not removed after the arrow func body
|
// e is added to localvars in inline_expression but not removed after the arrow func body
|
||||||
expect(compileExpr("(e => e)(e)")).toBe("(e=>e)(ctx['e'])");
|
expect(compileExpr("(e => e)(e)")).toBe("(_e=>_e)(ctx['e'])");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("assignation", () => {
|
test("assignation", () => {
|
||||||
|
|||||||
+103
-29
@@ -776,8 +776,6 @@ describe("qweb parser", () => {
|
|||||||
collection: "list",
|
collection: "list",
|
||||||
elem: "item",
|
elem: "item",
|
||||||
key: "item_index",
|
key: "item_index",
|
||||||
hasNoComponent: true,
|
|
||||||
isOnlyChild: false,
|
|
||||||
body: { type: ASTType.TEsc, expr: "item", defaultValue: "" },
|
body: { type: ASTType.TEsc, expr: "item", defaultValue: "" },
|
||||||
memo: "",
|
memo: "",
|
||||||
hasNoFirst: true,
|
hasNoFirst: true,
|
||||||
@@ -793,8 +791,6 @@ describe("qweb parser", () => {
|
|||||||
collection: "list",
|
collection: "list",
|
||||||
elem: "item",
|
elem: "item",
|
||||||
key: "item_index",
|
key: "item_index",
|
||||||
hasNoComponent: true,
|
|
||||||
isOnlyChild: false,
|
|
||||||
body: { type: ASTType.TEsc, expr: "item", defaultValue: "" },
|
body: { type: ASTType.TEsc, expr: "item", defaultValue: "" },
|
||||||
memo: "",
|
memo: "",
|
||||||
hasNoFirst: true,
|
hasNoFirst: true,
|
||||||
@@ -809,8 +805,6 @@ describe("qweb parser", () => {
|
|||||||
type: ASTType.TForEach,
|
type: ASTType.TForEach,
|
||||||
collection: "list",
|
collection: "list",
|
||||||
elem: "item",
|
elem: "item",
|
||||||
hasNoComponent: true,
|
|
||||||
isOnlyChild: false,
|
|
||||||
key: "item_index",
|
key: "item_index",
|
||||||
body: {
|
body: {
|
||||||
type: ASTType.DomNode,
|
type: ASTType.DomNode,
|
||||||
@@ -836,8 +830,6 @@ describe("qweb parser", () => {
|
|||||||
type: ASTType.TForEach,
|
type: ASTType.TForEach,
|
||||||
collection: "list",
|
collection: "list",
|
||||||
elem: "item",
|
elem: "item",
|
||||||
hasNoComponent: true,
|
|
||||||
isOnlyChild: false,
|
|
||||||
key: "item.id",
|
key: "item.id",
|
||||||
body: { type: ASTType.TEsc, expr: "item", defaultValue: "" },
|
body: { type: ASTType.TEsc, expr: "item", defaultValue: "" },
|
||||||
memo: "",
|
memo: "",
|
||||||
@@ -856,8 +848,6 @@ describe("qweb parser", () => {
|
|||||||
collection: "list",
|
collection: "list",
|
||||||
elem: "item",
|
elem: "item",
|
||||||
key: "item_index",
|
key: "item_index",
|
||||||
hasNoComponent: true,
|
|
||||||
isOnlyChild: false,
|
|
||||||
body: {
|
body: {
|
||||||
type: ASTType.DomNode,
|
type: ASTType.DomNode,
|
||||||
tag: "span",
|
tag: "span",
|
||||||
@@ -887,8 +877,6 @@ describe("qweb parser", () => {
|
|||||||
collection: "list",
|
collection: "list",
|
||||||
elem: "item",
|
elem: "item",
|
||||||
key: "item_index",
|
key: "item_index",
|
||||||
hasNoComponent: true,
|
|
||||||
isOnlyChild: false,
|
|
||||||
body: {
|
body: {
|
||||||
type: ASTType.TIf,
|
type: ASTType.TIf,
|
||||||
condition: "condition",
|
condition: "condition",
|
||||||
@@ -924,8 +912,6 @@ describe("qweb parser", () => {
|
|||||||
collection: "categories",
|
collection: "categories",
|
||||||
elem: "category",
|
elem: "category",
|
||||||
key: "category_index",
|
key: "category_index",
|
||||||
hasNoComponent: true,
|
|
||||||
isOnlyChild: false,
|
|
||||||
body: {
|
body: {
|
||||||
type: ASTType.DomNode,
|
type: ASTType.DomNode,
|
||||||
tag: "option",
|
tag: "option",
|
||||||
@@ -966,8 +952,6 @@ describe("qweb parser", () => {
|
|||||||
collection: "list",
|
collection: "list",
|
||||||
elem: "item",
|
elem: "item",
|
||||||
key: "item_index",
|
key: "item_index",
|
||||||
hasNoComponent: true,
|
|
||||||
isOnlyChild: true,
|
|
||||||
body: { type: ASTType.TEsc, expr: "item", defaultValue: "" },
|
body: { type: ASTType.TEsc, expr: "item", defaultValue: "" },
|
||||||
memo: "",
|
memo: "",
|
||||||
hasNoFirst: true,
|
hasNoFirst: true,
|
||||||
@@ -995,8 +979,6 @@ describe("qweb parser", () => {
|
|||||||
collection: "list",
|
collection: "list",
|
||||||
elem: "item",
|
elem: "item",
|
||||||
key: "item_index",
|
key: "item_index",
|
||||||
hasNoComponent: true,
|
|
||||||
isOnlyChild: false,
|
|
||||||
body: {
|
body: {
|
||||||
type: ASTType.DomNode,
|
type: ASTType.DomNode,
|
||||||
tag: "span",
|
tag: "span",
|
||||||
@@ -1022,8 +1004,6 @@ describe("qweb parser", () => {
|
|||||||
collection: "list",
|
collection: "list",
|
||||||
elem: "item",
|
elem: "item",
|
||||||
key: "item_index",
|
key: "item_index",
|
||||||
hasNoComponent: false,
|
|
||||||
isOnlyChild: false,
|
|
||||||
body: {
|
body: {
|
||||||
type: ASTType.TComponent,
|
type: ASTType.TComponent,
|
||||||
isDynamic: false,
|
isDynamic: false,
|
||||||
@@ -1048,8 +1028,6 @@ describe("qweb parser", () => {
|
|||||||
collection: "list",
|
collection: "list",
|
||||||
elem: "item",
|
elem: "item",
|
||||||
key: "item_index",
|
key: "item_index",
|
||||||
hasNoComponent: false,
|
|
||||||
isOnlyChild: false,
|
|
||||||
body: {
|
body: {
|
||||||
type: ASTType.TCall,
|
type: ASTType.TCall,
|
||||||
name: "blap",
|
name: "blap",
|
||||||
@@ -1073,8 +1051,6 @@ describe("qweb parser", () => {
|
|||||||
collection: "list",
|
collection: "list",
|
||||||
elem: "item",
|
elem: "item",
|
||||||
key: "item_index",
|
key: "item_index",
|
||||||
hasNoComponent: true,
|
|
||||||
isOnlyChild: false,
|
|
||||||
body: { type: ASTType.TEsc, expr: "item", defaultValue: "" },
|
body: { type: ASTType.TEsc, expr: "item", defaultValue: "" },
|
||||||
memo: "[row.x]",
|
memo: "[row.x]",
|
||||||
hasNoFirst: true,
|
hasNoFirst: true,
|
||||||
@@ -1156,6 +1132,81 @@ describe("qweb parser", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// t-model
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
test("t-model select", async () => {
|
||||||
|
expect(parse(`<select t-model="state.model"><option value="1" /></select>`)).toEqual({
|
||||||
|
type: 2,
|
||||||
|
tag: "select",
|
||||||
|
dynamicTag: null,
|
||||||
|
attrs: {},
|
||||||
|
on: {},
|
||||||
|
ref: null,
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 2,
|
||||||
|
tag: "option",
|
||||||
|
dynamicTag: null,
|
||||||
|
attrs: { value: "1" },
|
||||||
|
on: {},
|
||||||
|
ref: null,
|
||||||
|
content: [],
|
||||||
|
model: null,
|
||||||
|
ns: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
model: {
|
||||||
|
baseExpr: "state",
|
||||||
|
expr: "'model'",
|
||||||
|
targetAttr: "value",
|
||||||
|
specialInitTargetAttr: null,
|
||||||
|
eventType: "change",
|
||||||
|
shouldTrim: false,
|
||||||
|
shouldNumberize: false,
|
||||||
|
},
|
||||||
|
ns: null,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("t-model select dynamic options", async () => {
|
||||||
|
expect(
|
||||||
|
parse(`<select t-model="state.model"><option t-att-value="valueVar" /></select>`)
|
||||||
|
).toEqual({
|
||||||
|
type: 2,
|
||||||
|
tag: "select",
|
||||||
|
dynamicTag: null,
|
||||||
|
attrs: {},
|
||||||
|
on: {},
|
||||||
|
ref: null,
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 2,
|
||||||
|
tag: "option",
|
||||||
|
dynamicTag: null,
|
||||||
|
attrs: { "t-att-value": "valueVar" },
|
||||||
|
on: {},
|
||||||
|
ref: null,
|
||||||
|
content: [],
|
||||||
|
model: null,
|
||||||
|
ns: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
model: {
|
||||||
|
baseExpr: "state",
|
||||||
|
expr: "'model'",
|
||||||
|
targetAttr: "value",
|
||||||
|
specialInitTargetAttr: null,
|
||||||
|
eventType: "change",
|
||||||
|
shouldTrim: false,
|
||||||
|
shouldNumberize: false,
|
||||||
|
hasDynamicChildren: true,
|
||||||
|
},
|
||||||
|
ns: null,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// t-component
|
// t-component
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -1604,7 +1655,7 @@ describe("qweb parser", () => {
|
|||||||
attrs: {},
|
attrs: {},
|
||||||
content: [
|
content: [
|
||||||
{
|
{
|
||||||
type: 0,
|
type: ASTType.Text,
|
||||||
value: "word",
|
value: "word",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -1616,19 +1667,17 @@ describe("qweb parser", () => {
|
|||||||
type: ASTType.DomNode,
|
type: ASTType.DomNode,
|
||||||
ns: null,
|
ns: null,
|
||||||
},
|
},
|
||||||
type: 16,
|
type: ASTType.TTranslation,
|
||||||
},
|
},
|
||||||
collection: "list",
|
collection: "list",
|
||||||
elem: "item",
|
elem: "item",
|
||||||
hasNoComponent: true,
|
|
||||||
hasNoFirst: true,
|
hasNoFirst: true,
|
||||||
hasNoIndex: false,
|
hasNoIndex: false,
|
||||||
hasNoLast: true,
|
hasNoLast: true,
|
||||||
hasNoValue: true,
|
hasNoValue: true,
|
||||||
isOnlyChild: false,
|
|
||||||
key: "item_index",
|
key: "item_index",
|
||||||
memo: "",
|
memo: "",
|
||||||
type: 9,
|
type: ASTType.TForEach,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1787,4 +1836,29 @@ describe("qweb parser", () => {
|
|||||||
ns: null,
|
ns: null,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// t-portal
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
test("t-portal", async () => {
|
||||||
|
expect(parse(`<t t-portal="target">Content</t>`)).toEqual({
|
||||||
|
type: ASTType.TPortal,
|
||||||
|
target: "target",
|
||||||
|
content: { type: ASTType.Text, value: "Content" },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("t-portal with t-if", async () => {
|
||||||
|
expect(parse(`<t t-portal="target" t-if="condition">Content</t>`)).toEqual({
|
||||||
|
condition: "condition",
|
||||||
|
content: {
|
||||||
|
content: { type: ASTType.Text, value: "Content" },
|
||||||
|
target: "target",
|
||||||
|
type: ASTType.TPortal,
|
||||||
|
},
|
||||||
|
tElif: null,
|
||||||
|
tElse: null,
|
||||||
|
type: ASTType.TIf,
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { renderToString, snapshotEverything } from "../helpers";
|
import { renderToString, renderToBdom, snapshotEverything, makeTestFixture } from "../helpers";
|
||||||
|
import { mount } from "../../src/blockdom";
|
||||||
|
import { mount as mountComponent, Component, xml } from "../../src/index";
|
||||||
|
|
||||||
// NB: check the snapshots to see where the SVG namespaces are added
|
// NB: check the snapshots to see where the SVG namespaces are added
|
||||||
snapshotEverything();
|
snapshotEverything();
|
||||||
@@ -25,6 +27,91 @@ describe("properly support svg", () => {
|
|||||||
|
|
||||||
test("namespace to svg tags added even if already in svg namespace", () => {
|
test("namespace to svg tags added even if already in svg namespace", () => {
|
||||||
const template = `<svg><svg/></svg>`;
|
const template = `<svg><svg/></svg>`;
|
||||||
expect(renderToString(template)).toBe(`<svg><svg></svg></svg>`);
|
const bdom = renderToBdom(template);
|
||||||
|
const fixture = makeTestFixture();
|
||||||
|
|
||||||
|
mount(bdom, fixture);
|
||||||
|
const elems = fixture.querySelectorAll("svg");
|
||||||
|
expect(elems.length).toEqual(2);
|
||||||
|
for (const el of elems) {
|
||||||
|
expect(el.namespaceURI).toBe("http://www.w3.org/2000/svg");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("svg namespace added to sub-blocks", () => {
|
||||||
|
const template = `<svg><path t-if="path"/></svg>`;
|
||||||
|
|
||||||
|
expect(renderToString(template, { path: false })).toBe(`<svg></svg>`);
|
||||||
|
expect(renderToString(template, { path: true })).toBe(`<svg><path></path></svg>`);
|
||||||
|
|
||||||
|
const bdom = renderToBdom(template, { path: true });
|
||||||
|
const fixture = makeTestFixture();
|
||||||
|
|
||||||
|
mount(bdom, fixture);
|
||||||
|
const elems = fixture.querySelectorAll("svg, path");
|
||||||
|
expect(elems.length).toEqual(2);
|
||||||
|
for (const el of elems) {
|
||||||
|
expect(el.namespaceURI).toBe("http://www.w3.org/2000/svg");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("svg namespace added to sub templates if root tag is path", async () => {
|
||||||
|
const templates = `<t>
|
||||||
|
<t t-name="svg"><svg><t t-call="path" /></svg></t>
|
||||||
|
<t t-name="path"><path /></t>
|
||||||
|
</t>
|
||||||
|
`;
|
||||||
|
const fixture = makeTestFixture();
|
||||||
|
|
||||||
|
class Svg extends Component {
|
||||||
|
static template = "svg";
|
||||||
|
}
|
||||||
|
|
||||||
|
await mountComponent(Svg, fixture, { templates });
|
||||||
|
const elems = fixture.querySelectorAll("svg, path");
|
||||||
|
expect(elems.length).toEqual(2);
|
||||||
|
for (const el of elems) {
|
||||||
|
expect(el.namespaceURI).toBe("http://www.w3.org/2000/svg");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("svg creates new block if it is within html", async () => {
|
||||||
|
class Test extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<div>
|
||||||
|
<svg>
|
||||||
|
<polygon fill="#000000" points="0 0 4 4 8 0" transform="translate(5 7)"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
const fixture = makeTestFixture();
|
||||||
|
await mountComponent(Test, fixture);
|
||||||
|
const elems = fixture.querySelectorAll("svg, polygon");
|
||||||
|
expect(elems.length).toEqual(2);
|
||||||
|
for (const el of elems) {
|
||||||
|
expect(el.namespaceURI).toBe("http://www.w3.org/2000/svg");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("svg creates new block if it is within html -- 2", async () => {
|
||||||
|
class Test extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<div>
|
||||||
|
<svg>
|
||||||
|
<polygon fill="#000000" points="0 0 4 4 8 0" transform="translate(5 7)"/>
|
||||||
|
<path t-if="hasPath" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
hasPath = true;
|
||||||
|
}
|
||||||
|
const fixture = makeTestFixture();
|
||||||
|
await mountComponent(Test, fixture);
|
||||||
|
const elems = fixture.querySelectorAll("svg, polygon, path");
|
||||||
|
expect(elems.length).toEqual(3);
|
||||||
|
for (const el of elems) {
|
||||||
|
expect(el.namespaceURI).toBe("http://www.w3.org/2000/svg");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -43,4 +43,24 @@ describe("t-key", () => {
|
|||||||
})
|
})
|
||||||
).toBe("<ul><li>Chimay Rouge</li></ul>");
|
).toBe("<ul><li>Chimay Rouge</li></ul>");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("t-key on sub dom node pushes a child block in its parent", async () => {
|
||||||
|
const template = `
|
||||||
|
<div>
|
||||||
|
<t t-if="hasSpan"><span /></t>
|
||||||
|
<div t-key="key"><h1 /></div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
expect(renderToString(template, { key: "1" })).toBe("<div><div><h1></h1></div></div>");
|
||||||
|
expect(renderToString(template, { hasSpan: true, key: "1" })).toBe(
|
||||||
|
"<div><span></span><div><h1></h1></div></div>"
|
||||||
|
);
|
||||||
|
|
||||||
|
const template2 = `
|
||||||
|
<div t-key="key"><h1 /></div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
expect(renderToString(template2, { key: "1" })).toBe("<div><h1></h1></div>");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -37,6 +37,26 @@ 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>");
|
||||||
|
|||||||
@@ -38,6 +38,19 @@ describe("t-set", () => {
|
|||||||
expect(renderToString(template)).toBe("ok");
|
expect(renderToString(template)).toBe("ok");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("set from body literal (with t-if/t-else", () => {
|
||||||
|
const template = `
|
||||||
|
<t>
|
||||||
|
<t t-set="value">
|
||||||
|
<t t-if="condition">true</t>
|
||||||
|
<t t-else="">false</t>
|
||||||
|
</t>
|
||||||
|
<t t-esc="value"/>
|
||||||
|
</t>`;
|
||||||
|
expect(renderToString(template, { condition: true })).toBe("true");
|
||||||
|
expect(renderToString(template, { condition: false })).toBe("false");
|
||||||
|
});
|
||||||
|
|
||||||
test("set from attribute lookup", () => {
|
test("set from attribute lookup", () => {
|
||||||
const template = `<div><t t-set="stuff" t-value="value"/><t t-esc="stuff"/></div>`;
|
const template = `<div><t t-set="stuff" t-value="value"/><t t-esc="stuff"/></div>`;
|
||||||
expect(renderToString(template, { value: "ok" })).toBe("<div>ok</div>");
|
expect(renderToString(template, { value: "ok" })).toBe("<div>ok</div>");
|
||||||
|
|||||||
@@ -24,6 +24,17 @@ describe("loading templates", () => {
|
|||||||
expect(context.renderToString("hey")).toBe("<div>jupiler</div>");
|
expect(context.renderToString("hey")).toBe("<div>jupiler</div>");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("addTemplates does not modify its xml document in place", () => {
|
||||||
|
const data = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<templates id="template" xml:space="preserve"><div t-name="hey"><t t-esc="value"/></div></templates>`;
|
||||||
|
const xml = new DOMParser().parseFromString(data, "text/xml");
|
||||||
|
const context = new TestContext();
|
||||||
|
expect(xml.firstElementChild!.innerHTML).toBe(`<div t-name="hey"><t t-esc="value"/></div>`);
|
||||||
|
context.addTemplates(xml);
|
||||||
|
expect(context.renderToString("hey", { value: 123 })).toBe("<div>123</div>");
|
||||||
|
expect(xml.firstElementChild!.innerHTML).toBe(`<div t-name="hey"><t t-esc="value"/></div>`);
|
||||||
|
});
|
||||||
|
|
||||||
test("can load a few templates from a xml string", () => {
|
test("can load a few templates from a xml string", () => {
|
||||||
const data = `<?xml version="1.0" encoding="UTF-8"?>
|
const data = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<templates id="template" xml:space="preserve">
|
<templates id="template" xml:space="preserve">
|
||||||
|
|||||||
@@ -59,18 +59,20 @@ describe("translation support", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("can set translatable attributes", async () => {
|
test("can set and remove translatable attributes", async () => {
|
||||||
class SomeComponent extends Component {
|
class SomeComponent extends Component {
|
||||||
static template = xml`
|
static template = xml`
|
||||||
<div tomato="word" potato="word" title="word">text</div>
|
<div tomato="word" potato="word" title="word" label="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"],
|
translatableAttributes: ["potato", "-label"],
|
||||||
});
|
});
|
||||||
expect(fixture.innerHTML).toBe('<div tomato="word" potato="mot" title="word">text</div>');
|
expect(fixture.innerHTML).toBe(
|
||||||
|
'<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 () => {
|
||||||
|
|||||||
@@ -1,5 +1,45 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`basics GrandChild display is controlled by its GrandParent 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let Comp1 = ctx['myComp'];
|
||||||
|
return toggler(Comp1, component(Comp1, {displayGrandChild: ctx['displayGrandChild']}, key + \`__1\`, node, ctx));
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`basics GrandChild display is controlled by its GrandParent 2`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let b2;
|
||||||
|
if (ctx['props'].displayGrandChild) {
|
||||||
|
b2 = component(\`GrandChild\`, {}, key + \`__1\`, node, ctx);
|
||||||
|
}
|
||||||
|
return multi([b2]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`basics GrandChild display is controlled by its GrandParent 3`] = `
|
||||||
|
"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 Multi root component 1`] = `
|
exports[`basics Multi root component 1`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
@@ -23,7 +63,7 @@ exports[`basics a class component inside a class component, no external dom 1`]
|
|||||||
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\`, {}, key+\`__1\`,null, node, ctx);
|
return component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -41,6 +81,19 @@ 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
|
||||||
) {
|
) {
|
||||||
@@ -49,7 +102,7 @@ exports[`basics a component inside a component 1`] = `
|
|||||||
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(\`Child\`, {}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -92,7 +145,7 @@ exports[`basics can handle empty 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\`, {val: undefined}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`Child\`, {val: undefined}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -210,7 +263,7 @@ exports[`basics child can be updated 1`] = `
|
|||||||
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\`, {value: ctx['state'].counter}, key+\`__1\`,null, node, ctx);
|
return component(\`Child\`, {value: ctx['state'].counter}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -246,7 +299,7 @@ exports[`basics class parent, class child component with props 1`] = `
|
|||||||
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\`, {value: 42}, key+\`__1\`,null, node, ctx);
|
return component(\`Child\`, {value: 42}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -265,6 +318,59 @@ exports[`basics class parent, class child component with props 2`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`basics component children doesn't leak (if case) 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let b2;
|
||||||
|
if (ctx['ifVar']) {
|
||||||
|
b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
|
}
|
||||||
|
return multi([b2]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`basics component children doesn't leak (if case) 2`] = `
|
||||||
|
"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 component children doesn't leak (t-key case) 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
const tKey_1 = ctx['keyVar'];
|
||||||
|
return toggler(tKey_1, component(\`Child\`, {}, tKey_1 + key + \`__1\`, node, ctx));
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`basics component children doesn't leak (t-key case) 2`] = `
|
||||||
|
"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 component with dynamic content can be updated 1`] = `
|
exports[`basics component with dynamic content can be updated 1`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
@@ -312,7 +418,7 @@ exports[`basics higher order components parent and child 1`] = `
|
|||||||
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\`, {child: ctx['state'].child}, key+\`__1\`,null, node, ctx);
|
return component(\`Child\`, {child: ctx['state'].child}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -325,9 +431,9 @@ exports[`basics higher order components parent and child 2`] = `
|
|||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2,b3;
|
let b2,b3;
|
||||||
if (ctx['props'].child==='a') {
|
if (ctx['props'].child==='a') {
|
||||||
b2 = component(\`ChildA\`, {}, key+\`__1\`,null, node, ctx);
|
b2 = component(\`ChildA\`, {}, key + \`__1\`, node, ctx);
|
||||||
} else {
|
} else {
|
||||||
b3 = component(\`ChildB\`, {}, key+\`__2\`,null, node, ctx);
|
b3 = component(\`ChildB\`, {}, key + \`__2\`, node, ctx);
|
||||||
}
|
}
|
||||||
return multi([b2, b3]);
|
return multi([b2, b3]);
|
||||||
}
|
}
|
||||||
@@ -375,8 +481,8 @@ exports[`basics list of two sub components inside other nodes 1`] = `
|
|||||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||||
ctx[\`blip\`] = v_block2[i1];
|
ctx[\`blip\`] = v_block2[i1];
|
||||||
let key1 = ctx['blip'].id;
|
let key1 = ctx['blip'].id;
|
||||||
let b4 = component(\`SubWidget\`, {}, key+\`__1__\${key1}\`,null, node, ctx);
|
let b4 = component(\`SubWidget\`, {}, key + \`__1__\${key1}\`, node, ctx);
|
||||||
let b5 = component(\`SubWidget\`, {}, key+\`__2__\${key1}\`,null, node, ctx);
|
let b5 = component(\`SubWidget\`, {}, key + \`__2__\${key1}\`, node, ctx);
|
||||||
c_block2[i1] = withKey(block3([], [b4, b5]), key1);
|
c_block2[i1] = withKey(block3([], [b4, b5]), key1);
|
||||||
}
|
}
|
||||||
let b2 = list(c_block2);
|
let b2 = list(c_block2);
|
||||||
@@ -404,7 +510,7 @@ exports[`basics parent, child and grandchild 1`] = `
|
|||||||
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\`, {}, key+\`__1\`,null, node, ctx);
|
return component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -415,7 +521,7 @@ exports[`basics parent, child and grandchild 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(\`GrandChild\`, {}, key+\`__1\`,null, node, ctx);
|
return component(\`GrandChild\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -454,9 +560,9 @@ exports[`basics reconciliation alg is not confused in some specific situation 1`
|
|||||||
let block1 = createBlock(\`<div><block-child-0/><block-child-1/></div>\`);
|
let block1 = createBlock(\`<div><block-child-0/><block-child-1/></div>\`);
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2 = component(\`Child\`, {}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
const tKey_1 = 4;
|
const tKey_1 = 4;
|
||||||
let b3 = toggler(tKey_1, component(\`Child\`, {}, key+\`__2\`,tKey_1, node, ctx));
|
let b3 = toggler(tKey_1, component(\`Child\`, {}, tKey_1 + key + \`__2\`, node, ctx));
|
||||||
return block1([], [b2, b3]);
|
return block1([], [b2, b3]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -481,7 +587,7 @@ exports[`basics rerendering a widget with a sub widget 1`] = `
|
|||||||
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(\`Counter\`, {}, key+\`__1\`,null, node, ctx);
|
return component(\`Counter\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -511,9 +617,9 @@ exports[`basics same t-keys in two different places 1`] = `
|
|||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
const tKey_1 = 1;
|
const tKey_1 = 1;
|
||||||
let b2 = toggler(tKey_1, component(\`Child\`, {blip: '1'}, key+\`__1\`,tKey_1, node, ctx));
|
let b2 = toggler(tKey_1, component(\`Child\`, {blip: '1'}, tKey_1 + key + \`__1\`, node, ctx));
|
||||||
const tKey_2 = 1;
|
const tKey_2 = 1;
|
||||||
let b3 = toggler(tKey_2, component(\`Child\`, {blip: '2'}, key+\`__2\`,tKey_2, node, ctx));
|
let b3 = toggler(tKey_2, component(\`Child\`, {blip: '2'}, tKey_2 + key + \`__2\`, node, ctx));
|
||||||
return block1([], [b2, b3]);
|
return block1([], [b2, b3]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -591,7 +697,7 @@ exports[`basics sub components between t-ifs 1`] = `
|
|||||||
} else {
|
} else {
|
||||||
b3 = block3();
|
b3 = block3();
|
||||||
}
|
}
|
||||||
b4 = component(\`Child\`, {}, key+\`__1\`,null, node, ctx);
|
b4 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
if (ctx['state'].flag) {
|
if (ctx['state'].flag) {
|
||||||
b5 = block5();
|
b5 = block5();
|
||||||
}
|
}
|
||||||
@@ -626,7 +732,7 @@ exports[`basics t-elif works with t-component 1`] = `
|
|||||||
if (ctx['state'].flag) {
|
if (ctx['state'].flag) {
|
||||||
b2 = block2();
|
b2 = block2();
|
||||||
} else if (!ctx['state'].flag) {
|
} else if (!ctx['state'].flag) {
|
||||||
b3 = component(\`Child\`, {}, key+\`__1\`,null, node, ctx);
|
b3 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
return block1([], [b2, b3]);
|
return block1([], [b2, b3]);
|
||||||
}
|
}
|
||||||
@@ -659,7 +765,7 @@ exports[`basics t-else with empty string works with t-component 1`] = `
|
|||||||
if (ctx['state'].flag) {
|
if (ctx['state'].flag) {
|
||||||
b2 = block2();
|
b2 = block2();
|
||||||
} else {
|
} else {
|
||||||
b3 = component(\`Child\`, {}, key+\`__1\`,null, node, ctx);
|
b3 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
return block1([], [b2, b3]);
|
return block1([], [b2, b3]);
|
||||||
}
|
}
|
||||||
@@ -692,7 +798,7 @@ exports[`basics t-else works with t-component 1`] = `
|
|||||||
if (ctx['state'].flag) {
|
if (ctx['state'].flag) {
|
||||||
b2 = block2();
|
b2 = block2();
|
||||||
} else {
|
} else {
|
||||||
b3 = component(\`Child\`, {}, key+\`__1\`,null, node, ctx);
|
b3 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
return block1([], [b2, b3]);
|
return block1([], [b2, b3]);
|
||||||
}
|
}
|
||||||
@@ -722,7 +828,7 @@ exports[`basics t-if works with t-component 1`] = `
|
|||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2;
|
let b2;
|
||||||
if (ctx['state'].flag) {
|
if (ctx['state'].flag) {
|
||||||
b2 = component(\`Child\`, {}, key+\`__1\`,null, node, ctx);
|
b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
@@ -753,9 +859,9 @@ exports[`basics t-key on a component with t-if, and a sibling component 1`] = `
|
|||||||
let b2,b3;
|
let b2,b3;
|
||||||
if (false) {
|
if (false) {
|
||||||
const tKey_1 = 'str';
|
const tKey_1 = 'str';
|
||||||
b2 = toggler(tKey_1, component(\`Child\`, {}, key+\`__1\`,tKey_1, node, ctx));
|
b2 = toggler(tKey_1, component(\`Child\`, {}, tKey_1 + key + \`__1\`, node, ctx));
|
||||||
}
|
}
|
||||||
b3 = component(\`Child\`, {}, key+\`__2\`,null, node, ctx);
|
b3 = component(\`Child\`, {}, key + \`__2\`, node, ctx);
|
||||||
return block1([], [b2, b3]);
|
return block1([], [b2, b3]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -784,7 +890,7 @@ exports[`basics text after a conditional component 1`] = `
|
|||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2;
|
let b2;
|
||||||
if (ctx['state'].hasChild) {
|
if (ctx['state'].hasChild) {
|
||||||
b2 = component(\`Child\`, {}, key+\`__1\`,null, node, ctx);
|
b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
let txt1 = ctx['state'].text;
|
let txt1 = ctx['state'].text;
|
||||||
return block1([txt1], [b2]);
|
return block1([txt1], [b2]);
|
||||||
@@ -811,7 +917,7 @@ exports[`basics three level of components with collapsing root nodes 1`] = `
|
|||||||
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\`, {}, key+\`__1\`,null, node, ctx);
|
return component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -822,7 +928,7 @@ exports[`basics three level of components with collapsing root nodes 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(\`GrandChild\`, {}, key+\`__1\`,null, node, ctx);
|
return component(\`GrandChild\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -846,8 +952,8 @@ exports[`basics two child components 1`] = `
|
|||||||
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 = \\"\\") {
|
||||||
let b2 = component(\`Child\`, {}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
let b3 = component(\`Child\`, {}, key+\`__2\`,null, node, ctx);
|
let b3 = component(\`Child\`, {}, key + \`__2\`, node, ctx);
|
||||||
return multi([b2, b3]);
|
return multi([b2, b3]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -875,7 +981,7 @@ exports[`basics update props of component without concrete own node 1`] = `
|
|||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
const tKey_1 = ctx['childProps'].key;
|
const tKey_1 = ctx['childProps'].key;
|
||||||
let b2 = toggler(tKey_1, component(\`Child\`, ctx['childProps'], key+\`__1\`,tKey_1, node, ctx));
|
let b2 = toggler(tKey_1, component(\`Child\`, Object.assign({}, ctx['childProps']), tKey_1 + key + \`__1\`, node, ctx));
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -888,7 +994,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}, key+\`__1\`,tKey_1, node, ctx));
|
return toggler(tKey_1, component(\`Custom\`, {key: ctx['props'].key, subKey: ctx['props'].subKey}, tKey_1 + key + \`__1\`, node, ctx));
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -933,7 +1039,7 @@ exports[`basics updating widget immediately 1`] = `
|
|||||||
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\`, {flag: ctx['state'].flag}, key+\`__1\`,null, node, ctx);
|
return component(\`Child\`, {flag: ctx['state'].flag}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -974,7 +1080,7 @@ exports[`basics widget after a t-foreach 1`] = `
|
|||||||
}
|
}
|
||||||
ctx = ctx.__proto__;
|
ctx = ctx.__proto__;
|
||||||
let b2 = list(c_block2);
|
let b2 = list(c_block2);
|
||||||
let b4 = component(\`SomeComponent\`, {}, key+\`__1\`,null, node, ctx);
|
let b4 = component(\`SomeComponent\`, {}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2, b4]);
|
return block1([], [b2, b4]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -1001,7 +1107,7 @@ exports[`basics zero or one child components 1`] = `
|
|||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2;
|
let b2;
|
||||||
if (ctx['state'].hasChild) {
|
if (ctx['state'].hasChild) {
|
||||||
b2 = component(\`Child\`, {}, key+\`__1\`,null, node, ctx);
|
b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
return multi([b2]);
|
return multi([b2]);
|
||||||
}
|
}
|
||||||
@@ -1081,7 +1187,7 @@ exports[`support svg components add proper namespace to svg 1`] = `
|
|||||||
let block1 = createBlock(\`<svg block-ns=\\"http://www.w3.org/2000/svg\\"><block-child-0/></svg>\`);
|
let block1 = createBlock(\`<svg block-ns=\\"http://www.w3.org/2000/svg\\"><block-child-0/></svg>\`);
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2 = component(\`GComp\`, {}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`GComp\`, {}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -1124,6 +1230,20 @@ exports[`t-out in components can render list of t-out 1`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`t-out in components can switch the contents of two t-out repeatedly 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { safeOutput } = helpers;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let b2 = safeOutput(ctx['state'].a);
|
||||||
|
let b3 = safeOutput(ctx['state'].b);
|
||||||
|
return multi([b2, b3]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`t-out in components update properly on state changes 1`] = `
|
exports[`t-out in components update properly on state changes 1`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -1,5 +1,57 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Cascading renders after microtaskTick 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { prepareList, withKey } = helpers;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
|
let b3 = text(\` _ \`);
|
||||||
|
ctx = Object.create(ctx);
|
||||||
|
const [k_block4, v_block4, l_block4, c_block4] = prepareList(ctx['state']);
|
||||||
|
for (let i1 = 0; i1 < l_block4; i1++) {
|
||||||
|
ctx[\`elem\`] = v_block4[i1];
|
||||||
|
let key1 = ctx['elem'].id;
|
||||||
|
c_block4[i1] = withKey(text(ctx['elem'].id), key1);
|
||||||
|
}
|
||||||
|
let b4 = list(c_block4);
|
||||||
|
return multi([b2, b3, b4]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Cascading renders after microtaskTick 2`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { prepareList, withKey } = helpers;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
ctx = Object.create(ctx);
|
||||||
|
const [k_block1, v_block1, l_block1, c_block1] = prepareList(ctx['state']);
|
||||||
|
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||||
|
ctx[\`elem\`] = v_block1[i1];
|
||||||
|
let key1 = ctx['elem'].id;
|
||||||
|
c_block1[i1] = withKey(component(\`Element\`, {id: ctx['elem'].id}, key + \`__1__\${key1}\`, node, ctx), key1);
|
||||||
|
}
|
||||||
|
return list(c_block1);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Cascading renders after microtaskTick 3`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return text(ctx['props'].id);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`async rendering destroying a widget before start is over 1`] = `
|
exports[`async rendering destroying a widget before start is over 1`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
@@ -20,7 +72,7 @@ exports[`calling render in destroy 1`] = `
|
|||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
const tKey_1 = ctx['key'];
|
const tKey_1 = ctx['key'];
|
||||||
return toggler(tKey_1, component(\`B\`, {fromA: ctx['state']}, key+\`__1\`,tKey_1, node, ctx));
|
return toggler(tKey_1, component(\`B\`, {fromA: ctx['state']}, tKey_1 + key + \`__1\`, node, ctx));
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -31,7 +83,7 @@ exports[`calling render in destroy 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\`, {fromA: ctx['props'].fromA}, key+\`__1\`,null, node, ctx);
|
return component(\`C\`, {fromA: ctx['props'].fromA}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -74,7 +126,7 @@ exports[`changing state before first render does not trigger a render (with pare
|
|||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2;
|
let b2;
|
||||||
if (ctx['state'].flag) {
|
if (ctx['state'].flag) {
|
||||||
b2 = component(\`TestW\`, {}, key+\`__1\`,null, node, ctx);
|
b2 = component(\`TestW\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
@@ -117,7 +169,7 @@ exports[`concurrent renderings scenario 1 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(\`ComponentB\`, {fromA: ctx['state'].fromA}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`ComponentB\`, {fromA: ctx['state'].fromA}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -131,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\`,null, node, ctx);
|
let b2 = component(\`ComponentC\`, {fromA: ctx['props'].fromA, fromB: ctx['state'].fromB}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -161,7 +213,7 @@ exports[`concurrent renderings scenario 2 1`] = `
|
|||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let txt1 = ctx['state'].fromA;
|
let txt1 = ctx['state'].fromA;
|
||||||
let b2 = component(\`ComponentB\`, {fromA: ctx['state'].fromA}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`ComponentB\`, {fromA: ctx['state'].fromA}, key + \`__1\`, node, ctx);
|
||||||
return block1([txt1], [b2]);
|
return block1([txt1], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -175,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\`,null, node, ctx);
|
let b2 = component(\`ComponentC\`, {fromA: ctx['props'].fromA, fromB: ctx['state'].fromB}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -204,7 +256,7 @@ exports[`concurrent renderings scenario 2bis 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(\`ComponentB\`, {fromA: ctx['state'].fromA}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`ComponentB\`, {fromA: ctx['state'].fromA}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -218,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\`,null, node, ctx);
|
let b2 = component(\`ComponentC\`, {fromA: ctx['props'].fromA, fromB: ctx['state'].fromB}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -247,7 +299,7 @@ exports[`concurrent renderings scenario 3 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(\`ComponentB\`, {fromA: ctx['state'].fromA}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`ComponentB\`, {fromA: ctx['state'].fromA}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -261,7 +313,7 @@ exports[`concurrent renderings scenario 3 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}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`ComponentC\`, {fromA: ctx['props'].fromA}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -275,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\`,null, node, ctx);
|
let b2 = component(\`ComponentD\`, {fromA: ctx['props'].fromA, fromC: ctx['state'].fromC}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -304,7 +356,7 @@ exports[`concurrent renderings scenario 4 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(\`ComponentB\`, {fromA: ctx['state'].fromA}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`ComponentB\`, {fromA: ctx['state'].fromA}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -318,7 +370,7 @@ exports[`concurrent renderings scenario 4 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}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`ComponentC\`, {fromA: ctx['props'].fromA}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -332,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\`,null, node, ctx);
|
let b2 = component(\`ComponentD\`, {fromA: ctx['props'].fromA, fromC: ctx['state'].fromC}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -361,7 +413,7 @@ exports[`concurrent renderings scenario 5 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(\`ComponentB\`, {fromA: ctx['state'].fromA}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`ComponentB\`, {fromA: ctx['state'].fromA}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -389,7 +441,7 @@ exports[`concurrent renderings scenario 6 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(\`ComponentB\`, {fromA: ctx['state'].fromA}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`ComponentB\`, {fromA: ctx['state'].fromA}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -417,7 +469,7 @@ exports[`concurrent renderings scenario 7 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(\`ComponentB\`, {fromA: ctx['state'].fromA}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`ComponentB\`, {fromA: ctx['state'].fromA}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -446,7 +498,7 @@ exports[`concurrent renderings scenario 8 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(\`ComponentB\`, {fromA: ctx['state'].fromA}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`ComponentB\`, {fromA: ctx['state'].fromA}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -476,8 +528,8 @@ exports[`concurrent renderings scenario 9 1`] = `
|
|||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let txt1 = ctx['state'].fromA;
|
let txt1 = ctx['state'].fromA;
|
||||||
let b2 = component(\`ComponentB\`, {fromA: ctx['state'].fromA}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`ComponentB\`, {fromA: ctx['state'].fromA}, key + \`__1\`, node, ctx);
|
||||||
let b3 = component(\`ComponentC\`, {fromA: ctx['state'].fromA}, key+\`__2\`,null, node, ctx);
|
let b3 = component(\`ComponentC\`, {fromA: ctx['state'].fromA}, key + \`__2\`, node, ctx);
|
||||||
return block1([txt1], [b2, b3]);
|
return block1([txt1], [b2, b3]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -505,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\`,null, node, ctx);
|
let b2 = component(\`ComponentD\`, {fromA: ctx['props'].fromA, fromC: ctx['state'].fromC}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -534,7 +586,7 @@ exports[`concurrent renderings scenario 10 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(\`ComponentB\`, {value: ctx['state'].value}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`ComponentB\`, {value: ctx['state'].value}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -550,7 +602,7 @@ exports[`concurrent renderings scenario 10 2`] = `
|
|||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2;
|
let b2;
|
||||||
if (ctx['state'].hasChild) {
|
if (ctx['state'].hasChild) {
|
||||||
b2 = component(\`ComponentC\`, {value: ctx['props'].value}, key+\`__1\`,null, node, ctx);
|
b2 = component(\`ComponentC\`, {value: ctx['props'].value}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
@@ -579,7 +631,7 @@ exports[`concurrent renderings scenario 11 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\`, {val: ctx['state'].valA}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`Child\`, {val: ctx['state'].valA}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -608,7 +660,7 @@ exports[`concurrent renderings scenario 12 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\`, {val: ctx['val']}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`Child\`, {val: ctx['val']}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -637,9 +689,9 @@ exports[`concurrent renderings scenario 13 1`] = `
|
|||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2,b3;
|
let b2,b3;
|
||||||
b2 = component(\`Child\`, {}, key+\`__1\`,null, node, ctx);
|
b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
if (ctx['state'].bool) {
|
if (ctx['state'].bool) {
|
||||||
b3 = component(\`Child\`, {}, key+\`__2\`,null, node, ctx);
|
b3 = component(\`Child\`, {}, key + \`__2\`, node, ctx);
|
||||||
}
|
}
|
||||||
return block1([], [b2, b3]);
|
return block1([], [b2, b3]);
|
||||||
}
|
}
|
||||||
@@ -668,7 +720,7 @@ exports[`concurrent renderings scenario 14 1`] = `
|
|||||||
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(\`B\`, {fromA: ctx['state'].fromA}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`B\`, {fromA: ctx['state'].fromA}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -682,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\`,null, node, ctx);
|
let b2 = component(\`C\`, {fromB: ctx['state'].fromB, fromA: ctx['props'].fromA}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -712,7 +764,7 @@ exports[`concurrent renderings scenario 15 1`] = `
|
|||||||
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(\`B\`, {fromA: ctx['state'].fromA}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`B\`, {fromA: ctx['state'].fromA}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -726,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\`,null, node, ctx);
|
let b2 = component(\`C\`, {fromB: ctx['state'].fromB, fromA: ctx['props'].fromA}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -754,7 +806,7 @@ exports[`concurrent renderings scenario 16 1`] = `
|
|||||||
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(\`B\`, {fromA: ctx['state'].fromA}, key+\`__1\`,null, node, ctx);
|
return component(\`B\`, {fromA: ctx['state'].fromA}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -765,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\`,null, node, ctx);
|
return component(\`C\`, {fromB: ctx['state'].fromB, fromA: ctx['props'].fromA}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -784,7 +836,7 @@ exports[`concurrent renderings scenario 16 3`] = `
|
|||||||
b6 = text(ctx['state'].fromC);
|
b6 = text(ctx['state'].fromC);
|
||||||
b7 = text(\`: \`);
|
b7 = text(\`: \`);
|
||||||
if (ctx['state'].fromC===13) {
|
if (ctx['state'].fromC===13) {
|
||||||
b8 = component(\`D\`, {}, key+\`__1\`,null, node, ctx);
|
b8 = component(\`D\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
return multi([b2, b3, b4, b5, b6, b7, b8]);
|
return multi([b2, b3, b4, b5, b6, b7, b8]);
|
||||||
}
|
}
|
||||||
@@ -810,10 +862,10 @@ exports[`creating two async components, scenario 1 1`] = `
|
|||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2,b3;
|
let b2,b3;
|
||||||
if (ctx['state'].flagA) {
|
if (ctx['state'].flagA) {
|
||||||
b2 = component(\`ChildA\`, {}, key+\`__1\`,null, node, ctx);
|
b2 = component(\`ChildA\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
if (ctx['state'].flagB) {
|
if (ctx['state'].flagB) {
|
||||||
b3 = component(\`ChildB\`, {}, key+\`__2\`,null, node, ctx);
|
b3 = component(\`ChildB\`, {}, key + \`__2\`, node, ctx);
|
||||||
}
|
}
|
||||||
return multi([b2, b3]);
|
return multi([b2, b3]);
|
||||||
}
|
}
|
||||||
@@ -856,9 +908,9 @@ exports[`creating two async components, scenario 2 1`] = `
|
|||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2,b3;
|
let b2,b3;
|
||||||
b2 = component(\`ChildA\`, {val: ctx['state'].valA}, key+\`__1\`,null, node, ctx);
|
b2 = component(\`ChildA\`, {val: ctx['state'].valA}, key + \`__1\`, node, ctx);
|
||||||
if (ctx['state'].flagB) {
|
if (ctx['state'].flagB) {
|
||||||
b3 = component(\`ChildB\`, {val: ctx['state'].valB}, key+\`__2\`,null, node, ctx);
|
b3 = component(\`ChildB\`, {val: ctx['state'].valB}, key + \`__2\`, node, ctx);
|
||||||
}
|
}
|
||||||
return block1([], [b2, b3]);
|
return block1([], [b2, b3]);
|
||||||
}
|
}
|
||||||
@@ -902,9 +954,9 @@ exports[`creating two async components, scenario 3 (patching in the same frame)
|
|||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2,b3;
|
let b2,b3;
|
||||||
b2 = component(\`ChildA\`, {val: ctx['state'].valA}, key+\`__1\`,null, node, ctx);
|
b2 = component(\`ChildA\`, {val: ctx['state'].valA}, key + \`__1\`, node, ctx);
|
||||||
if (ctx['state'].flagB) {
|
if (ctx['state'].flagB) {
|
||||||
b3 = component(\`ChildB\`, {val: ctx['state'].valB}, key+\`__2\`,null, node, ctx);
|
b3 = component(\`ChildB\`, {val: ctx['state'].valB}, key + \`__2\`, node, ctx);
|
||||||
}
|
}
|
||||||
return block1([], [b2, b3]);
|
return block1([], [b2, b3]);
|
||||||
}
|
}
|
||||||
@@ -945,7 +997,7 @@ exports[`delay willUpdateProps 1`] = `
|
|||||||
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\`, {value: ctx['state'].value}, key+\`__1\`,null, node, ctx);
|
return component(\`Child\`, {value: ctx['state'].value}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -970,7 +1022,7 @@ exports[`delay willUpdateProps with rendering grandchild 1`] = `
|
|||||||
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(\`Parent\`, {state: ctx['state']}, key+\`__1\`,null, node, ctx);
|
return component(\`Parent\`, {state: ctx['state']}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -981,8 +1033,8 @@ exports[`delay willUpdateProps with rendering grandchild 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 = \\"\\") {
|
||||||
let b2 = component(\`DelayedChild\`, {value: ctx['props'].state.value}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`DelayedChild\`, {value: ctx['props'].state.value}, key + \`__1\`, node, ctx);
|
||||||
let b3 = component(\`ReactiveChild\`, {}, key+\`__2\`,null, node, ctx);
|
let b3 = component(\`ReactiveChild\`, {}, key + \`__2\`, node, ctx);
|
||||||
return multi([b2, b3]);
|
return multi([b2, b3]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -1015,6 +1067,33 @@ exports[`delay willUpdateProps with rendering grandchild 4`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`destroying/recreating a subcomponent, other scenario 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let b2,b3;
|
||||||
|
b2 = text(\`parent\`);
|
||||||
|
if (ctx['state'].hasChild) {
|
||||||
|
b3 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
|
}
|
||||||
|
return multi([b2, b3]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`destroying/recreating a subcomponent, other scenario 2`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return text(\`child\`);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`destroying/recreating a subwidget with different props (if start is not over) 1`] = `
|
exports[`destroying/recreating a subwidget with different props (if start is not over) 1`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
@@ -1025,7 +1104,7 @@ exports[`destroying/recreating a subwidget with different props (if start is not
|
|||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2;
|
let b2;
|
||||||
if (ctx['state'].val>1) {
|
if (ctx['state'].val>1) {
|
||||||
b2 = component(\`Child\`, {val: ctx['state'].val}, key+\`__1\`,null, node, ctx);
|
b2 = component(\`Child\`, {val: ctx['state'].val}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
@@ -1052,7 +1131,7 @@ exports[`parent and child rendered at exact same time 1`] = `
|
|||||||
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\`, {value: ctx['state'].value}, key+\`__1\`,null, node, ctx);
|
return component(\`Child\`, {value: ctx['state'].value}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -1078,7 +1157,7 @@ exports[`properly behave when destroyed/unmounted while rendering 1`] = `
|
|||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2;
|
let b2;
|
||||||
if (ctx['state'].flag) {
|
if (ctx['state'].flag) {
|
||||||
b2 = component(\`Child\`, {val: ctx['state'].val}, key+\`__1\`,null, node, ctx);
|
b2 = component(\`Child\`, {val: ctx['state'].val}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
@@ -1093,7 +1172,7 @@ exports[`properly behave when destroyed/unmounted while rendering 2`] = `
|
|||||||
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(\`SubChild\`, {}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubChild\`, {val: ctx['props'].val}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -1123,7 +1202,7 @@ exports[`rendering component again in next microtick 1`] = `
|
|||||||
let b2;
|
let b2;
|
||||||
let hdlr1 = [ctx['onClick'], ctx];
|
let hdlr1 = [ctx['onClick'], ctx];
|
||||||
if (ctx['env'].config.flag) {
|
if (ctx['env'].config.flag) {
|
||||||
b2 = component(\`Child\`, {}, key+\`__1\`,null, node, ctx);
|
b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
return block1([hdlr1], [b2]);
|
return block1([hdlr1], [b2]);
|
||||||
}
|
}
|
||||||
@@ -1143,6 +1222,72 @@ exports[`rendering component again in next microtick 2`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`t-foreach with dynamic async component 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { prepareList, withKey } = helpers;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
ctx = Object.create(ctx);
|
||||||
|
const [k_block1, v_block1, l_block1, c_block1] = prepareList(ctx['list']);
|
||||||
|
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||||
|
ctx[\`arr\`] = v_block1[i1];
|
||||||
|
ctx[\`arr_index\`] = i1;
|
||||||
|
let key1 = ctx['arr_index'];
|
||||||
|
let b3;
|
||||||
|
if (ctx['arr']) {
|
||||||
|
let Comp1 = ctx['myComp'];
|
||||||
|
b3 = toggler(Comp1, component(Comp1, {key: ctx['arr'][0]}, key + \`__1__\${key1}\`, node, ctx));
|
||||||
|
}
|
||||||
|
c_block1[i1] = withKey(multi([b3]), key1);
|
||||||
|
}
|
||||||
|
return list(c_block1);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`t-foreach with dynamic async component 2`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<div><block-text-0/></div>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let txt1 = ctx['props'].key;
|
||||||
|
return block1([txt1]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`t-key on dom node having a component 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
const tKey_1 = ctx['key'];
|
||||||
|
let Comp1 = ctx['myComp'];
|
||||||
|
let b2 = toggler(tKey_1, toggler(Comp1, component(Comp1, {key: ctx['key']}, tKey_1 + key + \`__1\`, node, ctx)));
|
||||||
|
return toggler(tKey_1, block1([], [b2]));
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`t-key on dom node having a component 2`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return text(ctx['props'].key);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`t-key on dynamic async component (toggler is never patched) 1`] = `
|
exports[`t-key on dynamic async component (toggler is never patched) 1`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
@@ -1151,7 +1296,7 @@ exports[`t-key on dynamic async component (toggler is never patched) 1`] = `
|
|||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
const tKey_1 = ctx['key'];
|
const tKey_1 = ctx['key'];
|
||||||
let Comp1 = ctx['myComp'];
|
let Comp1 = ctx['myComp'];
|
||||||
return toggler(tKey_1, toggler(Comp1, component(Comp1, {key: ctx['key']}, key+\`__1\`,tKey_1, node, ctx)));
|
return toggler(tKey_1, toggler(Comp1, component(Comp1, {key: ctx['key']}, tKey_1 + key + \`__1\`, node, ctx)));
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -1181,7 +1326,7 @@ exports[`two renderings initiated between willPatch and patched 1`] = `
|
|||||||
let b2;
|
let b2;
|
||||||
if (ctx['state'].flag) {
|
if (ctx['state'].flag) {
|
||||||
const tKey_1 = 'panel_'+ctx['state'].panel;
|
const tKey_1 = 'panel_'+ctx['state'].panel;
|
||||||
b2 = toggler(tKey_1, component(\`Panel\`, {val: ctx['state'].panel}, key+\`__1\`,tKey_1, node, ctx));
|
b2 = toggler(tKey_1, component(\`Panel\`, {val: ctx['state'].panel}, tKey_1 + key + \`__1\`, node, ctx));
|
||||||
}
|
}
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
@@ -1209,7 +1354,7 @@ exports[`two sequential renderings before an animation frame 1`] = `
|
|||||||
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\`, {value: ctx['state'].value}, key+\`__1\`,null, node, ctx);
|
return component(\`Child\`, {value: ctx['state'].value}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -1233,7 +1378,7 @@ exports[`update a sub-component twice in the same frame 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(\`ChildA\`, {val: ctx['state'].valA}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`ChildA\`, {val: ctx['state'].valA}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -1261,7 +1406,7 @@ exports[`update a sub-component twice in the same frame, 2 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(\`ChildA\`, {val: ctx['state'].valA}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`ChildA\`, {val: ctx['state'].valA}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
|
|||||||
@@ -1,12 +1,25 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`basics display a nice error if it cannot find component (in dev mode) 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(\`SomeMispelledComponent\`, props1, ctx);
|
||||||
|
return component(\`SomeMispelledComponent\`, props1, key + \`__1\`, node, ctx);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`basics display a nice error if it cannot find component 1`] = `
|
exports[`basics display a nice error if it cannot find component 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;
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
return component(\`SomeMispelledComponent\`, {}, key+\`__1\`,null, node, ctx);
|
return component(\`SomeMispelledComponent\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -19,7 +32,7 @@ exports[`basics no component catching error lead to full app destruction 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(\`ErrorComponent\`, {flag: ctx['state'].flag}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`ErrorComponent\`, {flag: ctx['state'].flag}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -51,7 +64,7 @@ exports[`basics simple catchError 1`] = `
|
|||||||
if (ctx['error']) {
|
if (ctx['error']) {
|
||||||
b2 = text(\`Error\`);
|
b2 = text(\`Error\`);
|
||||||
} else {
|
} else {
|
||||||
b3 = component(\`Boom\`, {}, key+\`__1\`,null, node, ctx);
|
b3 = component(\`Boom\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
return block1([], [b2, b3]);
|
return block1([], [b2, b3]);
|
||||||
}
|
}
|
||||||
@@ -72,19 +85,31 @@ exports[`basics simple catchError 2`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`can catch errors can catch an error in a component render function 1`] = `
|
exports[`can catch errors calling a hook outside setup should crash 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;
|
||||||
|
|
||||||
|
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`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { markRaw } = helpers;
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||||
|
|
||||||
function slot1(ctx, node, key = \\"\\") {
|
function slot1(ctx, node, key = \\"\\") {
|
||||||
return component(\`ErrorComponent\`, {flag: ctx['state'].flag}, key+\`__1\`,null, node, ctx);
|
return component(\`ErrorComponent\`, {flag: ctx['state'].flag}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b3 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key+\`__2\`,null, node, ctx);
|
let b3 = component(\`ErrorBoundary\`, {slots: markRaw({'default': {__render: slot1, __ctx: ctx}})}, key + \`__2\`, node, ctx);
|
||||||
return block1([], [b3]);
|
return block1([], [b3]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -128,15 +153,16 @@ exports[`can catch errors can catch an error in the constructor call of a compon
|
|||||||
"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 { markRaw } = helpers;
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||||
|
|
||||||
function slot1(ctx, node, key = \\"\\") {
|
function slot1(ctx, node, key = \\"\\") {
|
||||||
return component(\`ErrorComponent\`, {}, key+\`__1\`,null, node, ctx);
|
return component(\`ErrorComponent\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b3 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key+\`__2\`,null, node, ctx);
|
let b3 = component(\`ErrorBoundary\`, {slots: markRaw({'default': {__render: slot1, __ctx: ctx}})}, key + \`__2\`, node, ctx);
|
||||||
return block1([], [b3]);
|
return block1([], [b3]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -166,17 +192,18 @@ exports[`can catch errors can catch an error in the constructor call of a compon
|
|||||||
"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 { markRaw } = helpers;
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||||
|
|
||||||
function slot1(ctx, node, key = \\"\\") {
|
function slot1(ctx, node, key = \\"\\") {
|
||||||
let b3 = component(\`ClassicCompoent\`, {}, key+\`__1\`,null, node, ctx);
|
let b3 = component(\`ClassicCompoent\`, {}, key + \`__1\`, node, ctx);
|
||||||
let b4 = component(\`ErrorComponent\`, {}, key+\`__2\`,null, node, ctx);
|
let b4 = component(\`ErrorComponent\`, {}, key + \`__2\`, node, ctx);
|
||||||
return multi([b3, b4]);
|
return multi([b3, b4]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b5 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key+\`__3\`,null, node, ctx);
|
let b5 = component(\`ErrorBoundary\`, {slots: markRaw({'default': {__render: slot1, __ctx: ctx}})}, key + \`__3\`, node, ctx);
|
||||||
return block1([], [b5]);
|
return block1([], [b5]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -245,15 +272,16 @@ exports[`can catch errors can catch an error in the initial call of a component
|
|||||||
"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 { markRaw } = helpers;
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||||
|
|
||||||
function slot1(ctx, node, key = \\"\\") {
|
function slot1(ctx, node, key = \\"\\") {
|
||||||
return component(\`ErrorComponent\`, {}, key+\`__1\`,null, node, ctx);
|
return component(\`ErrorComponent\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b3 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key+\`__2\`,null, node, ctx);
|
let b3 = component(\`ErrorBoundary\`, {slots: markRaw({'default': {__render: slot1, __ctx: ctx}})}, key + \`__2\`, node, ctx);
|
||||||
return block1([], [b3]);
|
return block1([], [b3]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -297,17 +325,18 @@ exports[`can catch errors can catch an error in the initial call of a component
|
|||||||
"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 { markRaw } = helpers;
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||||
|
|
||||||
function slot1(ctx, node, key = \\"\\") {
|
function slot1(ctx, node, key = \\"\\") {
|
||||||
return component(\`ErrorComponent\`, {}, key+\`__1\`,null, node, ctx);
|
return component(\`ErrorComponent\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b3;
|
let b3;
|
||||||
if (ctx['state'].flag) {
|
if (ctx['state'].flag) {
|
||||||
b3 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key+\`__2\`,null, node, ctx);
|
b3 = component(\`ErrorBoundary\`, {slots: markRaw({'default': {__render: slot1, __ctx: ctx}})}, key + \`__2\`, node, ctx);
|
||||||
}
|
}
|
||||||
return block1([], [b3]);
|
return block1([], [b3]);
|
||||||
}
|
}
|
||||||
@@ -354,7 +383,7 @@ exports[`can catch errors can catch an error in the mounted call (in child of ch
|
|||||||
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(\`B\`, {}, key+\`__1\`,null, node, ctx);
|
return component(\`B\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -367,7 +396,7 @@ exports[`can catch errors can catch an error in the mounted call (in child of ch
|
|||||||
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(\`C\`, {}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`C\`, {}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -385,7 +414,7 @@ exports[`can catch errors can catch an error in the mounted call (in child of ch
|
|||||||
if (ctx['state'].error) {
|
if (ctx['state'].error) {
|
||||||
b2 = text(\`Error handled\`);
|
b2 = text(\`Error handled\`);
|
||||||
} else {
|
} else {
|
||||||
b3 = component(\`Boom\`, {}, key+\`__1\`,null, node, ctx);
|
b3 = component(\`Boom\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
return block1([], [b2, b3]);
|
return block1([], [b2, b3]);
|
||||||
}
|
}
|
||||||
@@ -417,7 +446,7 @@ exports[`can catch errors can catch an error in the mounted call (in root compon
|
|||||||
if (ctx['state'].error) {
|
if (ctx['state'].error) {
|
||||||
b2 = text(\`Error handled\`);
|
b2 = text(\`Error handled\`);
|
||||||
} else {
|
} else {
|
||||||
b3 = component(\`ErrorComponent\`, {}, key+\`__1\`,null, node, ctx);
|
b3 = component(\`ErrorComponent\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
return block1([], [b2, b3]);
|
return block1([], [b2, b3]);
|
||||||
}
|
}
|
||||||
@@ -441,15 +470,16 @@ exports[`can catch errors can catch an error in the mounted call 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 { markRaw } = helpers;
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||||
|
|
||||||
function slot1(ctx, node, key = \\"\\") {
|
function slot1(ctx, node, key = \\"\\") {
|
||||||
return component(\`ErrorComponent\`, {}, key+\`__1\`,null, node, ctx);
|
return component(\`ErrorComponent\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b3 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key+\`__2\`,null, node, ctx);
|
let b3 = component(\`ErrorBoundary\`, {slots: markRaw({'default': {__render: slot1, __ctx: ctx}})}, key + \`__2\`, node, ctx);
|
||||||
return block1([], [b3]);
|
return block1([], [b3]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -492,16 +522,17 @@ exports[`can catch errors can catch an error in the willPatch call 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 { markRaw } = helpers;
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><span><block-text-0/></span><block-child-0/></div>\`);
|
let block1 = createBlock(\`<div><span><block-text-0/></span><block-child-0/></div>\`);
|
||||||
|
|
||||||
function slot1(ctx, node, key = \\"\\") {
|
function slot1(ctx, node, key = \\"\\") {
|
||||||
return component(\`ErrorComponent\`, {message: ctx['state'].message}, key+\`__1\`,null, node, ctx);
|
return component(\`ErrorComponent\`, {message: ctx['state'].message}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let txt1 = ctx['state'].message;
|
let txt1 = ctx['state'].message;
|
||||||
let b3 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key+\`__2\`,null, node, ctx);
|
let b3 = component(\`ErrorBoundary\`, {slots: markRaw({'default': {__render: slot1, __ctx: ctx}})}, key + \`__2\`, node, ctx);
|
||||||
return block1([txt1], [b3]);
|
return block1([txt1], [b3]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -545,15 +576,16 @@ exports[`can catch errors can catch an error in the willStart call 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 { markRaw } = helpers;
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||||
|
|
||||||
function slot1(ctx, node, key = \\"\\") {
|
function slot1(ctx, node, key = \\"\\") {
|
||||||
return component(\`ErrorComponent\`, {}, key+\`__1\`,null, node, ctx);
|
return component(\`ErrorComponent\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b3 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key+\`__2\`,null, node, ctx);
|
let b3 = component(\`ErrorBoundary\`, {slots: markRaw({'default': {__render: slot1, __ctx: ctx}})}, key + \`__2\`, node, ctx);
|
||||||
return block1([], [b3]);
|
return block1([], [b3]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -596,17 +628,18 @@ exports[`can catch errors can catch an error origination from a child's willStar
|
|||||||
"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 { markRaw } = helpers;
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||||
|
|
||||||
function slot1(ctx, node, key = \\"\\") {
|
function slot1(ctx, node, key = \\"\\") {
|
||||||
let b3 = component(\`ClassicCompoent\`, {}, key+\`__1\`,null, node, ctx);
|
let b3 = component(\`ClassicCompoent\`, {}, key + \`__1\`, node, ctx);
|
||||||
let b4 = component(\`ErrorComponent\`, {}, key+\`__2\`,null, node, ctx);
|
let b4 = component(\`ErrorComponent\`, {}, key + \`__2\`, node, ctx);
|
||||||
return multi([b3, b4]);
|
return multi([b3, b4]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b5 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key+\`__3\`,null, node, ctx);
|
let b5 = component(\`ErrorBoundary\`, {slots: markRaw({'default': {__render: slot1, __ctx: ctx}})}, key + \`__3\`, node, ctx);
|
||||||
return block1([], [b5]);
|
return block1([], [b5]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -670,7 +703,7 @@ exports[`can catch errors catchError in catchError 1`] = `
|
|||||||
if (ctx['error']) {
|
if (ctx['error']) {
|
||||||
b2 = text(\`Error\`);
|
b2 = text(\`Error\`);
|
||||||
} else {
|
} else {
|
||||||
b3 = component(\`Child\`, {}, key+\`__1\`,null, node, ctx);
|
b3 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
return block1([], [b2, b3]);
|
return block1([], [b2, b3]);
|
||||||
}
|
}
|
||||||
@@ -685,7 +718,7 @@ exports[`can catch errors catchError in catchError 2`] = `
|
|||||||
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(\`Boom\`, {}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`Boom\`, {}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -709,11 +742,11 @@ exports[`can catch errors catching error, rethrow, render parent -- a main comp
|
|||||||
"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 { prepareList, capture, withKey } = helpers;
|
let { prepareList, capture, markRaw, withKey } = helpers;
|
||||||
|
|
||||||
function slot1(ctx, node, key = \\"\\") {
|
function slot1(ctx, node, key = \\"\\") {
|
||||||
let Comp1 = ctx['cp'].Comp;
|
let Comp1 = ctx['cp'].Comp;
|
||||||
return toggler(Comp1, component(Comp1, {}, key+\`__1\`,null, node, ctx));
|
return toggler(Comp1, component(Comp1, {}, key + \`__1\`, node, ctx));
|
||||||
}
|
}
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
@@ -724,7 +757,7 @@ exports[`can catch errors catching error, rethrow, render parent -- a main comp
|
|||||||
let key1 = ctx['cp'].id;
|
let key1 = ctx['cp'].id;
|
||||||
const v1 = ctx['cp'];
|
const v1 = ctx['cp'];
|
||||||
const ctx1 = capture(ctx);
|
const ctx1 = capture(ctx);
|
||||||
c_block1[i1] = withKey(component(\`ErrorHandler\`, {onError: ()=>this.cleanUp(v1.id),slots: {'default': {__render: slot1, __ctx: ctx1}}}, key+\`__2__\${key1}\`,null, node, ctx), key1);
|
c_block1[i1] = withKey(component(\`ErrorHandler\`, {onError: ()=>this.cleanUp(v1.id),slots: markRaw({'default': {__render: slot1, __ctx: ctx1}})}, key + \`__2__\${key1}\`, node, ctx), key1);
|
||||||
}
|
}
|
||||||
return list(c_block1);
|
return list(c_block1);
|
||||||
}
|
}
|
||||||
@@ -749,7 +782,7 @@ exports[`can catch errors catching error, rethrow, render parent -- a main comp
|
|||||||
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(\`ErrorComponent\`, {}, key+\`__1\`,null, node, ctx);
|
return component(\`ErrorComponent\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -780,20 +813,86 @@ exports[`can catch errors catching error, rethrow, render parent -- a main comp
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`can catch errors error in mounted on a component with a sibling (properly mounted) 1`] = `
|
exports[`can catch errors catching in child makes parent render 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { prepareList, capture, markRaw, withKey } = helpers;
|
||||||
|
|
||||||
|
function slot1(ctx, node, key = \\"\\") {
|
||||||
|
let Comp1 = ctx['elem'][1];
|
||||||
|
return toggler(Comp1, component(Comp1, {id: ctx['elem'][0]}, key + \`__1\`, node, ctx));
|
||||||
|
}
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
ctx = Object.create(ctx);
|
||||||
|
const [k_block1, v_block1, l_block1, c_block1] = prepareList(Object.entries(this.elements));
|
||||||
|
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||||
|
ctx[\`elem\`] = v_block1[i1];
|
||||||
|
let key1 = ctx['elem'][0];
|
||||||
|
const v1 = ctx['elem'];
|
||||||
|
const ctx1 = capture(ctx);
|
||||||
|
c_block1[i1] = withKey(component(\`Catch\`, {onError: (_error)=>this.onError(v1[0],_error),slots: markRaw({'default': {__render: slot1, __ctx: ctx1}})}, key + \`__2__\${key1}\`, node, ctx), key1);
|
||||||
|
}
|
||||||
|
return list(c_block1);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`can catch errors catching in child makes parent render 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, {});
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`can catch errors catching in child makes parent render 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;
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<div/>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return block1();
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`can catch errors catching in child makes parent render 4`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<div><block-text-0/></div>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let txt1 = 'Child '+ctx['props'].id;
|
||||||
|
return block1([txt1]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`can catch errors error in mounted on a component with a sibling (properly mounted) 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { markRaw } = helpers;
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><block-child-0/><block-child-1/></div>\`);
|
let block1 = createBlock(\`<div><block-child-0/><block-child-1/></div>\`);
|
||||||
|
|
||||||
function slot1(ctx, node, key = \\"\\") {
|
function slot1(ctx, node, key = \\"\\") {
|
||||||
return component(\`ErrorComponent\`, {}, key+\`__2\`,null, node, ctx);
|
return component(\`ErrorComponent\`, {}, key + \`__2\`, node, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2 = component(\`OK\`, {}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`OK\`, {}, key + \`__1\`, node, ctx);
|
||||||
let b4 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key+\`__3\`,null, node, ctx);
|
let b4 = component(\`ErrorBoundary\`, {slots: markRaw({'default': {__render: slot1, __ctx: ctx}})}, key + \`__3\`, node, ctx);
|
||||||
return block1([], [b2, b4]);
|
return block1([], [b2, b4]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -849,7 +948,7 @@ exports[`can catch errors onError in class inheritance is called if rethrown 1`]
|
|||||||
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(\`Concrete\`, {}, key+\`__1\`,null, node, ctx);
|
return component(\`Concrete\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -879,7 +978,7 @@ exports[`can catch errors onError in class inheritance is not called if no rethr
|
|||||||
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(\`Concrete\`, {}, key+\`__1\`,null, node, ctx);
|
return component(\`Concrete\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -911,7 +1010,7 @@ exports[`errors and promises a rendering error in a sub component will reject th
|
|||||||
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\`, {}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -953,7 +1052,7 @@ exports[`errors and promises a rendering error will reject the render promise (w
|
|||||||
let block1 = createBlock(\`<div><block-child-0/><block-text-0/></div>\`);
|
let block1 = createBlock(\`<div><block-child-0/><block-text-0/></div>\`);
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2 = component(\`Child\`, {}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
let txt1 = ctx['x'].y;
|
let txt1 = ctx['x'].y;
|
||||||
return block1([txt1], [b2]);
|
return block1([txt1], [b2]);
|
||||||
}
|
}
|
||||||
@@ -1003,6 +1102,19 @@ 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
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -1,5 +1,33 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`event handling Invalid handler throws an error 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<button block-handler-0=\\"click\\">click</button>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let hdlr1 = [ctx['dosomething'], ctx];
|
||||||
|
return block1([hdlr1]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`event handling handler is not called if component is destroyed 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<span block-handler-0=\\"click\\"/>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let hdlr1 = [ctx['click'], ctx];
|
||||||
|
return block1([hdlr1]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`event handling handler receive the event as argument 1`] = `
|
exports[`event handling handler receive the event as argument 1`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
@@ -9,7 +37,7 @@ exports[`event handling handler receive the event as argument 1`] = `
|
|||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let hdlr1 = [ctx['inc'], ctx];
|
let hdlr1 = [ctx['inc'], ctx];
|
||||||
let b2 = component(\`Child\`, {}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
let txt1 = ctx['state'].value;
|
let txt1 = ctx['state'].value;
|
||||||
return block1([hdlr1, txt1], [b2]);
|
return block1([hdlr1, txt1], [b2]);
|
||||||
}
|
}
|
||||||
@@ -29,6 +57,37 @@ exports[`event handling handler receive the event as argument 2`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`event handling input blur event is not called if component is destroyed 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<div><block-child-0/><textarea/></div>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let b2;
|
||||||
|
if (ctx['state'].cond) {
|
||||||
|
b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
|
}
|
||||||
|
return block1([], [b2]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`event handling input blur event is not called if component is destroyed 2`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<input block-handler-0=\\"blur\\"/>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let hdlr1 = [ctx['blur'], ctx];
|
||||||
|
return block1([hdlr1]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`event handling objects from scope are properly captured by t-on 1`] = `
|
exports[`event handling objects from scope are properly captured by t-on 1`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
@@ -46,7 +105,7 @@ exports[`event handling objects from scope are properly captured by t-on 1`] = `
|
|||||||
let key1 = ctx['item'];
|
let key1 = ctx['item'];
|
||||||
const v1 = ctx['onClick'];
|
const v1 = ctx['onClick'];
|
||||||
const v2 = ctx['item'];
|
const v2 = ctx['item'];
|
||||||
let hdlr1 = [ev=>v1(v2.val,ev), ctx];
|
let hdlr1 = [_ev=>v1(v2.val,_ev), ctx];
|
||||||
c_block2[i1] = withKey(block3([hdlr1]), key1);
|
c_block2[i1] = withKey(block3([hdlr1]), key1);
|
||||||
}
|
}
|
||||||
let b2 = list(c_block2);
|
let b2 = list(c_block2);
|
||||||
@@ -87,7 +146,7 @@ exports[`event handling t-on with handler bound to dynamic argument on a t-forea
|
|||||||
let key1 = ctx['item'];
|
let key1 = ctx['item'];
|
||||||
const v1 = ctx['onClick'];
|
const v1 = ctx['onClick'];
|
||||||
const v2 = ctx['item'];
|
const v2 = ctx['item'];
|
||||||
let hdlr1 = [ev=>v1(v2,ev), ctx];
|
let hdlr1 = [_ev=>v1(v2,_ev), ctx];
|
||||||
c_block2[i1] = withKey(block3([hdlr1]), key1);
|
c_block2[i1] = withKey(block3([hdlr1]), key1);
|
||||||
}
|
}
|
||||||
let b2 = list(c_block2);
|
let b2 = list(c_block2);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ exports[`basics basic use 1`] = `
|
|||||||
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\`, {p: 1}, key+\`__1\`,null, node, ctx);
|
return component(\`Child\`, {p: 1}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -33,10 +33,10 @@ exports[`basics can select a sub widget 1`] = `
|
|||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2,b3;
|
let b2,b3;
|
||||||
if (ctx['env'].options.flag) {
|
if (ctx['env'].options.flag) {
|
||||||
b2 = component(\`Child\`, {}, key+\`__1\`,null, node, ctx);
|
b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
if (!ctx['env'].options.flag) {
|
if (!ctx['env'].options.flag) {
|
||||||
b3 = component(\`OtherChild\`, {}, key+\`__2\`,null, node, ctx);
|
b3 = component(\`OtherChild\`, {}, key + \`__2\`, node, ctx);
|
||||||
}
|
}
|
||||||
return multi([b2, b3]);
|
return multi([b2, b3]);
|
||||||
}
|
}
|
||||||
@@ -77,10 +77,10 @@ exports[`basics can select a sub widget, part 2 1`] = `
|
|||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2,b3;
|
let b2,b3;
|
||||||
if (ctx['state'].flag) {
|
if (ctx['state'].flag) {
|
||||||
b2 = component(\`Child\`, {}, key+\`__1\`,null, node, ctx);
|
b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
if (!ctx['state'].flag) {
|
if (!ctx['state'].flag) {
|
||||||
b3 = component(\`OtherChild\`, {}, key+\`__2\`,null, node, ctx);
|
b3 = component(\`OtherChild\`, {}, key + \`__2\`, node, ctx);
|
||||||
}
|
}
|
||||||
return multi([b2, b3]);
|
return multi([b2, b3]);
|
||||||
}
|
}
|
||||||
@@ -119,7 +119,7 @@ exports[`basics sub widget is interactive 1`] = `
|
|||||||
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\`, {p: 1}, key+\`__1\`,null, node, ctx);
|
return component(\`Child\`, {p: 1}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -147,7 +147,7 @@ exports[`basics top level sub widget with a parent 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(\`ComponentB\`, {}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`ComponentB\`, {}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -159,7 +159,7 @@ exports[`basics top level sub widget with a parent 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(\`ComponentC\`, {}, key+\`__1\`,null, node, ctx);
|
return component(\`ComponentC\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ exports[`hooks can use onWillStart, onWillUpdateProps 1`] = `
|
|||||||
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(\`MyComponent\`, {value: ctx['state'].value}, key+\`__1\`,null, node, ctx);
|
return component(\`MyComponent\`, {value: ctx['state'].value}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -103,20 +103,78 @@ exports[`hooks mounted callbacks should be called in reverse order from willUnmo
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`hooks parent and child env 1`] = `
|
exports[`hooks parent and child env (with useChildSubEnv then useSubEnv) 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;
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2 = text(ctx['env'].val);
|
let b2 = text(ctx['env'].val);
|
||||||
let b3 = component(\`Child\`, {}, key+\`__1\`,null, node, ctx);
|
let b3 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
return multi([b2, b3]);
|
return multi([b2, b3]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`hooks parent and child env 2`] = `
|
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`] = `
|
||||||
|
"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) 2`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<div><block-text-0/></div>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let txt1 = ctx['env'].val;
|
||||||
|
return block1([txt1]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`hooks parent and child env (with 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 useSubEnv) 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;
|
||||||
@@ -144,7 +202,7 @@ exports[`hooks two different call to willPatch/patched should work 1`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`hooks use sub env does not pollute user env 1`] = `
|
exports[`hooks useChildSubEnv does not pollute user env 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;
|
||||||
@@ -158,18 +216,18 @@ exports[`hooks use sub env does not pollute user env 1`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`hooks use sub env supports arbitrary descriptor 1`] = `
|
exports[`hooks useChildSubEnv supports arbitrary descriptor 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;
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
return component(\`Child\`, {}, key+\`__1\`,null, node, ctx);
|
return component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`hooks use sub env supports arbitrary descriptor 2`] = `
|
exports[`hooks useChildSubEnv supports arbitrary descriptor 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;
|
||||||
@@ -251,7 +309,7 @@ exports[`hooks useExternalListener 1`] = `
|
|||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2;
|
let b2;
|
||||||
if (ctx['state'].flag) {
|
if (ctx['state'].flag) {
|
||||||
b2 = component(\`MyComponent\`, {}, key+\`__1\`,null, node, ctx);
|
b2 = component(\`MyComponent\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
return multi([b2]);
|
return multi([b2]);
|
||||||
}
|
}
|
||||||
@@ -287,3 +345,43 @@ exports[`hooks useRef hook: basic use 1`] = `
|
|||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`hooks useSubEnv modifies user env 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<div><block-text-0/></div>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let txt1 = ctx['env'].val;
|
||||||
|
return block1([txt1]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`hooks useSubEnv supports arbitrary descriptor 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`hooks useSubEnv supports arbitrary descriptor 2`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<div><block-text-0/> <block-text-1/></div>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let txt1 = ctx['env'].someVal;
|
||||||
|
let txt2 = ctx['env'].someVal2;
|
||||||
|
return block1([txt1, txt2]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ exports[`lifecycle hooks component semantics 1`] = `
|
|||||||
let block1 = createBlock(\`<div>A<block-child-0/><block-child-1/></div>\`);
|
let block1 = createBlock(\`<div>A<block-child-0/><block-child-1/></div>\`);
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2 = component(\`B\`, {}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`B\`, {}, key + \`__1\`, node, ctx);
|
||||||
let b3 = component(\`C\`, {}, key+\`__2\`,null, node, ctx);
|
let b3 = component(\`C\`, {}, key + \`__2\`, node, ctx);
|
||||||
return block1([], [b2, b3]);
|
return block1([], [b2, b3]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -50,11 +50,11 @@ exports[`lifecycle hooks component semantics 3`] = `
|
|||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2,b3,b4;
|
let b2,b3,b4;
|
||||||
b2 = component(\`D\`, {}, key+\`__1\`,null, node, ctx);
|
b2 = component(\`D\`, {}, key + \`__1\`, node, ctx);
|
||||||
if (ctx['state'].flag) {
|
if (ctx['state'].flag) {
|
||||||
b3 = component(\`E\`, {}, key+\`__2\`,null, node, ctx);
|
b3 = component(\`E\`, {}, key + \`__2\`, node, ctx);
|
||||||
} else {
|
} else {
|
||||||
b4 = component(\`F\`, {}, key+\`__3\`,null, node, ctx);
|
b4 = component(\`F\`, {}, key + \`__3\`, node, ctx);
|
||||||
}
|
}
|
||||||
return block1([], [b2, b3, b4]);
|
return block1([], [b2, b3, b4]);
|
||||||
}
|
}
|
||||||
@@ -110,7 +110,7 @@ exports[`lifecycle hooks components are unmounted and destroyed if no longer in
|
|||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2;
|
let b2;
|
||||||
if (ctx['state'].flag) {
|
if (ctx['state'].flag) {
|
||||||
let b3 = component(\`Child\`, {n: ctx['state'].n}, key+\`__1\`,null, node, ctx);
|
let b3 = component(\`Child\`, {n: ctx['state'].n}, key + \`__1\`, node, ctx);
|
||||||
b2 = block2([], [b3]);
|
b2 = block2([], [b3]);
|
||||||
}
|
}
|
||||||
return multi([b2]);
|
return multi([b2]);
|
||||||
@@ -140,7 +140,7 @@ exports[`lifecycle hooks components are unmounted destroyed if no longer in DOM
|
|||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2;
|
let b2;
|
||||||
if (ctx['state'].ok) {
|
if (ctx['state'].ok) {
|
||||||
b2 = component(\`Child\`, {}, key+\`__1\`,null, node, ctx);
|
b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
return multi([b2]);
|
return multi([b2]);
|
||||||
}
|
}
|
||||||
@@ -160,6 +160,34 @@ exports[`lifecycle hooks components are unmounted destroyed if no longer in DOM
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`lifecycle hooks destroy new children before being mountged 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let b2,b3,b4;
|
||||||
|
b2 = text(\`before\`);
|
||||||
|
if (ctx['state'].flag) {
|
||||||
|
b3 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
|
}
|
||||||
|
b4 = text(\`after\`);
|
||||||
|
return multi([b2, b3, b4]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`lifecycle hooks destroy new children before being mountged 2`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return text(\`child\`);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`lifecycle hooks hooks are called in proper order in widget creation/destruction 1`] = `
|
exports[`lifecycle hooks hooks are called in proper order in widget creation/destruction 1`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
@@ -168,7 +196,7 @@ exports[`lifecycle hooks hooks are called in proper order in widget creation/des
|
|||||||
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\`, {}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -187,6 +215,28 @@ exports[`lifecycle hooks hooks are called in proper order in widget creation/des
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`lifecycle hooks lifecycle callbacks are bound to component 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return component(\`Test\`, {rev: ctx['rev']}, key + \`__1\`, node, ctx);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`lifecycle hooks lifecycle callbacks are bound to component 2`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return text(ctx['props'].rev);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`lifecycle hooks lifecycle semantics 1`] = `
|
exports[`lifecycle hooks lifecycle semantics 1`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
@@ -195,7 +245,7 @@ exports[`lifecycle hooks lifecycle semantics 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\`, {a: ctx['state'].a}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`Child\`, {a: ctx['state'].a}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -222,7 +272,7 @@ exports[`lifecycle hooks lifecycle semantics, part 2 1`] = `
|
|||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2;
|
let b2;
|
||||||
if (ctx['state'].hasChild) {
|
if (ctx['state'].hasChild) {
|
||||||
b2 = component(\`Child\`, {}, key+\`__1\`,null, node, ctx);
|
b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
return multi([b2]);
|
return multi([b2]);
|
||||||
}
|
}
|
||||||
@@ -235,7 +285,7 @@ exports[`lifecycle hooks lifecycle semantics, part 2 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(\`GrandChild\`, {}, key+\`__1\`,null, node, ctx);
|
return component(\`GrandChild\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -261,7 +311,7 @@ exports[`lifecycle hooks lifecycle semantics, part 3 1`] = `
|
|||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2;
|
let b2;
|
||||||
if (ctx['state'].hasChild) {
|
if (ctx['state'].hasChild) {
|
||||||
b2 = component(\`Child\`, {}, key+\`__1\`,null, node, ctx);
|
b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
return multi([b2]);
|
return multi([b2]);
|
||||||
}
|
}
|
||||||
@@ -276,7 +326,7 @@ exports[`lifecycle hooks lifecycle semantics, part 4 1`] = `
|
|||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2;
|
let b2;
|
||||||
if (ctx['state'].hasChild) {
|
if (ctx['state'].hasChild) {
|
||||||
b2 = component(\`Child\`, {}, key+\`__1\`,null, node, ctx);
|
b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
return multi([b2]);
|
return multi([b2]);
|
||||||
}
|
}
|
||||||
@@ -289,7 +339,7 @@ exports[`lifecycle hooks lifecycle semantics, part 4 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(\`GrandChild\`, {}, key+\`__1\`,null, node, ctx);
|
return component(\`GrandChild\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -315,7 +365,7 @@ exports[`lifecycle hooks lifecycle semantics, part 5 1`] = `
|
|||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2;
|
let b2;
|
||||||
if (ctx['state'].hasChild) {
|
if (ctx['state'].hasChild) {
|
||||||
b2 = component(\`Child\`, {}, key+\`__1\`,null, node, ctx);
|
b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
return multi([b2]);
|
return multi([b2]);
|
||||||
}
|
}
|
||||||
@@ -341,7 +391,7 @@ exports[`lifecycle hooks lifecycle semantics, part 6 1`] = `
|
|||||||
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\`, {value: ctx['state'].value}, key+\`__1\`,null, node, ctx);
|
return component(\`Child\`, {value: ctx['state'].value}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -380,7 +430,7 @@ exports[`lifecycle hooks mounted hook is called on every mount, not just the fir
|
|||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2;
|
let b2;
|
||||||
if (ctx['state'].hasChild) {
|
if (ctx['state'].hasChild) {
|
||||||
b2 = component(\`Child\`, {}, key+\`__1\`,null, node, ctx);
|
b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
return multi([b2]);
|
return multi([b2]);
|
||||||
}
|
}
|
||||||
@@ -408,7 +458,7 @@ exports[`lifecycle hooks mounted hook is called on subcomponents, in proper orde
|
|||||||
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\`, {}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -437,7 +487,7 @@ exports[`lifecycle hooks mounted hook is called on subsubcomponents, in proper o
|
|||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2;
|
let b2;
|
||||||
if (ctx['state'].flag) {
|
if (ctx['state'].flag) {
|
||||||
b2 = component(\`Child\`, {}, key+\`__1\`,null, node, ctx);
|
b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
@@ -452,7 +502,7 @@ exports[`lifecycle hooks mounted hook is called on subsubcomponents, in proper o
|
|||||||
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(\`ChildChild\`, {}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`ChildChild\`, {}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -477,7 +527,7 @@ exports[`lifecycle hooks onWillRender 1`] = `
|
|||||||
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\`, {}, key+\`__1\`,null, node, ctx);
|
return component(\`Child\`, {someValue: ctx['state'].value}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -505,7 +555,7 @@ exports[`lifecycle hooks patched hook is called after updateProps 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\`, {a: ctx['state'].a}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`Child\`, {a: ctx['state'].a}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -588,7 +638,7 @@ exports[`lifecycle hooks sub widget (inside sub node): hooks are correctly calle
|
|||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2;
|
let b2;
|
||||||
if (ctx['state'].flag) {
|
if (ctx['state'].flag) {
|
||||||
b2 = component(\`Child\`, {}, key+\`__1\`,null, node, ctx);
|
b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
return multi([b2]);
|
return multi([b2]);
|
||||||
}
|
}
|
||||||
@@ -616,7 +666,7 @@ exports[`lifecycle hooks willPatch, patched hook are called on subsubcomponents,
|
|||||||
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\`, {n: ctx['state'].n}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`Child\`, {n: ctx['state'].n}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -630,7 +680,7 @@ exports[`lifecycle hooks willPatch, patched hook are called on subsubcomponents,
|
|||||||
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(\`ChildChild\`, {n: ctx['props'].n}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`ChildChild\`, {n: ctx['props'].n}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -656,7 +706,7 @@ exports[`lifecycle hooks willStart hook is called on sub component 1`] = `
|
|||||||
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\`, {}, key+\`__1\`,null, node, ctx);
|
return component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -711,7 +761,7 @@ exports[`lifecycle hooks willStart, mounted on subwidget rendered after main is
|
|||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2,b3;
|
let b2,b3;
|
||||||
if (ctx['state'].ok) {
|
if (ctx['state'].ok) {
|
||||||
b2 = component(\`Child\`, {}, key+\`__1\`,null, node, ctx);
|
b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
} else {
|
} else {
|
||||||
b3 = block3();
|
b3 = block3();
|
||||||
}
|
}
|
||||||
@@ -739,7 +789,7 @@ exports[`lifecycle hooks willUpdateProps hook is called 1`] = `
|
|||||||
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\`, {n: ctx['state'].n}, key+\`__1\`,null, node, ctx);
|
return component(\`Child\`, {n: ctx['state'].n}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ exports[`basics accept ES6-like syntax for props (with getters) 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\`, {greetings: ctx['greetings']}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`Child\`, {greetings: ctx['greetings']}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -42,7 +42,7 @@ exports[`basics arrow functions as prop correctly capture their scope 1`] = `
|
|||||||
let key1 = ctx['item'].val;
|
let key1 = ctx['item'].val;
|
||||||
const v1 = ctx['onClick'];
|
const v1 = ctx['onClick'];
|
||||||
const v2 = ctx['item'];
|
const v2 = ctx['item'];
|
||||||
c_block1[i1] = withKey(component(\`Child\`, {onClick: ev=>v1(v2.val,ev)}, key+\`__1__\${key1}\`,null, node, ctx), key1);
|
c_block1[i1] = withKey(component(\`Child\`, {onClick: _ev=>v1(v2.val,_ev)}, key + \`__1__\${key1}\`, node, ctx), key1);
|
||||||
}
|
}
|
||||||
return list(c_block1);
|
return list(c_block1);
|
||||||
}
|
}
|
||||||
@@ -71,7 +71,7 @@ exports[`basics explicit object prop 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\`, {value: ctx['state'].val}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`Child\`, {value: ctx['state'].val}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -97,7 +97,7 @@ exports[`basics prop names can contain - 1`] = `
|
|||||||
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\`, {'prop-name': 7}, key+\`__1\`,null, node, ctx);
|
return component(\`Child\`, {'prop-name': 7}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -122,7 +122,7 @@ exports[`basics support prop names that aren't valid bare object property names
|
|||||||
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\`, {'some-dashed-prop': 5,'a.b': 'keyword prop'}, key+\`__1\`,null, node, ctx);
|
return component(\`Child\`, {'some-dashed-prop': 5}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -158,7 +158,7 @@ exports[`basics t-set with a body expression can be passed in props, and then t-
|
|||||||
ctx = Object.create(ctx);
|
ctx = Object.create(ctx);
|
||||||
ctx[isBoundary] = 1
|
ctx[isBoundary] = 1
|
||||||
ctx[\`abc\`] = new LazyValue(value1, ctx, node);
|
ctx[\`abc\`] = new LazyValue(value1, ctx, node);
|
||||||
let b3 = component(\`Child\`, {val: ctx['abc']}, key+\`__1\`,null, node, ctx);
|
let b3 = component(\`Child\`, {val: ctx['abc']}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b3]);
|
return block1([], [b3]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -192,7 +192,7 @@ exports[`basics t-set with a body expression can be used as textual prop 1`] = `
|
|||||||
ctx = Object.create(ctx);
|
ctx = Object.create(ctx);
|
||||||
ctx[isBoundary] = 1
|
ctx[isBoundary] = 1
|
||||||
setContextValue(ctx, \\"abc\\", \`42\`);
|
setContextValue(ctx, \\"abc\\", \`42\`);
|
||||||
let b2 = component(\`Child\`, {val: ctx['abc']}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`Child\`, {val: ctx['abc']}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -224,7 +224,7 @@ exports[`basics t-set works 1`] = `
|
|||||||
ctx = Object.create(ctx);
|
ctx = Object.create(ctx);
|
||||||
ctx[isBoundary] = 1
|
ctx[isBoundary] = 1
|
||||||
setContextValue(ctx, \\"val\\", 42);
|
setContextValue(ctx, \\"val\\", 42);
|
||||||
let b2 = component(\`Child\`, {val: ctx['val']}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`Child\`, {val: ctx['val']}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -250,7 +250,7 @@ exports[`basics template string in prop 1`] = `
|
|||||||
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\`, {propName: \`1\${ctx['someVal']}3\`}, key+\`__1\`,null, node, ctx);
|
return component(\`Child\`, {propName: \`1\${ctx['someVal']}3\`}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -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\`,null, node, ctx);
|
return component(\`Child\`, {val: ctx['state'].val, fn: bind(ctx, ctx['someFunction'])}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -296,7 +296,7 @@ exports[`can bind function prop with bind suffix 1`] = `
|
|||||||
let { bind } = helpers;
|
let { bind } = helpers;
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
return component(\`Child\`, {doSomething: bind(ctx, ctx['doSomething'])}, key+\`__1\`,null, node, ctx);
|
return component(\`Child\`, {doSomething: bind(ctx, ctx['doSomething'])}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -1,6 +1,19 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`default props can set default required boolean values 1`] = `
|
exports[`default props a default prop cannot be defined on a mandatory prop 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;
|
||||||
@@ -8,15 +21,15 @@ exports[`default props can set default required boolean values 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 = \\"\\") {
|
||||||
const props1 = {}
|
const props1 = {};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`default props can set default required boolean values 2`] = `
|
exports[`default props can set default 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;
|
||||||
@@ -44,9 +57,9 @@ exports[`default props can set default values 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 = \\"\\") {
|
||||||
const props1 = {}
|
const props1 = {};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -74,9 +87,9 @@ exports[`default props default values are also set whenever component is updated
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['state'].p}
|
const props1 = {p: ctx['state'].p};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -102,9 +115,9 @@ 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\`,null, node, ctx);
|
return component(\`Child\`, props1, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -128,9 +141,9 @@ 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\`,null, node, ctx);
|
return component(\`Child\`, props1, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -156,9 +169,9 @@ exports[`props validation can validate a prop with multiple types 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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -185,9 +198,9 @@ exports[`props validation can validate a prop with multiple types 3`] = `
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -214,9 +227,9 @@ exports[`props validation can validate a prop with multiple types 5`] = `
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -230,9 +243,9 @@ exports[`props validation can validate an array with given primitive type 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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -259,9 +272,9 @@ exports[`props validation can validate an array with given primitive type 3`] =
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -288,9 +301,9 @@ exports[`props validation can validate an array with given primitive type 5`] =
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -304,9 +317,9 @@ exports[`props validation can validate an array with given primitive type 6`] =
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -320,9 +333,9 @@ exports[`props validation can validate an array with multiple sub element types
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -349,9 +362,9 @@ exports[`props validation can validate an array with multiple sub element types
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -378,9 +391,9 @@ exports[`props validation can validate an array with multiple sub element types
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -407,9 +420,9 @@ exports[`props validation can validate an array with multiple sub element types
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -423,9 +436,9 @@ exports[`props validation can validate an object with simple shape 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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -452,9 +465,9 @@ exports[`props validation can validate an object with simple shape 3`] = `
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -468,9 +481,9 @@ exports[`props validation can validate an object with simple shape 4`] = `
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -484,9 +497,9 @@ exports[`props validation can validate an object with simple shape 5`] = `
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -500,9 +513,9 @@ exports[`props validation can validate an optional 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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -529,9 +542,9 @@ exports[`props validation can validate an optional props 3`] = `
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -558,9 +571,9 @@ exports[`props validation can validate an optional props 5`] = `
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -574,9 +587,9 @@ exports[`props validation can validate recursively complicated prop def 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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -603,9 +616,9 @@ exports[`props validation can validate recursively complicated prop def 3`] = `
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -632,9 +645,9 @@ exports[`props validation can validate recursively complicated prop def 5`] = `
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -648,9 +661,9 @@ exports[`props validation default values are applied before validating props at
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['state'].p}
|
const props1 = {p: ctx['state'].p};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -678,9 +691,9 @@ exports[`props validation missing required boolean prop causes an error 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 = \\"\\") {
|
||||||
const props1 = {}
|
const props1 = {};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -694,9 +707,9 @@ exports[`props validation mix of optional and mandatory 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 = \\"\\") {
|
||||||
const props1 = {}
|
const props1 = {};
|
||||||
helpers.validateProps(\`Child\`, props1, ctx)
|
helpers.validateProps(\`Child\`, props1, ctx);
|
||||||
let b2 = component(\`Child\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`Child\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -710,9 +723,9 @@ exports[`props validation props are validated in dev mode (code snapshot) 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 = \\"\\") {
|
||||||
const props1 = {message: 1}
|
const props1 = {message: 1};
|
||||||
helpers.validateProps(\`Child\`, props1, ctx)
|
helpers.validateProps(\`Child\`, props1, ctx);
|
||||||
let b2 = component(\`Child\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`Child\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -740,9 +753,9 @@ exports[`props validation props are validated whenever component is updated 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 = \\"\\") {
|
||||||
const props1 = {p: ctx['state'].p}
|
const props1 = {p: ctx['state'].p};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -770,9 +783,9 @@ exports[`props validation props: list of strings 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 = \\"\\") {
|
||||||
const props1 = {}
|
const props1 = {};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -786,9 +799,9 @@ exports[`props validation validate simple types 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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -802,9 +815,9 @@ exports[`props validation validate simple types 2`] = `
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -831,9 +844,9 @@ exports[`props validation validate simple types 4`] = `
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -847,9 +860,9 @@ exports[`props validation validate simple types 5`] = `
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -863,9 +876,9 @@ exports[`props validation validate simple types 6`] = `
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -892,9 +905,9 @@ exports[`props validation validate simple types 8`] = `
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -908,9 +921,9 @@ exports[`props validation validate simple types 9`] = `
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -924,9 +937,9 @@ exports[`props validation validate simple types 10`] = `
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -953,9 +966,9 @@ exports[`props validation validate simple types 12`] = `
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -969,9 +982,9 @@ exports[`props validation validate simple types 13`] = `
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -985,9 +998,9 @@ exports[`props validation validate simple types 14`] = `
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -1014,9 +1027,9 @@ exports[`props validation validate simple types 16`] = `
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -1030,9 +1043,9 @@ exports[`props validation validate simple types 17`] = `
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -1046,9 +1059,9 @@ exports[`props validation validate simple types 18`] = `
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -1075,9 +1088,9 @@ exports[`props validation validate simple types 20`] = `
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -1091,9 +1104,9 @@ exports[`props validation validate simple types 21`] = `
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -1107,9 +1120,9 @@ exports[`props validation validate simple types 22`] = `
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -1136,9 +1149,9 @@ exports[`props validation validate simple types 24`] = `
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -1152,9 +1165,9 @@ exports[`props validation validate simple types, alternate form 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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -1168,9 +1181,9 @@ exports[`props validation validate simple types, alternate form 2`] = `
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -1197,9 +1210,9 @@ exports[`props validation validate simple types, alternate form 4`] = `
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -1213,9 +1226,9 @@ exports[`props validation validate simple types, alternate form 5`] = `
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -1229,9 +1242,9 @@ exports[`props validation validate simple types, alternate form 6`] = `
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -1258,9 +1271,9 @@ exports[`props validation validate simple types, alternate form 8`] = `
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -1274,9 +1287,9 @@ exports[`props validation validate simple types, alternate form 9`] = `
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -1290,9 +1303,9 @@ exports[`props validation validate simple types, alternate form 10`] = `
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -1319,9 +1332,9 @@ exports[`props validation validate simple types, alternate form 12`] = `
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -1335,9 +1348,9 @@ exports[`props validation validate simple types, alternate form 13`] = `
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -1351,9 +1364,9 @@ exports[`props validation validate simple types, alternate form 14`] = `
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -1380,9 +1393,9 @@ exports[`props validation validate simple types, alternate form 16`] = `
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -1396,9 +1409,9 @@ exports[`props validation validate simple types, alternate form 17`] = `
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -1412,9 +1425,9 @@ exports[`props validation validate simple types, alternate form 18`] = `
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -1441,9 +1454,9 @@ exports[`props validation validate simple types, alternate form 20`] = `
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -1457,9 +1470,9 @@ exports[`props validation validate simple types, alternate form 21`] = `
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -1473,9 +1486,9 @@ exports[`props validation validate simple types, alternate form 22`] = `
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -1502,9 +1515,9 @@ exports[`props validation validate simple types, alternate form 24`] = `
|
|||||||
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 = \\"\\") {
|
||||||
const props1 = {p: ctx['p']}
|
const props1 = {p: ctx['p']};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -1518,9 +1531,9 @@ exports[`props validation validation is only done in dev mode 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 = \\"\\") {
|
||||||
const props1 = {}
|
const props1 = {};
|
||||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
helpers.validateProps(\`SubComp\`, props1, ctx);
|
||||||
let b2 = component(\`SubComp\`, props1, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -1534,7 +1547,7 @@ exports[`props validation validation is only done in dev mode 2`] = `
|
|||||||
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(\`SubComp\`, {}, key+\`__1\`,null, node, ctx);
|
let b2 = component(\`SubComp\`, {}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ exports[`reactivity in lifecycle state changes in willUnmount do not trigger rer
|
|||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2;
|
let b2;
|
||||||
if (ctx['state'].flag) {
|
if (ctx['state'].flag) {
|
||||||
b2 = component(\`Child\`, {val: ctx['state'].val}, key+\`__1\`,null, node, ctx);
|
b2 = component(\`Child\`, {val: ctx['state'].val}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,11 +38,31 @@ 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
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
let { capture } = helpers;
|
let { capture, markRaw } = helpers;
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><span class=\\"counter\\"><block-text-0/></span><block-child-0/></div>\`);
|
let block1 = createBlock(\`<div><span class=\\"counter\\"><block-text-0/></span><block-child-0/></div>\`);
|
||||||
let block2 = createBlock(\`<button block-handler-0=\\"click\\" block-ref=\\"1\\">do something</button>\`);
|
let block2 = createBlock(\`<button block-handler-0=\\"click\\" block-ref=\\"1\\">do something</button>\`);
|
||||||
@@ -57,7 +77,7 @@ exports[`refs refs are properly bound in slots 1`] = `
|
|||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let txt1 = ctx['state'].val;
|
let txt1 = ctx['state'].val;
|
||||||
const ctx1 = capture(ctx);
|
const ctx1 = capture(ctx);
|
||||||
let b3 = component(\`Dialog\`, {slots: {'footer': {__render: slot1, __ctx: ctx1}}}, key+\`__1\`,null, node, ctx);
|
let b3 = component(\`Dialog\`, {slots: markRaw({'footer': {__render: slot1, __ctx: ctx1}})}, key + \`__1\`, node, ctx);
|
||||||
return block1([txt1], [b3]);
|
return block1([txt1], [b3]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
|
|||||||
@@ -0,0 +1,212 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`force render in case of existing render 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return component(\`B\`, {val: ctx['state'].val}, key + \`__1\`, node, ctx);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`force render in case of existing render 2`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let b2 = component(\`C\`, {}, key + \`__1\`, node, ctx);
|
||||||
|
let b3 = text(ctx['props'].val);
|
||||||
|
return multi([b2, b3]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`force render in case of existing render 3`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return text(\`C\`);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`rendering semantics can force a render to update sub tree 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['state'].value);
|
||||||
|
let b3 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
|
return multi([b2, b3]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`rendering semantics can force a render to update sub tree 2`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return text(\`child\`);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`rendering semantics can render a parent without rendering child 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['state'].value);
|
||||||
|
let b3 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
|
return multi([b2, b3]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`rendering semantics can render a parent without rendering child 2`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return text(\`child\`);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`rendering semantics props are reactive (nested prop) 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return component(\`Child\`, {a: ctx['state']}, key + \`__1\`, node, ctx);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`rendering semantics props are reactive (nested prop) 2`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return text(ctx['props'].a.b.c);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`rendering semantics props are reactive 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return component(\`Child\`, {a: ctx['state']}, key + \`__1\`, node, ctx);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`rendering semantics props are reactive 2`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return text(ctx['props'].a.b);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`rendering semantics render with deep=true followed by render with deep=false work as expected 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let b2 = text(\`parent\`);
|
||||||
|
let b3 = text(ctx['state'].value);
|
||||||
|
let b4 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
|
return multi([b2, b3, b4]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`rendering semantics render with deep=true followed by render with deep=false work as expected 2`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let b2 = text(\`child\`);
|
||||||
|
let b3 = text(ctx['env'].getValue());
|
||||||
|
return multi([b2, b3]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`rendering semantics rendering is atomic (for one subtree) 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['state'].obj.val);
|
||||||
|
let b3 = component(\`B\`, {obj: ctx['state'].obj}, key + \`__1\`, node, ctx);
|
||||||
|
return multi([b2, b3]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`rendering semantics rendering is atomic (for one subtree) 2`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return component(\`C\`, {obj: ctx['props'].obj}, key + \`__1\`, node, ctx);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`rendering semantics rendering is atomic (for one subtree) 3`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return text(ctx['props'].obj.val);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`rendering semantics works as expected for dynamic number of props 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return component(\`Child\`, Object.assign({}, ctx['state']), key + \`__1\`, node, ctx);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`rendering semantics works as expected for dynamic number of props 2`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return text(Object.keys(ctx['props']).length);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user