Skip to content

Commit

Permalink
fix(core): only open one annotation object edit modal at a time (#7898)
Browse files Browse the repository at this point in the history
  • Loading branch information
christianhg authored Dec 5, 2024
1 parent d98873c commit fbecbc8
Show file tree
Hide file tree
Showing 14 changed files with 198 additions and 59 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import {BehaviorSubject} from 'rxjs'
import {createContext} from 'sanity/_createContext'

/** @internal */
export type PortableTextEditorElement = HTMLDivElement | HTMLSpanElement

/**
* @internal
*/
export const PortableTextMemberItemElementRefsContext = createContext<
BehaviorSubject<Record<string, PortableTextEditorElement | null | undefined>>
>('sanity/_singletons/context/portable-text-member-item-element-refs', new BehaviorSubject({}))
1 change: 1 addition & 0 deletions packages/sanity/src/_singletons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export * from './context/PaneContext'
export * from './context/PaneLayoutContext'
export * from './context/PaneRouterContext'
export * from './context/PortableTextMarkersContext'
export * from './context/PortableTextMemberItemElementRefsContext'
export * from './context/PortableTextMemberItemsContext'
export * from './context/PresenceContext'
export * from './context/PresenceTrackerContexts'
Expand Down
19 changes: 16 additions & 3 deletions packages/sanity/src/core/form/inputs/PortableText/Compositor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,14 @@ import {
import {type RenderBlockActionsCallback} from '../../types/_transitional'
import {UploadTargetCard} from '../arrays/common/UploadTargetCard'
import {ExpandedLayer, Root} from './Compositor.styles'
import {useSetPortableTextMemberItemElementRef} from './contexts/PortableTextMemberItemElementRefsProvider'
import {Editor} from './Editor'
import {useHotkeys} from './hooks/useHotKeys'
import {useTrackFocusPath} from './hooks/useTrackFocusPath'
import {Annotation} from './object/Annotation'
import {BlockObject} from './object/BlockObject'
import {InlineObject} from './object/InlineObject'
import {AnnotationObjectEditModal} from './object/modals/AnnotationObjectEditModal'
import {TextBlock} from './text'

interface InputProps extends ArrayOfObjectsInputProps<PortableTextBlock> {
Expand All @@ -51,9 +53,6 @@ interface InputProps extends ArrayOfObjectsInputProps<PortableTextBlock> {
renderEditable?: PortableTextInputProps['renderEditable']
}

/** @internal */
export type PortableTextEditorElement = HTMLDivElement | HTMLSpanElement

/** @internal */
export function Compositor(props: Omit<InputProps, 'schemaType' | 'arrayFunctions'>): ReactNode {
const {
Expand Down Expand Up @@ -94,6 +93,7 @@ export function Compositor(props: Omit<InputProps, 'schemaType' | 'arrayFunction
} = props

const editor = usePortableTextEditor()
const setElementRef = useSetPortableTextMemberItemElementRef()

const boundaryElement = useBoundaryElement().element
const [wrapperElement, setWrapperElement] = useState<HTMLDivElement | null>(null)
Expand Down Expand Up @@ -155,6 +155,7 @@ export function Compositor(props: Omit<InputProps, 'schemaType' | 'arrayFunction
renderBlock={renderBlock}
schemaType={blockSchemaType}
selected={selected}
setElementRef={setElementRef}
value={block as PortableTextTextBlock}
>
{children}
Expand All @@ -180,6 +181,7 @@ export function Compositor(props: Omit<InputProps, 'schemaType' | 'arrayFunction
renderItem,
renderPreview,
scrollElement,
setElementRef,
],
)

Expand Down Expand Up @@ -216,6 +218,7 @@ export function Compositor(props: Omit<InputProps, 'schemaType' | 'arrayFunction
renderPreview={renderPreview}
schemaType={blockSchemaType}
selected={blockSelected}
setElementRef={setElementRef}
value={blockValue}
/>
)
Expand All @@ -239,6 +242,7 @@ export function Compositor(props: Omit<InputProps, 'schemaType' | 'arrayFunction
renderInput,
renderItem,
renderPreview,
setElementRef,
],
)

Expand Down Expand Up @@ -291,6 +295,7 @@ export function Compositor(props: Omit<InputProps, 'schemaType' | 'arrayFunction
renderPreview={renderPreview}
schemaType={childSchemaType}
selected={selected}
setElementRef={setElementRef}
value={child}
/>
)
Expand All @@ -312,6 +317,7 @@ export function Compositor(props: Omit<InputProps, 'schemaType' | 'arrayFunction
renderInput,
renderItem,
renderPreview,
setElementRef,
],
)

Expand Down Expand Up @@ -346,6 +352,7 @@ export function Compositor(props: Omit<InputProps, 'schemaType' | 'arrayFunction
renderPreview={renderPreview}
schemaType={aSchemaType}
selected={selected}
setElementRef={setElementRef}
value={aValue}
>
{children}
Expand All @@ -369,6 +376,7 @@ export function Compositor(props: Omit<InputProps, 'schemaType' | 'arrayFunction
renderInput,
renderItem,
renderPreview,
setElementRef,
],
)
const ariaDescribedBy = elementProps['aria-describedby']
Expand Down Expand Up @@ -500,6 +508,11 @@ export function Compositor(props: Omit<InputProps, 'schemaType' | 'arrayFunction
<Box data-wrapper="" ref={setWrapperElement}>
<Portal __unstable_name={isFullscreen ? 'expanded' : 'collapsed'}>
{isFullscreen ? <ExpandedLayer>{editorNode}</ExpandedLayer> : editorNode}
<AnnotationObjectEditModal
focused={focused}
onItemClose={onItemClose}
referenceBoundary={scrollElement}
/>
</Portal>
</Box>
<div data-border="" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import {Box, Flex, Text, useToast} from '@sanity/ui'
import {randomKey} from '@sanity/util/content'
import {sortBy} from 'lodash'
import {
type MutableRefObject,
type ReactNode,
startTransition,
useCallback,
Expand All @@ -39,7 +38,7 @@ import {immutableReconcile} from '../../store/utils/immutableReconcile'
import {type ResolvedUploader} from '../../studio/uploads/types'
import {type PortableTextInputProps} from '../../types'
import {extractPastedFiles} from '../common/fileTarget/utils/extractFiles'
import {Compositor, type PortableTextEditorElement} from './Compositor'
import {Compositor} from './Compositor'
import {PortableTextMarkersProvider} from './contexts/PortableTextMarkers'
import {PortableTextMemberItemsProvider} from './contexts/PortableTextMembers'
import {usePortableTextMemberItemsFromProps} from './hooks/usePortableTextMembers'
Expand All @@ -66,7 +65,6 @@ export interface PortableTextMemberItem {
key: string
member: ArrayOfObjectsItemMember
node: ObjectFormNode
elementRef?: MutableRefObject<PortableTextEditorElement | null>
input?: ReactNode
}
/** @public */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import {useCallback, useContext} from 'react'
import {useObservable} from 'react-rx'
import {type PortableTextMemberItem} from 'sanity'
import {
type PortableTextEditorElement,
PortableTextMemberItemElementRefsContext,
} from 'sanity/_singletons'

export type SetPortableTextMemberItemElementRef = ({
key,
elementRef,
}: {
key: PortableTextMemberItem['member']['key']
elementRef: PortableTextEditorElement | null
}) => void

export function usePortableTextMemberItemElementRefs(): Record<
PortableTextMemberItem['member']['key'],
PortableTextEditorElement | null | undefined
> {
const behaviorSubject = useContext(PortableTextMemberItemElementRefsContext)

return useObservable(behaviorSubject, {})
}

export function useSetPortableTextMemberItemElementRef(): SetPortableTextMemberItemElementRef {
const behaviorSubject = useContext(PortableTextMemberItemElementRefsContext)

return useCallback<SetPortableTextMemberItemElementRef>(
({key, elementRef}) => {
behaviorSubject.next({
...behaviorSubject.value,
[key]: elementRef,
})
},
[behaviorSubject],
)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {isEqual, pathFor} from '@sanity/util/paths'
import {createRef, type MutableRefObject, type ReactNode, useContext, useMemo, useRef} from 'react'
import {type MutableRefObject, type ReactNode, useContext, useMemo, useRef} from 'react'
import {type FormPatch, type Path, set} from 'sanity'
import {PortableTextMemberItemsContext} from 'sanity/_singletons'

Expand All @@ -10,7 +10,6 @@ import {isMemberArrayOfObjects} from '../../../members/object/fields/asserters'
import {type ArrayOfObjectsItemMember, type ObjectFormNode} from '../../../store'
import {type ObjectInputProps, type PortableTextInputProps} from '../../../types'
import {isArrayOfObjectsFieldMember, isBlockType} from '../_helpers'
import {type PortableTextEditorElement} from '../Compositor'
import {type PortableTextMemberItem} from '../PortableTextInput'

export function usePortableTextMemberItem(key: string): PortableTextMemberItem | undefined {
Expand Down Expand Up @@ -201,7 +200,6 @@ export function usePortableTextMemberItemsFromProps(
key,
member: item.member,
node: item.node,
elementRef: createRef<PortableTextEditorElement | null>(),
input,
}
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {isEqual} from '@sanity/util/paths'
import {useLayoutEffect} from 'react'
import scrollIntoView from 'scroll-into-view-if-needed'

import {usePortableTextMemberItemElementRefs} from '../contexts/PortableTextMemberItemElementRefsProvider'
import {usePortableTextMemberItems} from './usePortableTextMembers'

interface Props {
Expand All @@ -21,6 +22,7 @@ export function useTrackFocusPath(props: Props): void {
const {focusPath, boundaryElement, onItemClose} = props

const portableTextMemberItems = usePortableTextMemberItems()
const elementRefs = usePortableTextMemberItemElementRefs()
const editor = usePortableTextEditor()
const selection = usePortableTextEditorSelection()

Expand All @@ -46,8 +48,9 @@ export function useTrackFocusPath(props: Props): void {

// The related editor member to scroll to, or focus, according to the given focusPath
const relatedEditorItem = focusedItem || openItem
const elementRef = relatedEditorItem ? elementRefs[relatedEditorItem.member.key] : undefined

if (relatedEditorItem && relatedEditorItem.elementRef?.current) {
if (relatedEditorItem && elementRef) {
if (boundaryElement) {
// Scroll the boundary element into view (the scrollable element itself)
scrollIntoView(boundaryElement, {
Expand All @@ -56,7 +59,7 @@ export function useTrackFocusPath(props: Props): void {
inline: 'start',
})
// Scroll the member into view (the member within the scroll-boundary)
scrollIntoView(relatedEditorItem.elementRef.current, {
scrollIntoView(elementRef, {
scrollMode: 'if-needed',
boundary: boundaryElement,
block: 'nearest',
Expand Down Expand Up @@ -126,6 +129,7 @@ export function useTrackFocusPath(props: Props): void {
}, [
boundaryElement,
editor,
elementRefs,
focusPath,
onItemClose,
portableTextMemberItems,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ import {
} from '../../../types'
import {useFormBuilder} from '../../../useFormBuilder'
import {DefaultMarkers} from '../_legacyDefaultParts/Markers'
import {type SetPortableTextMemberItemElementRef} from '../contexts/PortableTextMemberItemElementRefsProvider'
import {debugRender} from '../debugRender'
import {useMemberValidation} from '../hooks/useMemberValidation'
import {usePortableTextMarkers} from '../hooks/usePortableTextMarkers'
import {usePortableTextMemberItem} from '../hooks/usePortableTextMembers'
import {Root, TooltipBox} from './Annotation.styles'
import {AnnotationToolbarPopover} from './AnnotationToolbarPopover'
import {ObjectEditModal} from './modals/ObjectEditModal'

interface AnnotationProps {
children: ReactElement
Expand All @@ -56,6 +56,7 @@ interface AnnotationProps {
renderItem: RenderArrayOfObjectsItemCallback
renderPreview: RenderPreviewCallback
selected: boolean
setElementRef: SetPortableTextMemberItemElementRef
schemaType: ObjectSchemaType
value: PortableTextObject
}
Expand All @@ -82,6 +83,7 @@ export function Annotation(props: AnnotationProps): ReactNode {
renderPreview,
schemaType,
selected,
setElementRef,
value,
} = props
const {Markers = DefaultMarkers} = useFormBuilder().__internal.components
Expand Down Expand Up @@ -224,12 +226,12 @@ export function Annotation(props: AnnotationProps): ReactNode {

const setRef = useCallback(
(elm: HTMLSpanElement) => {
if (memberItem?.elementRef) {
memberItem.elementRef.current = elm
if (memberItem) {
setElementRef({key: memberItem.member.key, elementRef: elm})
}
setSpanElement(elm) // update state here so the reference element is available on first render
},
[memberItem],
[memberItem, setElementRef, setSpanElement],
)

return useMemo(
Expand All @@ -252,9 +254,7 @@ export const DefaultAnnotationComponent = (props: BlockAnnotationProps) => {
__unstable_referenceBoundary: referenceBoundary,
__unstable_referenceElement: referenceElement,
children,
focused,
markers,
onClose,
onOpen,
onRemove,
open,
Expand Down Expand Up @@ -313,19 +313,6 @@ export const DefaultAnnotationComponent = (props: BlockAnnotationProps) => {
}
/>
)}
{open && (
<ObjectEditModal
defaultType="popover"
floatingBoundary={floatingBoundary}
onClose={onClose}
autoFocus={focused}
referenceBoundary={referenceBoundary}
referenceElement={referenceElement}
schemaType={schemaType}
>
{children}
</ObjectEditModal>
)}
</Root>
)
}
Loading

0 comments on commit fbecbc8

Please sign in to comment.