@samitouri / QOSami-HFS / commits / 5039f1c0

better code

Massimo Melina committed Dec 5, 2024 at 20:34 UTC 5039f1c055d6276777f6e9d9d6e1ad6d7383dd5c
1 file changed +18 -16
shared/dialogs.ts
+18 -16
@@ -20,15 +20,19 @@ export interface DialogOptions {
20 padding?: boolean
21 position?: [number, number]
22 dialogProps?: Record<string, any>
23 + restoreFocus?: boolean
24 + Container?: FunctionComponent<DialogOptions>
25 +}
26 +
27 +interface Dialog extends DialogOptions {
28 $id?: number
29 $opening?: NodeJS.Timeout
30 ts?: number
31 + close: (v?: any) => void
32 restoreFocus?: any
27 -
28 - Container?: FunctionComponent<DialogOptions>
33 }
34
31 -const dialogs = proxy<DialogOptions[]>([])
35 +const dialogs = proxy<Dialog[]>([])
36 const { history } = window
37
38 export const dialogsDefaults: Partial<DialogOptions> = {
@@ -181,27 +185,24 @@ export function componentOrNode(x: ReactNode | FunctionComponent) {
185 export function newDialog(options: DialogOptions) {
186 const $id = Math.random()
187 const ts = performance.now()
184 - options.$id = $id // object identity is not working because of the proxy. This is a possible workaround
185 - options.ts = ts
186 - if (document.activeElement)
187 - options.restoreFocus ??= ref(document.activeElement)
188 - options = objSameKeys(options, x => isValidElement(x) ? ref(x) : x) as typeof options // encapsulate elements as React will try to write, but valtio makes them readonly
188 + const d: Dialog = Object.assign(objSameKeys(options, x => isValidElement(x) ? ref(x) : x) as typeof options, { // encapsulate elements as React will try to write, but valtio makes them readonly
189 + close, ts, $id, // object identity is not working because it's proxied (valtio). This is a possible workaroundu
190 + restoreFocus: options.restoreFocus ?? ref(document.activeElement || {}),
191 + })
192 let cancelOpening = false
193 waitClosing.then(() => { // in case dialogs were just closed, account for window.history delay. You already didn't expect dialog to open immediately, but at state change
194 if (cancelOpening) return
192 - dialogs.push(options)
193 - options = dialogs[dialogs.length - 1] // replace with proxy object, to stay in sync with its changes
194 - if (options.closable !== false)
195 + dialogs.push(d)
196 + if (dialogs.at(-1)!.closable !== false) // use proxy object, to stay in sync with its changes
197 history.pushState({ $dialog: $id, ts, idx: 1 + (history.state?.idx || 0) }, '')
198 })
197 - return { close }
199 + return d
200
201 function close(v?:any) {
200 - cancelOpening = true // in case it was not open yet
202 + cancelOpening = true
203 const i = dialogs.findIndex(x => (x as any).$id === $id)
204 if (i < 0) return
203 - if (history.state?.$dialog === $id)
204 - options.closed = back()
205 + d.closed = history.state?.$dialog === $id ? back() : Promise.resolve()
206 closeDialogAt(i, v)
207 return options.closed
208 }
@@ -227,7 +228,8 @@ function closeDialogAt(i: number, value?: any) {
228 const [d] = dialogs.splice(i,1)
229 d.restoreFocus?.focus?.() // if element is not HTMLElement, it doesn't have focus method
230 d.closingValue = value && typeof value === 'object' ? ref(value) : value // since this is being assigned to a valtio proxy, ref is necessary to avoid crashing with unusual (and possibly accidental) objects like React's SynteticEvents
230 - Promise.resolve(d.closed).then(() =>
231 + d.closed ??= Promise.resolve()
232 + d.closed.then(() =>
233 d?.onClose?.(value))
234 return d
235 }