From fff2e752f561d3028a6d247c48f267eec4101b0c Mon Sep 17 00:00:00 2001 From: Ismael Ramon Date: Tue, 21 Jan 2025 20:11:02 +0100 Subject: [PATCH 1/3] Add end method from caller scope --- lib/createCallable/index.tsx | 46 +++++++++++++++++++++--------------- lib/createCallable/types.ts | 7 ++++++ 2 files changed, 34 insertions(+), 19 deletions(-) diff --git a/lib/createCallable/index.tsx b/lib/createCallable/index.tsx index 94be91a..6d8a55c 100644 --- a/lib/createCallable/index.tsx +++ b/lib/createCallable/index.tsx @@ -1,6 +1,7 @@ import { useState, useEffect } from 'react' import type { UserComponent as UserComponentType, + PrivateResolve, PrivateStackState, PrivateStackStateSetter, Callable, @@ -13,36 +14,43 @@ export function createCallable( let $setStack: PrivateStackStateSetter | null = null let $nextKey = 0 + const createEnd = (promise: Promise) => (response: Response) => { + if (!$setStack) return + const scopedSetStack = $setStack + + scopedSetStack((prev) => { + const target = prev.find((c) => c.promise === promise) + if (!target) return prev + + target.resolve(response) + return prev.map((c) => + c.promise !== promise ? c : { ...c, ended: true }, + ) + }) + + globalThis.setTimeout( + () => scopedSetStack((prev) => prev.filter((c) => c.promise !== promise)), + unmountingDelay, + ) + } + return { call: (props) => { if (!$setStack) throw new Error('No found!') const key = String($nextKey++) - let resolve: (value: Response | PromiseLike) => void + let resolve: PrivateResolve const promise = new Promise((res) => { resolve = res }) - const end = (response: Response) => { - resolve(response) - if (!$setStack) return - const scopedSetStack = $setStack - - if (unmountingDelay > 0) { - scopedSetStack((prev) => - prev.map((c) => (c.key !== key ? c : { ...c, ended: true })), - ) - } - - globalThis.setTimeout( - () => scopedSetStack((prev) => prev.filter((c) => c.key !== key)), - unmountingDelay, - ) - } - - $setStack((prev) => [...prev, { key, props, end, ended: false }]) + $setStack((prev) => [ + ...prev, + { key, props, promise, resolve, end: createEnd(promise), ended: false }, + ]) return promise }, + end: (promise, response) => createEnd(promise)(response), Root: (rootProps: RootProps) => { const [stack, setStack] = useState>([]) diff --git a/lib/createCallable/types.ts b/lib/createCallable/types.ts index bab8f9b..a85cf60 100644 --- a/lib/createCallable/types.ts +++ b/lib/createCallable/types.ts @@ -1,6 +1,12 @@ +export type PrivateResolve = ( + value: Response | PromiseLike, +) => void + export interface PrivateCallContext { key: string props: Props + promise: Promise + resolve: PrivateResolve end: (response: Response) => void ended: boolean } @@ -45,4 +51,5 @@ export type UserComponent = React.FunctionComponent< export type Callable = { Root: React.FunctionComponent call: CallFunction + end: (promise: Promise, response: Response) => void } From cd99a47714614018f8d196e86ad094b4b2b9e56e Mon Sep 17 00:00:00 2001 From: Ismael Ramon Date: Tue, 21 Jan 2025 20:12:17 +0100 Subject: [PATCH 2/3] Raise maxSize a bit --- bundlesize.config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundlesize.config.json b/bundlesize.config.json index 62a0fb0..557969d 100644 --- a/bundlesize.config.json +++ b/bundlesize.config.json @@ -2,7 +2,7 @@ "files": [ { "path": "./dist/*.js", - "maxSize": "555 B" + "maxSize": "650 B" } ] } From c1fe1dc53987f390f882f9b3a5ff3b1438ee79c3 Mon Sep 17 00:00:00 2001 From: Ismael Ramon Date: Wed, 22 Jan 2025 13:57:24 +0100 Subject: [PATCH 3/3] Add docs for caller scope end method --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index d047a0f..0a00936 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,22 @@ const accepted = await Confirm.call({ message: 'Continue?' }) Check out [the demo site](https://react-call.desko.dev/) to see some live examples of other React components being called. +# Extra controls + +The returned promise can also be used to end the call from the caller scope: + +```tsx +const promise = Confirm.call({ message: 'Continue?' }) + +// For example, you could dismiss it on some event subscription +onImportantEvent(() => { + Confirm.end(promise, false) +}) + +// While still awaiting the response where needed +const accepted = await promise +``` + # Exit animations To animate the exit of your component when `call.end()` is run, just pass the duration of your animation in milliseconds to createCallable as a second argument: