Skip to content

Commit

Permalink
fix(form): fix issue with PT-input open close state for modals
Browse files Browse the repository at this point in the history
Pressing Escape on Annotation modals would sometimes reset the selection.
  • Loading branch information
skogsmaskin authored and mariuslundgard committed Oct 3, 2022
1 parent aa8561c commit 32e302b
Show file tree
Hide file tree
Showing 6 changed files with 63 additions and 43 deletions.
8 changes: 6 additions & 2 deletions packages/sanity/src/form/inputs/PortableText/Compositor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,10 @@ export function Compositor(props: InputProps) {

const boundaryElm = isFullscreen ? scrollElement : boundaryElement

const handleEditModalClose = useCallback(() => {
onCloseItem()
}, [onCloseItem])

const children = useMemo(
() =>
boundaryElm && (
Expand All @@ -286,7 +290,7 @@ export function Compositor(props: InputProps) {
kind={dMemberItem.kind}
key={dMemberItem.member.key}
memberItem={dMemberItem}
onClose={onCloseItem}
onClose={handleEditModalClose}
scrollElement={boundaryElm}
>
<FormInput absolutePath={dMemberItem.node.path} {...(props as FIXME)} />
Expand All @@ -296,7 +300,7 @@ export function Compositor(props: InputProps) {
</BoundaryElementProvider>
</>
),
[boundaryElm, editorNode, openMemberItems, onCloseItem, props]
[boundaryElm, editorNode, openMemberItems, props, handleEditModalClose]
)

const portal = usePortal()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
RenderAttributes,
PortableTextEditor,
usePortableTextEditor,
usePortableTextEditorSelection,
} from '@sanity/portable-text-editor'
import {Path} from '@sanity/types'
import React, {useCallback, useEffect, useMemo, useState} from 'react'
Expand Down Expand Up @@ -129,6 +128,7 @@ export const InlineObject = React.forwardRef(function InlineObject(
const memberItem = usePortableTextMemberItem(pathToString(path))
const {validation, hasError, hasWarning} = useMemberValidation(memberItem?.node)
const hasValidationMarkers = validation.length > 0
const [popoverOpen, setPopoverOpen] = useState<boolean>(true)

const tone = useMemo(() => {
if (hasError) {
Expand Down Expand Up @@ -198,9 +198,27 @@ export const InlineObject = React.forwardRef(function InlineObject(
PortableTextEditor.blur(editor)
if (memberItem) {
onOpenItem(memberItem.node.path)
setPopoverOpen(false)
}
}, [editor, memberItem, onOpenItem])

const handlePopoverClose = useCallback(() => {
if (memberItem?.member.open) {
setPopoverOpen(true)
}
setPopoverOpen(false)
}, [memberItem?.member.open])

useEffect(() => {
if (memberItem?.member.open) {
setPopoverOpen(false)
} else if (focused) {
setPopoverOpen(true)
} else {
setPopoverOpen(false)
}
}, [editor, focused, memberItem?.member.open, selected])

return (
<>
<Root
Expand All @@ -215,15 +233,16 @@ export const InlineObject = React.forwardRef(function InlineObject(
contentEditable={false}
ref={memberItem?.elementRef as React.RefObject<HTMLDivElement>}
onClick={readOnly ? openItem : undefined}
onDoubleClick={openItem}
>
<span ref={forwardedRef} onDoubleClick={openItem}>
{markersToolTip || preview}
</span>
<span ref={forwardedRef}>{markersToolTip || preview}</span>
</Root>
{focused && !readOnly && (
<InlineObjectToolbarPopover
open={popoverOpen}
onDelete={handleRemoveClick}
onEdit={openItem}
onClose={handlePopoverClose}
referenceElement={memberItem?.elementRef?.current || null}
scrollElement={scrollElement}
title={type?.title || type.name}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, {useRef, useCallback, useEffect} from 'react'
import React, {useRef, useCallback, useEffect, useState} from 'react'
import {
Box,
Button,
Expand All @@ -21,6 +21,8 @@ const ToolbarPopover = styled(Popover)`
const POPOVER_FALLBACK_PLACEMENTS: PopoverProps['fallbackPlacements'] = ['top', 'bottom']

interface InlineObjectToolbarPopoverProps {
open: boolean
onClose: () => void
onDelete: (event: React.MouseEvent<HTMLButtonElement>) => void
onEdit: (event: React.MouseEvent<HTMLButtonElement>) => void
referenceElement: HTMLElement | null
Expand All @@ -29,7 +31,7 @@ interface InlineObjectToolbarPopoverProps {
}

export function InlineObjectToolbarPopover(props: InlineObjectToolbarPopoverProps) {
const {onEdit, onDelete, referenceElement, scrollElement, title} = props
const {onClose, onEdit, onDelete, referenceElement, scrollElement, title, open} = props
const {sanity} = useTheme()
const editButtonRef = useRef<HTMLButtonElement | null>(null)
const popoverScheme = sanity.color.dark ? 'light' : 'dark'
Expand All @@ -38,24 +40,25 @@ export function InlineObjectToolbarPopover(props: InlineObjectToolbarPopoverProp
// Close floating toolbar on Escape
// Focus to edit button on Tab
useGlobalKeyDown(
useCallback((event) => {
if (!open) {
return
}
if (event.key === 'Escape') {
event.preventDefault()
event.stopPropagation()
isTabbing.current = false
}
if (event.key === 'Tab') {
if (!isTabbing.current) {
useCallback(
(event) => {
if (event.key === 'Escape') {
event.preventDefault()
event.stopPropagation()
editButtonRef.current?.focus()
isTabbing.current = true
isTabbing.current = false
onClose()
}
}
}, [])
if (event.key === 'Tab') {
if (!isTabbing.current) {
event.preventDefault()
event.stopPropagation()
editButtonRef.current?.focus()
isTabbing.current = true
}
}
},
[onClose]
)
)

useEffect(() => {
Expand Down Expand Up @@ -97,7 +100,7 @@ export function InlineObjectToolbarPopover(props: InlineObjectToolbarPopoverProp
</Box>
}
fallbackPlacements={POPOVER_FALLBACK_PLACEMENTS}
open
open={open}
placement="top"
portal="editor"
referenceElement={referenceElement}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,10 @@ export function ObjectEditModal(props: {
const initialSelection = useRef<EditorSelection | null>(PortableTextEditor.getSelection(editor))

const handleClose = useCallback(() => {
onClose()
// Force a new selection here as the selection is a callback dep. for showing the popup
PortableTextEditor.select(editor, null)
PortableTextEditor.focus(editor)
PortableTextEditor.select(editor, initialSelection.current)
onClose()
}, [editor, onClose])

const title = <>Edit {memberItem.node.schemaType.title}</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,9 +176,9 @@ export const Annotation = function Annotation(props: AnnotationProps) {
<span ref={memberItem?.elementRef}>{markersToolTip || text}</span>
{focused && !readOnly && (
<AnnotationToolbarPopover
textElement={textElement}
annotationElement={annotationRef?.current}
scrollElement={scrollElement}
textElement={textElement || undefined}
annotationElement={annotationRef.current || undefined}
scrollElement={scrollElement || undefined}
onEdit={handleEditClick}
onDelete={handleRemoveClick}
title={type?.title || type.name}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
} from '@sanity/ui'
import styled from 'styled-components'
import {EditIcon, TrashIcon} from '@sanity/icons'
import {PortableTextEditor, usePortableTextEditor} from '@sanity/portable-text-editor'

const ToolbarPopover = styled(Popover)`
&[data-popper-reference-hidden='true'] {
Expand All @@ -25,9 +24,9 @@ interface AnnotationToolbarPopoverProps {
/**
* Needed to update the popover position on scroll
*/
scrollElement: HTMLElement | null
annotationElement: HTMLElement | null
textElement: HTMLElement | null
scrollElement?: HTMLElement
annotationElement?: HTMLElement
textElement?: HTMLElement
onEdit: (event: React.MouseEvent<HTMLButtonElement>) => void
onDelete: (event: React.MouseEvent<HTMLButtonElement>) => void
title: string
Expand All @@ -45,10 +44,9 @@ export function AnnotationToolbarPopover(props: AnnotationToolbarPopoverProps) {
} | null>(null)
const isClosingRef = useRef<boolean>(false)
const rangeRef = useRef<Range | null>(null)
const editButtonRef = useRef<HTMLButtonElement | null>(null)
const editButtonRef = useRef<HTMLButtonElement>(null)
const isTabbing = useRef<boolean>(false)
const {sanity} = useTheme()
const editor = usePortableTextEditor()

const popoverScheme = sanity.color.dark ? 'light' : 'dark'

Expand All @@ -63,10 +61,10 @@ export function AnnotationToolbarPopover(props: AnnotationToolbarPopoverProps) {
return cursorRect
},
}
}, [cursorRect]) as HTMLElement | null
}, [cursorRect]) as HTMLElement

useEffect(() => {
if (!open) {
if (!open || !scrollElement) {
return undefined
}

Expand All @@ -76,10 +74,10 @@ export function AnnotationToolbarPopover(props: AnnotationToolbarPopoverProps) {
}
}

scrollElement?.addEventListener('scroll', handleScroll, {passive: true})
scrollElement.addEventListener('scroll', handleScroll, {passive: true})

return () => {
scrollElement?.removeEventListener('scroll', handleScroll)
scrollElement.removeEventListener('scroll', handleScroll)
}
}, [open, scrollElement])

Expand All @@ -95,7 +93,6 @@ export function AnnotationToolbarPopover(props: AnnotationToolbarPopoverProps) {
event.stopPropagation()
setOpen(false)
isTabbing.current = false
PortableTextEditor.focus(editor)
}
if (event.key === 'Tab') {
if (!isTabbing.current) {
Expand All @@ -106,7 +103,7 @@ export function AnnotationToolbarPopover(props: AnnotationToolbarPopoverProps) {
}
}
},
[editButtonRef, open, editor]
[open]
)
)

Expand All @@ -115,11 +112,9 @@ export function AnnotationToolbarPopover(props: AnnotationToolbarPopoverProps) {
function handleSelectionChange() {
if (!textElement) return
const winSelection = window.getSelection()

if (!winSelection) {
return
}

const {anchorNode, anchorOffset, focusNode, focusOffset} = winSelection

setSelection({
Expand Down

0 comments on commit 32e302b

Please sign in to comment.