From d701c3ca12daf1de2ba94dc24ae08641f98aea9b Mon Sep 17 00:00:00 2001 From: Eneko Garrido Date: Sun, 24 Mar 2024 13:38:37 +0100 Subject: [PATCH 01/11] Kebab to camelcase utils on TypeScript --- .../interactivity/src/utils/kebab-to-camelcase.js | 14 -------------- .../interactivity/src/utils/kebab-to-camelcase.ts | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 14 deletions(-) delete mode 100644 packages/interactivity/src/utils/kebab-to-camelcase.js create mode 100644 packages/interactivity/src/utils/kebab-to-camelcase.ts diff --git a/packages/interactivity/src/utils/kebab-to-camelcase.js b/packages/interactivity/src/utils/kebab-to-camelcase.js deleted file mode 100644 index a2c0d3403db3c0..00000000000000 --- a/packages/interactivity/src/utils/kebab-to-camelcase.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Transforms a kebab-case string to camelCase. - * - * @param {string} str The kebab-case string to transform to camelCase. - * @return {string} The transformed camelCase string. - */ -export function kebabToCamelCase( str ) { - return str - .replace( /^-+|-+$/g, '' ) - .toLowerCase() - .replace( /-([a-z])/g, function ( match, group1 ) { - return group1.toUpperCase(); - } ); -} diff --git a/packages/interactivity/src/utils/kebab-to-camelcase.ts b/packages/interactivity/src/utils/kebab-to-camelcase.ts new file mode 100644 index 00000000000000..1e0e2a550d3aa7 --- /dev/null +++ b/packages/interactivity/src/utils/kebab-to-camelcase.ts @@ -0,0 +1,14 @@ +/** + * Transforms a kebab-case string to camelCase. + * + * @param str The kebab-case string to transform to camelCase. + * @return The transformed camelCase string. + */ +export function kebabToCamelCase( str: string ): string { + return str + .replace( /^-+|-+$/g, '' ) + .toLowerCase() + .replace( /-([a-z])/g, function ( group1: string ) { + return group1.toUpperCase(); + } ); +} From 5851df85d81e8f4e8ba88ca23fbe6f20198e219d Mon Sep 17 00:00:00 2001 From: Eneko Garrido Date: Sun, 24 Mar 2024 13:39:43 +0100 Subject: [PATCH 02/11] Add types for utils, refactor utils.js to ts --- packages/interactivity/src/types.ts | 15 +++ .../interactivity/src/{utils.js => utils.ts} | 107 ++++++++++++------ 2 files changed, 86 insertions(+), 36 deletions(-) create mode 100644 packages/interactivity/src/types.ts rename packages/interactivity/src/{utils.js => utils.ts} (65%) diff --git a/packages/interactivity/src/types.ts b/packages/interactivity/src/types.ts new file mode 100644 index 00000000000000..17c848120345fa --- /dev/null +++ b/packages/interactivity/src/types.ts @@ -0,0 +1,15 @@ +export interface Flusher { + flush: () => void; + dispose: () => void; +} +export interface ParentElement extends Element { + __k: { + nodeType: 1; + parentNode: ParentElement; + firstChild: Node; + childNodes: Node[]; + insertBefore: Function; + appendChild: Function; + removeChild: Function; + }; +} diff --git a/packages/interactivity/src/utils.js b/packages/interactivity/src/utils.ts similarity index 65% rename from packages/interactivity/src/utils.js rename to packages/interactivity/src/utils.ts index 37a5f5eb6a97ba..a3cc3bae7dcf64 100644 --- a/packages/interactivity/src/utils.js +++ b/packages/interactivity/src/utils.ts @@ -20,9 +20,16 @@ import { setNamespace, resetNamespace, } from './hooks'; +import type { Flusher, ParentElement } from './types'; -const afterNextFrame = ( callback ) => { - return new Promise( ( resolve ) => { +/** + * Executes a callback function after the next frame is rendered. + * + * @param callback - The callback function to be executed. + * @return A promise that resolves after the callback function is executed. + */ +const afterNextFrame = ( callback: Function ) => { + return new Promise< void >( ( resolve ) => { const done = () => { clearTimeout( timeout ); window.cancelAnimationFrame( raf ); @@ -36,12 +43,20 @@ const afterNextFrame = ( callback ) => { } ); }; -// Using the mangled properties: -// this.c: this._callback -// this.x: this._compute -// https://github.com/preactjs/signals/blob/main/mangle.json -function createFlusher( compute, notify ) { - let flush; +/** + * Creates a Flusher object that can be used to flush computed values and notify listeners. + * + * Using the mangled properties: + * this.c: this._callback + * this.x: this._compute + * https://github.com/preactjs/signals/blob/main/mangle.json + * + * @param compute - The function that computes the value to be flushed. + * @param notify - The function that notifies listeners when the value is flushed. + * @return The Flusher object with `flush` and `dispose` properties. + */ +function createFlusher( compute: Function, notify: () => void ): Flusher { + let flush: () => void; const dispose = effect( function () { flush = this.c.bind( this ); this.x = compute; @@ -51,13 +66,22 @@ function createFlusher( compute, notify ) { return { flush, dispose }; } -// Version of `useSignalEffect` with a `useEffect`-like execution. This hook -// implementation comes from this PR, but we added short-cirtuiting to avoid -// infinite loops: https://github.com/preactjs/signals/pull/290 -export function useSignalEffect( callback ) { +/** + * Custom hook that executes a callback function whenever a signal is triggered. + * Version of `useSignalEffect` with a `useEffect`-like execution. This hook + * implementation comes from this PR, but we added short-cirtuiting to avoid + * infinite loops: https://github.com/preactjs/signals/pull/290 + * + * @param callback - The callback function to be executed. + */ +export function useSignalEffect( callback: Function ) { _useEffect( () => { let eff = null; let isExecuting = false; + + /** + * Notifies the callback function to execute after the next frame. + */ const notify = async () => { if ( eff && ! isExecuting ) { isExecuting = true; @@ -65,6 +89,7 @@ export function useSignalEffect( callback ) { isExecuting = false; } }; + eff = createFlusher( callback, notify ); return eff.dispose; }, [] ); @@ -75,17 +100,17 @@ export function useSignalEffect( callback ) { * accessible whenever the function runs. This is primarily to make the scope * available inside hook callbacks. * - * @param {Function} func The passed function. - * @return {Function} The wrapped function. + * @param func The passed function. + * @return The wrapped function. */ -export const withScope = ( func ) => { +export const withScope = ( func: Function ): ( () => void ) => { const scope = getScope(); const ns = getNamespace(); if ( func?.constructor?.name === 'GeneratorFunction' ) { return async ( ...args ) => { const gen = func( ...args ); - let value; - let it; + let value: any; + let it: any; while ( true ) { setNamespace( ns ); setScope( scope ); @@ -125,9 +150,9 @@ export const withScope = ( func ) => { * This hook makes the element's scope available so functions like * `getElement()` and `getContext()` can be used inside the passed callback. * - * @param {Function} callback The hook callback. + * @param callback The hook callback. */ -export function useWatch( callback ) { +export function useWatch( callback: Function ) { useSignalEffect( withScope( callback ) ); } @@ -138,9 +163,9 @@ export function useWatch( callback ) { * This hook makes the element's scope available so functions like * `getElement()` and `getContext()` can be used inside the passed callback. * - * @param {Function} callback The hook callback. + * @param callback The hook callback. */ -export function useInit( callback ) { +export function useInit( callback: Function ) { _useEffect( withScope( callback ), [] ); } @@ -152,12 +177,12 @@ export function useInit( callback ) { * available so functions like `getElement()` and `getContext()` can be used * inside the passed callback. * - * @param {Function} callback Imperative function that can return a cleanup - * function. - * @param {any[]} inputs If present, effect will only activate if the - * values in the list change (using `===`). + * @param callback Imperative function that can return a cleanup + * function. + * @param inputs If present, effect will only activate if the + * values in the list change (using `===`). */ -export function useEffect( callback, inputs ) { +export function useEffect( callback: Function, inputs: any[] ) { _useEffect( withScope( callback ), inputs ); } @@ -169,12 +194,12 @@ export function useEffect( callback, inputs ) { * scope available so functions like `getElement()` and `getContext()` can be * used inside the passed callback. * - * @param {Function} callback Imperative function that can return a cleanup - * function. - * @param {any[]} inputs If present, effect will only activate if the - * values in the list change (using `===`). + * @param callback Imperative function that can return a cleanup + * function. + * @param inputs If present, effect will only activate if the + * values in the list change (using `===`). */ -export function useLayoutEffect( callback, inputs ) { +export function useLayoutEffect( callback: Function, inputs: any[] ) { _useLayoutEffect( withScope( callback ), inputs ); } @@ -218,12 +243,22 @@ export function useMemo( factory, inputs ) { return _useMemo( withScope( factory ), inputs ); } -// For wrapperless hydration. -// See https://gist.github.com/developit/f4c67a2ede71dc2fab7f357f39cff28c -export const createRootFragment = ( parent, replaceNode ) => { +/** + * Creates a root fragment by replacing a node or an array of nodes in a parent element. + * For wrapperless hydration. + * See https://gist.github.com/developit/f4c67a2ede71dc2fab7f357f39cff28c + * + * @param parent - The parent element where the nodes will be replaced. + * @param replaceNode - The node or array of nodes to replace in the parent element. + * @return The created root fragment. + */ +export const createRootFragment = ( + parent: ParentElement, + replaceNode: Node | Node[] +) => { replaceNode = [].concat( replaceNode ); const s = replaceNode[ replaceNode.length - 1 ].nextSibling; - function insert( c, r ) { + function insert( c: any, r: any ) { parent.insertBefore( c, r || s ); } return ( parent.__k = { @@ -233,7 +268,7 @@ export const createRootFragment = ( parent, replaceNode ) => { childNodes: replaceNode, insertBefore: insert, appendChild: insert, - removeChild( c ) { + removeChild( c: Node ) { parent.removeChild( c ); }, } ); From 9a3943240087200eef793880cb053683aded5ca4 Mon Sep 17 00:00:00 2001 From: Eneko Garrido Date: Sun, 24 Mar 2024 14:05:38 +0100 Subject: [PATCH 03/11] Fix kebab camelcase convert --- packages/interactivity/src/utils/kebab-to-camelcase.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/interactivity/src/utils/kebab-to-camelcase.ts b/packages/interactivity/src/utils/kebab-to-camelcase.ts index 1e0e2a550d3aa7..c541965c81b618 100644 --- a/packages/interactivity/src/utils/kebab-to-camelcase.ts +++ b/packages/interactivity/src/utils/kebab-to-camelcase.ts @@ -8,7 +8,7 @@ export function kebabToCamelCase( str: string ): string { return str .replace( /^-+|-+$/g, '' ) .toLowerCase() - .replace( /-([a-z])/g, function ( group1: string ) { + .replace( /-([a-z])/g, function ( _match, group1: string ) { return group1.toUpperCase(); } ); } From 536da90adfaf8ac22b1a9658c89e43ff32717686 Mon Sep 17 00:00:00 2001 From: Eneko Date: Thu, 4 Apr 2024 01:09:43 +0200 Subject: [PATCH 04/11] Remove unnecessary hyphen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Greg Ziółkowski --- packages/interactivity/src/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/interactivity/src/utils.ts b/packages/interactivity/src/utils.ts index a3cc3bae7dcf64..14417ca63dff3d 100644 --- a/packages/interactivity/src/utils.ts +++ b/packages/interactivity/src/utils.ts @@ -248,7 +248,7 @@ export function useMemo( factory, inputs ) { * For wrapperless hydration. * See https://gist.github.com/developit/f4c67a2ede71dc2fab7f357f39cff28c * - * @param parent - The parent element where the nodes will be replaced. + * @param parent The parent element where the nodes will be replaced. * @param replaceNode - The node or array of nodes to replace in the parent element. * @return The created root fragment. */ From 5d556c78a11fe621005208ae69cb79f207fde9bf Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Thu, 4 Apr 2024 15:33:19 +0200 Subject: [PATCH 05/11] Remove - from jsdoc --- packages/interactivity/src/utils.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/interactivity/src/utils.ts b/packages/interactivity/src/utils.ts index 14417ca63dff3d..667473afbe0f6c 100644 --- a/packages/interactivity/src/utils.ts +++ b/packages/interactivity/src/utils.ts @@ -25,7 +25,7 @@ import type { Flusher, ParentElement } from './types'; /** * Executes a callback function after the next frame is rendered. * - * @param callback - The callback function to be executed. + * @param callback The callback function to be executed. * @return A promise that resolves after the callback function is executed. */ const afterNextFrame = ( callback: Function ) => { @@ -51,8 +51,8 @@ const afterNextFrame = ( callback: Function ) => { * this.x: this._compute * https://github.com/preactjs/signals/blob/main/mangle.json * - * @param compute - The function that computes the value to be flushed. - * @param notify - The function that notifies listeners when the value is flushed. + * @param compute The function that computes the value to be flushed. + * @param notify The function that notifies listeners when the value is flushed. * @return The Flusher object with `flush` and `dispose` properties. */ function createFlusher( compute: Function, notify: () => void ): Flusher { @@ -72,7 +72,7 @@ function createFlusher( compute: Function, notify: () => void ): Flusher { * implementation comes from this PR, but we added short-cirtuiting to avoid * infinite loops: https://github.com/preactjs/signals/pull/290 * - * @param callback - The callback function to be executed. + * @param callback The callback function to be executed. */ export function useSignalEffect( callback: Function ) { _useEffect( () => { @@ -249,7 +249,7 @@ export function useMemo( factory, inputs ) { * See https://gist.github.com/developit/f4c67a2ede71dc2fab7f357f39cff28c * * @param parent The parent element where the nodes will be replaced. - * @param replaceNode - The node or array of nodes to replace in the parent element. + * @param replaceNode The node or array of nodes to replace in the parent element. * @return The created root fragment. */ export const createRootFragment = ( From 4f7cf984f34da2ef8dcb853f939f1d832d7e3c75 Mon Sep 17 00:00:00 2001 From: Carlos Bravo Date: Tue, 9 Apr 2024 16:35:34 +0200 Subject: [PATCH 06/11] Address suggestions --- packages/interactivity/src/types.ts | 15 ----------- packages/interactivity/src/utils.ts | 26 ++++++++++++------- .../src/utils/kebab-to-camelcase.ts | 2 +- 3 files changed, 17 insertions(+), 26 deletions(-) delete mode 100644 packages/interactivity/src/types.ts diff --git a/packages/interactivity/src/types.ts b/packages/interactivity/src/types.ts deleted file mode 100644 index 17c848120345fa..00000000000000 --- a/packages/interactivity/src/types.ts +++ /dev/null @@ -1,15 +0,0 @@ -export interface Flusher { - flush: () => void; - dispose: () => void; -} -export interface ParentElement extends Element { - __k: { - nodeType: 1; - parentNode: ParentElement; - firstChild: Node; - childNodes: Node[]; - insertBefore: Function; - appendChild: Function; - removeChild: Function; - }; -} diff --git a/packages/interactivity/src/utils.ts b/packages/interactivity/src/utils.ts index 667473afbe0f6c..831ca471532c73 100644 --- a/packages/interactivity/src/utils.ts +++ b/packages/interactivity/src/utils.ts @@ -20,7 +20,11 @@ import { setNamespace, resetNamespace, } from './hooks'; -import type { Flusher, ParentElement } from './types'; + +interface Flusher { + flush: () => void; + dispose: () => void; +} /** * Executes a callback function after the next frame is rendered. @@ -28,7 +32,7 @@ import type { Flusher, ParentElement } from './types'; * @param callback The callback function to be executed. * @return A promise that resolves after the callback function is executed. */ -const afterNextFrame = ( callback: Function ) => { +const afterNextFrame = ( callback: () => void ) => { return new Promise< void >( ( resolve ) => { const done = () => { clearTimeout( timeout ); @@ -55,7 +59,7 @@ const afterNextFrame = ( callback: Function ) => { * @param notify The function that notifies listeners when the value is flushed. * @return The Flusher object with `flush` and `dispose` properties. */ -function createFlusher( compute: Function, notify: () => void ): Flusher { +function createFlusher( compute: () => unknown, notify: () => void ): Flusher { let flush: () => void; const dispose = effect( function () { flush = this.c.bind( this ); @@ -63,7 +67,7 @@ function createFlusher( compute: Function, notify: () => void ): Flusher { this.c = notify; return compute(); } ); - return { flush, dispose }; + return { flush, dispose } as const; } /** @@ -74,14 +78,11 @@ function createFlusher( compute: Function, notify: () => void ): Flusher { * * @param callback The callback function to be executed. */ -export function useSignalEffect( callback: Function ) { +export function useSignalEffect( callback: () => unknown ) { _useEffect( () => { let eff = null; let isExecuting = false; - /** - * Notifies the callback function to execute after the next frame. - */ const notify = async () => { if ( eff && ! isExecuting ) { isExecuting = true; @@ -253,15 +254,20 @@ export function useMemo( factory, inputs ) { * @return The created root fragment. */ export const createRootFragment = ( - parent: ParentElement, + parent: Element, replaceNode: Node | Node[] ) => { replaceNode = [].concat( replaceNode ); const s = replaceNode[ replaceNode.length - 1 ].nextSibling; function insert( c: any, r: any ) { + /** + * c address for children. + * r address for root. + * s address for sibling. + */ parent.insertBefore( c, r || s ); } - return ( parent.__k = { + return ( ( parent as any ).__k = { nodeType: 1, parentNode: parent, firstChild: replaceNode[ 0 ], diff --git a/packages/interactivity/src/utils/kebab-to-camelcase.ts b/packages/interactivity/src/utils/kebab-to-camelcase.ts index c541965c81b618..b6c3c6f3071245 100644 --- a/packages/interactivity/src/utils/kebab-to-camelcase.ts +++ b/packages/interactivity/src/utils/kebab-to-camelcase.ts @@ -2,7 +2,7 @@ * Transforms a kebab-case string to camelCase. * * @param str The kebab-case string to transform to camelCase. - * @return The transformed camelCase string. + * @return The transformed camelCase string. */ export function kebabToCamelCase( str: string ): string { return str From 02a9153d8d427f33047241c7100a5f887781a2f7 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Wed, 10 Apr 2024 13:31:26 +0200 Subject: [PATCH 07/11] Expand single character variable names --- packages/interactivity/src/utils.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/packages/interactivity/src/utils.ts b/packages/interactivity/src/utils.ts index 831ca471532c73..5265d85f94e873 100644 --- a/packages/interactivity/src/utils.ts +++ b/packages/interactivity/src/utils.ts @@ -258,14 +258,9 @@ export const createRootFragment = ( replaceNode: Node | Node[] ) => { replaceNode = [].concat( replaceNode ); - const s = replaceNode[ replaceNode.length - 1 ].nextSibling; - function insert( c: any, r: any ) { - /** - * c address for children. - * r address for root. - * s address for sibling. - */ - parent.insertBefore( c, r || s ); + const sibling = replaceNode[ replaceNode.length - 1 ].nextSibling; + function insert( child: any, root: any ) { + parent.insertBefore( child, root || sibling ); } return ( ( parent as any ).__k = { nodeType: 1, From 22022f36ceb6375a4420b5eeadd3fb4a61f995cf Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Wed, 10 Apr 2024 13:34:04 +0200 Subject: [PATCH 08/11] Make flusher properties readonly --- packages/interactivity/src/utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/interactivity/src/utils.ts b/packages/interactivity/src/utils.ts index 5265d85f94e873..2de95ff141f1b7 100644 --- a/packages/interactivity/src/utils.ts +++ b/packages/interactivity/src/utils.ts @@ -22,8 +22,8 @@ import { } from './hooks'; interface Flusher { - flush: () => void; - dispose: () => void; + readonly flush: () => void; + readonly dispose: () => void; } /** From 05e0423afa4545c9c2937ee46b03b68653dd38cc Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Wed, 10 Apr 2024 13:44:39 +0200 Subject: [PATCH 09/11] Improve types and move JSDoc types to TS --- packages/interactivity/src/utils.ts | 39 +++++++++++++++-------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/packages/interactivity/src/utils.ts b/packages/interactivity/src/utils.ts index 2de95ff141f1b7..8e332db9c9db6b 100644 --- a/packages/interactivity/src/utils.ts +++ b/packages/interactivity/src/utils.ts @@ -6,6 +6,8 @@ import { useCallback as _useCallback, useEffect as _useEffect, useLayoutEffect as _useLayoutEffect, + type EffectCallback, + type Inputs, } from 'preact/hooks'; import { effect } from '@preact/signals'; @@ -153,7 +155,7 @@ export const withScope = ( func: Function ): ( () => void ) => { * * @param callback The hook callback. */ -export function useWatch( callback: Function ) { +export function useWatch( callback: () => unknown ) { useSignalEffect( withScope( callback ) ); } @@ -166,7 +168,7 @@ export function useWatch( callback: Function ) { * * @param callback The hook callback. */ -export function useInit( callback: Function ) { +export function useInit( callback: EffectCallback ) { _useEffect( withScope( callback ), [] ); } @@ -183,7 +185,7 @@ export function useInit( callback: Function ) { * @param inputs If present, effect will only activate if the * values in the list change (using `===`). */ -export function useEffect( callback: Function, inputs: any[] ) { +export function useEffect( callback: EffectCallback, inputs: Inputs ) { _useEffect( withScope( callback ), inputs ); } @@ -200,7 +202,7 @@ export function useEffect( callback: Function, inputs: any[] ) { * @param inputs If present, effect will only activate if the * values in the list change (using `===`). */ -export function useLayoutEffect( callback: Function, inputs: any[] ) { +export function useLayoutEffect( callback: EffectCallback, inputs: Inputs ) { _useLayoutEffect( withScope( callback ), inputs ); } @@ -212,16 +214,17 @@ export function useLayoutEffect( callback: Function, inputs: any[] ) { * scope available so functions like `getElement()` and `getContext()` can be * used inside the passed callback. * - * @template {Function} T The callback function type. - * - * @param {T} callback Callback function. - * @param {ReadonlyArray} inputs If present, the callback will only be updated if the - * values in the list change (using `===`). + * @param callback Callback function. + * @param inputs If present, the callback will only be updated if the + * values in the list change (using `===`). * - * @return {T} The callback function. + * @return The callback function. */ -export function useCallback( callback, inputs ) { - return _useCallback( withScope( callback ), inputs ); +export function useCallback< T extends Function >( + callback: T, + inputs: Inputs +): T { + return _useCallback< T >( withScope( callback ), inputs ); } /** @@ -232,15 +235,13 @@ export function useCallback( callback, inputs ) { * available so functions like `getElement()` and `getContext()` can be used * inside the passed factory function. * - * @template {unknown} T The memoized value. - * - * @param {() => T} factory Factory function that returns that value for memoization. - * @param {ReadonlyArray} inputs If present, the factory will only be run to recompute if - * the values in the list change (using `===`). + * @param factory Factory function that returns that value for memoization. + * @param inputs If present, the factory will only be run to recompute if + * the values in the list change (using `===`). * - * @return {T} The memoized value. + * @return The memoized value. */ -export function useMemo( factory, inputs ) { +export function useMemo< T >( factory: () => T, inputs: Inputs ): T { return _useMemo( withScope( factory ), inputs ); } From 6cec535c5c1d3c2aafcbd98aaa27b0dec2dd575d Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Wed, 10 Apr 2024 14:01:43 +0200 Subject: [PATCH 10/11] Fix withScope type --- packages/interactivity/src/utils.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/interactivity/src/utils.ts b/packages/interactivity/src/utils.ts index 8e332db9c9db6b..6fe2e59bb337f3 100644 --- a/packages/interactivity/src/utils.ts +++ b/packages/interactivity/src/utils.ts @@ -103,10 +103,23 @@ export function useSignalEffect( callback: () => unknown ) { * accessible whenever the function runs. This is primarily to make the scope * available inside hook callbacks. * + * Asyncronous functions should use generators that yield promises instead of awaiting them. + * See the documentation for details: https://developer.wordpress.org/block-editor/reference-guides/packages/packages-interactivity/packages-interactivity-api-reference/#the-store + * * @param func The passed function. * @return The wrapped function. */ -export const withScope = ( func: Function ): ( () => void ) => { +export function withScope< + Func extends ( ...args: any[] ) => Generator< any, any >, +>( + func: Func +): ( + ...args: Parameters< Func > +) => ReturnType< Func > extends Generator< any, infer Return > + ? Promise< Return > + : never; +export function withScope< Func extends Function >( func: Func ): Func; +export function withScope( func ) { const scope = getScope(); const ns = getNamespace(); if ( func?.constructor?.name === 'GeneratorFunction' ) { @@ -143,7 +156,7 @@ export const withScope = ( func: Function ): ( () => void ) => { resetScope(); } }; -}; +} /** * Accepts a function that contains imperative code which runs whenever any of From 2baa5f56e1fb786f6008b1f9ffbed493a44f84d8 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Wed, 10 Apr 2024 14:09:27 +0200 Subject: [PATCH 11/11] Some internal withScope typing --- packages/interactivity/src/utils.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/interactivity/src/utils.ts b/packages/interactivity/src/utils.ts index 6fe2e59bb337f3..e9162f0dd7cd79 100644 --- a/packages/interactivity/src/utils.ts +++ b/packages/interactivity/src/utils.ts @@ -119,12 +119,12 @@ export function withScope< ? Promise< Return > : never; export function withScope< Func extends Function >( func: Func ): Func; -export function withScope( func ) { +export function withScope( func: ( ...args: unknown[] ) => unknown ) { const scope = getScope(); const ns = getNamespace(); if ( func?.constructor?.name === 'GeneratorFunction' ) { - return async ( ...args ) => { - const gen = func( ...args ); + return async ( ...args: Parameters< typeof func > ) => { + const gen = func( ...args ) as Generator; let value: any; let it: any; while ( true ) { @@ -146,7 +146,7 @@ export function withScope( func ) { return value; }; } - return ( ...args ) => { + return ( ...args: Parameters< typeof func > ) => { setNamespace( ns ); setScope( scope ); try {