web/modal: don't capture input in context menu modal

This commit is contained in:
Tulir Asokan 2024-12-29 17:30:27 +02:00
parent f83b914af0
commit 08a1712850
5 changed files with 38 additions and 15 deletions

View file

@ -13,7 +13,7 @@
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
import React, { JSX, useCallback, useLayoutEffect, useReducer, useRef } from "react" import React, { JSX, useCallback, useEffect, useLayoutEffect, useReducer, useRef } from "react"
import { ModalCloseContext, ModalContext, ModalState } from "./contexts.ts" import { ModalCloseContext, ModalContext, ModalState } from "./contexts.ts"
const ModalWrapper = ({ children }: { children: React.ReactNode }) => { const ModalWrapper = ({ children }: { children: React.ReactNode }) => {
@ -40,7 +40,7 @@ const ModalWrapper = ({ children }: { children: React.ReactNode }) => {
evt.stopPropagation() evt.stopPropagation()
} }
const openModal = useCallback((newState: ModalState) => { const openModal = useCallback((newState: ModalState) => {
if (!history.state?.modal) { if (!history.state?.modal && newState.captureInput !== false) {
history.pushState({ ...(history.state ?? {}), modal: true }, "") history.pushState({ ...(history.state ?? {}), modal: true }, "")
} }
setState(newState) setState(newState)
@ -50,6 +50,9 @@ const ModalWrapper = ({ children }: { children: React.ReactNode }) => {
if (wrapperRef.current && (!document.activeElement || !wrapperRef.current.contains(document.activeElement))) { if (wrapperRef.current && (!document.activeElement || !wrapperRef.current.contains(document.activeElement))) {
wrapperRef.current.focus() wrapperRef.current.focus()
} }
}, [state])
useEffect(() => {
window.closeModal = onClickWrapper
const listener = (evt: PopStateEvent) => { const listener = (evt: PopStateEvent) => {
if (!evt.state?.modal) { if (!evt.state?.modal) {
setState(null) setState(null)
@ -57,7 +60,7 @@ const ModalWrapper = ({ children }: { children: React.ReactNode }) => {
} }
window.addEventListener("popstate", listener) window.addEventListener("popstate", listener)
return () => window.removeEventListener("popstate", listener) return () => window.removeEventListener("popstate", listener)
}, [state]) }, [])
let modal: JSX.Element | null = null let modal: JSX.Element | null = null
if (state) { if (state) {
let content = <ModalCloseContext value={onClickWrapper}>{state.content}</ModalCloseContext> let content = <ModalCloseContext value={onClickWrapper}>{state.content}</ModalCloseContext>
@ -68,15 +71,19 @@ const ModalWrapper = ({ children }: { children: React.ReactNode }) => {
</div> </div>
</div> </div>
} }
modal = <div if (state.captureInput !== false) {
className={`overlay modal ${state.dimmed ? "dimmed" : ""}`} modal = <div
onClick={onClickWrapper} className={`overlay modal ${state.dimmed ? "dimmed" : ""}`}
onKeyDown={onKeyWrapper} onClick={onClickWrapper}
tabIndex={-1} onKeyDown={onKeyWrapper}
ref={wrapperRef} tabIndex={-1}
> ref={wrapperRef}
{content} >
</div> {content}
</div>
} else {
modal = content
}
} }
return <ModalContext value={openModal}> return <ModalContext value={openModal}>
{children} {children}

View file

@ -32,6 +32,7 @@ export interface ModalState {
boxClass?: string boxClass?: string
innerBoxClass?: string innerBoxClass?: string
onClose?: () => void onClose?: () => void
captureInput?: boolean
} }
type openModal = (state: ModalState) => void type openModal = (state: ModalState) => void

View file

@ -107,9 +107,21 @@ const TimelineEvent = ({ evt, prevEvt, disableMenu, smallReplies }: TimelineEven
return return
} }
mouseEvt.preventDefault() mouseEvt.preventDefault()
openModal({ if (window.hackyOpenEventContextMenu === evt.event_id) {
content: <EventFixedMenu evt={evt} roomCtx={roomCtx} />, window.closeModal()
}) window.hackyOpenEventContextMenu = undefined
} else {
openModal({
content: <EventFixedMenu evt={evt} roomCtx={roomCtx} />,
captureInput: false,
onClose: () => {
if (window.hackyOpenEventContextMenu === evt.event_id) {
window.hackyOpenEventContextMenu = undefined
}
},
})
window.hackyOpenEventContextMenu = evt.event_id
}
} }
const memberEvt = useRoomMember(client, roomCtx.store, evt.sender) const memberEvt = useRoomMember(client, roomCtx.store, evt.sender)
const memberEvtContent = memberEvt?.content as MemberEventContent | undefined const memberEvtContent = memberEvt?.content as MemberEventContent | undefined

View file

@ -28,6 +28,7 @@ div.event-fixed-menu {
box-sizing: border-box; box-sizing: border-box;
justify-content: right; justify-content: right;
flex-direction: row-reverse; flex-direction: row-reverse;
overflow-x: auto;
> button { > button {
width: 3rem; width: 3rem;

View file

@ -14,5 +14,7 @@ declare global {
mainScreenContext: MainScreenContextFields mainScreenContext: MainScreenContextFields
openLightbox: (params: { src: string, alt: string }) => void openLightbox: (params: { src: string, alt: string }) => void
gcSettings: GCSettings gcSettings: GCSettings
hackyOpenEventContextMenu?: string
closeModal: () => void
} }
} }