mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 875ebdcfb0 | |||
| 2e07799250 | |||
| 0d9d21a5c1 | |||
| 836e12b1c5 |
@@ -376,15 +376,16 @@ 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)
|
||||
or an object (the current item will be the current key).
|
||||
`t-foreach` can iterate on any iterable, and also has special support for objects
|
||||
and maps, it will expose the key of the current iteration as the contents of the
|
||||
`t-as`, and the corresponding value with the same name and the suffix `_value`.
|
||||
|
||||
In addition to the name passed via t-as, `t-foreach` provides a few other
|
||||
variables for various data points (note: `$as` will be replaced with the name
|
||||
passed to `t-as`):
|
||||
In addition to the name passed via t-as, `t-foreach` provides a few other useful
|
||||
variables (note: `$as` will be replaced with the name passed to `t-as`):
|
||||
|
||||
- `$as_value`: the current iteration value, identical to `$as` for lists and
|
||||
integers, but for objects, it provides the value (where `$as` provides the key)
|
||||
- `$as_value`: the current iteration value, identical to `$as` for arrays and
|
||||
other iterables, but for objects and maps, it provides the value (where `$as`
|
||||
provides the key)
|
||||
- `$as_index`: the current iteration index (the first item of the iteration has index 0)
|
||||
- `$as_first`: whether the current item is the first of the iteration
|
||||
(equivalent to `$as_index == 0`)
|
||||
|
||||
+98
-66
@@ -122,14 +122,16 @@ function handleError(params) {
|
||||
}
|
||||
const node = "node" in params ? params.node : params.fiber.node;
|
||||
const fiber = "fiber" in params ? params.fiber : node.fiber;
|
||||
// resets the fibers on components if possible. This is important so that
|
||||
// new renderings can be properly included in the initial one, if any.
|
||||
let current = fiber;
|
||||
do {
|
||||
current.node.fiber = current;
|
||||
current = current.parent;
|
||||
} while (current);
|
||||
fibersInError.set(fiber.root, error);
|
||||
if (fiber) {
|
||||
// resets the fibers on components if possible. This is important so that
|
||||
// new renderings can be properly included in the initial one, if any.
|
||||
let current = fiber;
|
||||
do {
|
||||
current.node.fiber = current;
|
||||
current = current.parent;
|
||||
} while (current);
|
||||
fibersInError.set(fiber.root, error);
|
||||
}
|
||||
const handled = _handleError(node, error);
|
||||
if (!handled) {
|
||||
console.warn(`[Owl] Unhandled error. Destroying the root component`);
|
||||
@@ -311,20 +313,13 @@ function updateClass(val, oldVal) {
|
||||
* @returns a batched version of the original callback
|
||||
*/
|
||||
function batched(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;
|
||||
// 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.
|
||||
// Schedule this before calling the callback so that calls to the batched function
|
||||
// within the callback will proceed only after resetting called to false, and have
|
||||
// a chance to execute the callback again
|
||||
Promise.resolve().then(() => (called = false));
|
||||
callback();
|
||||
let scheduled = false;
|
||||
return async (...args) => {
|
||||
if (!scheduled) {
|
||||
scheduled = true;
|
||||
await Promise.resolve();
|
||||
scheduled = false;
|
||||
callback(...args);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1657,8 +1652,7 @@ function cancelFibers(fibers) {
|
||||
let node = fiber.node;
|
||||
fiber.render = throwOnRender;
|
||||
if (node.status === 0 /* NEW */) {
|
||||
node.destroy();
|
||||
delete node.parent.children[node.parentKey];
|
||||
node.cancel();
|
||||
}
|
||||
node.fiber = null;
|
||||
if (fiber.bdom) {
|
||||
@@ -2385,6 +2379,9 @@ class ComponentNode {
|
||||
}
|
||||
}
|
||||
async render(deep) {
|
||||
if (this.status >= 2 /* CANCELLED */) {
|
||||
return;
|
||||
}
|
||||
let current = this.fiber;
|
||||
if (current && (current.root.locked || current.bdom === true)) {
|
||||
await Promise.resolve();
|
||||
@@ -2410,7 +2407,7 @@ class ComponentNode {
|
||||
this.fiber = fiber;
|
||||
this.app.scheduler.addFiber(fiber);
|
||||
await Promise.resolve();
|
||||
if (this.status === 2 /* DESTROYED */) {
|
||||
if (this.status >= 2 /* CANCELLED */) {
|
||||
return;
|
||||
}
|
||||
// We only want to actually render the component if the following two
|
||||
@@ -2428,6 +2425,18 @@ class ComponentNode {
|
||||
fiber.render();
|
||||
}
|
||||
}
|
||||
cancel() {
|
||||
this._cancel();
|
||||
delete this.parent.children[this.parentKey];
|
||||
this.app.scheduler.scheduleDestroy(this);
|
||||
}
|
||||
_cancel() {
|
||||
this.status = 2 /* CANCELLED */;
|
||||
const children = this.children;
|
||||
for (let childKey in children) {
|
||||
children[childKey]._cancel();
|
||||
}
|
||||
}
|
||||
destroy() {
|
||||
let shouldRemove = this.status === 1 /* MOUNTED */;
|
||||
this._destroy();
|
||||
@@ -2455,7 +2464,7 @@ class ComponentNode {
|
||||
this.app.handleError({ error: e, node: this });
|
||||
}
|
||||
}
|
||||
this.status = 2 /* DESTROYED */;
|
||||
this.status = 3 /* DESTROYED */;
|
||||
}
|
||||
async updateAndRender(props, parentFiber) {
|
||||
this.nextProps = props;
|
||||
@@ -2988,12 +2997,22 @@ function prepareList(collection) {
|
||||
keys = collection;
|
||||
values = collection;
|
||||
}
|
||||
else if (collection) {
|
||||
values = Object.keys(collection);
|
||||
keys = Object.values(collection);
|
||||
else if (collection instanceof Map) {
|
||||
keys = [...collection.keys()];
|
||||
values = [...collection.values()];
|
||||
}
|
||||
else if (collection && typeof collection === "object") {
|
||||
if (Symbol.iterator in collection) {
|
||||
keys = [...collection];
|
||||
values = keys;
|
||||
}
|
||||
else {
|
||||
values = Object.keys(collection);
|
||||
keys = Object.values(collection);
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new OwlError("Invalid loop expression");
|
||||
throw new OwlError(`Invalid loop expression: "${collection}" is not iterable`);
|
||||
}
|
||||
const n = values.length;
|
||||
return [keys, values, n, new Array(n)];
|
||||
@@ -4820,10 +4839,10 @@ function parseNode(node, ctx) {
|
||||
parseTCall(node, ctx) ||
|
||||
parseTCallBlock(node) ||
|
||||
parseTEscNode(node, ctx) ||
|
||||
parseTOutNode(node, ctx) ||
|
||||
parseTKey(node, ctx) ||
|
||||
parseTTranslation(node, ctx) ||
|
||||
parseTSlot(node, ctx) ||
|
||||
parseTOutNode(node, ctx) ||
|
||||
parseComponent(node, ctx) ||
|
||||
parseDOMNode(node, ctx) ||
|
||||
parseTSetNode(node, ctx) ||
|
||||
@@ -4936,10 +4955,8 @@ function parseDOMNode(node, ctx) {
|
||||
const typeAttr = node.getAttribute("type");
|
||||
const isInput = tagName === "input";
|
||||
const isSelect = tagName === "select";
|
||||
const isTextarea = tagName === "textarea";
|
||||
const isCheckboxInput = isInput && typeAttr === "checkbox";
|
||||
const isRadioInput = isInput && typeAttr === "radio";
|
||||
const isOtherInput = isInput && !isCheckboxInput && !isRadioInput;
|
||||
const hasLazyMod = attr.includes(".lazy");
|
||||
const hasNumberMod = attr.includes(".number");
|
||||
const hasTrimMod = attr.includes(".trim");
|
||||
@@ -4951,8 +4968,8 @@ function parseDOMNode(node, ctx) {
|
||||
specialInitTargetAttr: isRadioInput ? "checked" : null,
|
||||
eventType,
|
||||
hasDynamicChildren: false,
|
||||
shouldTrim: hasTrimMod && (isOtherInput || isTextarea),
|
||||
shouldNumberize: hasNumberMod && (isOtherInput || isTextarea),
|
||||
shouldTrim: hasTrimMod,
|
||||
shouldNumberize: hasNumberMod,
|
||||
};
|
||||
if (isSelect) {
|
||||
// don't pollute the original ctx
|
||||
@@ -5015,9 +5032,6 @@ function parseTEscNode(node, ctx) {
|
||||
content: [tesc],
|
||||
};
|
||||
}
|
||||
if (ast.type === 11 /* TComponent */) {
|
||||
throw new OwlError("t-esc is not supported on Component nodes");
|
||||
}
|
||||
return tesc;
|
||||
}
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -5459,19 +5473,21 @@ function normalizeTIf(el) {
|
||||
*
|
||||
* @param el the element containing the tree that should be normalized
|
||||
*/
|
||||
function normalizeTEsc(el) {
|
||||
const elements = [...el.querySelectorAll("[t-esc]")].filter((el) => el.tagName[0] === el.tagName[0].toUpperCase() || el.hasAttribute("t-component"));
|
||||
for (const el of elements) {
|
||||
if (el.childNodes.length) {
|
||||
throw new OwlError("Cannot have t-esc on a component that already has content");
|
||||
function normalizeTEscTOut(el) {
|
||||
for (const d of ["t-esc", "t-out"]) {
|
||||
const elements = [...el.querySelectorAll(`[${d}]`)].filter((el) => el.tagName[0] === el.tagName[0].toUpperCase() || el.hasAttribute("t-component"));
|
||||
for (const el of elements) {
|
||||
if (el.childNodes.length) {
|
||||
throw new OwlError(`Cannot have ${d} on a component that already has content`);
|
||||
}
|
||||
const value = el.getAttribute(d);
|
||||
el.removeAttribute(d);
|
||||
const t = el.ownerDocument.createElement("t");
|
||||
if (value != null) {
|
||||
t.setAttribute(d, value);
|
||||
}
|
||||
el.appendChild(t);
|
||||
}
|
||||
const value = el.getAttribute("t-esc");
|
||||
el.removeAttribute("t-esc");
|
||||
const t = el.ownerDocument.createElement("t");
|
||||
if (value != null) {
|
||||
t.setAttribute("t-esc", value);
|
||||
}
|
||||
el.appendChild(t);
|
||||
}
|
||||
}
|
||||
/**
|
||||
@@ -5482,7 +5498,7 @@ function normalizeTEsc(el) {
|
||||
*/
|
||||
function normalizeXML(el) {
|
||||
normalizeTIf(el);
|
||||
normalizeTEsc(el);
|
||||
normalizeTEscTOut(el);
|
||||
}
|
||||
/**
|
||||
* Parses an XML string into an XML document, throwing errors on parser errors
|
||||
@@ -5535,7 +5551,7 @@ function compile(template, options = {}) {
|
||||
}
|
||||
|
||||
// do not modify manually. This file is generated by the release script.
|
||||
const version = "2.1.3";
|
||||
const version = "2.2";
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Scheduler
|
||||
@@ -5545,11 +5561,18 @@ class Scheduler {
|
||||
this.tasks = new Set();
|
||||
this.frame = 0;
|
||||
this.delayedRenders = [];
|
||||
this.cancelledNodes = new Set();
|
||||
this.requestAnimationFrame = Scheduler.requestAnimationFrame;
|
||||
}
|
||||
addFiber(fiber) {
|
||||
this.tasks.add(fiber.root);
|
||||
}
|
||||
scheduleDestroy(node) {
|
||||
this.cancelledNodes.add(node);
|
||||
if (this.frame === 0) {
|
||||
this.frame = this.requestAnimationFrame(() => this.processTasks());
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Process all current tasks. This only applies to the fibers that are ready.
|
||||
* Other tasks are left unchanged.
|
||||
@@ -5559,21 +5582,28 @@ class Scheduler {
|
||||
let renders = this.delayedRenders;
|
||||
this.delayedRenders = [];
|
||||
for (let f of renders) {
|
||||
if (f.root && f.node.status !== 2 /* DESTROYED */ && f.node.fiber === f) {
|
||||
if (f.root && f.node.status !== 3 /* DESTROYED */ && f.node.fiber === f) {
|
||||
f.render();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.frame === 0) {
|
||||
this.frame = this.requestAnimationFrame(() => {
|
||||
this.frame = 0;
|
||||
this.tasks.forEach((fiber) => this.processFiber(fiber));
|
||||
for (let task of this.tasks) {
|
||||
if (task.node.status === 2 /* DESTROYED */) {
|
||||
this.tasks.delete(task);
|
||||
}
|
||||
}
|
||||
});
|
||||
this.frame = this.requestAnimationFrame(() => this.processTasks());
|
||||
}
|
||||
}
|
||||
processTasks() {
|
||||
this.frame = 0;
|
||||
for (let node of this.cancelledNodes) {
|
||||
node._destroy();
|
||||
}
|
||||
this.cancelledNodes.clear();
|
||||
for (let task of this.tasks) {
|
||||
this.processFiber(task);
|
||||
}
|
||||
for (let task of this.tasks) {
|
||||
if (task.node.status === 3 /* DESTROYED */) {
|
||||
this.tasks.delete(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
processFiber(fiber) {
|
||||
@@ -5586,7 +5616,7 @@ class Scheduler {
|
||||
this.tasks.delete(fiber);
|
||||
return;
|
||||
}
|
||||
if (fiber.node.status === 2 /* DESTROYED */) {
|
||||
if (fiber.node.status === 3 /* DESTROYED */) {
|
||||
this.tasks.delete(fiber);
|
||||
return;
|
||||
}
|
||||
@@ -5810,9 +5840,11 @@ function status(component) {
|
||||
switch (component.__owl__.status) {
|
||||
case 0 /* NEW */:
|
||||
return "new";
|
||||
case 2 /* CANCELLED */:
|
||||
return "cancelled";
|
||||
case 1 /* MOUNTED */:
|
||||
return "mounted";
|
||||
case 2 /* DESTROYED */:
|
||||
case 3 /* DESTROYED */:
|
||||
return "destroyed";
|
||||
}
|
||||
}
|
||||
@@ -5952,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 };
|
||||
|
||||
|
||||
__info__.date = '2023-06-28T09:17:13.630Z';
|
||||
__info__.hash = '432ff44';
|
||||
__info__.date = '2023-07-19T13:22:24.480Z';
|
||||
__info__.hash = '2e07799';
|
||||
__info__.url = 'https://github.com/odoo/owl';
|
||||
|
||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@odoo/owl",
|
||||
"version": "2.1.4",
|
||||
"version": "2.2.1",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@odoo/owl",
|
||||
"version": "2.1.4",
|
||||
"version": "2.2.1",
|
||||
"description": "Odoo Web Library (OWL)",
|
||||
"main": "dist/owl.cjs.js",
|
||||
"module": "dist/owl.es.js",
|
||||
|
||||
+18
-19
@@ -235,10 +235,10 @@ function parseNode(node: Node, ctx: ParsingContext): AST | null {
|
||||
parseTCall(node, ctx) ||
|
||||
parseTCallBlock(node, ctx) ||
|
||||
parseTEscNode(node, ctx) ||
|
||||
parseTOutNode(node, ctx) ||
|
||||
parseTKey(node, ctx) ||
|
||||
parseTTranslation(node, ctx) ||
|
||||
parseTSlot(node, ctx) ||
|
||||
parseTOutNode(node, ctx) ||
|
||||
parseComponent(node, ctx) ||
|
||||
parseDOMNode(node, ctx) ||
|
||||
parseTSetNode(node, ctx) ||
|
||||
@@ -444,9 +444,6 @@ function parseTEscNode(node: Element, ctx: ParsingContext): AST | null {
|
||||
content: [tesc],
|
||||
};
|
||||
}
|
||||
if (ast.type === ASTType.TComponent) {
|
||||
throw new OwlError("t-esc is not supported on Component nodes");
|
||||
}
|
||||
return tesc;
|
||||
}
|
||||
|
||||
@@ -941,21 +938,23 @@ function normalizeTIf(el: Element) {
|
||||
*
|
||||
* @param el the element containing the tree that should be normalized
|
||||
*/
|
||||
function normalizeTEsc(el: Element) {
|
||||
const elements = [...el.querySelectorAll("[t-esc]")].filter(
|
||||
(el) => el.tagName[0] === el.tagName[0].toUpperCase() || el.hasAttribute("t-component")
|
||||
);
|
||||
for (const el of elements) {
|
||||
if (el.childNodes.length) {
|
||||
throw new OwlError("Cannot have t-esc on a component that already has content");
|
||||
function normalizeTEscTOut(el: Element) {
|
||||
for (const d of ["t-esc", "t-out"]) {
|
||||
const elements = [...el.querySelectorAll(`[${d}]`)].filter(
|
||||
(el) => el.tagName[0] === el.tagName[0].toUpperCase() || el.hasAttribute("t-component")
|
||||
);
|
||||
for (const el of elements) {
|
||||
if (el.childNodes.length) {
|
||||
throw new OwlError(`Cannot have ${d} on a component that already has content`);
|
||||
}
|
||||
const value = el.getAttribute(d);
|
||||
el.removeAttribute(d);
|
||||
const t = el.ownerDocument.createElement("t");
|
||||
if (value != null) {
|
||||
t.setAttribute(d, value);
|
||||
}
|
||||
el.appendChild(t);
|
||||
}
|
||||
const value = el.getAttribute("t-esc");
|
||||
el.removeAttribute("t-esc");
|
||||
const t = el.ownerDocument.createElement("t");
|
||||
if (value != null) {
|
||||
t.setAttribute("t-esc", value);
|
||||
}
|
||||
el.appendChild(t);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -967,7 +966,7 @@ function normalizeTEsc(el: Element) {
|
||||
*/
|
||||
function normalizeXML(el: Element) {
|
||||
normalizeTIf(el);
|
||||
normalizeTEsc(el);
|
||||
normalizeTEscTOut(el);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -93,17 +93,6 @@ function normalizeNode(node: HTMLElement | Text) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode 2 numbers and 1 boolean in a number, using 31 bits:
|
||||
* n1 => encoded in 16 most significant bits
|
||||
* n2 => encoded in 15 next bits
|
||||
* boolean => encoded in last significant bit.
|
||||
* This code assumes that n1 and n2 are small enough to fit in that number of bits
|
||||
*/
|
||||
function encodeValue(n1: number, n2: number, b: boolean): number {
|
||||
return (((n1 << 15) | n2) << 1) | (b ? 1 : 0);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// building a intermediate tree
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -317,7 +306,7 @@ interface IndexedLocation extends Location {
|
||||
|
||||
interface Child {
|
||||
parentRefIdx: number;
|
||||
afterRefIdx: number;
|
||||
afterRefIdx?: number;
|
||||
isOnlyChild?: boolean;
|
||||
}
|
||||
|
||||
@@ -385,7 +374,6 @@ function updateCtx(ctx: BlockCtx, tree: IntermediateTree) {
|
||||
// tree is the parentnode here
|
||||
ctx.children[info.idx] = {
|
||||
parentRefIdx: info.refIdx!,
|
||||
afterRefIdx: 0,
|
||||
isOnlyChild: true,
|
||||
};
|
||||
} else {
|
||||
@@ -513,6 +501,7 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass {
|
||||
}));
|
||||
const locN = locations.length;
|
||||
const childN = children.length;
|
||||
const childrenLocs = children;
|
||||
const isDynamic = refN > 0;
|
||||
|
||||
// these values are defined here to make them faster to lookup in the class
|
||||
@@ -567,19 +556,6 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass {
|
||||
}
|
||||
|
||||
if (isDynamic) {
|
||||
const nextSibling = nodeGetNextSibling;
|
||||
const firstChild = nodeGetFirstChild;
|
||||
const bitPackedCollectors = new Uint32Array(
|
||||
collectors.map((c) => {
|
||||
return encodeValue(c.idx, c.prevIdx, c.getVal === nextSibling);
|
||||
})
|
||||
);
|
||||
const childrenLocs = new Uint32Array(
|
||||
children.map((c) => {
|
||||
return encodeValue(c.afterRefIdx, c.parentRefIdx, Boolean(c.isOnlyChild));
|
||||
})
|
||||
);
|
||||
|
||||
Block.prototype.mount = function mount(parent: HTMLElement, afterNode: Node | null) {
|
||||
const el = nodeCloneNode.call(template, true);
|
||||
// collecting references
|
||||
@@ -587,17 +563,12 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass {
|
||||
this.refs = refs;
|
||||
refs[0] = el;
|
||||
for (let i = 0; i < colN; i++) {
|
||||
let info = bitPackedCollectors[i];
|
||||
// decode info
|
||||
const fn = (info & 1) === 1 ? nextSibling : firstChild;
|
||||
info = info >> 1;
|
||||
const prevIdx = info & 0b111111111111111;
|
||||
const idx = info >> 15;
|
||||
refs[idx] = fn.call(refs[prevIdx]);
|
||||
const w = collectors[i];
|
||||
refs[w.idx] = w.getVal.call(refs[w.prevIdx]);
|
||||
}
|
||||
|
||||
// applying data to all update points
|
||||
if (locN !== 0) {
|
||||
if (locN) {
|
||||
const data = this.data!;
|
||||
for (let i = 0; i < locN; i++) {
|
||||
const loc = locations[i];
|
||||
@@ -608,21 +579,15 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass {
|
||||
nodeInsertBefore.call(parent, el, afterNode);
|
||||
|
||||
// preparing all children
|
||||
if (childN !== 0) {
|
||||
if (childN) {
|
||||
const children = this.children;
|
||||
for (let i = 0; i < childN; i++) {
|
||||
const child = children![i];
|
||||
if (child !== undefined) {
|
||||
let info = childrenLocs[i];
|
||||
// decode info
|
||||
const isOnlyChild = info & 1;
|
||||
info = info >> 1;
|
||||
const parentRefIdx = info & 0b111111111111111;
|
||||
const afterRefIdx = info >> 15;
|
||||
|
||||
const afterNode = afterRefIdx !== 0 ? refs[afterRefIdx] : null;
|
||||
child.isOnlyChild = isOnlyChild as any;
|
||||
child.mount(refs[parentRefIdx] as any, afterNode);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -636,7 +601,7 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass {
|
||||
}
|
||||
const refs = this.refs!;
|
||||
// update texts/attributes/
|
||||
if (locN !== 0) {
|
||||
if (locN) {
|
||||
const data1 = this.data!;
|
||||
const data2 = other.data!;
|
||||
for (let i = 0; i < locN; i++) {
|
||||
@@ -651,14 +616,14 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass {
|
||||
}
|
||||
|
||||
// update children
|
||||
if (childN !== 0) {
|
||||
if (childN) {
|
||||
let children1 = this.children;
|
||||
const children2 = other.children;
|
||||
for (let i = 0; i < childN; i++) {
|
||||
const child1 = children1![i];
|
||||
const child2 = children2![i];
|
||||
if (child1 !== undefined) {
|
||||
if (child2 !== undefined) {
|
||||
if (child1) {
|
||||
if (child2) {
|
||||
child1.patch(child2, withBeforeRemove);
|
||||
} else {
|
||||
if (withBeforeRemove) {
|
||||
@@ -667,15 +632,10 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass {
|
||||
child1.remove();
|
||||
children1![i] = undefined;
|
||||
}
|
||||
} else if (child2 !== undefined) {
|
||||
let info = childrenLocs[i];
|
||||
// decode info
|
||||
info = info >> 1;
|
||||
const parentRefIdx = info & 0b111111111111111;
|
||||
const afterRefIdx = info >> 15;
|
||||
|
||||
const afterNode = afterRefIdx !== 0 ? refs[afterRefIdx] : null;
|
||||
child2.mount(refs[parentRefIdx] as any, afterNode);
|
||||
} else if (child2) {
|
||||
const loc = childrenLocs[i];
|
||||
const afterNode = loc.afterRefIdx ? refs[loc.afterRefIdx] : null;
|
||||
child2.mount(refs[loc.parentRefIdx] as any, afterNode);
|
||||
children1![i] = child2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ function createElementHandler(evName: string, capture: boolean = false): EventHa
|
||||
}
|
||||
|
||||
function remove(this: HTMLElement) {
|
||||
(this as any)[eventKey] = false;
|
||||
delete (this as any)[eventKey];
|
||||
this.removeEventListener(evName, listener, { capture });
|
||||
}
|
||||
function update(this: HTMLElement, data: any) {
|
||||
|
||||
@@ -28,7 +28,7 @@ class VList {
|
||||
this.anchor = _anchor;
|
||||
nodeInsertBefore.call(parent, _anchor, afterNode);
|
||||
const l = children.length;
|
||||
if (l !== 0) {
|
||||
if (l) {
|
||||
const mount = children[0].mount;
|
||||
for (let i = 0; i < l; i++) {
|
||||
mount.call(children[i], parent, _anchor);
|
||||
@@ -186,7 +186,7 @@ class VList {
|
||||
} else {
|
||||
for (let i = startIdx1; i <= endIdx1; i++) {
|
||||
let ch = ch1[i];
|
||||
if (ch !== null) {
|
||||
if (ch) {
|
||||
if (withBeforeRemove) {
|
||||
beforeRemove.call(ch);
|
||||
}
|
||||
@@ -200,7 +200,7 @@ class VList {
|
||||
beforeRemove() {
|
||||
const children = this.children;
|
||||
const l = children.length;
|
||||
if (l !== 0) {
|
||||
if (l) {
|
||||
const beforeRemove = children[0].beforeRemove;
|
||||
for (let i = 0; i < l; i++) {
|
||||
beforeRemove.call(children[i]);
|
||||
@@ -215,7 +215,7 @@ class VList {
|
||||
} else {
|
||||
const children = this.children;
|
||||
const l = children.length;
|
||||
if (l !== 0) {
|
||||
if (l) {
|
||||
const remove = children[0].remove;
|
||||
for (let i = 0; i < l; i++) {
|
||||
remove.call(children[i]);
|
||||
@@ -240,7 +240,7 @@ export function list(children: VNode[]): VNode<VList> {
|
||||
}
|
||||
|
||||
function createMapping(ch1: any[], startIdx1: number, endIdx2: number): { [key: string]: any } {
|
||||
const mapping: any = {};
|
||||
let mapping: any = {};
|
||||
for (let i = startIdx1; i <= endIdx2; i++) {
|
||||
mapping[ch1[i].key] = i;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ export class VMulti {
|
||||
const anchors = new Array(l);
|
||||
for (let i = 0; i < l; i++) {
|
||||
let child = children[i];
|
||||
if (child !== undefined) {
|
||||
if (child) {
|
||||
child.mount(parent, afterNode);
|
||||
} else {
|
||||
const childAnchor = document.createTextNode("");
|
||||
@@ -44,7 +44,7 @@ export class VMulti {
|
||||
const anchors = this.anchors;
|
||||
for (let i = 0, l = children.length; i < l; i++) {
|
||||
let child = children[i];
|
||||
if (child !== undefined) {
|
||||
if (child) {
|
||||
child.moveBeforeDOMNode(node, parent);
|
||||
} else {
|
||||
const anchor = anchors![i];
|
||||
@@ -56,14 +56,14 @@ export class VMulti {
|
||||
moveBeforeVNode(other: VMulti | null, afterNode: Node | null) {
|
||||
if (other) {
|
||||
const next = other!.children[0];
|
||||
afterNode = (next !== undefined ? next.firstNode() : other!.anchors![0]) || null;
|
||||
afterNode = (next ? next.firstNode() : other!.anchors![0]) || null;
|
||||
}
|
||||
const children = this.children;
|
||||
const parent = this.parentEl;
|
||||
const anchors = this.anchors;
|
||||
for (let i = 0, l = children.length; i < l; i++) {
|
||||
let child = children[i];
|
||||
if (child !== undefined) {
|
||||
if (child) {
|
||||
child.moveBeforeVNode(null, afterNode);
|
||||
} else {
|
||||
const anchor = anchors![i];
|
||||
@@ -83,8 +83,8 @@ export class VMulti {
|
||||
for (let i = 0, l = children1.length; i < l; i++) {
|
||||
const vn1 = children1[i];
|
||||
const vn2 = children2[i];
|
||||
if (vn1 !== undefined) {
|
||||
if (vn2 !== undefined) {
|
||||
if (vn1) {
|
||||
if (vn2) {
|
||||
vn1.patch(vn2, withBeforeRemove);
|
||||
} else {
|
||||
const afterNode = vn1.firstNode()!;
|
||||
@@ -97,7 +97,7 @@ export class VMulti {
|
||||
vn1.remove();
|
||||
children1[i] = undefined;
|
||||
}
|
||||
} else if (vn2 !== undefined) {
|
||||
} else if (vn2) {
|
||||
children1[i] = vn2;
|
||||
const anchor = anchors[i];
|
||||
vn2.mount(parentEl, anchor);
|
||||
@@ -110,7 +110,7 @@ export class VMulti {
|
||||
const children = this.children;
|
||||
for (let i = 0, l = children.length; i < l; i++) {
|
||||
const child = children[i];
|
||||
if (child !== undefined) {
|
||||
if (child) {
|
||||
child.beforeRemove();
|
||||
}
|
||||
}
|
||||
@@ -125,7 +125,7 @@ export class VMulti {
|
||||
const anchors = this.anchors;
|
||||
for (let i = 0, l = children.length; i < l; i++) {
|
||||
const child = children[i];
|
||||
if (child !== undefined) {
|
||||
if (child) {
|
||||
child.remove();
|
||||
} else {
|
||||
nodeRemoveChild.call(parentEl, anchors![i]);
|
||||
@@ -136,7 +136,7 @@ export class VMulti {
|
||||
|
||||
firstNode(): Node | undefined {
|
||||
const child = this.children[0];
|
||||
return child !== undefined ? child.firstNode() : this.anchors![0];
|
||||
return child ? child.firstNode() : this.anchors![0];
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
|
||||
@@ -30,7 +30,7 @@ function callSlot(
|
||||
const slots = ctx.props.slots || {};
|
||||
const { __render, __ctx, __scope } = slots[name] || {};
|
||||
const slotScope = ObjectCreate(__ctx || {});
|
||||
if (__scope !== undefined) {
|
||||
if (__scope) {
|
||||
slotScope[__scope] = extra;
|
||||
}
|
||||
const slotBDom = __render ? __render(slotScope, parent, key) : null;
|
||||
@@ -60,18 +60,26 @@ function withKey(elem: any, k: string) {
|
||||
return elem;
|
||||
}
|
||||
|
||||
function prepareList(collection: any): [any[], any[], number, any[]] {
|
||||
let keys: any[];
|
||||
let values: any[];
|
||||
function prepareList(collection: unknown): [unknown[], unknown[], number, undefined[]] {
|
||||
let keys: unknown[];
|
||||
let values: unknown[];
|
||||
|
||||
if (Array.isArray(collection)) {
|
||||
keys = collection;
|
||||
values = collection;
|
||||
} else if (collection) {
|
||||
values = Object.keys(collection);
|
||||
keys = Object.values(collection);
|
||||
} else if (collection instanceof Map) {
|
||||
keys = [...collection.keys()];
|
||||
values = [...collection.values()];
|
||||
} else if (collection && typeof collection === "object") {
|
||||
if (Symbol.iterator in collection) {
|
||||
keys = [...(<Iterable<unknown>>collection)];
|
||||
values = keys;
|
||||
} else {
|
||||
values = Object.keys(collection);
|
||||
keys = Object.values(collection);
|
||||
}
|
||||
} else {
|
||||
throw new OwlError("Invalid loop expression");
|
||||
throw new OwlError(`Invalid loop expression: "${collection}" is not iterable`);
|
||||
}
|
||||
const n = values.length;
|
||||
return [keys, values, n, new Array(n)];
|
||||
|
||||
@@ -11,7 +11,7 @@ export type Callback = () => void;
|
||||
export function batched(callback: Callback): Callback {
|
||||
let scheduled = false;
|
||||
return async (...args) => {
|
||||
if (scheduled === false) {
|
||||
if (!scheduled) {
|
||||
scheduled = true;
|
||||
await Promise.resolve();
|
||||
scheduled = false;
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
// do not modify manually. This file is generated by the release script.
|
||||
export const version = "2.1.4";
|
||||
export const version = "2.2.1";
|
||||
|
||||
@@ -77,6 +77,62 @@ exports[`t-foreach iterate on items 1`] = `
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-foreach iterate, Map param 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, 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['value']);;
|
||||
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||
ctx[\`item\`] = v_block1[i1];
|
||||
ctx[\`item_index\`] = i1;
|
||||
ctx[\`item_value\`] = k_block1[i1];
|
||||
const key1 = ctx['item_index'];
|
||||
const b3 = text(\` [\`);
|
||||
const b4 = text(ctx['item_index']);
|
||||
const b5 = text(\`: \`);
|
||||
const b6 = text(ctx['item']);
|
||||
const b7 = text(\` \`);
|
||||
const b8 = text(ctx['item_value']);
|
||||
const b9 = text(\`] \`);
|
||||
c_block1[i1] = withKey(multi([b3, b4, b5, b6, b7, b8, b9]), key1);
|
||||
}
|
||||
return list(c_block1);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-foreach iterate, Set param 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, 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['value']);;
|
||||
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||
ctx[\`item\`] = v_block1[i1];
|
||||
ctx[\`item_index\`] = i1;
|
||||
ctx[\`item_value\`] = k_block1[i1];
|
||||
const key1 = ctx['item_index'];
|
||||
const b3 = text(\` [\`);
|
||||
const b4 = text(ctx['item_index']);
|
||||
const b5 = text(\`: \`);
|
||||
const b6 = text(ctx['item']);
|
||||
const b7 = text(\` \`);
|
||||
const b8 = text(ctx['item_value']);
|
||||
const b9 = text(\`] \`);
|
||||
c_block1[i1] = withKey(multi([b3, b4, b5, b6, b7, b8, b9]), key1);
|
||||
}
|
||||
return list(c_block1);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-foreach iterate, dict param 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
@@ -108,6 +164,62 @@ exports[`t-foreach iterate, dict param 1`] = `
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-foreach iterate, generator param 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, 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['gen']());;
|
||||
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||
ctx[\`item\`] = v_block1[i1];
|
||||
ctx[\`item_index\`] = i1;
|
||||
ctx[\`item_value\`] = k_block1[i1];
|
||||
const key1 = ctx['item_index'];
|
||||
const b3 = text(\` [\`);
|
||||
const b4 = text(ctx['item_index']);
|
||||
const b5 = text(\`: \`);
|
||||
const b6 = text(ctx['item']);
|
||||
const b7 = text(\` \`);
|
||||
const b8 = text(ctx['item_value']);
|
||||
const b9 = text(\`] \`);
|
||||
c_block1[i1] = withKey(multi([b3, b4, b5, b6, b7, b8, b9]), key1);
|
||||
}
|
||||
return list(c_block1);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-foreach iterate, iterable param 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, 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['map'].values());;
|
||||
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||
ctx[\`item\`] = v_block1[i1];
|
||||
ctx[\`item_index\`] = i1;
|
||||
ctx[\`item_value\`] = k_block1[i1];
|
||||
const key1 = ctx['item_index'];
|
||||
const b3 = text(\` [\`);
|
||||
const b4 = text(ctx['item_index']);
|
||||
const b5 = text(\`: \`);
|
||||
const b6 = text(ctx['item']);
|
||||
const b7 = text(\` \`);
|
||||
const b8 = text(ctx['item_value']);
|
||||
const b9 = text(\`] \`);
|
||||
c_block1[i1] = withKey(multi([b3, b4, b5, b6, b7, b8, b9]), key1);
|
||||
}
|
||||
return list(c_block1);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-foreach iterate, position 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
|
||||
@@ -1569,6 +1569,12 @@ describe("qweb parser", () => {
|
||||
);
|
||||
});
|
||||
|
||||
test("component with t-out", async () => {
|
||||
expect(parse(`<MyComponent t-out="someValue"/>`)).toEqual(
|
||||
parse(`<MyComponent><t t-out="someValue"/></MyComponent>`)
|
||||
);
|
||||
});
|
||||
|
||||
test("component with t-esc and content", async () => {
|
||||
expect(() => parse(`<MyComponent t-esc="someValue">Some content</MyComponent>`)).toThrow(
|
||||
"Cannot have t-esc on a component that already has content"
|
||||
|
||||
@@ -105,6 +105,64 @@ describe("t-foreach", () => {
|
||||
expect(renderToString(template, context)).toBe(expected);
|
||||
});
|
||||
|
||||
test("iterate, Map param", () => {
|
||||
const template = `
|
||||
<t t-foreach="value" t-as="item" t-key="item_index">
|
||||
[<t t-esc="item_index"/>: <t t-esc="item"/> <t t-esc="item_value"/>]
|
||||
</t>`;
|
||||
const expected = ` [0: 1 a] [1: 2 b] [2: 3 c] `;
|
||||
const context = {
|
||||
value: new Map([
|
||||
["a", 1],
|
||||
["b", 2],
|
||||
["c", 3],
|
||||
]),
|
||||
};
|
||||
expect(renderToString(template, context)).toBe(expected);
|
||||
});
|
||||
|
||||
test("iterate, Set param", () => {
|
||||
const template = `
|
||||
<t t-foreach="value" t-as="item" t-key="item_index">
|
||||
[<t t-esc="item_index"/>: <t t-esc="item"/> <t t-esc="item_value"/>]
|
||||
</t>`;
|
||||
const expected = ` [0: 1 1] [1: 2 2] [2: 3 3] `;
|
||||
const context = { value: new Set([1, 2, 3]) };
|
||||
expect(renderToString(template, context)).toBe(expected);
|
||||
});
|
||||
|
||||
test("iterate, iterable param", () => {
|
||||
const template = `
|
||||
<t t-foreach="map.values()" t-as="item" t-key="item_index">
|
||||
[<t t-esc="item_index"/>: <t t-esc="item"/> <t t-esc="item_value"/>]
|
||||
</t>`;
|
||||
const expected = ` [0: 1 1] [1: 2 2] [2: 3 3] `;
|
||||
const context = {
|
||||
map: new Map([
|
||||
["a", 1],
|
||||
["b", 2],
|
||||
["c", 3],
|
||||
]),
|
||||
};
|
||||
expect(renderToString(template, context)).toBe(expected);
|
||||
});
|
||||
|
||||
test("iterate, generator param", () => {
|
||||
const template = `
|
||||
<t t-foreach="gen()" t-as="item" t-key="item_index">
|
||||
[<t t-esc="item_index"/>: <t t-esc="item"/> <t t-esc="item_value"/>]
|
||||
</t>`;
|
||||
const expected = ` [0: 1 1] [1: 2 2] [2: 3 3] `;
|
||||
const context = {
|
||||
*gen() {
|
||||
yield 1;
|
||||
yield 2;
|
||||
yield 3;
|
||||
},
|
||||
};
|
||||
expect(renderToString(template, context)).toBe(expected);
|
||||
});
|
||||
|
||||
test("does not pollute the rendering context", () => {
|
||||
const template = `
|
||||
<div>
|
||||
@@ -193,7 +251,9 @@ describe("t-foreach", () => {
|
||||
|
||||
test("throws error if invalid loop expression", () => {
|
||||
const test = `<div><t t-foreach="abc" t-as="item" t-key="item"><span t-key="item_index"/></t></div>`;
|
||||
expect(() => renderToString(test)).toThrow("Invalid loop expression");
|
||||
expect(() => renderToString(test)).toThrow(
|
||||
'Invalid loop expression: "undefined" is not iterable'
|
||||
);
|
||||
});
|
||||
|
||||
test("t-foreach with t-if inside", () => {
|
||||
|
||||
Reference in New Issue
Block a user