From 7a3e50c86fe9d4bbd42012470a87363eb9a4b7bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Fri, 25 Feb 2022 10:57:58 +0100 Subject: [PATCH] [IMP] update owl to v2.0.0-alpha.3 --- owl.js | 620 ++++++++++++++++++++++++++++++++------------------------- 1 file changed, 350 insertions(+), 270 deletions(-) diff --git a/owl.js b/owl.js index b2ba3273..69bbf8d9 100644 --- a/owl.js +++ b/owl.js @@ -212,7 +212,7 @@ } function makePropSetter(name) { return function setProp(value) { - this[name] = value; + this[name] = value || ""; }; } function isProp(tag, key) { @@ -1501,6 +1501,210 @@ 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, * to perform various useful tasks in the compiled code. @@ -1510,7 +1714,7 @@ } function callSlot(ctx, parent, key, name, dynamic, extra, defaultContent) { key = key + "__slot_" + name; - const slots = (ctx.props && ctx.props.slots) || {}; + const slots = ctx.props[TARGET].slots || {}; const { __render, __ctx, __scope } = slots[name] || {}; const slotScope = Object.create(__ctx || {}); if (__scope) { @@ -1778,8 +1982,7 @@ function makeChildFiber(node, parent) { let current = node.fiber; if (current) { - let root = parent.root; - cancelFibers(root, current.children); + cancelFibers(current.children); current.root = null; } return new Fiber(node, parent); @@ -1788,9 +1991,8 @@ let current = node.fiber; if (current) { let root = current.root; - root.counter -= cancelFibers(root, current.children); + root.counter = root.counter + 1 - cancelFibers(current.children); current.children = []; - root.counter++; current.bdom = null; if (fibersInError.has(current)) { fibersInError.delete(current); @@ -1811,15 +2013,14 @@ /** * @returns number of not-yet rendered fibers cancelled */ - function cancelFibers(root, fibers) { + function cancelFibers(fibers) { let result = 0; for (let fiber of fibers) { fiber.node.fiber = null; - fiber.root = root; if (!fiber.bdom) { result++; } - result += cancelFibers(root, fiber.children); + result += cancelFibers(fiber.children); } return result; } @@ -1828,9 +2029,11 @@ this.bdom = null; this.children = []; this.appliedToDom = false; + this.deep = false; this.node = node; this.parent = parent; if (parent) { + this.deep = parent.deep; const root = parent.root; root.counter++; this.root = root; @@ -1873,7 +2076,7 @@ } current = undefined; // Step 2: patching the dom - node.patch(); + node._patch(); this.locked = false; // Step 4: calling all mounted lifecycle hooks let mountedFibers = this.mounted; @@ -1960,6 +2163,39 @@ function useComponent() { 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) { let node = ctx.children[key]; let isDynamic = typeof name !== "string"; @@ -1977,7 +2213,10 @@ } const parentFiber = ctx.fiber; if (node) { - node.updateAndRender(props, parentFiber); + const currentProps = node.component.props[TARGET]; + if (parentFiber.deep || arePropsDifferent(currentProps, props)) { + node.updateAndRender(props, parentFiber); + } } else { // new component @@ -1993,8 +2232,7 @@ } node = new ComponentNode(C, props, ctx.app, ctx); ctx.children[key] = node; - const fiber = makeChildFiber(node, parentFiber); - node.initiateRender(fiber); + node.initiateRender(new Fiber(node, parentFiber)); } return node; } @@ -2019,6 +2257,7 @@ applyDefaultProps(props, C); const env = (parent && parent.childEnv) || app.env; this.childEnv = env; + props = useState(props); this.component = new C(props, env, this); this.renderFn = app.getTemplate(C.template).bind(this.component, this.component, this); this.component.setup(); @@ -2046,20 +2285,29 @@ this._render(fiber); } } - async render() { + async render(deep = false) { let current = this.fiber; if (current && current.root.locked) { await Promise.resolve(); // situation may have changed after the microtask tick current = this.fiber; } - if (current && !current.bdom && !fibersInError.has(current)) { - return; + if (current) { + 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; } const fiber = makeRootFiber(this); + fiber.deep = deep; this.fiber = fiber; this.app.scheduler.addFiber(fiber); await Promise.resolve(); @@ -2118,6 +2366,9 @@ this.fiber = fiber; const component = this.component; applyDefaultProps(props, component.constructor); + currentNode = this; + props = useState(props); + currentNode = null; const prom = Promise.all(this.willUpdateProps.map((f) => f.call(component, props))); await prom; if (fiber !== this.fiber) { @@ -2177,6 +2428,14 @@ this.bdom.moveBefore(other ? other.bdom : null, afterNode); } 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; this.bdom.patch(this.fiber.bdom, 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 // ----------------------------------------------------------------------------- function onWillStart(fn) { 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) { 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) { 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) { 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) { 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) { 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) { 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) { const node = getCurrent(); const renderFn = node.renderFn; - node.renderFn = () => { + const decorate = node.app.dev ? wrapError : (fn) => fn; + node.renderFn = decorate(() => { fn.call(node.component); return renderFn(); - }; + }, "onWillRender"); } function onRendered(fn) { const node = getCurrent(); const renderFn = node.renderFn; - node.renderFn = () => { + const decorate = node.app.dev ? wrapError : (fn) => fn; + node.renderFn = decorate(() => { const result = renderFn(); fn.call(node.component); return result; - }; + }, "onRendered"); } function onError(callback) { const node = getCurrent(); @@ -3549,17 +3841,13 @@ slotDef = `{${slotStr.join(", ")}}`; } if (slotDef && !(ast.dynamicProps || hasSlotsProp)) { - props.push(`slots: ${slotDef}`); + this.helpers.add("markRaw"); + props.push(`slots: markRaw(${slotDef})`); } const propStr = `{${props.join(",")}}`; let propString = propStr; if (ast.dynamicProps) { - if (!props.length) { - propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)})`; - } - else { - propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)}, ${propStr})`; - } + propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)}${props.length ? ", " + propStr : ""})`; } let propVar; if ((slotDef && (ast.dynamicProps || hasSlotsProp)) || this.dev) { @@ -3568,7 +3856,8 @@ propString = propVar; } 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 const key = this.generateComponentKey(); @@ -3768,6 +4057,9 @@ if (tagName === "t" && !dynamicTag) { return null; } + if (tagName.startsWith("block-")) { + throw new Error(`Invalid tag name: '${tagName}'`); + } ctx = Object.assign({}, ctx); if (tagName === "pre") { ctx.inPreTag = true; @@ -3833,6 +4125,9 @@ ctx.tModelInfo = model; } } + else if (attr.startsWith("block-")) { + throw new Error(`Invalid attribute: '${attr}'`); + } else if (attr !== "t-name") { if (attr.startsWith("t-") && !attr.startsWith("t-att")) { throw new Error(`Unknown QWeb directive: '${attr}'`); @@ -4450,7 +4745,13 @@ if (!(name in this.templates)) { const rawTemplate = this.rawTemplates[name]; 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); // first add a function to lazily get the template, in case there is a @@ -4491,8 +4792,8 @@ this.__owl__ = node; } setup() { } - render() { - this.__owl__.render(); + render(deep = false) { + this.__owl__.render(deep); } } Component.template = ""; @@ -4626,10 +4927,13 @@ // interactions with other code, such as test frameworks that override them 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. -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 { constructor(Root, 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; } if (this.dev && !config.test) { - console.info(DEV_MSG); + console.info(DEV_MSG()); } const descrs = Object.getOwnPropertyDescriptors(config.env || {}); 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; } - // 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 // ----------------------------------------------------------------------------- @@ -5075,6 +5154,7 @@ See https://github.com/odoo/owl/blob/master/doc/reference/config.md#mode for mor config.shouldNormalizeDom = false; config.mainEventHandler = mainEventHandler; UTILS.Portal = Portal; + UTILS.markRaw = markRaw; const blockDom = { config, // 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 }); - __info__.version = '2.0.0-alpha.2'; - __info__.date = '2022-02-14T12:42:57.502Z'; - __info__.hash = '4a922ed'; + __info__.version = '2.0.0-alpha.3'; + __info__.date = '2022-02-25T09:57:47.473Z'; + __info__.hash = '076b0d7'; __info__.url = 'https://github.com/odoo/owl';