Skip to content

Commit

Permalink
feat(portable-text-editor): implement isSelectionOverlapping method (
Browse files Browse the repository at this point in the history
…#5870)

* feat(portable-text-editor): implement `isSelectionsOverlapping` method

* test(portable-text-editor): add `isSelectionsOverlapping` test
  • Loading branch information
hermanwikner committed Feb 28, 2024
1 parent f83e8e4 commit 1d41af7
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -292,4 +292,11 @@ export class PortableTextEditor extends Component<PortableTextEditorProps> {
debug('Host redoing')
editor.editable?.redo()
}
static isSelectionsOverlapping = (
editor: PortableTextEditor,
selectionA: EditorSelection,
selectionB: EditorSelection,
) => {
return editor.editable?.isSelectionsOverlapping(selectionA, selectionB)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import {describe, expect, it, jest} from '@jest/globals'
import {type PortableTextBlock} from '@sanity/types'
import {render, waitFor} from '@testing-library/react'
import {createRef, type RefObject} from 'react'

import {PortableTextEditorTester, schemaType} from '../../__tests__/PortableTextEditorTester'
import {PortableTextEditor} from '../../PortableTextEditor'

const INITIAL_VALUE: PortableTextBlock[] = [
{
_key: 'a',
_type: 'block',
children: [
{
_key: 'a1',
_type: 'span',
marks: [],
text: 'This is some text in the block',
},
],
markDefs: [],
style: 'normal',
},
]

describe('plugin:withEditableAPI: .isSelectionsOverlapping', () => {
it('returns true if the selections are partially overlapping', async () => {
const editorRef: RefObject<PortableTextEditor> = createRef()
const onChange = jest.fn()
render(
<PortableTextEditorTester
onChange={onChange}
ref={editorRef}
schemaType={schemaType}
value={INITIAL_VALUE}
/>,
)
const selectionA = {
focus: {path: [{_key: 'a'}, 'children', {_key: 'a1'}], offset: 4},
anchor: {path: [{_key: 'a'}, 'children', {_key: 'a1'}], offset: 8},
}

const selectionB = {
focus: {path: [{_key: 'a'}, 'children', {_key: 'a1'}], offset: 2},
anchor: {path: [{_key: 'a'}, 'children', {_key: 'a1'}], offset: 6},
}

await waitFor(() => {
if (editorRef.current) {
const isOverlapping = PortableTextEditor.isSelectionsOverlapping(
editorRef.current,
selectionA,
selectionB,
)

expect(isOverlapping).toBe(true)
}
})
})

it('returns true if the selections are fully overlapping', async () => {
const editorRef: RefObject<PortableTextEditor> = createRef()
const onChange = jest.fn()
render(
<PortableTextEditorTester
onChange={onChange}
ref={editorRef}
schemaType={schemaType}
value={INITIAL_VALUE}
/>,
)
const selectionA = {
focus: {path: [{_key: 'a'}, 'children', {_key: 'a1'}], offset: 4},
anchor: {path: [{_key: 'a'}, 'children', {_key: 'a1'}], offset: 8},
}

const selectionB = {
focus: {path: [{_key: 'a'}, 'children', {_key: 'a1'}], offset: 4},
anchor: {path: [{_key: 'a'}, 'children', {_key: 'a1'}], offset: 8},
}

await waitFor(() => {
if (editorRef.current) {
const isOverlapping = PortableTextEditor.isSelectionsOverlapping(
editorRef.current,
selectionA,
selectionB,
)

expect(isOverlapping).toBe(true)
}
})
})

it('return true if selection is fully inside another selection', async () => {
const editorRef: RefObject<PortableTextEditor> = createRef()
const onChange = jest.fn()
render(
<PortableTextEditorTester
onChange={onChange}
ref={editorRef}
schemaType={schemaType}
value={INITIAL_VALUE}
/>,
)
const selectionA = {
focus: {path: [{_key: 'a'}, 'children', {_key: 'a1'}], offset: 2},
anchor: {path: [{_key: 'a'}, 'children', {_key: 'a1'}], offset: 10},
}

const selectionB = {
focus: {path: [{_key: 'a'}, 'children', {_key: 'a1'}], offset: 4},
anchor: {path: [{_key: 'a'}, 'children', {_key: 'a1'}], offset: 6},
}

await waitFor(() => {
if (editorRef.current) {
const isOverlapping = PortableTextEditor.isSelectionsOverlapping(
editorRef.current,
selectionA,
selectionB,
)

expect(isOverlapping).toBe(true)
}
})
})

it('returns false if the selections are not overlapping', async () => {
const editorRef: RefObject<PortableTextEditor> = createRef()
const onChange = jest.fn()
render(
<PortableTextEditorTester
onChange={onChange}
ref={editorRef}
schemaType={schemaType}
value={INITIAL_VALUE}
/>,
)
const selectionA = {
focus: {path: [{_key: 'a'}, 'children', {_key: 'a1'}], offset: 4},
anchor: {path: [{_key: 'a'}, 'children', {_key: 'a1'}], offset: 8},
}

const selectionB = {
focus: {path: [{_key: 'a'}, 'children', {_key: 'a1'}], offset: 10},
anchor: {path: [{_key: 'a'}, 'children', {_key: 'a1'}], offset: 12},
}

await waitFor(() => {
if (editorRef.current) {
const isOverlapping = PortableTextEditor.isSelectionsOverlapping(
editorRef.current,
selectionA,
selectionB,
)

expect(isOverlapping).toBe(false)
}
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,19 @@ export function createWithEditableAPI(
getFragment: () => {
return fromSlateValue(editor.getFragment(), types.block.name)
},
isSelectionsOverlapping: (selectionA: EditorSelection, selectionB: EditorSelection) => {
// Convert the selections to Slate ranges
const rangeA = toSlateRange(selectionA, editor)
const rangeB = toSlateRange(selectionB, editor)

// Make sure the ranges are valid
const isValidRanges = Range.isRange(rangeA) && Range.isRange(rangeB)

// Check if the ranges are overlapping
const isOverlapping = isValidRanges && Range.includes(rangeA, rangeB)

return isOverlapping
},
})
return editor
}
Expand Down
1 change: 1 addition & 0 deletions packages/@sanity/portable-text-editor/src/types/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export interface EditableAPI {
isCollapsedSelection: () => boolean
isExpandedSelection: () => boolean
isMarkActive: (mark: string) => boolean
isSelectionsOverlapping: (selectionA: EditorSelection, selectionB: EditorSelection) => boolean
isVoid: (element: PortableTextBlock | PortableTextChild) => boolean
marks: () => string[]
redo: () => void
Expand Down

0 comments on commit 1d41af7

Please sign in to comment.