Compare commits

..

1 Commits

Author SHA1 Message Date
Julien Carion (juca) 8d0a46c5dc [IMP] devtools: update the devtools documentation
This commit updates the devtools documentation to match with its latest
content and updates screenshots to odoo v17.
2023-11-13 12:24:04 +01:00
22 changed files with 141 additions and 359 deletions
-2
View File
@@ -61,8 +61,6 @@ The `config` object is an object with some of the following keys:
templates (see [translations](translations.md)) templates (see [translations](translations.md))
- **`templates (string | xml document)`**: all the templates that will be used by - **`templates (string | xml document)`**: all the templates that will be used by
the components created by the application. the components created by the application.
- **`getTemplate ((s: string) => Element | Function | string | void)`**: a function that will be called by owl when it
needs a template. If undefined is returned, owl looks into the app templates.
- **`warnIfNoStaticProps (boolean, default=false)`**: if true, Owl will log a warning - **`warnIfNoStaticProps (boolean, default=false)`**: if true, Owl will log a warning
whenever it encounters a component that does not provide a [static props description](props.md#props-validation). whenever it encounters a component that does not provide a [static props description](props.md#props-validation).
+1 -1
View File
@@ -41,7 +41,7 @@ one. There can also be multiple apps loaded in the page like in website:
There is a convenient search bar at the top of the components tree which will help finding There is a convenient search bar at the top of the components tree which will help finding
the components tou want in the tree and also, an element picker can be used to directly select the components tou want in the tree and also, an element picker can be used to directly select
the component you want to focus on in the page which is especially useful when 1trying to find the component you want to focus on in the page which is especially useful when trying to find
what you want. Just click on the elements picker icon and click on the element you want to focus what you want. Just click on the elements picker icon and click on the element you want to focus
on in the page and it will be selected in the devtools accordingly. Hovering any element in the on in the page and it will be selected in the devtools accordingly. Hovering any element in the
page in this mode will highlight it and the same happens anytime in the components tree. page in this mode will highlight it and the same happens anytime in the components tree.
+54 -37
View File
@@ -1850,9 +1850,8 @@ const NO_CALLBACK = () => {
}; };
const objectToString = Object.prototype.toString; const objectToString = Object.prototype.toString;
const objectHasOwnProperty = Object.prototype.hasOwnProperty; const objectHasOwnProperty = Object.prototype.hasOwnProperty;
// Use arrays because Array.includes is faster than Set.has for small arrays const SUPPORTED_RAW_TYPES = new Set(["Object", "Array", "Set", "Map", "WeakMap"]);
const SUPPORTED_RAW_TYPES = ["Object", "Array", "Set", "Map", "WeakMap"]; const COLLECTION_RAWTYPES = new Set(["Set", "Map", "WeakMap"]);
const COLLECTION_RAW_TYPES = ["Set", "Map", "WeakMap"];
/** /**
* extract "RawType" from strings like "[object RawType]" => this lets us ignore * extract "RawType" from strings like "[object RawType]" => this lets us ignore
* many native objects such as Promise (whose toString is [object Promise]) * many native objects such as Promise (whose toString is [object Promise])
@@ -1875,7 +1874,7 @@ function canBeMadeReactive(value) {
if (typeof value !== "object") { if (typeof value !== "object") {
return false; return false;
} }
return SUPPORTED_RAW_TYPES.includes(rawType(value)); return SUPPORTED_RAW_TYPES.has(rawType(value));
} }
/** /**
* Creates a reactive from the given object/callback if possible and returns it, * Creates a reactive from the given object/callback if possible and returns it,
@@ -2045,7 +2044,7 @@ function reactive(target, callback = NO_CALLBACK) {
const reactivesForTarget = reactiveCache.get(target); const reactivesForTarget = reactiveCache.get(target);
if (!reactivesForTarget.has(callback)) { if (!reactivesForTarget.has(callback)) {
const targetRawType = rawType(target); const targetRawType = rawType(target);
const handler = COLLECTION_RAW_TYPES.includes(targetRawType) const handler = COLLECTION_RAWTYPES.has(targetRawType)
? collectionsProxyHandler(target, callback, targetRawType) ? collectionsProxyHandler(target, callback, targetRawType)
: basicProxyHandler(callback); : basicProxyHandler(callback);
const proxy = new Proxy(target, handler); const proxy = new Proxy(target, handler);
@@ -2074,7 +2073,7 @@ function basicProxyHandler(callback) {
set(target, key, value, receiver) { set(target, key, value, receiver) {
const hadKey = objectHasOwnProperty.call(target, key); const hadKey = objectHasOwnProperty.call(target, key);
const originalValue = Reflect.get(target, key, receiver); const originalValue = Reflect.get(target, key, receiver);
const ret = Reflect.set(target, key, toRaw(value), receiver); const ret = Reflect.set(target, key, value, receiver);
if (!hadKey && objectHasOwnProperty.call(target, key)) { if (!hadKey && objectHasOwnProperty.call(target, key)) {
notifyReactives(target, KEYCHANGES); notifyReactives(target, KEYCHANGES);
} }
@@ -3160,14 +3159,8 @@ const helpers = {
makeRefWrapper, makeRefWrapper,
}; };
/** const bdom = { text, createBlock, list, multi, html, toggler, comment };
* Parses an XML string into an XML document, throwing errors on parser errors function parseXML$1(xml) {
* instead of returning an XML document containing the parseerror.
*
* @param xml the string to parse
* @returns an XML document corresponding to the content of the string
*/
function parseXML(xml) {
const parser = new DOMParser(); const parser = new DOMParser();
const doc = parser.parseFromString(xml, "text/xml"); const doc = parser.parseFromString(xml, "text/xml");
if (doc.getElementsByTagName("parsererror").length) { if (doc.getElementsByTagName("parsererror").length) {
@@ -3194,9 +3187,7 @@ function parseXML(xml) {
throw new OwlError(msg); throw new OwlError(msg);
} }
return doc; return doc;
} }
const bdom = { text, createBlock, list, multi, html, toggler, comment };
class TemplateSet { class TemplateSet {
constructor(config = {}) { constructor(config = {}) {
this.rawTemplates = Object.create(globalTemplates); this.rawTemplates = Object.create(globalTemplates);
@@ -3206,16 +3197,8 @@ class TemplateSet {
this.translateFn = config.translateFn; this.translateFn = config.translateFn;
this.translatableAttributes = config.translatableAttributes; this.translatableAttributes = config.translatableAttributes;
if (config.templates) { if (config.templates) {
if (config.templates instanceof Document || typeof config.templates === "string") { this.addTemplates(config.templates);
this.addTemplates(config.templates);
}
else {
for (const name in config.templates) {
this.addTemplate(name, config.templates[name]);
}
}
} }
this.getRawTemplate = config.getTemplate;
} }
static registerTemplate(name, fn) { static registerTemplate(name, fn) {
globalTemplates[name] = fn; globalTemplates[name] = fn;
@@ -3245,16 +3228,15 @@ class TemplateSet {
// empty string // empty string
return; return;
} }
xml = xml instanceof Document ? xml : parseXML(xml); xml = xml instanceof Document ? xml : parseXML$1(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");
this.addTemplate(name, template); this.addTemplate(name, template);
} }
} }
getTemplate(name) { getTemplate(name) {
var _a;
if (!(name in this.templates)) { if (!(name in this.templates)) {
const rawTemplate = ((_a = this.getRawTemplate) === null || _a === void 0 ? void 0 : _a.call(this, name)) || this.rawTemplates[name]; const rawTemplate = this.rawTemplates[name];
if (rawTemplate === undefined) { if (rawTemplate === undefined) {
let extraInfo = ""; let extraInfo = "";
try { try {
@@ -4964,9 +4946,9 @@ function parseDOMNode(node, ctx) {
const isSelect = tagName === "select"; const isSelect = tagName === "select";
const isCheckboxInput = isInput && typeAttr === "checkbox"; const isCheckboxInput = isInput && typeAttr === "checkbox";
const isRadioInput = isInput && typeAttr === "radio"; const isRadioInput = isInput && typeAttr === "radio";
const hasTrimMod = attr.includes(".trim"); const hasLazyMod = attr.includes(".lazy");
const hasLazyMod = hasTrimMod || attr.includes(".lazy");
const hasNumberMod = attr.includes(".number"); const hasNumberMod = attr.includes(".number");
const hasTrimMod = attr.includes(".trim");
const eventType = isRadioInput ? "click" : isSelect || hasLazyMod ? "change" : "input"; const eventType = isRadioInput ? "click" : isSelect || hasLazyMod ? "change" : "input";
model = { model = {
baseExpr, baseExpr,
@@ -5297,14 +5279,14 @@ function parseComponent(node, ctx) {
// be ignored) // be ignored)
let el = slotNode.parentElement; let el = slotNode.parentElement;
let isInSubComponent = false; let isInSubComponent = false;
while (el && el !== clone) { while (el !== clone) {
if (el.hasAttribute("t-component") || el.tagName[0] === el.tagName[0].toUpperCase()) { if (el.hasAttribute("t-component") || el.tagName[0] === el.tagName[0].toUpperCase()) {
isInSubComponent = true; isInSubComponent = true;
break; break;
} }
el = el.parentElement; el = el.parentElement;
} }
if (isInSubComponent || !el) { if (isInSubComponent) {
continue; continue;
} }
slotNode.removeAttribute("t-set-slot"); slotNode.removeAttribute("t-set-slot");
@@ -5512,6 +5494,41 @@ function normalizeTEscTOut(el) {
function normalizeXML(el) { function normalizeXML(el) {
normalizeTIf(el); normalizeTIf(el);
normalizeTEscTOut(el); normalizeTEscTOut(el);
}
/**
* Parses an XML string into an XML document, throwing errors on parser errors
* instead of returning an XML document containing the parseerror.
*
* @param xml the string to parse
* @returns an XML document corresponding to the content of the string
*/
function parseXML(xml) {
const parser = new DOMParser();
const doc = parser.parseFromString(xml, "text/xml");
if (doc.getElementsByTagName("parsererror").length) {
let msg = "Invalid XML in template.";
const parsererrorText = doc.getElementsByTagName("parsererror")[0].textContent;
if (parsererrorText) {
msg += "\nThe parser has produced the following error message:\n" + parsererrorText;
const re = /\d+/g;
const firstMatch = re.exec(parsererrorText);
if (firstMatch) {
const lineNumber = Number(firstMatch[0]);
const line = xml.split("\n")[lineNumber - 1];
const secondMatch = re.exec(parsererrorText);
if (line && secondMatch) {
const columnIndex = Number(secondMatch[0]) - 1;
if (line[columnIndex]) {
msg +=
`\nThe error might be located at xml line ${lineNumber} column ${columnIndex}\n` +
`${line}\n${"-".repeat(columnIndex - 1)}^`;
}
}
}
}
throw new OwlError(msg);
}
return doc;
} }
function compile(template, options = {}) { function compile(template, options = {}) {
@@ -5538,7 +5555,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.2.9"; const version = "2.2.6";
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Scheduler // Scheduler
@@ -5885,7 +5902,7 @@ function useChildSubEnv(envExtension) {
* *
* @template T * @template T
* @param {Effect<T>} effect the effect to run on component mount and/or patch * @param {Effect<T>} effect the effect to run on component mount and/or patch
* @param {()=>[...T]} [computeDependencies=()=>[NaN]] a callback to compute * @param {()=>T} [computeDependencies=()=>[NaN]] a callback to compute
* dependencies that will decide if the effect needs to be cleaned up and * dependencies that will decide if the effect needs to be cleaned up and
* run again. If the dependencies did not change, the effect will not run * run again. If the dependencies did not change, the effect will not run
* again. The default value returns an array containing only NaN because * again. The default value returns an array containing only NaN because
@@ -5967,6 +5984,6 @@ TemplateSet.prototype._compileTemplate = function _compileTemplate(name, templat
export { App, Component, EventBus, OwlError, __info__, blockDom, 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__, blockDom, 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 = '2024-01-12T14:43:56.804Z'; __info__.date = '2023-09-25T11:48:01.531Z';
__info__.hash = '7b3e39b'; __info__.hash = '752160f';
__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.2.9", "version": "2.2.6",
"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.2.9", "version": "2.2.6",
"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",
-38
View File
@@ -1,38 +0,0 @@
import { OwlError } from "./owl_error";
/**
* Parses an XML string into an XML document, throwing errors on parser errors
* instead of returning an XML document containing the parseerror.
*
* @param xml the string to parse
* @returns an XML document corresponding to the content of the string
*/
export function parseXML(xml: string): XMLDocument {
const parser = new DOMParser();
const doc = parser.parseFromString(xml, "text/xml");
if (doc.getElementsByTagName("parsererror").length) {
let msg = "Invalid XML in template.";
const parsererrorText = doc.getElementsByTagName("parsererror")[0].textContent;
if (parsererrorText) {
msg += "\nThe parser has produced the following error message:\n" + parsererrorText;
const re = /\d+/g;
const firstMatch = re.exec(parsererrorText);
if (firstMatch) {
const lineNumber = Number(firstMatch[0]);
const line = xml.split("\n")[lineNumber - 1];
const secondMatch = re.exec(parsererrorText);
if (line && secondMatch) {
const columnIndex = Number(secondMatch[0]) - 1;
if (line[columnIndex]) {
msg +=
`\nThe error might be located at xml line ${lineNumber} column ${columnIndex}\n` +
`${line}\n${"-".repeat(columnIndex - 1)}^`;
}
}
}
}
throw new OwlError(msg);
}
return doc;
}
+41 -5
View File
@@ -1,5 +1,4 @@
import { OwlError } from "../common/owl_error"; import { OwlError } from "../common/owl_error";
import { parseXML } from "../common/utils";
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// AST Type definition // AST Type definition
@@ -367,9 +366,9 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
const isSelect = tagName === "select"; const isSelect = tagName === "select";
const isCheckboxInput = isInput && typeAttr === "checkbox"; const isCheckboxInput = isInput && typeAttr === "checkbox";
const isRadioInput = isInput && typeAttr === "radio"; const isRadioInput = isInput && typeAttr === "radio";
const hasTrimMod = attr.includes(".trim"); const hasLazyMod = attr.includes(".lazy");
const hasLazyMod = hasTrimMod || attr.includes(".lazy");
const hasNumberMod = attr.includes(".number"); const hasNumberMod = attr.includes(".number");
const hasTrimMod = attr.includes(".trim");
const eventType = isRadioInput ? "click" : isSelect || hasLazyMod ? "change" : "input"; const eventType = isRadioInput ? "click" : isSelect || hasLazyMod ? "change" : "input";
model = { model = {
@@ -741,14 +740,14 @@ function parseComponent(node: Element, ctx: ParsingContext): AST | null {
// be ignored) // be ignored)
let el = slotNode.parentElement!; let el = slotNode.parentElement!;
let isInSubComponent = false; let isInSubComponent = false;
while (el && el !== clone) { while (el !== clone) {
if (el!.hasAttribute("t-component") || el!.tagName[0] === el!.tagName[0].toUpperCase()) { if (el!.hasAttribute("t-component") || el!.tagName[0] === el!.tagName[0].toUpperCase()) {
isInSubComponent = true; isInSubComponent = true;
break; break;
} }
el = el.parentElement!; el = el.parentElement!;
} }
if (isInSubComponent || !el) { if (isInSubComponent) {
continue; continue;
} }
@@ -973,3 +972,40 @@ function normalizeXML(el: Element) {
normalizeTIf(el); normalizeTIf(el);
normalizeTEscTOut(el); normalizeTEscTOut(el);
} }
/**
* Parses an XML string into an XML document, throwing errors on parser errors
* instead of returning an XML document containing the parseerror.
*
* @param xml the string to parse
* @returns an XML document corresponding to the content of the string
*/
function parseXML(xml: string): XMLDocument {
const parser = new DOMParser();
const doc = parser.parseFromString(xml, "text/xml");
if (doc.getElementsByTagName("parsererror").length) {
let msg = "Invalid XML in template.";
const parsererrorText = doc.getElementsByTagName("parsererror")[0].textContent;
if (parsererrorText) {
msg += "\nThe parser has produced the following error message:\n" + parsererrorText;
const re = /\d+/g;
const firstMatch = re.exec(parsererrorText);
if (firstMatch) {
const lineNumber = Number(firstMatch[0]);
const line = xml.split("\n")[lineNumber - 1];
const secondMatch = re.exec(parsererrorText);
if (line && secondMatch) {
const columnIndex = Number(secondMatch[0]) - 1;
if (line[columnIndex]) {
msg +=
`\nThe error might be located at xml line ${lineNumber} column ${columnIndex}\n` +
`${line}\n${"-".repeat(columnIndex - 1)}^`;
}
}
}
}
throw new OwlError(msg);
}
return doc;
}
+1 -1
View File
@@ -23,7 +23,7 @@ export type ComponentConstructor<P extends Props = any, E = any> = (new (
export class Component<Props = any, Env = any> { export class Component<Props = any, Env = any> {
static template: string = ""; static template: string = "";
static props?: Schema; static props?: any;
static defaultProps?: any; static defaultProps?: any;
props: Props; props: Props;
+1 -1
View File
@@ -65,7 +65,7 @@ export class Portal extends Component {
type: String, type: String,
}, },
slots: true, slots: true,
} as const; };
setup() { setup() {
const node: any = this.__owl__; const node: any = this.__owl__;
+4 -5
View File
@@ -20,9 +20,8 @@ type CollectionRawType = "Set" | "Map" | "WeakMap";
const objectToString = Object.prototype.toString; const objectToString = Object.prototype.toString;
const objectHasOwnProperty = Object.prototype.hasOwnProperty; const objectHasOwnProperty = Object.prototype.hasOwnProperty;
// Use arrays because Array.includes is faster than Set.has for small arrays const SUPPORTED_RAW_TYPES = new Set(["Object", "Array", "Set", "Map", "WeakMap"]);
const SUPPORTED_RAW_TYPES = ["Object", "Array", "Set", "Map", "WeakMap"]; const COLLECTION_RAWTYPES = new Set(["Set", "Map", "WeakMap"]);
const COLLECTION_RAW_TYPES = ["Set", "Map", "WeakMap"];
/** /**
* extract "RawType" from strings like "[object RawType]" => this lets us ignore * extract "RawType" from strings like "[object RawType]" => this lets us ignore
@@ -46,7 +45,7 @@ function canBeMadeReactive(value: any): boolean {
if (typeof value !== "object") { if (typeof value !== "object") {
return false; return false;
} }
return SUPPORTED_RAW_TYPES.includes(rawType(value)); return SUPPORTED_RAW_TYPES.has(rawType(value));
} }
/** /**
* Creates a reactive from the given object/callback if possible and returns it, * Creates a reactive from the given object/callback if possible and returns it,
@@ -221,7 +220,7 @@ export function reactive<T extends Target>(target: T, callback: Callback = NO_CA
const reactivesForTarget = reactiveCache.get(target)!; const reactivesForTarget = reactiveCache.get(target)!;
if (!reactivesForTarget.has(callback)) { if (!reactivesForTarget.has(callback)) {
const targetRawType = rawType(target); const targetRawType = rawType(target);
const handler = COLLECTION_RAW_TYPES.includes(targetRawType) const handler = COLLECTION_RAWTYPES.has(targetRawType)
? collectionsProxyHandler(target as Collection, callback, targetRawType as CollectionRawType) ? collectionsProxyHandler(target as Collection, callback, targetRawType as CollectionRawType)
: basicProxyHandler<T>(callback); : basicProxyHandler<T>(callback);
const proxy = new Proxy(target, handler as ProxyHandler<T>) as Reactive<T>; const proxy = new Proxy(target, handler as ProxyHandler<T>) as Reactive<T>;
+33 -13
View File
@@ -4,16 +4,44 @@ import { getCurrent } from "./component_node";
import { Portal, portalTemplate } from "./portal"; import { Portal, portalTemplate } from "./portal";
import { helpers } from "./template_helpers"; import { helpers } from "./template_helpers";
import { OwlError } from "../common/owl_error"; import { OwlError } from "../common/owl_error";
import { parseXML } from "../common/utils";
const bdom = { text, createBlock, list, multi, html, toggler, comment }; const bdom = { text, createBlock, list, multi, html, toggler, comment };
function parseXML(xml: string): Document {
const parser = new DOMParser();
const doc = parser.parseFromString(xml, "text/xml");
if (doc.getElementsByTagName("parsererror").length) {
let msg = "Invalid XML in template.";
const parsererrorText = doc.getElementsByTagName("parsererror")[0].textContent;
if (parsererrorText) {
msg += "\nThe parser has produced the following error message:\n" + parsererrorText;
const re = /\d+/g;
const firstMatch = re.exec(parsererrorText);
if (firstMatch) {
const lineNumber = Number(firstMatch[0]);
const line = xml.split("\n")[lineNumber - 1];
const secondMatch = re.exec(parsererrorText);
if (line && secondMatch) {
const columnIndex = Number(secondMatch[0]) - 1;
if (line[columnIndex]) {
msg +=
`\nThe error might be located at xml line ${lineNumber} column ${columnIndex}\n` +
`${line}\n${"-".repeat(columnIndex - 1)}^`;
}
}
}
}
throw new OwlError(msg);
}
return doc;
}
export interface TemplateSetConfig { export interface TemplateSetConfig {
dev?: boolean; dev?: boolean;
translatableAttributes?: string[]; translatableAttributes?: string[];
translateFn?: (s: string) => string; translateFn?: (s: string) => string;
templates?: string | Document | Record<string, string>; templates?: string | Document;
getTemplate?: (s: string) => Element | Function | string | void;
} }
export class TemplateSet { export class TemplateSet {
@@ -23,7 +51,6 @@ export class TemplateSet {
dev: boolean; dev: boolean;
rawTemplates: typeof globalTemplates = Object.create(globalTemplates); rawTemplates: typeof globalTemplates = Object.create(globalTemplates);
templates: { [name: string]: Template } = {}; templates: { [name: string]: Template } = {};
getRawTemplate?: (s: string) => Element | Function | string | void;
translateFn?: (s: string) => string; translateFn?: (s: string) => string;
translatableAttributes?: string[]; translatableAttributes?: string[];
Portal = Portal; Portal = Portal;
@@ -33,15 +60,8 @@ export class TemplateSet {
this.translateFn = config.translateFn; this.translateFn = config.translateFn;
this.translatableAttributes = config.translatableAttributes; this.translatableAttributes = config.translatableAttributes;
if (config.templates) { if (config.templates) {
if (config.templates instanceof Document || typeof config.templates === "string") { this.addTemplates(config.templates);
this.addTemplates(config.templates);
} else {
for (const name in config.templates) {
this.addTemplate(name, config.templates[name]);
}
}
} }
this.getRawTemplate = config.getTemplate;
} }
addTemplate(name: string, template: string | Element) { addTemplate(name: string, template: string | Element) {
@@ -80,7 +100,7 @@ export class TemplateSet {
getTemplate(name: string): Template { getTemplate(name: string): Template {
if (!(name in this.templates)) { if (!(name in this.templates)) {
const rawTemplate = this.getRawTemplate?.(name) || this.rawTemplates[name]; const rawTemplate = this.rawTemplates[name];
if (rawTemplate === undefined) { if (rawTemplate === undefined) {
let extraInfo = ""; let extraInfo = "";
try { try {
-1
View File
@@ -8,7 +8,6 @@ type BaseType =
| typeof Date | typeof Date
| typeof Object | typeof Object
| typeof Array | typeof Array
| typeof Function
| true | true
| "*"; | "*";
+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.2.9"; export const version = "2.2.6";
-13
View File
@@ -57,19 +57,6 @@ exports[`app can configure an app with props 1`] = `
}" }"
`; `;
exports[`app can load templates from an object name-string 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let block1 = createBlock(\`<div class=\\"hello\\">hello</div>\`);
return function template(ctx, node, key = \\"\\") {
return block1();
}
}"
`;
exports[`app can mount app in an iframe 1`] = ` exports[`app can mount app in an iframe 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
-17
View File
@@ -167,21 +167,4 @@ describe("app", () => {
] ]
`); `);
}); });
test("can load templates from an object name-string", async () => {
const templates = {
hello: `<div class="hello">hello</div>`,
world: `<div>world</div>`,
};
class SomeComponent extends Component {
static template = "hello";
}
const app = new App(SomeComponent, { templates });
await app.mount(fixture);
expect(fixture.querySelector(".hello")).toBeDefined();
// Only the "hello" template is used, so the "world" template is not yet loaded
expect(Object.keys(app.templates)).toEqual(["hello"]);
expect(Object.keys(app.rawTemplates)).toEqual(["hello", "world"]);
});
}); });
@@ -101,55 +101,3 @@ exports[`loading templates can load a few templates from an XMLDocument 2`] = `
} }
}" }"
`; `;
exports[`loading templates getTemplate: element returned (2) 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let block1 = createBlock(\`<div>Hello World!</div>\`);
return function template(ctx, node, key = \\"\\") {
return block1();
}
}"
`;
exports[`loading templates getTemplate: element returned 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let block1 = createBlock(\`<div>Hello World!</div>\`);
return function template(ctx, node, key = \\"\\") {
return block1();
}
}"
`;
exports[`loading templates getTemplate: template string returned 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let block1 = createBlock(\`<div>Hello World!</div>\`);
return function template(ctx, node, key = \\"\\") {
return block1();
}
}"
`;
exports[`loading templates getTemplate: undefined returned 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let block1 = createBlock(\`<div>Hello World!</div>\`);
return function template(ctx, node, key = \\"\\") {
return block1();
}
}"
`;
-58
View File
@@ -78,62 +78,4 @@ describe("loading templates", () => {
context.addTemplates(xml); context.addTemplates(xml);
expect(Object.keys(context.rawTemplates)).toEqual([]); expect(Object.keys(context.rawTemplates)).toEqual([]);
}); });
test("getTemplate: element returned", () => {
const context = new TestContext({
getTemplate: (name) => {
if (name === "main") {
const data = `<div>Hello World!</div>`;
const xml = new DOMParser().parseFromString(data, "text/xml");
return xml.firstChild as Element;
}
return;
},
});
const result = context.renderToString("main");
expect(result).toBe("<div>Hello World!</div>");
});
test("getTemplate: element returned (2)", () => {
const context = new TestContext({
getTemplate: (name) => {
if (name === "main") {
const doc = new Document();
const div = doc.createElement("div");
div.append(doc.createTextNode("Hello World!"));
return div;
}
return;
},
});
const result = context.renderToString("main");
expect(result).toBe("<div>Hello World!</div>");
});
test("getTemplate: template string returned", () => {
const context = new TestContext({
getTemplate: (name) => {
if (name === "main") {
return `<div>Hello World!</div>`;
}
return;
},
});
const result = context.renderToString("main");
expect(result).toBe("<div>Hello World!</div>");
});
test("getTemplate: undefined returned", () => {
const context = new TestContext({
getTemplate: () => {},
});
const data = `<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<div t-name="main">Hello World!</div>
</templates>`;
const xml = new DOMParser().parseFromString(data, "text/xml");
context.addTemplates(xml);
const result = context.renderToString("main");
expect(result).toBe("<div>Hello World!</div>");
});
}); });
@@ -1066,45 +1066,6 @@ exports[`slots multiple slots containing components 3`] = `
}" }"
`; `;
exports[`slots named slot inside named slot in t-component 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { capture, markRaw } = helpers;
const comp1 = app.createComponent(null, false, true, false, []);
const comp2 = app.createComponent(\`Child\`, true, true, false, []);
function slot1(ctx, node, key = \\"\\") {
const b2 = text(\` outer \`);
const ctx2 = capture(ctx);
const Comp1 = ctx['Child'];
const b4 = toggler(Comp1, comp1({slots: markRaw({'brol': {__render: slot2.bind(this), __ctx: ctx2}})}, (Comp1).name + key + \`__1\`, node, this, Comp1));
return multi([b2, b4]);
}
function slot2(ctx, node, key = \\"\\") {
return text(ctx['value']);
}
return function template(ctx, node, key = \\"\\") {
const ctx1 = capture(ctx);
return comp2({slots: markRaw({'brol': {__render: slot1.bind(this), __ctx: ctx1}})}, key + \`__2\`, node, this, null);
}
}"
`;
exports[`slots named slot inside named slot in t-component 2`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { callSlot } = helpers;
return function template(ctx, node, key = \\"\\") {
return callSlot(ctx, node, key, 'brol', false, {});
}
}"
`;
exports[`slots named slot inside slot 1`] = ` exports[`slots named slot inside slot 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
@@ -44,26 +44,7 @@ exports[`t-model directive .trim modifier 1`] = `
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { toNumber } = helpers; let { toNumber } = helpers;
let block1 = createBlock(\`<div><input block-property-0=\\"value\\" block-handler-1=\\"change\\"/><span><block-text-2/></span></div>\`); let block1 = createBlock(\`<div><input block-property-0=\\"value\\" block-handler-1=\\"input\\"/><span><block-text-2/></span></div>\`);
return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['state'];
const expr1 = 'text';
let prop1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value.trim(); }];
let txt1 = ctx['state'].text;
return block1([prop1, hdlr1, txt1]);
}
}"
`;
exports[`t-model directive .trim modifier implies .lazy modifier 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { toNumber } = helpers;
let block1 = createBlock(\`<div><input block-property-0=\\"value\\" block-handler-1=\\"change\\"/><span><block-text-2/></span></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['state']; const bExpr1 = ctx['state'];
+2 -2
View File
@@ -594,7 +594,7 @@ describe("props validation", () => {
test("props: can be defined with a boolean", async () => { test("props: can be defined with a boolean", async () => {
class SubComp extends Component { class SubComp extends Component {
static props = { message: true } as const; static props = { message: true };
} }
expect(() => { expect(() => {
validateProps(SubComp as any, {}); validateProps(SubComp as any, {});
@@ -636,7 +636,7 @@ describe("props validation", () => {
test("props: extra props cause an error, part 2", async () => { test("props: extra props cause an error, part 2", async () => {
class SubComp extends Component { class SubComp extends Component {
static props = { message: true } as const; static props = { message: true };
} }
expect(() => { expect(() => {
validateProps(SubComp as any, { message: 1, flag: true }); validateProps(SubComp as any, { message: 1, flag: true });
-25
View File
@@ -1673,31 +1673,6 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("<div><div><p>Ablip</p><div><p>Bblip</p></div></div></div>"); expect(fixture.innerHTML).toBe("<div><div><p>Ablip</p><div><p>Bblip</p></div></div></div>");
}); });
test("named slot inside named slot in t-component", async () => {
class Child extends Component {
static template = xml`<t t-slot="brol"/>`;
}
class Parent extends Component {
static template = xml`
<Child>
<t t-set-slot="brol">
outer
<t t-component="Child">
<t t-set-slot="brol">
<t t-esc="value"/>
</t>
</t>
</t>
</Child>`;
static components = { Child };
Child = Child;
value = "inner";
}
await mount(Parent, fixture);
expect(fixture.innerHTML).toBe(" outer inner");
});
test("can render only empty slot", async () => { test("can render only empty slot", async () => {
class Parent extends Component { class Parent extends Component {
static template = xml`<t t-slot="default"/>`; static template = xml`<t t-slot="default"/>`;
-26
View File
@@ -331,32 +331,6 @@ describe("t-model directive", () => {
expect(fixture.innerHTML).toBe("<div><input><span>test</span></div>"); expect(fixture.innerHTML).toBe("<div><input><span>test</span></div>");
}); });
test(".trim modifier implies .lazy modifier", async () => {
class SomeComponent extends Component {
static template = xml`
<div>
<input t-model.trim="state.text"/>
<span><t t-esc="state.text"/></span>
</div>
`;
state = useState({ text: "" });
}
const comp = await mount(SomeComponent, fixture);
expect(fixture.innerHTML).toBe("<div><input><span></span></div>");
const input = fixture.querySelector("input")!;
input.value = "test ";
input.dispatchEvent(new Event("input"));
await nextTick();
expect(comp.state.text).toBe("");
expect(fixture.innerHTML).toBe("<div><input><span></span></div>");
input.dispatchEvent(new Event("change"));
await nextTick();
expect(comp.state.text).toBe("test");
expect(fixture.innerHTML).toBe("<div><input><span>test</span></div>");
});
test(".number modifier", async () => { test(".number modifier", async () => {
class SomeComponent extends Component { class SomeComponent extends Component {
static template = xml` static template = xml`