Skip to content

Commit

Permalink
feature(form): improve readOnly mode for PT-input with v2 parity
Browse files Browse the repository at this point in the history
  • Loading branch information
skogsmaskin authored and mariuslundgard committed Oct 3, 2022
1 parent 0d13e12 commit a7b2f81
Show file tree
Hide file tree
Showing 12 changed files with 70 additions and 99 deletions.
6 changes: 4 additions & 2 deletions packages/sanity/src/form/inputs/PortableText/Compositor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
usePortal,
} from '@sanity/ui'
import {ChangeIndicator} from '../../../components/changeIndicators'
import {ArrayOfObjectsInputProps, RenderCustomMarkers} from '../../types'
import {ArrayOfObjectsInputProps, FIXME, RenderCustomMarkers} from '../../types'
import {ActivateOnFocus} from '../../components/ActivateOnFocus/ActivateOnFocus'
import {EMPTY_ARRAY} from '../../utils/empty'
import {FormInput} from '../../FormInput'
Expand Down Expand Up @@ -236,6 +236,7 @@ export function Compositor(props: InputProps) {
<Editor
hotkeys={editorHotkeys}
initialSelection={initialSelection}
isActive={isActive}
isFullscreen={isFullscreen}
onOpenItem={onOpenItem}
onCopy={onCopy}
Expand All @@ -257,6 +258,7 @@ export function Compositor(props: InputProps) {
editorHotkeys,
handleToggleFullscreen,
initialSelection,
isActive,
isFullscreen,
onCopy,
onOpenItem,
Expand Down Expand Up @@ -287,7 +289,7 @@ export function Compositor(props: InputProps) {
onClose={onCloseItem}
scrollElement={boundaryElm}
>
<FormInput absolutePath={dMemberItem.node.path} {...(props as any)} />
<FormInput absolutePath={dMemberItem.node.path} {...(props as FIXME)} />
</ObjectEditModal>
)
})}
Expand Down
15 changes: 9 additions & 6 deletions packages/sanity/src/form/inputs/PortableText/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ import {useSpellcheck} from './hooks/useSpellCheck'
import {useScrollSelectionIntoView} from './hooks/useScrollSelectionIntoView'

