Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

block editor fixes #735

Merged
merged 7 commits into from
Jul 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions build/api/react-repeater-dnd-kit.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import { Active } from '@dnd-kit/core';
import { ClientRect as ClientRect_2 } from '@dnd-kit/core';
import { Context } from 'react';
import { DndContextProps } from '@dnd-kit/core';
import { DraggableAttributes } from '@dnd-kit/core';
import { EntityAccessor } from '@contember/react-binding';
import { JSX as JSX_2 } from 'react/jsx-runtime';
Expand All @@ -25,9 +26,9 @@ import { UniqueIdentifier } from '@dnd-kit/core';
export const RepeaterActiveEntityContext: Context<EntityAccessor | undefined>;

// @public (undocumented)
export const RepeaterSortable: ({ children }: {
export const RepeaterSortable: ({ children, onDragStart, onDragEnd: onDragEndIn, onDragCancel: onDragCancelIn, ...props }: {
children: ReactNode;
}) => JSX_2.Element;
} & DndContextProps) => JSX_2.Element;

// @public (undocumented)
export const RepeaterSortableDragOverlay: ({ children }: {
Expand Down
2 changes: 1 addition & 1 deletion build/api/react-ui-lib.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1240,7 +1240,7 @@ export interface EditorCanvasProps<P extends HTMLTextAreaDivTargetProps> {
}

// @public (undocumented)
export const EditorEditableCanvas: ({ className, ...editableProps }: EditorEditableCanvasProps) => JSX_2.Element;
export const EditorEditableCanvas: (editableProps: EditorEditableCanvasProps) => JSX_2.Element;

// Warning: (ae-forgotten-export) The symbol "EditableProps" needs to be exported by the entry point index.d.ts
//
Expand Down
10 changes: 5 additions & 5 deletions packages/playground/admin/app/pages/custom.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,17 @@ type MyComponentProps = {
}

const UseFieldComponent = Component<MyComponentProps>(
({ field }) => {
({ field: fieldName }) => {
// The `useField` hook is used to get and update the field value from data-binding.
const { value, updateValue } = useField<number>(field)
const increment = () => updateValue((value ?? 0) + 1)
const decrement = () => updateValue((value ?? 0) - 1)
const field = useField<number>(fieldName)
const increment = () => field.updateValue((field.value ?? 0) + 1)
const decrement = () => field.updateValue((field.value ?? 0) - 1)

return (
<div className={'flex gap-4 items-center'}>
<Button onClick={decrement}>Decrement</Button>
<div className={'w-8 h-8 border flex justify-center items-center'}>
<div>{value}</div>
<div>{field.value}</div>
</div>
<Button onClick={increment}>Increment</Button>
</div>
Expand Down
37 changes: 24 additions & 13 deletions packages/playground/admin/lib-extra/legacy-editor/BlockEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { ReactNode } from 'react'
import { ReactNode, useCallback } from 'react'
import { EditorCanvas, EditorEditableCanvas } from '@app/lib/editor'
import { BlockEditor, BlockEditorProps } from '@contember/react-slate-editor-legacy'
import { Component } from '@contember/interface'
import { SortableBlock } from './SortableBlock'
import { ReferenceElementRenderer } from './ReferenceElementRenderer'
import { RepeaterSortable, useRepeaterSortedEntities } from '@contember/react-repeater-dnd-kit'
import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable'
import { useSlateStatic } from 'slate-react'
import { DragEndEvent } from '@dnd-kit/core'
import { Transforms } from 'slate'

export type BlockEditorFieldProps =
& Omit<BlockEditorProps, 'renderSortableBlock' | 'renderReference'>
Expand All @@ -17,24 +18,34 @@ export type BlockEditorFieldProps =
export const BlockEditorField = Component<BlockEditorFieldProps>(({ placeholder, children, ...props }) => {
return (
<BlockEditor {...props} renderSortableBlock={SortableBlock} renderReference={ReferenceElementRenderer}>
<RepeaterSortable>
<BlockEditorInner placeholder={placeholder}>
{children}
</BlockEditorInner>
</RepeaterSortable>
<BlockEditorSortable placeholder={placeholder}>
{children}
</BlockEditorSortable>
</BlockEditor>
)
})

export const BlockEditorInner = ({ children, placeholder }: {
placeholder?: string
children: ReactNode
}) => {
const BlockEditorSortable = ({ children, placeholder }: { children: ReactNode, placeholder?: string }) => {
const editor = useSlateStatic()
const entities = useRepeaterSortedEntities()
const onDragEnd = useCallback(({ active, over }: DragEndEvent) => {
if (!over) {
return
}
const activeNodeIndex = entities.findIndex(it => it.id === active.id)
const overNodeIndex = entities.findIndex(it => it.id === over.id)

if (activeNodeIndex === undefined || overNodeIndex === undefined || activeNodeIndex === overNodeIndex) {
return
}
Transforms.moveNodes(editor, {
at: [activeNodeIndex],
to: [overNodeIndex],
})
}, [editor, entities])

return (
<SortableContext items={entities} strategy={verticalListSortingStrategy}>
<RepeaterSortable onDragEnd={onDragEnd} autoScroll={{ layoutShiftCompensation: false }}>
<EditorCanvas
underlyingComponent={EditorEditableCanvas}
componentProps={{
Expand All @@ -53,6 +64,6 @@ export const BlockEditorInner = ({ children, placeholder }: {
>
{children}
</EditorCanvas>
</SortableContext>
</RepeaterSortable>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import { GripVerticalIcon } from 'lucide-react'
import { RepeaterDropIndicator } from '@app/lib/repeater'
import { uic } from '@app/lib/utils'

export const BlockeEditorHandle = uic('button', {
baseClass: 'absolute top-1/2 -left-3 h-6 w-6 flex justify-end items-center opacity-10 hover:opacity-100 transition-opacity -translate-y-1/2',
export const BlockeEditorHandle = uic('span', {
baseClass: 'absolute top-1/2 -left-3 h-6 w-6 flex justify-end items-center opacity-10 hover:opacity-100 transition-opacity -translate-y-1/2 cursor-grab',
beforeChildren: <GripVerticalIcon size={16} />,
})

Expand Down
38 changes: 16 additions & 22 deletions packages/react-repeater-dnd-kit/src/components/RepeaterSortable.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,13 @@
import React, { ReactNode, useCallback, useMemo, useState } from 'react'
import {
closestCenter,
DndContext,
DragEndEvent,
KeyboardSensor,
MeasuringStrategy,
MouseSensor,
TouchSensor,
UniqueIdentifier,
useSensor,
useSensors,
} from '@dnd-kit/core'
import type { DragStartEvent } from '@dnd-kit/core'
import { closestCenter, DndContext, DndContextProps, DragCancelEvent, DragEndEvent, KeyboardSensor, MeasuringStrategy, MouseSensor, TouchSensor, UniqueIdentifier, useSensor, useSensors } from '@dnd-kit/core'
import { RepeaterActiveEntityContext } from '../contexts'
import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable'
import { useRepeaterSortedEntities } from '@contember/react-repeater'
import { useRepeaterMethods } from '@contember/react-repeater'
import { useRepeaterMethods, useRepeaterSortedEntities } from '@contember/react-repeater'

export const RepeaterSortable = ({ children }: {
export const RepeaterSortable = ({ children, onDragStart, onDragEnd: onDragEndIn, onDragCancel: onDragCancelIn, ...props }: {
children: ReactNode
}) => {
} & DndContextProps) => {
const [activeId, setActiveId] = useState<UniqueIdentifier | null>(null)
const { moveItem } = useRepeaterMethods()
const entities = useRepeaterSortedEntities()
Expand All @@ -37,19 +26,22 @@ export const RepeaterSortable = ({ children }: {
useSensor(KeyboardSensor),
)

const onDragCancel = () => {
const onDragCancel = (event: DragCancelEvent) => {
onDragCancelIn?.(event)
setActiveId(null)
}

const onDragEnd = useCallback(({ active, over }: DragEndEvent) => {
const onDragEnd = useCallback((event: DragEndEvent) => {
onDragEndIn?.(event)
setActiveId(null)
const { active, over } = event
const activeEntity = entities.find(it => it.id === active.id)
const overIndex = over ? entities.findIndex(it => it.id === over.id) : undefined
if (!activeEntity || overIndex === undefined) {
return
}
moveItem?.(activeEntity, overIndex)
}, [entities, moveItem])
}, [entities, moveItem, onDragEndIn])

return (
<DndContext
Expand All @@ -59,12 +51,14 @@ export const RepeaterSortable = ({ children }: {
strategy: MeasuringStrategy.Always,
},
}}
onDragStart={({ active }) => {
setActiveId(active.id)
collisionDetection={closestCenter}
{...props}
onDragStart={(event: DragStartEvent) => {
onDragStart?.(event)
setActiveId(event.active.id)
}}
onDragEnd={onDragEnd}
onDragCancel={onDragCancel}
collisionDetection={closestCenter}
>
<RepeaterActiveEntityContext.Provider value={activeItem}>
<SortableContext items={entities} strategy={verticalListSortingStrategy}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ const BlockEditorComponent: FunctionComponent<BlockEditorProps> = Component(
return createEditor({ defaultElementType: paragraphElementType, plugins, entity, environment, children })
})

const { nodes, onChange, sortedBlocksRef, refreshBlocks } = useBlockEditorState({
const { nodes, onChange, sortedBlocksRef, refreshBlocks, sortedBlocks } = useBlockEditorState({
editor,
blockList: blockListProps,
contentField,
Expand Down Expand Up @@ -170,7 +170,7 @@ const BlockEditorComponent: FunctionComponent<BlockEditorProps> = Component(
<Repeater {...blockListProps} sortableBy={sortableBy}>

<ReferencesProvider getReferencedEntity={getReferencedEntity}>
<SortedBlocksContext.Provider value={sortedBlocksRef.current}>
<SortedBlocksContext.Provider value={sortedBlocks}>
<EditorReferenceBlocksContext.Provider value={editorReferenceBlocks}>
<Slate editor={editor} initialValue={nodes} onChange={onChange}>
<SyncValue nodes={nodes}/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export interface useBlockEditorStateResult {
onChange: () => void
nodes: Descendant[]
sortedBlocksRef: MutableRefObject<EntityAccessor[]>
sortedBlocks: EntityAccessor[]
refreshBlocks: () => void
}

Expand Down Expand Up @@ -42,5 +43,5 @@ export const useBlockEditorState = ({ editor, blockList, sortableBy, contentFiel
const onChange = useBlockEditorOnChange({ editor, blockList, contentField, blockElementCache, sortedBlocksRef, refreshBlocks })
const nodes = useBlockEditorSlateNodes({ editor, blockElementCache, blockElementPathRefs, blockContentField: contentField, topLevelBlocks: sortedBlocks })

return { onChange, nodes, sortedBlocksRef, refreshBlocks }
return { onChange, nodes, sortedBlocksRef, refreshBlocks, sortedBlocks }
}
4 changes: 2 additions & 2 deletions packages/react-ui-lib/src/editor/common/editor-canvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ export const EditorCanvas = memo(<P extends HTMLTextAreaDivTargetProps>({
}: EditorCanvasProps<P>) => {

return (
<div data-focus-ring={dataAttribute(focusRing)} className={'relative w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50'}>
<Component className="focus-visible:ring-0" {...props} />
<div data-focus-ring={dataAttribute(focusRing)} className={'relative w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-within:outline-none focus-within:ring-1 focus-within:ring-ring disabled:cursor-not-allowed disabled:opacity-50'}>
<Component className="outline-none" {...props} />
{children}
</div>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export interface EditorEditableCanvasProps extends EditableProps {

}

export const EditorEditableCanvas = ({ className, ...editableProps }: EditorEditableCanvasProps) => {
export const EditorEditableCanvas = (editableProps: EditorEditableCanvasProps) => {
const editor = useSlate()
const pathRef = useRef<Path | undefined>(undefined)

Expand Down
4 changes: 2 additions & 2 deletions packages/react-ui-lib/src/editor/sortable-block.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { DropIndicator } from '../ui/sortable'
import { Portal } from '@radix-ui/react-portal'
import { DragOverlay } from '@dnd-kit/core'

export const BlockEditorHandle = uic('button', {
baseClass: 'absolute top-1/2 -left-3 h-6 w-6 flex justify-end items-center opacity-10 hover:opacity-100 transition-opacity -translate-y-1/2',
export const BlockEditorHandle = uic('span', {
baseClass: 'absolute top-1/2 -left-3 h-6 w-6 flex justify-end items-center opacity-10 hover:opacity-100 transition-opacity -translate-y-1/2 cursor-grab',
beforeChildren: <GripVerticalIcon size={16} />,
})

Expand Down
Loading