about summary refs log tree commit diff
path: root/src/state/dialogs
diff options
context:
space:
mode:
Diffstat (limited to 'src/state/dialogs')
-rw-r--r--src/state/dialogs/index.tsx85
1 files changed, 70 insertions, 15 deletions
diff --git a/src/state/dialogs/index.tsx b/src/state/dialogs/index.tsx
index 4cafaa086..26bb6792f 100644
--- a/src/state/dialogs/index.tsx
+++ b/src/state/dialogs/index.tsx
@@ -1,20 +1,39 @@
 import React from 'react'
-import {DialogControlProps} from '#/components/Dialog'
+import {SharedValue, useSharedValue} from 'react-native-reanimated'
 
-const DialogContext = React.createContext<{
+import {DialogControlRefProps} from '#/components/Dialog'
+import {Provider as GlobalDialogsProvider} from '#/components/dialogs/Context'
+
+interface IDialogContext {
+  /**
+   * The currently active `useDialogControl` hooks.
+   */
   activeDialogs: React.MutableRefObject<
-    Map<string, React.MutableRefObject<DialogControlProps>>
+    Map<string, React.MutableRefObject<DialogControlRefProps>>
   >
-}>({
-  activeDialogs: {
-    current: new Map(),
-  },
-})
+  /**
+   * The currently open dialogs, referenced by their IDs, generated from
+   * `useId`.
+   */
+  openDialogs: React.MutableRefObject<Set<string>>
+  /**
+   * The counterpart to `accessibilityViewIsModal` for Android. This property
+   * applies to the parent of all non-modal views, and prevents TalkBack from
+   * navigating within content beneath an open dialog.
+   *
+   * @see https://reactnative.dev/docs/accessibility#importantforaccessibility-android
+   */
+  importantForAccessibility: SharedValue<'auto' | 'no-hide-descendants'>
+}
+
+const DialogContext = React.createContext<IDialogContext>({} as IDialogContext)
 
 const DialogControlContext = React.createContext<{
-  closeAllDialogs(): void
+  closeAllDialogs(): boolean
+  setDialogIsOpen(id: string, isOpen: boolean): void
 }>({
-  closeAllDialogs: () => {},
+  closeAllDialogs: () => false,
+  setDialogIsOpen: () => {},
 })
 
 export function useDialogStateContext() {
@@ -27,17 +46,53 @@ export function useDialogStateControlContext() {
 
 export function Provider({children}: React.PropsWithChildren<{}>) {
   const activeDialogs = React.useRef<
-    Map<string, React.MutableRefObject<DialogControlProps>>
+    Map<string, React.MutableRefObject<DialogControlRefProps>>
   >(new Map())
+  const openDialogs = React.useRef<Set<string>>(new Set())
+  const importantForAccessibility = useSharedValue<
+    'auto' | 'no-hide-descendants'
+  >('auto')
+
   const closeAllDialogs = React.useCallback(() => {
-    activeDialogs.current.forEach(dialog => dialog.current.close())
+    openDialogs.current.forEach(id => {
+      const dialog = activeDialogs.current.get(id)
+      if (dialog) dialog.current.close()
+    })
+    return openDialogs.current.size > 0
   }, [])
-  const context = React.useMemo(() => ({activeDialogs}), [])
-  const controls = React.useMemo(() => ({closeAllDialogs}), [closeAllDialogs])
+
+  const setDialogIsOpen = React.useCallback(
+    (id: string, isOpen: boolean) => {
+      if (isOpen) {
+        openDialogs.current.add(id)
+        importantForAccessibility.value = 'no-hide-descendants'
+      } else {
+        openDialogs.current.delete(id)
+        if (openDialogs.current.size < 1) {
+          importantForAccessibility.value = 'auto'
+        }
+      }
+    },
+    [importantForAccessibility],
+  )
+
+  const context = React.useMemo<IDialogContext>(
+    () => ({
+      activeDialogs,
+      openDialogs,
+      importantForAccessibility,
+    }),
+    [importantForAccessibility, activeDialogs, openDialogs],
+  )
+  const controls = React.useMemo(
+    () => ({closeAllDialogs, setDialogIsOpen}),
+    [closeAllDialogs, setDialogIsOpen],
+  )
+
   return (
     <DialogContext.Provider value={context}>
       <DialogControlContext.Provider value={controls}>
-        {children}
+        <GlobalDialogsProvider>{children}</GlobalDialogsProvider>
       </DialogControlContext.Provider>
     </DialogContext.Provider>
   )