mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[IMP] update owl to v2.0.0-alpha.3
This commit is contained in:
@@ -212,7 +212,7 @@
|
|||||||
}
|
}
|
||||||
function makePropSetter(name) {
|
function makePropSetter(name) {
|
||||||
return function setProp(value) {
|
return function setProp(value) {
|
||||||
this[name] = value;
|
this[name] = value || "";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
function isProp(tag, key) {
|
function isProp(tag, key) {
|
||||||
@@ -1501,6 +1501,210 @@
|
|||||||
return new Markup(value);
|
return new Markup(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Allows to get the target of a Reactive (used for making a new Reactive from the underlying object)
|
||||||
|
const TARGET = Symbol("Target");
|
||||||
|
// Escape hatch to prevent reactivity system to turn something into a reactive
|
||||||
|
const SKIP = Symbol("Skip");
|
||||||
|
// Special key to subscribe to, to be notified of key creation/deletion
|
||||||
|
const KEYCHANGES = Symbol("Key changes");
|
||||||
|
const objectToString = Object.prototype.toString;
|
||||||
|
/**
|
||||||
|
* Checks whether a given value can be made into a reactive object.
|
||||||
|
*
|
||||||
|
* @param value the value to check
|
||||||
|
* @returns whether the value can be made reactive
|
||||||
|
*/
|
||||||
|
function canBeMadeReactive(value) {
|
||||||
|
if (typeof value !== "object") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// extract "RawType" from strings like "[object RawType]" => this lets us
|
||||||
|
// ignore many native objects such as Promise (whose toString is [object Promise])
|
||||||
|
// or Date ([object Date]).
|
||||||
|
const rawType = objectToString.call(value).slice(8, -1);
|
||||||
|
return rawType === "Object" || rawType === "Array";
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Mark an object or array so that it is ignored by the reactivity system
|
||||||
|
*
|
||||||
|
* @param value the value to mark
|
||||||
|
* @returns the object itself
|
||||||
|
*/
|
||||||
|
function markRaw(value) {
|
||||||
|
value[SKIP] = true;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Given a reactive objet, return the raw (non reactive) underlying object
|
||||||
|
*
|
||||||
|
* @param value a reactive value
|
||||||
|
* @returns the underlying value
|
||||||
|
*/
|
||||||
|
function toRaw(value) {
|
||||||
|
return value[TARGET] || value;
|
||||||
|
}
|
||||||
|
const targetToKeysToCallbacks = new WeakMap();
|
||||||
|
/**
|
||||||
|
* Observes a given key on a target with an callback. The callback will be
|
||||||
|
* called when the given key changes on the target.
|
||||||
|
*
|
||||||
|
* @param target the target whose key should be observed
|
||||||
|
* @param key the key to observe (or Symbol(KEYCHANGES) for key creation
|
||||||
|
* or deletion)
|
||||||
|
* @param callback the function to call when the key changes
|
||||||
|
*/
|
||||||
|
function observeTargetKey(target, key, callback) {
|
||||||
|
if (!targetToKeysToCallbacks.get(target)) {
|
||||||
|
targetToKeysToCallbacks.set(target, new Map());
|
||||||
|
}
|
||||||
|
const keyToCallbacks = targetToKeysToCallbacks.get(target);
|
||||||
|
if (!keyToCallbacks.get(key)) {
|
||||||
|
keyToCallbacks.set(key, new Set());
|
||||||
|
}
|
||||||
|
keyToCallbacks.get(key).add(callback);
|
||||||
|
if (!callbacksToTargets.has(callback)) {
|
||||||
|
callbacksToTargets.set(callback, new Set());
|
||||||
|
}
|
||||||
|
callbacksToTargets.get(callback).add(target);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Notify Reactives that are observing a given target that a key has changed on
|
||||||
|
* the target.
|
||||||
|
*
|
||||||
|
* @param target target whose Reactives should be notified that the target was
|
||||||
|
* changed.
|
||||||
|
* @param key the key that changed (or Symbol `KEYCHANGES` if a key was created
|
||||||
|
* or deleted)
|
||||||
|
*/
|
||||||
|
function notifyReactives(target, key) {
|
||||||
|
const keyToCallbacks = targetToKeysToCallbacks.get(target);
|
||||||
|
if (!keyToCallbacks) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const callbacks = keyToCallbacks.get(key);
|
||||||
|
if (!callbacks) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Loop on copy because clearReactivesForCallback will modify the set in place
|
||||||
|
for (const callback of [...callbacks]) {
|
||||||
|
clearReactivesForCallback(callback);
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const callbacksToTargets = new WeakMap();
|
||||||
|
/**
|
||||||
|
* Clears all subscriptions of the Reactives associated with a given callback.
|
||||||
|
*
|
||||||
|
* @param callback the callback for which the reactives need to be cleared
|
||||||
|
*/
|
||||||
|
function clearReactivesForCallback(callback) {
|
||||||
|
const targetsToClear = callbacksToTargets.get(callback);
|
||||||
|
if (!targetsToClear) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const target of targetsToClear) {
|
||||||
|
const observedKeys = targetToKeysToCallbacks.get(target);
|
||||||
|
if (!observedKeys) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (const callbacks of observedKeys.values()) {
|
||||||
|
callbacks.delete(callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
targetsToClear.clear();
|
||||||
|
}
|
||||||
|
const reactiveCache = new WeakMap();
|
||||||
|
/**
|
||||||
|
* Creates a reactive proxy for an object. Reading data on the reactive object
|
||||||
|
* subscribes to changes to the data. Writing data on the object will cause the
|
||||||
|
* notify callback to be called if there are suscriptions to that data. Nested
|
||||||
|
* objects and arrays are automatically made reactive as well.
|
||||||
|
*
|
||||||
|
* Whenever you are notified of a change, all subscriptions are cleared, and if
|
||||||
|
* you would like to be notified of any further changes, you should go read
|
||||||
|
* the underlying data again. We assume that if you don't go read it again after
|
||||||
|
* being notified, it means that you are no longer interested in that data.
|
||||||
|
*
|
||||||
|
* Subscriptions:
|
||||||
|
* + Reading a property on an object will subscribe you to changes in the value
|
||||||
|
* of that property.
|
||||||
|
* + Accessing an object keys (eg with Object.keys or with `for..in`) will
|
||||||
|
* subscribe you to the creation/deletion of keys. Checking the presence of a
|
||||||
|
* key on the object with 'in' has the same effect.
|
||||||
|
* - getOwnPropertyDescriptor does not currently subscribe you to the property.
|
||||||
|
* This is a choice that was made because changing a key's value will trigger
|
||||||
|
* this trap and we do not want to subscribe by writes. This also means that
|
||||||
|
* Object.hasOwnProperty doesn't subscribe as it goes through this trap.
|
||||||
|
*
|
||||||
|
* @param target the object for which to create a reactive proxy
|
||||||
|
* @param callback the function to call when an observed property of the
|
||||||
|
* reactive has changed
|
||||||
|
* @returns a proxy that tracks changes to it
|
||||||
|
*/
|
||||||
|
function reactive(target, callback = () => { }) {
|
||||||
|
if (!canBeMadeReactive(target)) {
|
||||||
|
throw new Error(`Cannot make the given value reactive`);
|
||||||
|
}
|
||||||
|
if (SKIP in target) {
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
const originalTarget = target[TARGET];
|
||||||
|
if (originalTarget) {
|
||||||
|
return reactive(originalTarget, callback);
|
||||||
|
}
|
||||||
|
if (!reactiveCache.has(target)) {
|
||||||
|
reactiveCache.set(target, new Map());
|
||||||
|
}
|
||||||
|
const reactivesForTarget = reactiveCache.get(target);
|
||||||
|
if (!reactivesForTarget.has(callback)) {
|
||||||
|
const proxy = new Proxy(target, {
|
||||||
|
get(target, key, proxy) {
|
||||||
|
if (key === TARGET) {
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
observeTargetKey(target, key, callback);
|
||||||
|
const value = Reflect.get(target, key, proxy);
|
||||||
|
if (!canBeMadeReactive(value)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return reactive(value, callback);
|
||||||
|
},
|
||||||
|
set(target, key, value, proxy) {
|
||||||
|
const isNewKey = !Object.hasOwnProperty.call(target, key);
|
||||||
|
const originalValue = Reflect.get(target, key, proxy);
|
||||||
|
const ret = Reflect.set(target, key, value, proxy);
|
||||||
|
if (isNewKey) {
|
||||||
|
notifyReactives(target, KEYCHANGES);
|
||||||
|
}
|
||||||
|
// While Array length may trigger the set trap, it's not actually set by this
|
||||||
|
// method but is updated behind the scenes, and the trap is not called with the
|
||||||
|
// new value. We disable the "same-value-optimization" for it because of that.
|
||||||
|
if (originalValue !== value || (Array.isArray(target) && key === "length")) {
|
||||||
|
notifyReactives(target, key);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
},
|
||||||
|
deleteProperty(target, key) {
|
||||||
|
const ret = Reflect.deleteProperty(target, key);
|
||||||
|
notifyReactives(target, KEYCHANGES);
|
||||||
|
notifyReactives(target, key);
|
||||||
|
return ret;
|
||||||
|
},
|
||||||
|
ownKeys(target) {
|
||||||
|
observeTargetKey(target, KEYCHANGES, callback);
|
||||||
|
return Reflect.ownKeys(target);
|
||||||
|
},
|
||||||
|
has(target, key) {
|
||||||
|
// TODO: this observes all key changes instead of only the presence of the argument key
|
||||||
|
observeTargetKey(target, KEYCHANGES, callback);
|
||||||
|
return Reflect.has(target, key);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
reactivesForTarget.set(callback, proxy);
|
||||||
|
}
|
||||||
|
return reactivesForTarget.get(callback);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This file contains utility functions that will be injected in each template,
|
* This file contains utility functions that will be injected in each template,
|
||||||
* to perform various useful tasks in the compiled code.
|
* to perform various useful tasks in the compiled code.
|
||||||
@@ -1510,7 +1714,7 @@
|
|||||||
}
|
}
|
||||||
function callSlot(ctx, parent, key, name, dynamic, extra, defaultContent) {
|
function callSlot(ctx, parent, key, name, dynamic, extra, defaultContent) {
|
||||||
key = key + "__slot_" + name;
|
key = key + "__slot_" + name;
|
||||||
const slots = (ctx.props && ctx.props.slots) || {};
|
const slots = ctx.props[TARGET].slots || {};
|
||||||
const { __render, __ctx, __scope } = slots[name] || {};
|
const { __render, __ctx, __scope } = slots[name] || {};
|
||||||
const slotScope = Object.create(__ctx || {});
|
const slotScope = Object.create(__ctx || {});
|
||||||
if (__scope) {
|
if (__scope) {
|
||||||
@@ -1778,8 +1982,7 @@
|
|||||||
function makeChildFiber(node, parent) {
|
function makeChildFiber(node, parent) {
|
||||||
let current = node.fiber;
|
let current = node.fiber;
|
||||||
if (current) {
|
if (current) {
|
||||||
let root = parent.root;
|
cancelFibers(current.children);
|
||||||
cancelFibers(root, current.children);
|
|
||||||
current.root = null;
|
current.root = null;
|
||||||
}
|
}
|
||||||
return new Fiber(node, parent);
|
return new Fiber(node, parent);
|
||||||
@@ -1788,9 +1991,8 @@
|
|||||||
let current = node.fiber;
|
let current = node.fiber;
|
||||||
if (current) {
|
if (current) {
|
||||||
let root = current.root;
|
let root = current.root;
|
||||||
root.counter -= cancelFibers(root, current.children);
|
root.counter = root.counter + 1 - cancelFibers(current.children);
|
||||||
current.children = [];
|
current.children = [];
|
||||||
root.counter++;
|
|
||||||
current.bdom = null;
|
current.bdom = null;
|
||||||
if (fibersInError.has(current)) {
|
if (fibersInError.has(current)) {
|
||||||
fibersInError.delete(current);
|
fibersInError.delete(current);
|
||||||
@@ -1811,15 +2013,14 @@
|
|||||||
/**
|
/**
|
||||||
* @returns number of not-yet rendered fibers cancelled
|
* @returns number of not-yet rendered fibers cancelled
|
||||||
*/
|
*/
|
||||||
function cancelFibers(root, fibers) {
|
function cancelFibers(fibers) {
|
||||||
let result = 0;
|
let result = 0;
|
||||||
for (let fiber of fibers) {
|
for (let fiber of fibers) {
|
||||||
fiber.node.fiber = null;
|
fiber.node.fiber = null;
|
||||||
fiber.root = root;
|
|
||||||
if (!fiber.bdom) {
|
if (!fiber.bdom) {
|
||||||
result++;
|
result++;
|
||||||
}
|
}
|
||||||
result += cancelFibers(root, fiber.children);
|
result += cancelFibers(fiber.children);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -1828,9 +2029,11 @@
|
|||||||
this.bdom = null;
|
this.bdom = null;
|
||||||
this.children = [];
|
this.children = [];
|
||||||
this.appliedToDom = false;
|
this.appliedToDom = false;
|
||||||
|
this.deep = false;
|
||||||
this.node = node;
|
this.node = node;
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
if (parent) {
|
if (parent) {
|
||||||
|
this.deep = parent.deep;
|
||||||
const root = parent.root;
|
const root = parent.root;
|
||||||
root.counter++;
|
root.counter++;
|
||||||
this.root = root;
|
this.root = root;
|
||||||
@@ -1873,7 +2076,7 @@
|
|||||||
}
|
}
|
||||||
current = undefined;
|
current = undefined;
|
||||||
// Step 2: patching the dom
|
// Step 2: patching the dom
|
||||||
node.patch();
|
node._patch();
|
||||||
this.locked = false;
|
this.locked = false;
|
||||||
// Step 4: calling all mounted lifecycle hooks
|
// Step 4: calling all mounted lifecycle hooks
|
||||||
let mountedFibers = this.mounted;
|
let mountedFibers = this.mounted;
|
||||||
@@ -1960,6 +2163,39 @@
|
|||||||
function useComponent() {
|
function useComponent() {
|
||||||
return currentNode.component;
|
return currentNode.component;
|
||||||
}
|
}
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Integration with reactivity system (useState)
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
const batchedRenderFunctions = new WeakMap();
|
||||||
|
/**
|
||||||
|
* Creates a reactive object that will be observed by the current component.
|
||||||
|
* Reading data from the returned object (eg during rendering) will cause the
|
||||||
|
* component to subscribe to that data and be rerendered when it changes.
|
||||||
|
*
|
||||||
|
* @param state the state to observe
|
||||||
|
* @returns a reactive object that will cause the component to re-render on
|
||||||
|
* relevant changes
|
||||||
|
* @see reactive
|
||||||
|
*/
|
||||||
|
function useState(state) {
|
||||||
|
const node = getCurrent();
|
||||||
|
let render = batchedRenderFunctions.get(node);
|
||||||
|
if (!render) {
|
||||||
|
render = batched(node.render.bind(node));
|
||||||
|
batchedRenderFunctions.set(node, render);
|
||||||
|
// manual implementation of onWillDestroy to break cyclic dependency
|
||||||
|
node.willDestroy.push(clearReactivesForCallback.bind(null, render));
|
||||||
|
}
|
||||||
|
return reactive(state, render);
|
||||||
|
}
|
||||||
|
function arePropsDifferent(props1, props2) {
|
||||||
|
for (let k in props1) {
|
||||||
|
if (props1[k] !== props2[k]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Object.keys(props1).length !== Object.keys(props2).length;
|
||||||
|
}
|
||||||
function component(name, props, key, ctx, parent) {
|
function component(name, props, key, ctx, parent) {
|
||||||
let node = ctx.children[key];
|
let node = ctx.children[key];
|
||||||
let isDynamic = typeof name !== "string";
|
let isDynamic = typeof name !== "string";
|
||||||
@@ -1977,7 +2213,10 @@
|
|||||||
}
|
}
|
||||||
const parentFiber = ctx.fiber;
|
const parentFiber = ctx.fiber;
|
||||||
if (node) {
|
if (node) {
|
||||||
node.updateAndRender(props, parentFiber);
|
const currentProps = node.component.props[TARGET];
|
||||||
|
if (parentFiber.deep || arePropsDifferent(currentProps, props)) {
|
||||||
|
node.updateAndRender(props, parentFiber);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// new component
|
// new component
|
||||||
@@ -1993,8 +2232,7 @@
|
|||||||
}
|
}
|
||||||
node = new ComponentNode(C, props, ctx.app, ctx);
|
node = new ComponentNode(C, props, ctx.app, ctx);
|
||||||
ctx.children[key] = node;
|
ctx.children[key] = node;
|
||||||
const fiber = makeChildFiber(node, parentFiber);
|
node.initiateRender(new Fiber(node, parentFiber));
|
||||||
node.initiateRender(fiber);
|
|
||||||
}
|
}
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
@@ -2019,6 +2257,7 @@
|
|||||||
applyDefaultProps(props, C);
|
applyDefaultProps(props, C);
|
||||||
const env = (parent && parent.childEnv) || app.env;
|
const env = (parent && parent.childEnv) || app.env;
|
||||||
this.childEnv = env;
|
this.childEnv = env;
|
||||||
|
props = useState(props);
|
||||||
this.component = new C(props, env, this);
|
this.component = new C(props, env, this);
|
||||||
this.renderFn = app.getTemplate(C.template).bind(this.component, this.component, this);
|
this.renderFn = app.getTemplate(C.template).bind(this.component, this.component, this);
|
||||||
this.component.setup();
|
this.component.setup();
|
||||||
@@ -2046,20 +2285,29 @@
|
|||||||
this._render(fiber);
|
this._render(fiber);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async render() {
|
async render(deep = false) {
|
||||||
let current = this.fiber;
|
let current = this.fiber;
|
||||||
if (current && current.root.locked) {
|
if (current && current.root.locked) {
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
// situation may have changed after the microtask tick
|
// situation may have changed after the microtask tick
|
||||||
current = this.fiber;
|
current = this.fiber;
|
||||||
}
|
}
|
||||||
if (current && !current.bdom && !fibersInError.has(current)) {
|
if (current) {
|
||||||
return;
|
if (!current.bdom && !fibersInError.has(current)) {
|
||||||
|
if (deep) {
|
||||||
|
// we want the render from this point on to be with deep=true
|
||||||
|
current.deep = deep;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// if current rendering was with deep=true, we want this one to be the same
|
||||||
|
deep = deep || current.deep;
|
||||||
}
|
}
|
||||||
if (!this.bdom && !current) {
|
else if (!this.bdom) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const fiber = makeRootFiber(this);
|
const fiber = makeRootFiber(this);
|
||||||
|
fiber.deep = deep;
|
||||||
this.fiber = fiber;
|
this.fiber = fiber;
|
||||||
this.app.scheduler.addFiber(fiber);
|
this.app.scheduler.addFiber(fiber);
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
@@ -2118,6 +2366,9 @@
|
|||||||
this.fiber = fiber;
|
this.fiber = fiber;
|
||||||
const component = this.component;
|
const component = this.component;
|
||||||
applyDefaultProps(props, component.constructor);
|
applyDefaultProps(props, component.constructor);
|
||||||
|
currentNode = this;
|
||||||
|
props = useState(props);
|
||||||
|
currentNode = null;
|
||||||
const prom = Promise.all(this.willUpdateProps.map((f) => f.call(component, props)));
|
const prom = Promise.all(this.willUpdateProps.map((f) => f.call(component, props)));
|
||||||
await prom;
|
await prom;
|
||||||
if (fiber !== this.fiber) {
|
if (fiber !== this.fiber) {
|
||||||
@@ -2177,6 +2428,14 @@
|
|||||||
this.bdom.moveBefore(other ? other.bdom : null, afterNode);
|
this.bdom.moveBefore(other ? other.bdom : null, afterNode);
|
||||||
}
|
}
|
||||||
patch() {
|
patch() {
|
||||||
|
if (this.fiber && this.fiber.parent) {
|
||||||
|
// we only patch here renderings coming from above. renderings initiated
|
||||||
|
// by the component will be patched independently in the appropriate
|
||||||
|
// fiber.complete
|
||||||
|
this._patch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_patch() {
|
||||||
const hasChildren = Object.keys(this.children).length > 0;
|
const hasChildren = Object.keys(this.children).length > 0;
|
||||||
this.bdom.patch(this.fiber.bdom, hasChildren);
|
this.bdom.patch(this.fiber.bdom, hasChildren);
|
||||||
if (hasChildren) {
|
if (hasChildren) {
|
||||||
@@ -2206,53 +2465,86 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function wrapError(fn, hookName) {
|
||||||
|
const error = new Error(`The following error occurred in ${hookName}: `);
|
||||||
|
return (...args) => {
|
||||||
|
try {
|
||||||
|
const result = fn(...args);
|
||||||
|
if (result instanceof Promise) {
|
||||||
|
return result.catch((cause) => {
|
||||||
|
error.cause = cause;
|
||||||
|
if (cause instanceof Error) {
|
||||||
|
error.message += `"${cause.message}"`;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
catch (cause) {
|
||||||
|
if (cause instanceof Error) {
|
||||||
|
error.message += `"${cause.message}"`;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// hooks
|
// hooks
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
function onWillStart(fn) {
|
function onWillStart(fn) {
|
||||||
const node = getCurrent();
|
const node = getCurrent();
|
||||||
node.willStart.push(fn.bind(node.component));
|
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
||||||
|
node.willStart.push(decorate(fn.bind(node.component), "onWillStart"));
|
||||||
}
|
}
|
||||||
function onWillUpdateProps(fn) {
|
function onWillUpdateProps(fn) {
|
||||||
const node = getCurrent();
|
const node = getCurrent();
|
||||||
node.willUpdateProps.push(fn.bind(node.component));
|
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
||||||
|
node.willUpdateProps.push(decorate(fn.bind(node.component), "onWillUpdateProps"));
|
||||||
}
|
}
|
||||||
function onMounted(fn) {
|
function onMounted(fn) {
|
||||||
const node = getCurrent();
|
const node = getCurrent();
|
||||||
node.mounted.push(fn.bind(node.component));
|
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
||||||
|
node.mounted.push(decorate(fn.bind(node.component), "onMounted"));
|
||||||
}
|
}
|
||||||
function onWillPatch(fn) {
|
function onWillPatch(fn) {
|
||||||
const node = getCurrent();
|
const node = getCurrent();
|
||||||
node.willPatch.unshift(fn.bind(node.component));
|
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
||||||
|
node.willPatch.unshift(decorate(fn.bind(node.component), "onWillPatch"));
|
||||||
}
|
}
|
||||||
function onPatched(fn) {
|
function onPatched(fn) {
|
||||||
const node = getCurrent();
|
const node = getCurrent();
|
||||||
node.patched.push(fn.bind(node.component));
|
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
||||||
|
node.patched.push(decorate(fn.bind(node.component), "onPatched"));
|
||||||
}
|
}
|
||||||
function onWillUnmount(fn) {
|
function onWillUnmount(fn) {
|
||||||
const node = getCurrent();
|
const node = getCurrent();
|
||||||
node.willUnmount.unshift(fn.bind(node.component));
|
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
||||||
|
node.willUnmount.unshift(decorate(fn.bind(node.component), "onWillUnmount"));
|
||||||
}
|
}
|
||||||
function onWillDestroy(fn) {
|
function onWillDestroy(fn) {
|
||||||
const node = getCurrent();
|
const node = getCurrent();
|
||||||
node.willDestroy.push(fn.bind(node.component));
|
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
||||||
|
node.willDestroy.push(decorate(fn.bind(node.component), "onWillDestroy"));
|
||||||
}
|
}
|
||||||
function onWillRender(fn) {
|
function onWillRender(fn) {
|
||||||
const node = getCurrent();
|
const node = getCurrent();
|
||||||
const renderFn = node.renderFn;
|
const renderFn = node.renderFn;
|
||||||
node.renderFn = () => {
|
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
||||||
|
node.renderFn = decorate(() => {
|
||||||
fn.call(node.component);
|
fn.call(node.component);
|
||||||
return renderFn();
|
return renderFn();
|
||||||
};
|
}, "onWillRender");
|
||||||
}
|
}
|
||||||
function onRendered(fn) {
|
function onRendered(fn) {
|
||||||
const node = getCurrent();
|
const node = getCurrent();
|
||||||
const renderFn = node.renderFn;
|
const renderFn = node.renderFn;
|
||||||
node.renderFn = () => {
|
const decorate = node.app.dev ? wrapError : (fn) => fn;
|
||||||
|
node.renderFn = decorate(() => {
|
||||||
const result = renderFn();
|
const result = renderFn();
|
||||||
fn.call(node.component);
|
fn.call(node.component);
|
||||||
return result;
|
return result;
|
||||||
};
|
}, "onRendered");
|
||||||
}
|
}
|
||||||
function onError(callback) {
|
function onError(callback) {
|
||||||
const node = getCurrent();
|
const node = getCurrent();
|
||||||
@@ -3549,17 +3841,13 @@
|
|||||||
slotDef = `{${slotStr.join(", ")}}`;
|
slotDef = `{${slotStr.join(", ")}}`;
|
||||||
}
|
}
|
||||||
if (slotDef && !(ast.dynamicProps || hasSlotsProp)) {
|
if (slotDef && !(ast.dynamicProps || hasSlotsProp)) {
|
||||||
props.push(`slots: ${slotDef}`);
|
this.helpers.add("markRaw");
|
||||||
|
props.push(`slots: markRaw(${slotDef})`);
|
||||||
}
|
}
|
||||||
const propStr = `{${props.join(",")}}`;
|
const propStr = `{${props.join(",")}}`;
|
||||||
let propString = propStr;
|
let propString = propStr;
|
||||||
if (ast.dynamicProps) {
|
if (ast.dynamicProps) {
|
||||||
if (!props.length) {
|
propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)}${props.length ? ", " + propStr : ""})`;
|
||||||
propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)})`;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)}, ${propStr})`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
let propVar;
|
let propVar;
|
||||||
if ((slotDef && (ast.dynamicProps || hasSlotsProp)) || this.dev) {
|
if ((slotDef && (ast.dynamicProps || hasSlotsProp)) || this.dev) {
|
||||||
@@ -3568,7 +3856,8 @@
|
|||||||
propString = propVar;
|
propString = propVar;
|
||||||
}
|
}
|
||||||
if (slotDef && (ast.dynamicProps || hasSlotsProp)) {
|
if (slotDef && (ast.dynamicProps || hasSlotsProp)) {
|
||||||
this.addLine(`${propVar}.slots = Object.assign(${slotDef}, ${propVar}.slots)`);
|
this.helpers.add("markRaw");
|
||||||
|
this.addLine(`${propVar}.slots = markRaw(Object.assign(${slotDef}, ${propVar}.slots))`);
|
||||||
}
|
}
|
||||||
// cmap key
|
// cmap key
|
||||||
const key = this.generateComponentKey();
|
const key = this.generateComponentKey();
|
||||||
@@ -3768,6 +4057,9 @@
|
|||||||
if (tagName === "t" && !dynamicTag) {
|
if (tagName === "t" && !dynamicTag) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (tagName.startsWith("block-")) {
|
||||||
|
throw new Error(`Invalid tag name: '${tagName}'`);
|
||||||
|
}
|
||||||
ctx = Object.assign({}, ctx);
|
ctx = Object.assign({}, ctx);
|
||||||
if (tagName === "pre") {
|
if (tagName === "pre") {
|
||||||
ctx.inPreTag = true;
|
ctx.inPreTag = true;
|
||||||
@@ -3833,6 +4125,9 @@
|
|||||||
ctx.tModelInfo = model;
|
ctx.tModelInfo = model;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (attr.startsWith("block-")) {
|
||||||
|
throw new Error(`Invalid attribute: '${attr}'`);
|
||||||
|
}
|
||||||
else if (attr !== "t-name") {
|
else if (attr !== "t-name") {
|
||||||
if (attr.startsWith("t-") && !attr.startsWith("t-att")) {
|
if (attr.startsWith("t-") && !attr.startsWith("t-att")) {
|
||||||
throw new Error(`Unknown QWeb directive: '${attr}'`);
|
throw new Error(`Unknown QWeb directive: '${attr}'`);
|
||||||
@@ -4450,7 +4745,13 @@
|
|||||||
if (!(name in this.templates)) {
|
if (!(name in this.templates)) {
|
||||||
const rawTemplate = this.rawTemplates[name];
|
const rawTemplate = this.rawTemplates[name];
|
||||||
if (rawTemplate === undefined) {
|
if (rawTemplate === undefined) {
|
||||||
throw new Error(`Missing template: "${name}"`);
|
let extraInfo = "";
|
||||||
|
try {
|
||||||
|
const componentName = getCurrent().component.constructor.name;
|
||||||
|
extraInfo = ` (for component "${componentName}")`;
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
throw new Error(`Missing template: "${name}"${extraInfo}`);
|
||||||
}
|
}
|
||||||
const templateFn = this._compileTemplate(name, rawTemplate);
|
const templateFn = this._compileTemplate(name, rawTemplate);
|
||||||
// first add a function to lazily get the template, in case there is a
|
// first add a function to lazily get the template, in case there is a
|
||||||
@@ -4491,8 +4792,8 @@
|
|||||||
this.__owl__ = node;
|
this.__owl__ = node;
|
||||||
}
|
}
|
||||||
setup() { }
|
setup() { }
|
||||||
render() {
|
render(deep = false) {
|
||||||
this.__owl__.render();
|
this.__owl__.render(deep);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Component.template = "";
|
Component.template = "";
|
||||||
@@ -4626,10 +4927,13 @@
|
|||||||
// interactions with other code, such as test frameworks that override them
|
// interactions with other code, such as test frameworks that override them
|
||||||
Scheduler.requestAnimationFrame = window.requestAnimationFrame.bind(window);
|
Scheduler.requestAnimationFrame = window.requestAnimationFrame.bind(window);
|
||||||
|
|
||||||
const DEV_MSG = `Owl is running in 'dev' mode.
|
const DEV_MSG = () => {
|
||||||
|
const hash = window.owl ? window.owl.__info__.hash : "master";
|
||||||
|
return `Owl is running in 'dev' mode.
|
||||||
|
|
||||||
This is not suitable for production use.
|
This is not suitable for production use.
|
||||||
See https://github.com/odoo/owl/blob/master/doc/reference/config.md#mode for more information.`;
|
See https://github.com/odoo/owl/blob/${hash}/doc/reference/app.md#configuration for more information.`;
|
||||||
|
};
|
||||||
class App extends TemplateSet {
|
class App extends TemplateSet {
|
||||||
constructor(Root, config = {}) {
|
constructor(Root, config = {}) {
|
||||||
super(config);
|
super(config);
|
||||||
@@ -4640,7 +4944,7 @@ See https://github.com/odoo/owl/blob/master/doc/reference/config.md#mode for mor
|
|||||||
this.dev = true;
|
this.dev = true;
|
||||||
}
|
}
|
||||||
if (this.dev && !config.test) {
|
if (this.dev && !config.test) {
|
||||||
console.info(DEV_MSG);
|
console.info(DEV_MSG());
|
||||||
}
|
}
|
||||||
const descrs = Object.getOwnPropertyDescriptors(config.env || {});
|
const descrs = Object.getOwnPropertyDescriptors(config.env || {});
|
||||||
this.env = Object.freeze(Object.defineProperties({}, descrs));
|
this.env = Object.freeze(Object.defineProperties({}, descrs));
|
||||||
@@ -4745,231 +5049,6 @@ See https://github.com/odoo/owl/blob/master/doc/reference/config.md#mode for mor
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allows to get the target of a Reactive (used for making a new Reactive from the underlying object)
|
|
||||||
const TARGET = Symbol("Target");
|
|
||||||
// Escape hatch to prevent reactivity system to turn something into a reactive
|
|
||||||
const SKIP = Symbol("Skip");
|
|
||||||
// Special key to subscribe to, to be notified of key creation/deletion
|
|
||||||
const KEYCHANGES = Symbol("Key changes");
|
|
||||||
const objectToString = Object.prototype.toString;
|
|
||||||
/**
|
|
||||||
* Checks whether a given value can be made into a reactive object.
|
|
||||||
*
|
|
||||||
* @param value the value to check
|
|
||||||
* @returns whether the value can be made reactive
|
|
||||||
*/
|
|
||||||
function canBeMadeReactive(value) {
|
|
||||||
if (typeof value !== "object") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// extract "RawType" from strings like "[object RawType]" => this lets us
|
|
||||||
// ignore many native objects such as Promise (whose toString is [object Promise])
|
|
||||||
// or Date ([object Date]).
|
|
||||||
const rawType = objectToString.call(value).slice(8, -1);
|
|
||||||
return rawType === "Object" || rawType === "Array";
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Mark an object or array so that it is ignored by the reactivity system
|
|
||||||
*
|
|
||||||
* @param value the value to mark
|
|
||||||
* @returns the object itself
|
|
||||||
*/
|
|
||||||
function markRaw(value) {
|
|
||||||
value[SKIP] = true;
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Given a reactive objet, return the raw (non reactive) underlying object
|
|
||||||
*
|
|
||||||
* @param value a reactive value
|
|
||||||
* @returns the underlying value
|
|
||||||
*/
|
|
||||||
function toRaw(value) {
|
|
||||||
return value[TARGET];
|
|
||||||
}
|
|
||||||
const targetToKeysToCallbacks = new WeakMap();
|
|
||||||
/**
|
|
||||||
* Observes a given key on a target with an callback. The callback will be
|
|
||||||
* called when the given key changes on the target.
|
|
||||||
*
|
|
||||||
* @param target the target whose key should be observed
|
|
||||||
* @param key the key to observe (or Symbol(KEYCHANGES) for key creation
|
|
||||||
* or deletion)
|
|
||||||
* @param callback the function to call when the key changes
|
|
||||||
*/
|
|
||||||
function observeTargetKey(target, key, callback) {
|
|
||||||
if (!targetToKeysToCallbacks.get(target)) {
|
|
||||||
targetToKeysToCallbacks.set(target, new Map());
|
|
||||||
}
|
|
||||||
const keyToCallbacks = targetToKeysToCallbacks.get(target);
|
|
||||||
if (!keyToCallbacks.get(key)) {
|
|
||||||
keyToCallbacks.set(key, new Set());
|
|
||||||
}
|
|
||||||
keyToCallbacks.get(key).add(callback);
|
|
||||||
if (!callbacksToTargets.has(callback)) {
|
|
||||||
callbacksToTargets.set(callback, new Set());
|
|
||||||
}
|
|
||||||
callbacksToTargets.get(callback).add(target);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Notify Reactives that are observing a given target that a key has changed on
|
|
||||||
* the target.
|
|
||||||
*
|
|
||||||
* @param target target whose Reactives should be notified that the target was
|
|
||||||
* changed.
|
|
||||||
* @param key the key that changed (or Symbol `KEYCHANGES` if a key was created
|
|
||||||
* or deleted)
|
|
||||||
*/
|
|
||||||
function notifyReactives(target, key) {
|
|
||||||
const keyToCallbacks = targetToKeysToCallbacks.get(target);
|
|
||||||
if (!keyToCallbacks) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const callbacks = keyToCallbacks.get(key);
|
|
||||||
if (!callbacks) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Loop on copy because clearReactivesForCallback will modify the set in place
|
|
||||||
for (const callback of [...callbacks]) {
|
|
||||||
clearReactivesForCallback(callback);
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const callbacksToTargets = new WeakMap();
|
|
||||||
/**
|
|
||||||
* Clears all subscriptions of the Reactives associated with a given callback.
|
|
||||||
*
|
|
||||||
* @param callback the callback for which the reactives need to be cleared
|
|
||||||
*/
|
|
||||||
function clearReactivesForCallback(callback) {
|
|
||||||
const targetsToClear = callbacksToTargets.get(callback);
|
|
||||||
if (!targetsToClear) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (const target of targetsToClear) {
|
|
||||||
const observedKeys = targetToKeysToCallbacks.get(target);
|
|
||||||
if (!observedKeys) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (const callbacks of observedKeys.values()) {
|
|
||||||
callbacks.delete(callback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
targetsToClear.clear();
|
|
||||||
}
|
|
||||||
const reactiveCache = new WeakMap();
|
|
||||||
/**
|
|
||||||
* Creates a reactive proxy for an object. Reading data on the reactive object
|
|
||||||
* subscribes to changes to the data. Writing data on the object will cause the
|
|
||||||
* notify callback to be called if there are suscriptions to that data. Nested
|
|
||||||
* objects and arrays are automatically made reactive as well.
|
|
||||||
*
|
|
||||||
* Whenever you are notified of a change, all subscriptions are cleared, and if
|
|
||||||
* you would like to be notified of any further changes, you should go read
|
|
||||||
* the underlying data again. We assume that if you don't go read it again after
|
|
||||||
* being notified, it means that you are no longer interested in that data.
|
|
||||||
*
|
|
||||||
* Subscriptions:
|
|
||||||
* + Reading a property on an object will subscribe you to changes in the value
|
|
||||||
* of that property.
|
|
||||||
* + Accessing an object keys (eg with Object.keys or with `for..in`) will
|
|
||||||
* subscribe you to the creation/deletion of keys. Checking the presence of a
|
|
||||||
* key on the object with 'in' has the same effect.
|
|
||||||
* - getOwnPropertyDescriptor does not currently subscribe you to the property.
|
|
||||||
* This is a choice that was made because changing a key's value will trigger
|
|
||||||
* this trap and we do not want to subscribe by writes. This also means that
|
|
||||||
* Object.hasOwnProperty doesn't subscribe as it goes through this trap.
|
|
||||||
*
|
|
||||||
* @param target the object for which to create a reactive proxy
|
|
||||||
* @param callback the function to call when an observed property of the
|
|
||||||
* reactive has changed
|
|
||||||
* @returns a proxy that tracks changes to it
|
|
||||||
*/
|
|
||||||
function reactive(target, callback = () => { }) {
|
|
||||||
if (!canBeMadeReactive(target)) {
|
|
||||||
throw new Error(`Cannot make the given value reactive`);
|
|
||||||
}
|
|
||||||
if (SKIP in target) {
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
const originalTarget = target[TARGET];
|
|
||||||
if (originalTarget) {
|
|
||||||
return reactive(originalTarget, callback);
|
|
||||||
}
|
|
||||||
if (!reactiveCache.has(target)) {
|
|
||||||
reactiveCache.set(target, new Map());
|
|
||||||
}
|
|
||||||
const reactivesForTarget = reactiveCache.get(target);
|
|
||||||
if (!reactivesForTarget.has(callback)) {
|
|
||||||
const proxy = new Proxy(target, {
|
|
||||||
get(target, key, proxy) {
|
|
||||||
if (key === TARGET) {
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
observeTargetKey(target, key, callback);
|
|
||||||
const value = Reflect.get(target, key, proxy);
|
|
||||||
if (!canBeMadeReactive(value)) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
return reactive(value, callback);
|
|
||||||
},
|
|
||||||
set(target, key, value, proxy) {
|
|
||||||
const isNewKey = !Object.hasOwnProperty.call(target, key);
|
|
||||||
const originalValue = Reflect.get(target, key, proxy);
|
|
||||||
const ret = Reflect.set(target, key, value, proxy);
|
|
||||||
if (isNewKey) {
|
|
||||||
notifyReactives(target, KEYCHANGES);
|
|
||||||
}
|
|
||||||
// While Array length may trigger the set trap, it's not actually set by this
|
|
||||||
// method but is updated behind the scenes, and the trap is not called with the
|
|
||||||
// new value. We disable the "same-value-optimization" for it because of that.
|
|
||||||
if (originalValue !== value || (Array.isArray(target) && key === "length")) {
|
|
||||||
notifyReactives(target, key);
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
},
|
|
||||||
deleteProperty(target, key) {
|
|
||||||
const ret = Reflect.deleteProperty(target, key);
|
|
||||||
notifyReactives(target, KEYCHANGES);
|
|
||||||
notifyReactives(target, key);
|
|
||||||
return ret;
|
|
||||||
},
|
|
||||||
ownKeys(target) {
|
|
||||||
observeTargetKey(target, KEYCHANGES, callback);
|
|
||||||
return Reflect.ownKeys(target);
|
|
||||||
},
|
|
||||||
has(target, key) {
|
|
||||||
// TODO: this observes all key changes instead of only the presence of the argument key
|
|
||||||
observeTargetKey(target, KEYCHANGES, callback);
|
|
||||||
return Reflect.has(target, key);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
reactivesForTarget.set(callback, proxy);
|
|
||||||
}
|
|
||||||
return reactivesForTarget.get(callback);
|
|
||||||
}
|
|
||||||
const batchedRenderFunctions = new WeakMap();
|
|
||||||
/**
|
|
||||||
* Creates a reactive object that will be observed by the current component.
|
|
||||||
* Reading data from the returned object (eg during rendering) will cause the
|
|
||||||
* component to subscribe to that data and be rerendered when it changes.
|
|
||||||
*
|
|
||||||
* @param state the state to observe
|
|
||||||
* @returns a reactive object that will cause the component to re-render on
|
|
||||||
* relevant changes
|
|
||||||
* @see reactive
|
|
||||||
*/
|
|
||||||
function useState(state) {
|
|
||||||
const node = getCurrent();
|
|
||||||
if (!batchedRenderFunctions.has(node)) {
|
|
||||||
batchedRenderFunctions.set(node, batched(() => node.render()));
|
|
||||||
onWillDestroy(() => clearReactivesForCallback(render));
|
|
||||||
}
|
|
||||||
const render = batchedRenderFunctions.get(node);
|
|
||||||
const reactiveState = reactive(state, render);
|
|
||||||
return reactiveState;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// useRef
|
// useRef
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
@@ -5075,6 +5154,7 @@ See https://github.com/odoo/owl/blob/master/doc/reference/config.md#mode for mor
|
|||||||
config.shouldNormalizeDom = false;
|
config.shouldNormalizeDom = false;
|
||||||
config.mainEventHandler = mainEventHandler;
|
config.mainEventHandler = mainEventHandler;
|
||||||
UTILS.Portal = Portal;
|
UTILS.Portal = Portal;
|
||||||
|
UTILS.markRaw = markRaw;
|
||||||
const blockDom = {
|
const blockDom = {
|
||||||
config,
|
config,
|
||||||
// bdom entry points
|
// bdom entry points
|
||||||
@@ -5129,9 +5209,9 @@ See https://github.com/odoo/owl/blob/master/doc/reference/config.md#mode for mor
|
|||||||
Object.defineProperty(exports, '__esModule', { value: true });
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
|
|
||||||
|
|
||||||
__info__.version = '2.0.0-alpha.2';
|
__info__.version = '2.0.0-alpha.3';
|
||||||
__info__.date = '2022-02-14T12:42:57.502Z';
|
__info__.date = '2022-02-25T09:57:47.473Z';
|
||||||
__info__.hash = '4a922ed';
|
__info__.hash = '076b0d7';
|
||||||
__info__.url = 'https://github.com/odoo/owl';
|
__info__.url = 'https://github.com/odoo/owl';
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user