From 0f9f08b1ef795215975c7b041d0e94a992d22124 Mon Sep 17 00:00:00 2001
From: Hailey <me@haileyok.com>
Date: Fri, 8 Mar 2024 14:31:24 -0800
Subject: Fix reactivity of dialogs (Dialogs Pt. 1) (#3146)

* Improve a11y on ios

* Format

* Remove android

* Fix android

* Revert some changes

* use sharedvalue for `importantForAccessibility`

* add back `isOpen`

* fix some more types

---------

Co-authored-by: Eric Bailey <git@esb.lol>
---
 src/state/dialogs/index.tsx | 60 +++++++++++++++++++++++++++++++--------------
 1 file changed, 41 insertions(+), 19 deletions(-)

(limited to 'src/state/dialogs/index.tsx')

diff --git a/src/state/dialogs/index.tsx b/src/state/dialogs/index.tsx
index 90aaca4f8..951105a50 100644
--- a/src/state/dialogs/index.tsx
+++ b/src/state/dialogs/index.tsx
@@ -1,8 +1,9 @@
 import React from 'react'
+import {SharedValue, useSharedValue} from 'react-native-reanimated'
 import {DialogControlRefProps} from '#/components/Dialog'
 import {Provider as GlobalDialogsProvider} from '#/components/dialogs/Context'
 
-const DialogContext = React.createContext<{
+interface IDialogContext {
   /**
    * The currently active `useDialogControl` hooks.
    */
@@ -13,13 +14,18 @@ const DialogContext = React.createContext<{
    * The currently open dialogs, referenced by their IDs, generated from
    * `useId`.
    */
-  openDialogs: string[]
-}>({
-  activeDialogs: {
-    current: new Map(),
-  },
-  openDialogs: [],
-})
+  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(): boolean
@@ -41,26 +47,42 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
   const activeDialogs = React.useRef<
     Map<string, React.MutableRefObject<DialogControlRefProps>>
   >(new Map())
-  const [openDialogs, setOpenDialogs] = React.useState<string[]>([])
+  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())
-    return openDialogs.length > 0
-  }, [openDialogs])
+    return openDialogs.current.size > 0
+  }, [])
 
   const setDialogIsOpen = React.useCallback(
     (id: string, isOpen: boolean) => {
-      setOpenDialogs(prev => {
-        const filtered = prev.filter(dialogId => dialogId !== id) as string[]
-        return isOpen ? [...filtered, id] : filtered
-      })
+      if (isOpen) {
+        openDialogs.current.add(id)
+        importantForAccessibility.value = 'no-hide-descendants'
+      } else {
+        openDialogs.current.delete(id)
+        if (openDialogs.current.size < 1) {
+          importantForAccessibility.value = 'auto'
+        }
+      }
     },
-    [setOpenDialogs],
+    [importantForAccessibility],
   )
 
-  const context = React.useMemo(
-    () => ({activeDialogs, openDialogs}),
-    [openDialogs],
+  const context = React.useMemo<IDialogContext>(
+    () => ({
+      activeDialogs: {
+        current: new Map(),
+      },
+      openDialogs: {
+        current: new Set(),
+      },
+      importantForAccessibility,
+    }),
+    [importantForAccessibility],
   )
   const controls = React.useMemo(
     () => ({closeAllDialogs, setDialogIsOpen}),
-- 
cgit 1.4.1

