mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9fe2da704e | |||
| 21b1661d39 | |||
| ec05b1f5e3 | |||
| 8083678f03 | |||
| 61c2ec5d83 | |||
| e579a993fd | |||
| ea3c6f7bf0 | |||
| 9cfafc30b5 | |||
| ad42c583c6 |
@@ -14,7 +14,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [10.x, 12.x, 14.x]
|
||||
node-version: [12.x, 14.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
@@ -120,7 +120,7 @@ npm install @odoo/owl
|
||||
|
||||
If you want to use a simple `<script>` tag, the last release can be downloaded here:
|
||||
|
||||
- [owl-1.4.0](https://github.com/odoo/owl/releases/tag/v1.4.0)
|
||||
- [owl-1.4.3](https://github.com/odoo/owl/releases/tag/v1.4.3)
|
||||
|
||||
## License
|
||||
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@odoo/owl",
|
||||
"version": "1.4.0",
|
||||
"version": "1.4.3",
|
||||
"description": "Odoo Web Library (OWL)",
|
||||
"main": "dist/owl.cjs.js",
|
||||
"browser": "dist/owl.iife.js",
|
||||
@@ -10,7 +10,7 @@
|
||||
"dist"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10.15.3"
|
||||
"node": ">=12.18.3"
|
||||
},
|
||||
"scripts": {
|
||||
"build:bundle": "rollup -c",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# 🦉 OWL Roadmap 🦉
|
||||
|
||||
- Current version: 1.4.0
|
||||
- Current version: 1.4.3
|
||||
- Status: stable
|
||||
|
||||
This roadmap is only an attempt at predicting Owl's future. Everything may
|
||||
|
||||
@@ -257,16 +257,33 @@ export function compileExprToArray(expr: string, scope: { [key: string]: QWebVar
|
||||
const tokens = tokenize(expr);
|
||||
|
||||
let i = 0;
|
||||
let stack = []; // to track last opening [ or {
|
||||
|
||||
while (i < tokens.length) {
|
||||
let token = tokens[i];
|
||||
let prevToken = tokens[i - 1];
|
||||
let nextToken = tokens[i + 1];
|
||||
let groupType = stack[stack.length - 1];
|
||||
|
||||
switch (token.type) {
|
||||
case "LEFT_BRACE":
|
||||
case "LEFT_BRACKET":
|
||||
stack.push(token.type);
|
||||
break;
|
||||
case "RIGHT_BRACE":
|
||||
case "RIGHT_BRACKET":
|
||||
stack.pop();
|
||||
}
|
||||
|
||||
let isVar = token.type === "SYMBOL" && !RESERVED_WORDS.includes(token.value);
|
||||
if (token.type === "SYMBOL" && !RESERVED_WORDS.includes(token.value)) {
|
||||
if (prevToken) {
|
||||
// normalize missing tokens: {a} should be equivalent to {a:a}
|
||||
if (isLeftSeparator(prevToken) && isRightSeparator(nextToken)) {
|
||||
if (
|
||||
groupType === "LEFT_BRACE" &&
|
||||
isLeftSeparator(prevToken) &&
|
||||
isRightSeparator(nextToken)
|
||||
) {
|
||||
tokens.splice(i + 1, 0, { type: "COLON", value: ":" }, { ...token });
|
||||
nextToken = tokens[i + 1];
|
||||
}
|
||||
|
||||
+18
-3
@@ -137,10 +137,25 @@ const UTILS: Utils = {
|
||||
}
|
||||
return result;
|
||||
},
|
||||
/**
|
||||
* This method combines the current context with the variables defined in a
|
||||
* scope for use in a slot.
|
||||
*
|
||||
* The implementation is kind of tricky because we want to preserve the
|
||||
* prototype chain structure of the cloned result. So we need to traverse the
|
||||
* prototype chain, cloning each level respectively.
|
||||
*/
|
||||
combine(context, scope) {
|
||||
const clone = Object.create(context);
|
||||
for (let k in scope) {
|
||||
clone[k] = scope[k];
|
||||
let clone = context;
|
||||
const scopeStack = [];
|
||||
while (!isComponent(scope)) {
|
||||
scopeStack.push(scope);
|
||||
scope = scope.__proto__;
|
||||
}
|
||||
while (scopeStack.length) {
|
||||
let scope = scopeStack.pop();
|
||||
clone = Object.create(clone);
|
||||
Object.assign(clone, scope);
|
||||
}
|
||||
return clone;
|
||||
},
|
||||
|
||||
+2
-1
@@ -231,7 +231,8 @@ function updateClass(oldVnode: VNode, vnode: VNode): void {
|
||||
elm = vnode.elm as Element;
|
||||
|
||||
for (name in oldClass) {
|
||||
if (name && !klass[name]) {
|
||||
if (name && !klass[name] && !Object.prototype.hasOwnProperty.call(klass, name)) {
|
||||
// was `true` and now not provided
|
||||
elm.classList.remove(name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1257,4 +1257,117 @@ describe("t-slot directive", () => {
|
||||
`<div><div class="slotted"><div class="slot"><div class="child"></div></div></div></div>`
|
||||
);
|
||||
});
|
||||
|
||||
test("t-slot scope context", async () => {
|
||||
expect.assertions(1);
|
||||
|
||||
class Wrapper extends Component {
|
||||
static template = xml`<t t-slot="default"/>`;
|
||||
}
|
||||
|
||||
let dialog;
|
||||
|
||||
class Dialog extends Component {
|
||||
static template = xml`
|
||||
<Wrapper>
|
||||
<div t-on-click="onClick">
|
||||
<t t-slot="default" />
|
||||
</div>
|
||||
</Wrapper>
|
||||
`;
|
||||
|
||||
static components = { Wrapper };
|
||||
|
||||
setup() {
|
||||
dialog = this;
|
||||
}
|
||||
|
||||
onClick(ev) {
|
||||
// we do not use expect(this).toBe(dialog) here because if it fails, it
|
||||
// may blow up jest because it then tries to compute a diff, which is
|
||||
// infinite if there is a cycle
|
||||
expect(this === dialog).toBe(true);
|
||||
}
|
||||
}
|
||||
|
||||
class Parent extends Component {
|
||||
static template = xml`
|
||||
<Dialog>
|
||||
<button>The Button</button>
|
||||
</Dialog>`;
|
||||
static components = { Dialog };
|
||||
}
|
||||
|
||||
await mount(Parent, { target: fixture });
|
||||
|
||||
document.querySelector("button").click();
|
||||
await nextTick();
|
||||
});
|
||||
|
||||
test("t-slot in recursive templates", async () => {
|
||||
QWeb.registerTemplate(
|
||||
"_test_recursive_template",
|
||||
`
|
||||
<Wrapper>
|
||||
<t t-esc="name" />
|
||||
<t t-foreach="items" t-as="item">
|
||||
|
||||
<t t-if="!item.children.length">
|
||||
<t t-esc="item.name" />
|
||||
</t>
|
||||
|
||||
<t t-else="" t-call="_test_recursive_template">
|
||||
<t t-set="name" t-value="item.name" />
|
||||
<t t-set="items" t-value="item.children" />
|
||||
</t>
|
||||
|
||||
</t>
|
||||
</Wrapper>`
|
||||
);
|
||||
|
||||
class Wrapper extends Component {
|
||||
static template = xml`
|
||||
<wrapper>
|
||||
<t t-slot="default"/>
|
||||
</wrapper>`;
|
||||
}
|
||||
|
||||
class Parent extends Component {
|
||||
static template = "_test_recursive_template";
|
||||
static components = { Wrapper };
|
||||
name = "foo";
|
||||
items = [
|
||||
{
|
||||
name: "foo-0",
|
||||
children: [
|
||||
{ name: "foo-00", children: [] },
|
||||
{
|
||||
name: "foo-01",
|
||||
children: [
|
||||
{ name: "foo-010", children: [] },
|
||||
{ name: "foo-011", children: [] },
|
||||
{
|
||||
name: "foo-012",
|
||||
children: [
|
||||
{ name: "foo-0120", children: [] },
|
||||
{ name: "foo-0121", children: [] },
|
||||
{ name: "foo-0122", children: [] },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{ name: "foo-02", children: [] },
|
||||
],
|
||||
},
|
||||
{ name: "foo-1", children: [] },
|
||||
{ name: "foo-2", children: [] },
|
||||
];
|
||||
}
|
||||
|
||||
await mount(Parent, { target: fixture });
|
||||
|
||||
expect(fixture.innerHTML).toBe(
|
||||
"<wrapper>foo<wrapper>foo-0foo-00<wrapper>foo-01foo-010foo-011<wrapper>foo-012foo-0120foo-0121foo-0122</wrapper></wrapper>foo-02</wrapper>foo-1foo-2</wrapper>"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -207,6 +207,17 @@ describe("expression evaluation", () => {
|
||||
expect(compileExpr("{a,b:3,c}", {})).toBe("{a:scope['a'],b:3,c:scope['c']}");
|
||||
});
|
||||
|
||||
test("works with short object description and lists ", () => {
|
||||
expect(compileExpr("[a, b]", {})).toBe("[scope['a'],scope['b']]");
|
||||
expect(compileExpr("[a, b, c]", {})).toBe("[scope['a'],scope['b'],scope['c']]");
|
||||
expect(compileExpr("[a, {b, c},d]", {})).toBe(
|
||||
"[scope['a'],{b:scope['b'],c:scope['c']},scope['d']]"
|
||||
);
|
||||
expect(compileExpr("{a:[b, {c, d: e}]}", {})).toBe(
|
||||
"{a:[scope['b'],{c:scope['c'],d:scope['e']}]}"
|
||||
);
|
||||
});
|
||||
|
||||
test("template strings", () => {
|
||||
expect(compileExpr("`hey`", {})).toBe("`hey`");
|
||||
expect(compileExpr("`hey ${you}`", {})).toBe("`hey ${scope['you']}`");
|
||||
|
||||
Reference in New Issue
Block a user