Compare commits

..

1 Commits

Author SHA1 Message Date
Abdallah (ameb) 2b943e7278 Update templates.md
an => a
2025-05-13 09:43:58 +04:00
10 changed files with 13 additions and 247 deletions
-22
View File
@@ -320,28 +320,6 @@ class ComponentB extends owl.Component {
Note: the props validation code is done by using the [validate utility function](utils.md#validate). Note: the props validation code is done by using the [validate utility function](utils.md#validate).
### `slots` prop
If a component that uses [slots](slots.md) also lists or validates its props, then
you will have to explicitely allow the `slots` prop (with an `Object` type), or
allow extra props using the `*` notation mentioned above. This is because slots
are provided to a component [as props](slots.md#slots-and-props).
For example:
```js
class MyComponent extends Component {
static props = [someProp, slots?];
}
class MyComponentWithValidation extends Component {
static props = {
someProp: {type: Number, optional: true},
slots : {type: Object, optional: true},
}
}
```
## Good Practices ## Good Practices
A `props` object is a collection of values that come from the parent. As such, A `props` object is a collection of values that come from the parent. As such,
+1 -1
View File
@@ -680,7 +680,7 @@ class MyComponent extends Component {
mount(MyComponent, document.body); mount(MyComponent, document.body);
``` ```
This function simply generates an unique string id, and register the template This function simply generates a unique string id, and register the template
under that id in the internals of Owl, then return the id. under that id in the internals of Owl, then return the id.
## Rendering svg ## Rendering svg
+6 -15
View File
@@ -3850,16 +3850,7 @@ class CodeTarget {
return key; return key;
} }
} }
const TRANSLATABLE_ATTRS = [ const TRANSLATABLE_ATTRS = ["label", "title", "placeholder", "alt"];
"alt",
"aria-label",
"aria-placeholder",
"aria-roledescription",
"aria-valuetext",
"label",
"placeholder",
"title",
];
const translationRE = /^(\s*)([\s\S]+?)(\s*)$/; const translationRE = /^(\s*)([\s\S]+?)(\s*)$/;
class CodeGenerator { class CodeGenerator {
constructor(ast, options) { constructor(ast, options) {
@@ -4977,11 +4968,11 @@ function parseNode(node, ctx) {
parseTPortal(node, ctx) || parseTPortal(node, ctx) ||
parseTCall(node, ctx) || parseTCall(node, ctx) ||
parseTCallBlock(node) || parseTCallBlock(node) ||
parseTTranslation(node, ctx) ||
parseTTranslationContext(node, ctx) ||
parseTEscNode(node, ctx) || parseTEscNode(node, ctx) ||
parseTOutNode(node, ctx) || parseTOutNode(node, ctx) ||
parseTKey(node, ctx) || parseTKey(node, ctx) ||
parseTTranslation(node, ctx) ||
parseTTranslationContext(node, ctx) ||
parseTSlot(node, ctx) || parseTSlot(node, ctx) ||
parseComponent(node, ctx) || parseComponent(node, ctx) ||
parseDOMNode(node, ctx) || parseDOMNode(node, ctx) ||
@@ -5767,7 +5758,7 @@ function compile(template, options = {
} }
// do not modify manually. This file is generated by the release script. // do not modify manually. This file is generated by the release script.
const version = "2.8.0"; const version = "2.7.0";
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Scheduler // Scheduler
@@ -6238,6 +6229,6 @@ TemplateSet.prototype._compileTemplate = function _compileTemplate(name, templat
export { App, Component, EventBus, OwlError, __info__, batched, blockDom, htmlEscape, loadFile, markRaw, markup, mount, onError, onMounted, onPatched, onRendered, onWillDestroy, onWillPatch, onWillRender, onWillStart, onWillUnmount, onWillUpdateProps, reactive, status, toRaw, useChildSubEnv, useComponent, useEffect, useEnv, useExternalListener, useRef, useState, useSubEnv, validate, validateType, whenReady, xml }; export { App, Component, EventBus, OwlError, __info__, batched, blockDom, htmlEscape, loadFile, markRaw, markup, mount, onError, onMounted, onPatched, onRendered, onWillDestroy, onWillPatch, onWillRender, onWillStart, onWillUnmount, onWillUpdateProps, reactive, status, toRaw, useChildSubEnv, useComponent, useEffect, useEnv, useExternalListener, useRef, useState, useSubEnv, validate, validateType, whenReady, xml };
__info__.date = '2025-06-30T12:46:06.424Z'; __info__.date = '2025-03-26T12:58:40.935Z';
__info__.hash = 'b620502'; __info__.hash = 'e788e36';
__info__.url = 'https://github.com/odoo/owl'; __info__.url = 'https://github.com/odoo/owl';
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@odoo/owl", "name": "@odoo/owl",
"version": "2.8.0", "version": "2.7.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@odoo/owl", "name": "@odoo/owl",
"version": "2.8.0", "version": "2.7.0",
"description": "Odoo Web Library (OWL)", "description": "Odoo Web Library (OWL)",
"main": "dist/owl.cjs.js", "main": "dist/owl.cjs.js",
"module": "dist/owl.es.js", "module": "dist/owl.es.js",
+2 -2
View File
@@ -253,11 +253,11 @@ function parseNode(node: Node, ctx: ParsingContext): AST | null {
parseTPortal(node, ctx) || parseTPortal(node, ctx) ||
parseTCall(node, ctx) || parseTCall(node, ctx) ||
parseTCallBlock(node, ctx) || parseTCallBlock(node, ctx) ||
parseTTranslation(node, ctx) ||
parseTTranslationContext(node, ctx) ||
parseTEscNode(node, ctx) || parseTEscNode(node, ctx) ||
parseTOutNode(node, ctx) || parseTOutNode(node, ctx) ||
parseTKey(node, ctx) || parseTKey(node, ctx) ||
parseTTranslation(node, ctx) ||
parseTTranslationContext(node, ctx) ||
parseTSlot(node, ctx) || parseTSlot(node, ctx) ||
parseComponent(node, ctx) || parseComponent(node, ctx) ||
parseDOMNode(node, ctx) || parseDOMNode(node, ctx) ||
-43
View File
@@ -1,5 +1,4 @@
import { OwlError } from "../common/owl_error"; import { OwlError } from "../common/owl_error";
import { ComponentNode, getCurrent } from "./component_node";
export type Callback = () => void; export type Callback = () => void;
/** /**
@@ -82,52 +81,10 @@ export function validateTarget(target: HTMLElement | ShadowRoot) {
} }
export class EventBus extends EventTarget { export class EventBus extends EventTarget {
constructor(events?: string[]) {
if (events) {
let node: ComponentNode | null = null;
try {
node = getCurrent();
} catch {}
if (node?.app?.dev) {
return new DebugEventBus(events);
}
}
super();
}
trigger(name: string, payload?: any) { trigger(name: string, payload?: any) {
this.dispatchEvent(new CustomEvent(name, { detail: payload })); this.dispatchEvent(new CustomEvent(name, { detail: payload }));
} }
} }
class DebugEventBus extends EventBus {
private events: Set<string>;
constructor(events: string[]) {
super();
this.events = new Set(events);
}
addEventListener(
type: string,
listener: EventListenerOrEventListenerObject | null,
options?: boolean | AddEventListenerOptions
): void {
if (!this.events.has(type)) {
throw new OwlError(`EventBus: subscribing to unknown event '${type}'`);
}
super.addEventListener(type, listener, options);
}
trigger(name: string, payload?: any) {
if (!this.events.has(name)) {
throw new OwlError(`EventBus: triggering unknown event '${name}'`);
}
super.trigger(name, payload);
}
dispatchEvent(event: Event): boolean {
if (!this.events.has(event.type)) {
throw new OwlError(`EventBus: dispatching unknown event '${event.type}'`);
}
return super.dispatchEvent(event);
}
}
export function whenReady(fn?: any): Promise<void> { export function whenReady(fn?: any): Promise<void> {
return new Promise(function (resolve) { return new Promise(function (resolve) {
+1 -1
View File
@@ -1,2 +1,2 @@
// do not modify manually. This file is generated by the release script. // do not modify manually. This file is generated by the release script.
export const version = "2.8.0"; export const version = "2.7.0";
-98
View File
@@ -1971,54 +1971,6 @@ describe("qweb parser", () => {
}); });
}); });
test('t-translation="off": interaction with t-esc', async () => {
expect(parse(`<span t-esc="a" t-translation="off"/>`)).toEqual({
type: ASTType.TTranslation,
content: {
attrs: null,
attrsTranslationCtx: null,
content: [
{
defaultValue: "",
expr: "a",
type: ASTType.TEsc,
},
],
dynamicTag: null,
model: null,
ns: null,
on: null,
ref: null,
tag: "span",
type: ASTType.DomNode,
},
});
});
test('t-translation="off": interaction with t-out', async () => {
expect(parse(`<span t-out="a" t-translation="off"/>`)).toEqual({
type: ASTType.TTranslation,
content: {
attrs: null,
attrsTranslationCtx: null,
content: [
{
body: null,
expr: "a",
type: ASTType.TOut,
},
],
dynamicTag: null,
model: null,
ns: null,
on: null,
ref: null,
tag: "span",
type: ASTType.DomNode,
},
});
});
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// t-translation-context // t-translation-context
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -2056,56 +2008,6 @@ describe("qweb parser", () => {
}); });
}); });
test("t-translation-context: interaction with t-esc", async () => {
expect(parse(`<span t-esc="a" t-translation-context="fr"/>`)).toEqual({
type: ASTType.TTranslationContext,
content: {
attrs: null,
attrsTranslationCtx: null,
content: [
{
defaultValue: "",
expr: "a",
type: ASTType.TEsc,
},
],
dynamicTag: null,
model: null,
ns: null,
on: null,
ref: null,
tag: "span",
type: ASTType.DomNode,
},
translationCtx: "fr",
});
});
test("t-translation-context: interaction with t-out", async () => {
expect(parse(`<span t-out="a" t-translation-context="fr"/>`)).toEqual({
type: ASTType.TTranslationContext,
content: {
attrs: null,
attrsTranslationCtx: null,
content: [
{
body: null,
expr: "a",
type: ASTType.TOut,
},
],
dynamicTag: null,
model: null,
ns: null,
on: null,
ref: null,
tag: "span",
type: ASTType.DomNode,
},
translationCtx: "fr",
});
});
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// t-translation-context-attr // t-translation-context-attr
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
+1 -63
View File
@@ -1,7 +1,5 @@
import { batched, EventBus, htmlEscape, markup } from "../src/runtime/utils"; import { batched, EventBus, htmlEscape, markup } from "../src/runtime/utils";
import { makeTestFixture, nextMicroTick } from "./helpers"; import { nextMicroTick } from "./helpers";
import { getCurrent } from "../src/runtime/component_node";
import { Component, mount, xml } from "../src";
describe("event bus behaviour", () => { describe("event bus behaviour", () => {
test("can subscribe and be notified", () => { test("can subscribe and be notified", () => {
@@ -35,66 +33,6 @@ describe("event bus behaviour", () => {
bus.addEventListener("event", (ev: any) => expect(ev.detail).toBe("hello world")); bus.addEventListener("event", (ev: any) => expect(ev.detail).toBe("hello world"));
bus.trigger("event", "hello world"); bus.trigger("event", "hello world");
}); });
test("events are not validated if the bus is created outside of dev mode", async () => {
let bus_empty: EventBus | null = null;
class Root extends Component {
static template = xml`<div/>`;
setup() {
getCurrent(); // checks that we're in a component context
bus_empty = new EventBus([]);
}
}
await mount(Root, makeTestFixture());
bus_empty!.addEventListener("a", () => {});
bus_empty!.trigger("a");
bus_empty!.dispatchEvent(new CustomEvent("a"));
});
test("events are validated if the bus is created in dev mode & events are provided", async () => {
let bus: EventBus | null = null;
let bus_empty: EventBus | null = null;
let bbus_no_validation: EventBus | null = null;
class Root extends Component {
static template = xml`<div/>`;
setup() {
getCurrent(); // checks that we're in a component context
bus = new EventBus(["a", "b"]);
bus_empty = new EventBus([]);
bbus_no_validation = new EventBus();
}
}
await mount(Root, makeTestFixture(), { test: true });
bbus_no_validation!.addEventListener("c", () => {});
bbus_no_validation!.trigger("c");
bbus_no_validation!.dispatchEvent(new CustomEvent("c"));
bus!.addEventListener("a", () => {});
bus!.trigger("a");
bus!.dispatchEvent(new CustomEvent("a"));
expect(() => bus!.addEventListener("c", () => {})).toThrow(
"EventBus: subscribing to unknown event 'c'"
);
expect(() => bus!.trigger("c")).toThrow("EventBus: triggering unknown event 'c'");
expect(() => bus!.dispatchEvent(new CustomEvent("c"))).toThrow(
"EventBus: dispatching unknown event 'c'"
);
expect(() => bus_empty!.addEventListener("a", () => {})).toThrow(
"EventBus: subscribing to unknown event 'a'"
);
expect(() => bus_empty!.trigger("a")).toThrow("EventBus: triggering unknown event 'a'");
expect(() => bus_empty!.dispatchEvent(new CustomEvent("a"))).toThrow(
"EventBus: dispatching unknown event 'a'"
);
});
}); });
describe("batched", () => { describe("batched", () => {