interface EditorProps {
hotkeys: HotkeyOptions
initialSelection?: EditorSelection
isActive: boolean
isFullscreen: boolean
hotkeys: HotkeyOptions
onCopy?: OnCopyFn
onOpenItem: (path: Path) => void
onPaste?: OnPasteFn
Expand All @@ -51,6 +52,7 @@ export function Editor(props: EditorProps) {
const {
hotkeys,
initialSelection,
isActive,
isFullscreen,
onCopy,
onOpenItem,
Expand Down Expand Up @@ -105,7 +107,6 @@ export function Editor(props: EditorProps) {
onCopy={onCopy}
onPaste={onPaste}
ref={editableRef}
readOnly={readOnly}
renderAnnotation={renderAnnotation}
renderBlock={renderBlock}
renderChild={renderChild}
Expand All @@ -121,7 +122,6 @@ export function Editor(props: EditorProps) {
initialSelection,
onCopy,
onPaste,
readOnly,
renderAnnotation,
renderBlock,
renderChild,
Expand All @@ -140,7 +140,7 @@ export function Editor(props: EditorProps) {

return (
<Root $fullscreen={isFullscreen} data-testid="pt-editor" onMouseDown={handleMouseDown}>
{!readOnly && (
{isActive && (
<ToolbarCard data-testid="pt-editor__toolbar-card" shadow={1}>
<Toolbar
isFullscreen={isFullscreen}
Expand All @@ -152,10 +152,13 @@ export function Editor(props: EditorProps) {
</ToolbarCard>
)}

<EditableCard flex={1}>
<EditableCard flex={1} tone={readOnly ? 'transparent' : 'default'}>
<Scroller ref={setScrollElement}>
<EditableContainer padding={isFullscreen ? 2 : 0} sizing="border" width={1}>
<EditableWrapper $isFullscreen={isFullscreen} $readOnly={readOnly}>
<EditableWrapper
$isFullscreen={isFullscreen}
tone={readOnly ? 'transparent' : 'default'}
>
<BoundaryElementProvider element={isFullscreen ? scrollElement : boundaryElement}>
{editable}
</BoundaryElementProvider>
Expand Down
22 changes: 11 additions & 11 deletions packages/sanity/src/form/inputs/PortableText/PortableTextInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export function PortableTextInput(props: PortableTextInputProps) {
onInsert,
onPaste,
path,
readOnly: readOnlyFromProps,
readOnly,
renderBlockActions,
renderCustomMarkers,
schemaType: type,
Expand All @@ -115,9 +115,12 @@ export function PortableTextInput(props: PortableTextInputProps) {
const [isFullscreen, setIsFullscreen] = useState(false)
const [isActive, setIsActive] = useState(false)

const readOnly = useMemo(() => {
return isActive ? Boolean(readOnlyFromProps) : true
}, [isActive, readOnlyFromProps])
// Set as active whenever we have focus inside the editor.
useEffect(() => {
if (hasFocus || focusPath.length > 1) {
setIsActive(true)
}
}, [hasFocus, focusPath])

const toast = useToast()
const portableTextMemberItemsRef: React.MutableRefObject<PortableTextMemberItem[]> = useRef([])
Expand Down Expand Up @@ -318,14 +321,11 @@ export function PortableTextInput(props: PortableTextInputProps) {
const handleActivate = useCallback((): void => {
if (!isActive) {
setIsActive(true)
// Focus the editor in the next tick if needed
// Next tick because we are in a re-rendering phase of the editor at this point (activating it).
if (!hasFocus) {
setTimeout(() => {
if (editorRef.current) {
PortableTextEditor.focus(editorRef.current)
}
})
if (editorRef.current) {
PortableTextEditor.focus(editorRef.current)
}
setHasFocus(true)
}
}
}, [hasFocus, isActive])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,4 @@ export const BlockActionsInner = styled(Flex)`
export const TooltipBox = styled(Box)`
max-width: 250px;
`
export const BlockPreview = styled(Box)((props: {theme: Theme}) => {
const color = props.theme.sanity.color.input
return css`
background-color: ${color.default.enabled.bg};
`
})
export const BlockPreview = styled(Box)``
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ export function BlockObject(props: BlockObjectProps) {
data-image-preview={isImagePreview ? '' : undefined}
data-invalid={hasError ? '' : undefined}
data-markers={hasMarkers && renderCustomMarkers ? '' : undefined}
data-read-only={readOnly ? '' : undefined}
data-selected={selected ? '' : undefined}
data-testid="pte-block-object"
data-warning={hasWarning ? '' : undefined}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,12 +125,10 @@ export const InlineObject = React.forwardRef(function InlineObject(
} = props
const {Markers} = useFormBuilder().__internal.components
const editor = usePortableTextEditor()
const editorSelection = usePortableTextEditorSelection()
const markers = usePortableTextMarkers(path)
const memberItem = usePortableTextMemberItem(pathToString(path))
const {validation, hasError, hasWarning} = useMemberValidation(memberItem?.node)
const hasValidationMarkers = validation.length > 0
const [showPopover, setShowPopover] = useState(false)

const tone = useMemo(() => {
if (hasError) {
Expand All @@ -147,12 +145,6 @@ export const InlineObject = React.forwardRef(function InlineObject(
return undefined
}, [focused, hasError, hasWarning, selected])

useEffect(() => {
if (memberItem?.elementRef?.current) {
setShowPopover(!readOnly && focused && selected)
}
}, [focused, selected, editorSelection, memberItem?.elementRef, readOnly])

const preview = useMemo(
() => (
<PreviewSpan>
Expand Down Expand Up @@ -194,7 +186,6 @@ export const InlineObject = React.forwardRef(function InlineObject(
(event: React.MouseEvent<HTMLButtonElement>): void => {
event.preventDefault()
event.stopPropagation()
setShowPopover(false)
const point = {path, offset: 0}
const selection = {anchor: point, focus: point}
PortableTextEditor.delete(editor, selection, {mode: 'children'})
Expand All @@ -203,8 +194,7 @@ export const InlineObject = React.forwardRef(function InlineObject(
[editor, path]
)

const handleEditClick = useCallback((): void => {
setShowPopover(false)
const openItem = useCallback((): void => {
PortableTextEditor.blur(editor)
if (memberItem) {
onOpenItem(memberItem.node.path)
Expand All @@ -224,17 +214,16 @@ export const InlineObject = React.forwardRef(function InlineObject(
forwardedAs="span"
contentEditable={false}
ref={memberItem?.elementRef as React.RefObject<HTMLDivElement>}
onClick={readOnly ? openItem : undefined}
>
<span ref={forwardedRef} onDoubleClick={handleEditClick}>
<span ref={forwardedRef} onDoubleClick={openItem}>
{markersToolTip || preview}
</span>
</Root>
{showPopover && (
{focused && !readOnly && (
<InlineObjectToolbarPopover
open={showPopover}
setOpen={setShowPopover}
onDelete={handleRemoveClick}
onEdit={handleEditClick}
onEdit={openItem}
referenceElement={memberItem?.elementRef?.current || null}
scrollElement={scrollElement}
title={type?.title || type.name}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,13 @@ const POPOVER_FALLBACK_PLACEMENTS: PopoverProps['fallbackPlacements'] = ['top',
interface InlineObjectToolbarPopoverProps {
onDelete: (event: React.MouseEvent<HTMLButtonElement>) => void
onEdit: (event: React.MouseEvent<HTMLButtonElement>) => void
open: boolean
referenceElement: HTMLElement | null
scrollElement: HTMLElement | null
setOpen: (open: boolean) => void
title: string
}

export function InlineObjectToolbarPopover(props: InlineObjectToolbarPopoverProps) {
const {open, onEdit, onDelete, referenceElement, scrollElement, setOpen, title} = props
const {onEdit, onDelete, referenceElement, scrollElement, title} = props
const {sanity} = useTheme()
const editButtonRef = useRef<HTMLButtonElement | null>(null)
const popoverScheme = sanity.color.dark ? 'light' : 'dark'
Expand All @@ -40,35 +38,31 @@ 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') {
useCallback((event) => {
if (!open) {
return
}
if (event.key === 'Escape') {
event.preventDefault()
event.stopPropagation()
isTabbing.current = false
}
if (event.key === 'Tab') {
if (!isTabbing.current) {
event.preventDefault()
event.stopPropagation()
isTabbing.current = false
setOpen(false)
}
if (event.key === 'Tab') {
if (!isTabbing.current) {
event.preventDefault()
event.stopPropagation()
editButtonRef.current?.focus()
isTabbing.current = true
}
editButtonRef.current?.focus()
isTabbing.current = true
}
},
[open, setOpen]
)
}
}, [])
)

useEffect(() => {
if (open && isTabbing.current) {
if (isTabbing.current) {
editButtonRef.current?.focus()
}
}, [open])
}, [])

return (
<div contentEditable={false}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ export function DefaultEditDialog(props: DefaultEditDialogProps) {
__unstable_autoFocus
id={dialogId || ''}
onClose={onClose}
onClickOutside={onClose}
header={title}
portal="default"
width={width}
Expand Down
30 changes: 10 additions & 20 deletions packages/sanity/src/form/inputs/PortableText/text/Annotation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ import {
PortableTextEditor,
RenderAttributes,
usePortableTextEditor,
usePortableTextEditorSelection,
} from '@sanity/portable-text-editor'
import {ObjectSchemaType, Path} from '@sanity/types'
import {Box, Theme, ThemeColorToneKey, Tooltip} from '@sanity/ui'
import React, {SyntheticEvent, useCallback, useEffect, useMemo, useRef, useState} from 'react'
import React, {SyntheticEvent, useCallback, useMemo, useRef, useState} from 'react'
import styled, {css} from 'styled-components'
import {FIXME, RenderCustomMarkers} from '../../../types'
import {DefaultMarkers} from '../_legacyDefaultParts/Markers'
Expand Down Expand Up @@ -66,7 +65,7 @@ const TooltipBox = styled(Box).attrs({forwardedAs: 'span'})`

export const Annotation = function Annotation(props: AnnotationProps) {
const {
attributes: {focused, path, selected},
attributes: {focused, path},
children,
onOpenItem,
renderCustomMarkers,
Expand All @@ -78,7 +77,6 @@ export const Annotation = function Annotation(props: AnnotationProps) {
const {Markers = DefaultMarkers} = useFormBuilder().__internal.components
const annotationRef = useRef<HTMLElement>(null)
const editor = usePortableTextEditor()
const editorSelection = usePortableTextEditorSelection()
const markDefPath = useMemo(
() => [path[0]].concat(['markDefs', {_key: value._key}]),
[path, value._key]
Expand All @@ -87,7 +85,6 @@ export const Annotation = function Annotation(props: AnnotationProps) {
const memberItem = usePortableTextMemberItem(pathToString(markDefPath))
const {validation, hasError, hasWarning} = useMemberValidation(memberItem?.node)
const markers = usePortableTextMarkers(path)
const [showPopover, setShowPopover] = useState(false)

const text = useMemo(
() => (
Expand All @@ -99,15 +96,11 @@ export const Annotation = function Annotation(props: AnnotationProps) {
[children]
)

useEffect(() => {
setShowPopover(true)
}, [editorSelection])

useEffect(() => {
if (memberItem?.elementRef?.current) {
setShowPopover(!readOnly && focused && selected)
const openItem = useCallback(() => {
if (memberItem) {
onOpenItem(memberItem.node.path)
}
}, [focused, selected, memberItem?.elementRef, readOnly])
}, [memberItem, onOpenItem])

const markersToolTip = useMemo(
() =>
Expand All @@ -133,15 +126,12 @@ export const Annotation = function Annotation(props: AnnotationProps) {

const handleEditClick = useCallback(
(event: SyntheticEvent): void => {
setShowPopover(false)
PortableTextEditor.blur(editor)
event.preventDefault()
event.stopPropagation()
if (memberItem) {
onOpenItem(memberItem.node.path)
}
openItem()
},
[editor, memberItem, onOpenItem]
[editor, openItem]
)

const handleRemoveClick = useCallback(
Expand Down Expand Up @@ -181,11 +171,11 @@ export const Annotation = function Annotation(props: AnnotationProps) {
data-error={hasError ? '' : undefined}
data-warning={hasWarning ? '' : undefined}
data-custom-markers={hasCustomMarkers ? '' : undefined}
onClick={readOnly ? openItem : undefined}
>
<span ref={memberItem?.elementRef}>{markersToolTip || text}</span>
{showPopover && (
{focused && !readOnly && (
<AnnotationToolbarPopover
focused={focused}
textElement={textElement}
annotationElement={annotationRef?.current}
scrollElement={scrollElement}
Expand Down

0 comments on commit a7b2f81

Please sign in to comment.