mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
eec7cc4ea7
`useEffect` type is not compatible with later versions of Typescript. (I tried with Typescript 5.2, I didn't check which specific version had the breaking change) This prevents other projects using owl (such as o-spreadsheet) to upgrade their own version of Typescript. `<T extends [...T]>` raises the two following errors: `Type parameter 'T' has a circular constraint.ts(2313)` `A rest element type must be an array type.`
139 lines
4.8 KiB
TypeScript
139 lines
4.8 KiB
TypeScript
import type { Env } from "./app";
|
|
import { getCurrent } from "./component_node";
|
|
import { onMounted, onPatched, onWillUnmount } from "./lifecycle_hooks";
|
|
import { inOwnerDocument } from "./utils";
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// useRef
|
|
// -----------------------------------------------------------------------------
|
|
|
|
/**
|
|
* The purpose of this hook is to allow components to get a reference to a sub
|
|
* html node or component.
|
|
*/
|
|
export function useRef<T extends HTMLElement = HTMLElement>(name: string): { el: T | null } {
|
|
const node = getCurrent();
|
|
const refs = node.refs;
|
|
return {
|
|
get el(): T | null {
|
|
const el = refs[name];
|
|
return inOwnerDocument(el) ? el : null;
|
|
},
|
|
};
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// useEnv and useSubEnv
|
|
// -----------------------------------------------------------------------------
|
|
|
|
/**
|
|
* This hook is useful as a building block for some customized hooks, that may
|
|
* need a reference to the env of the component calling them.
|
|
*/
|
|
export function useEnv<E extends Env>(): E {
|
|
return getCurrent().component.env as any;
|
|
}
|
|
|
|
function extendEnv(currentEnv: Object, extension: Object): Object {
|
|
const env = Object.create(currentEnv);
|
|
const descrs = Object.getOwnPropertyDescriptors(extension);
|
|
return Object.freeze(Object.defineProperties(env, descrs));
|
|
}
|
|
|
|
/**
|
|
* This hook is a simple way to let components use a sub environment. Note that
|
|
* like for all hooks, it is important that this is only called in the
|
|
* constructor method.
|
|
*/
|
|
export function useSubEnv(envExtension: Env) {
|
|
const node = getCurrent();
|
|
node.component.env = extendEnv(node.component.env as any, envExtension);
|
|
useChildSubEnv(envExtension);
|
|
}
|
|
|
|
export function useChildSubEnv(envExtension: Env) {
|
|
const node = getCurrent();
|
|
node.childEnv = extendEnv(node.childEnv, envExtension);
|
|
}
|
|
// -----------------------------------------------------------------------------
|
|
// useEffect
|
|
// -----------------------------------------------------------------------------
|
|
|
|
type EffectDeps<T extends unknown[]> = T | (T extends [...infer H, never] ? EffectDeps<H> : never);
|
|
|
|
/**
|
|
* @template T
|
|
* @param {...T} dependencies the dependencies computed by computeDependencies
|
|
* @returns {void|(()=>void)} a cleanup function that reverses the side
|
|
* effects of the effect callback.
|
|
*/
|
|
type Effect<T extends unknown[]> = (...dependencies: EffectDeps<T>) => void | (() => void);
|
|
|
|
/**
|
|
* This hook will run a callback when a component is mounted and patched, and
|
|
* will run a cleanup function before patching and before unmounting the
|
|
* the component.
|
|
*
|
|
* @template T
|
|
* @param {Effect<T>} effect the effect to run on component mount and/or patch
|
|
* @param {()=>[...T]} [computeDependencies=()=>[NaN]] a callback to compute
|
|
* dependencies that will decide if the effect needs to be cleaned up and
|
|
* run again. If the dependencies did not change, the effect will not run
|
|
* again. The default value returns an array containing only NaN because
|
|
* NaN !== NaN, which will cause the effect to rerun on every patch.
|
|
*/
|
|
export function useEffect<T extends unknown[]>(
|
|
effect: Effect<T>,
|
|
computeDependencies: () => [...T] = () => [NaN] as never
|
|
) {
|
|
let cleanup: (() => void) | void;
|
|
let dependencies: T;
|
|
onMounted(() => {
|
|
dependencies = computeDependencies();
|
|
cleanup = effect(...dependencies);
|
|
});
|
|
|
|
onPatched(() => {
|
|
const newDeps = computeDependencies();
|
|
const shouldReapply = newDeps.some((val, i) => val !== dependencies[i]);
|
|
if (shouldReapply) {
|
|
dependencies = newDeps;
|
|
if (cleanup) {
|
|
cleanup();
|
|
}
|
|
cleanup = effect(...dependencies);
|
|
}
|
|
});
|
|
|
|
onWillUnmount(() => cleanup && cleanup());
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// useExternalListener
|
|
// -----------------------------------------------------------------------------
|
|
|
|
/**
|
|
* When a component needs to listen to DOM Events on element(s) that are not
|
|
* part of his hierarchy, we can use the `useExternalListener` hook.
|
|
* It will correctly add and remove the event listener, whenever the
|
|
* component is mounted and unmounted.
|
|
*
|
|
* Example:
|
|
* a menu needs to listen to the click on window to be closed automatically
|
|
*
|
|
* Usage:
|
|
* in the constructor of the OWL component that needs to be notified,
|
|
* `useExternalListener(window, 'click', this._doSomething);`
|
|
* */
|
|
export function useExternalListener(
|
|
target: EventTarget,
|
|
eventName: string,
|
|
handler: EventListener,
|
|
eventParams?: AddEventListenerOptions
|
|
) {
|
|
const node = getCurrent();
|
|
const boundHandler = handler.bind(node.component);
|
|
onMounted(() => target.addEventListener(eventName, boundHandler, eventParams));
|
|
onWillUnmount(() => target.removeEventListener(eventName, boundHandler, eventParams));
|
|
}
|