mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[IMP] qweb: add preliminary support for transitions
Still very naive... Part of #66
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
# 🦉 Animations 🦉
|
||||
|
||||
|
||||
Animation is a complex topic. There are many different use cases, and many
|
||||
solutions and technologies.
|
||||
|
||||
## 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
|
||||
<button class="flash" t-on-click="doSomething">Click</button>
|
||||
```
|
||||
|
||||
with the following CSS:
|
||||
|
||||
```css
|
||||
.flash {
|
||||
transition: background 0.5s;
|
||||
}
|
||||
|
||||
.flash:active {
|
||||
background-color: #41454a;
|
||||
transition: background 0s;
|
||||
}
|
||||
```
|
||||
|
||||
will produce a nice flash effect whenever the user click (or activate with the
|
||||
keyboard) the button.
|
||||
|
||||
## CSS Transitions (single element)
|
||||
|
||||
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 (see [QWeb documentation](doc/qweb.md#t-transition-directive)).
|
||||
+49
@@ -16,6 +16,7 @@
|
||||
- [Component: `t-widget`, `t-props`](#component-t-widget-t-props)
|
||||
- [`t-ref` directive](#t-ref-directive)
|
||||
- [`t-key` directive](#t-key-directive)
|
||||
- [`t-transition` directive](#t-transition)
|
||||
- [Debugging (`t-debug` and `t-log`)](#debugging-t-debug-and-t-log)
|
||||
- [White spaces](#white-spaces)
|
||||
- [Root nodes](#root-nodes)
|
||||
@@ -253,6 +254,54 @@ There are three main use cases:
|
||||
- *animations*: give a different identity to a component. Ex: thread id with
|
||||
animations on add/remove message.
|
||||
|
||||
### `t-transition` directive
|
||||
|
||||
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
|
||||
will happen:
|
||||
|
||||
At node creation:
|
||||
|
||||
- the css classes `name-enter` and `name-enter-active` will be added before the
|
||||
node is added to 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),
|
||||
- the css class `name-enter-active` will be removed whenever a css transition
|
||||
ends.
|
||||
|
||||
At node destruction:
|
||||
- the css classes `name-leave` and `name-leave-active` will be added before the
|
||||
node is removed to the DOM,
|
||||
- the css class `name-leave` will be removed on the next animation frame (so it
|
||||
can be used to trigger css transition effects),
|
||||
- the css class `name-leave-active` will be removed whenever a css transition
|
||||
ends. Only then will the element be removed from the DOM.
|
||||
|
||||
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 .5s;
|
||||
}
|
||||
.fade-enter, .fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
```
|
||||
|
||||
Note: you will find more information on animations in the [animation](doc/animations.md)
|
||||
documentation.
|
||||
|
||||
### Debugging (`t-debug` and `t-log`)
|
||||
|
||||
The javascript QWeb implementation provides two useful debugging directives:
|
||||
|
||||
@@ -11,4 +11,5 @@
|
||||
|
||||
## Miscellaneous
|
||||
- [Quick Start](quick_start.md)
|
||||
- [Animations](animations.md)
|
||||
- [Comparison with React/Vue](comparison.md)
|
||||
|
||||
+54
-4
@@ -61,6 +61,37 @@ const UTILS = {
|
||||
}
|
||||
}
|
||||
return classes.join(" ");
|
||||
},
|
||||
nextFrame(cb: () => void) {
|
||||
requestAnimationFrame(() => requestAnimationFrame(cb));
|
||||
},
|
||||
transitionCreate(elm: HTMLElement, name: string) {
|
||||
elm.classList.add(name + "-enter");
|
||||
elm.classList.add(name + "-enter-active");
|
||||
},
|
||||
transitionInsert(elm: HTMLElement, name: string) {
|
||||
const finalize = () => {
|
||||
elm.classList.remove(name + "-enter-active");
|
||||
elm.classList.remove(name + "-enter-to");
|
||||
};
|
||||
elm.addEventListener("transitionend", finalize);
|
||||
this.nextFrame(() => {
|
||||
elm.classList.remove(name + "-enter");
|
||||
elm.classList.add(name + "-enter-to");
|
||||
});
|
||||
},
|
||||
transitionRemove(elm: HTMLElement, name: string, rm: () => void) {
|
||||
elm.classList.add(name + "-leave");
|
||||
elm.classList.add(name + "-leave-active");
|
||||
elm.addEventListener("transitionend", () => {
|
||||
elm.classList.remove(name + "-leave-active");
|
||||
elm.classList.remove(name + "-enter-to");
|
||||
rm();
|
||||
});
|
||||
this.nextFrame(() => {
|
||||
elm.classList.remove(name + "-leave");
|
||||
elm.classList.add(name + "-leave-to");
|
||||
});
|
||||
}
|
||||
};
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -240,6 +271,7 @@ export class QWeb {
|
||||
callDirective,
|
||||
onDirective,
|
||||
refDirective,
|
||||
transitionDirective,
|
||||
debugDirective,
|
||||
logDirective,
|
||||
widgetDirective
|
||||
@@ -983,16 +1015,34 @@ const onDirective: Directive = {
|
||||
const refDirective: Directive = {
|
||||
name: "ref",
|
||||
priority: 95,
|
||||
atNodeCreation({ ctx, node }) {
|
||||
let ref = node.getAttribute("t-ref")!;
|
||||
ctx.addLine(`p${ctx.parentNode}.hook = {
|
||||
atNodeCreation({ ctx, nodeID, value }) {
|
||||
ctx.addLine(`p${nodeID}.hook = {
|
||||
create: (_, n) => context.refs[${ctx.formatExpression(
|
||||
ref
|
||||
value
|
||||
)}] = n.elm,
|
||||
};`);
|
||||
}
|
||||
};
|
||||
|
||||
const transitionDirective: Directive = {
|
||||
name: "transition",
|
||||
priority: 96,
|
||||
atNodeCreation({ ctx, value }) {
|
||||
let name = value;
|
||||
ctx.addLine(`p${ctx.parentNode}.hook = {
|
||||
create: (_, n) => {
|
||||
this.utils.transitionCreate(n.elm, '${name}');
|
||||
},
|
||||
insert: vn => {
|
||||
this.utils.transitionInsert(vn.elm, '${name}');
|
||||
},
|
||||
remove: (vn, rm) => {
|
||||
this.utils.transitionRemove(vn.elm, '${name}', rm);
|
||||
}
|
||||
};`);
|
||||
}
|
||||
};
|
||||
|
||||
const debugDirective: Directive = {
|
||||
name: "debug",
|
||||
priority: 99,
|
||||
|
||||
@@ -1,5 +1,32 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`animations t-transition, on a simple node 1`] = `
|
||||
"function anonymous(context,extra
|
||||
) {
|
||||
var h = this.utils.h;
|
||||
var c1 = [], p1 = {key:1};
|
||||
var vn1 = h('div', p1, c1);
|
||||
var c2 = [], p2 = {key:2};
|
||||
var vn2 = h('span', p2, c2);
|
||||
c1.push(vn2);
|
||||
p2.hook = {
|
||||
create: (_, n) => {
|
||||
this.utils.transitionCreate(n.elm, 'chimay');
|
||||
},
|
||||
insert: vn => {
|
||||
this.utils.transitionInsert(vn.elm, 'chimay');
|
||||
},
|
||||
remove: (vn, rm) => {
|
||||
this.utils.transitionRemove(vn.elm, 'chimay', rm);
|
||||
}
|
||||
};
|
||||
c2.push({text: \`blue\`});
|
||||
return vn1;
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`animations t-transition, on a simple node 2`] = `"<span class=\\"chimay-enter chimay-enter-active\\">blue</span>"`;
|
||||
|
||||
exports[`attributes dynamic attribute falsy variable 1`] = `
|
||||
"function anonymous(context,extra
|
||||
) {
|
||||
|
||||
+18
-5
@@ -1,5 +1,5 @@
|
||||
import { patch } from "../src/vdom";
|
||||
import { EvalContext, QWeb } from "../src/qweb";
|
||||
import { patch } from "../src/vdom";
|
||||
import { normalize } from "./helpers";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -1098,12 +1098,12 @@ describe("t-key", () => {
|
||||
describe("debugging", () => {
|
||||
test("t-debug", () => {
|
||||
const consoleLog = console.log;
|
||||
console.log = jest.fn()
|
||||
console.log = jest.fn();
|
||||
qweb.addTemplate(
|
||||
"test",
|
||||
`<div t-debug="1"><t t-if="true"><span t-debug="1">hey</span></t></div>`
|
||||
);
|
||||
qweb.render('test');
|
||||
qweb.render("test");
|
||||
expect(qweb.templates.test.fn.toString()).toMatchSnapshot();
|
||||
|
||||
expect(console.log).toHaveBeenCalledTimes(1);
|
||||
@@ -1112,7 +1112,7 @@ describe("debugging", () => {
|
||||
|
||||
test("t-log", () => {
|
||||
const consoleLog = console.log;
|
||||
console.log = jest.fn()
|
||||
console.log = jest.fn();
|
||||
|
||||
qweb.addTemplate(
|
||||
"test",
|
||||
@@ -1121,10 +1121,23 @@ describe("debugging", () => {
|
||||
<t t-log="foo + 3"/>
|
||||
</div>`
|
||||
);
|
||||
qweb.render('test');
|
||||
qweb.render("test");
|
||||
expect(qweb.templates.test.fn.toString()).toMatchSnapshot();
|
||||
|
||||
expect(console.log).toHaveBeenCalledWith(45);
|
||||
console.log = consoleLog;
|
||||
});
|
||||
});
|
||||
|
||||
describe("animations", () => {
|
||||
test("t-transition, on a simple node", async () => {
|
||||
// this test does not test much, because it is not easy to test timing
|
||||
// transitions... we should do a little more effort for these tests.
|
||||
qweb.addTemplate(
|
||||
"test",
|
||||
`<div><span t-transition="chimay">blue</span></div>`
|
||||
);
|
||||
let dom: HTMLElement = <HTMLElement>renderToDOM(qweb, "test");
|
||||
expect(dom.innerHTML).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user