@portabletext/editor
Advanced tools
Comparing version 1.33.2 to 1.33.3
@@ -1,2 +0,2 @@ | ||
import { isSpan, isKeyedSegment, reverseSelection, spanSelectionPointToBlockOffset, getBlockStartPoint, getBlockEndPoint, blockOffsetToSpanSelectionPoint, isEqualSelectionPoints } from "./util.reverse-selection.js"; | ||
import { isSpan, reverseSelection, isKeyedSegment, spanSelectionPointToBlockOffset, getBlockStartPoint, getBlockEndPoint, blockOffsetToSpanSelectionPoint, isEqualSelectionPoints } from "./util.reverse-selection.js"; | ||
import { isPortableTextListBlock, isPortableTextTextBlock, isKeySegment, isPortableTextSpan } from "@sanity/types"; | ||
@@ -398,23 +398,24 @@ function createGuards({ | ||
return text; | ||
for (const block of value) | ||
if (!(isKeyedSegment(forwardSelection.anchor.path[0]) && block._key !== forwardSelection.anchor.path[0]._key) && isPortableTextTextBlock(block)) { | ||
const startBlockKey = isKeyedSegment(forwardSelection.anchor.path[0]) ? forwardSelection.anchor.path[0]._key : void 0, endBlockKey = isKeyedSegment(forwardSelection.focus.path[0]) ? forwardSelection.focus.path[0]._key : void 0, startChildKey = isKeyedSegment(forwardSelection.anchor.path[2]) ? forwardSelection.anchor.path[2]._key : void 0, endChildKey = isKeyedSegment(forwardSelection.focus.path[2]) ? forwardSelection.focus.path[2]._key : void 0; | ||
let startFound = !1; | ||
if (!startBlockKey || !endBlockKey) | ||
return text; | ||
for (const block of value) { | ||
if (block._key === startBlockKey) { | ||
if (!isPortableTextTextBlock(block)) | ||
continue; | ||
for (const child of block.children) | ||
if (isPortableTextSpan(child)) { | ||
if (isKeyedSegment(forwardSelection.anchor.path[2]) && child._key === forwardSelection.anchor.path[2]._key && isKeyedSegment(forwardSelection.focus.path[2]) && child._key === forwardSelection.focus.path[2]._key) { | ||
text = text + child.text.slice(forwardSelection.anchor.offset, forwardSelection.focus.offset); | ||
break; | ||
} | ||
if (isKeyedSegment(forwardSelection.anchor.path[2]) && child._key === forwardSelection.anchor.path[2]._key) { | ||
text = text + child.text.slice(forwardSelection.anchor.offset); | ||
continue; | ||
} | ||
if (isKeyedSegment(forwardSelection.focus.path[2]) && child._key === forwardSelection.focus.path[2]._key) { | ||
text = text + child.text.slice(0, forwardSelection.focus.offset); | ||
break; | ||
} | ||
text.length > 0 && (text = text + child.text); | ||
} | ||
if (isKeyedSegment(forwardSelection.focus.path[0]) && block._key === forwardSelection.focus.path[0]._key) | ||
break; | ||
if (child._key === startChildKey && (startFound = !0), !!startFound && (isPortableTextSpan(child) && startChildKey && (child._key === startChildKey && child._key === endChildKey ? text = text + child.text.slice(forwardSelection.anchor.offset, forwardSelection.focus.offset) : child._key === startChildKey ? text = text + child.text.slice(forwardSelection.anchor.offset) : child._key === endChildKey ? text = text + child.text.slice(0, forwardSelection.focus.offset) : text = text + child.text), child._key === endChildKey)) | ||
break; | ||
continue; | ||
} | ||
if (block._key === endBlockKey) { | ||
if (!isPortableTextTextBlock(block)) | ||
continue; | ||
for (const child of block.children) | ||
if (isPortableTextSpan(child) && endChildKey && (text = text + child.text.slice(0, forwardSelection.focus.offset)), child._key === endChildKey) | ||
break; | ||
break; | ||
} | ||
} | ||
return text; | ||
@@ -470,3 +471,6 @@ }, isSelectionCollapsed = ({ | ||
} | ||
}).split(/\s+/).at(0), caretWordStartOffset = textDirectlyBefore ? { | ||
}).split(/\s+/).at(0); | ||
if ((textDirectlyBefore === void 0 || textDirectlyBefore === "") && (textDirectlyAfter === void 0 || textDirectlyAfter === "")) | ||
return null; | ||
const caretWordStartOffset = textDirectlyBefore ? { | ||
...selectionStartOffset, | ||
@@ -479,6 +483,8 @@ offset: selectionStartOffset.offset - textDirectlyBefore.length | ||
value: context.value, | ||
blockOffset: caretWordStartOffset | ||
blockOffset: caretWordStartOffset, | ||
direction: "backward" | ||
}), caretWordEndSelectionPoint = blockOffsetToSpanSelectionPoint({ | ||
value: context.value, | ||
blockOffset: caretWordEndOffset | ||
blockOffset: caretWordEndOffset, | ||
direction: "forward" | ||
}); | ||
@@ -485,0 +491,0 @@ if (!caretWordStartSelectionPoint || !caretWordEndSelectionPoint) |
@@ -85,6 +85,8 @@ import { isPortableTextTextBlock, isPortableTextSpan } from "@sanity/types"; | ||
value, | ||
blockOffset: offsets.anchor | ||
blockOffset: offsets.anchor, | ||
direction: backward ? "backward" : "forward" | ||
}), focus = blockOffsetToSpanSelectionPoint({ | ||
value, | ||
blockOffset: offsets.focus | ||
blockOffset: offsets.focus, | ||
direction: backward ? "forward" : "backward" | ||
}); | ||
@@ -91,0 +93,0 @@ return !anchor || !focus ? null : { |
@@ -7,18 +7,12 @@ import { isPortableTextTextBlock, isPortableTextSpan } from "@sanity/types"; | ||
value, | ||
blockOffset | ||
blockOffset, | ||
direction | ||
}) { | ||
let offsetLeft = blockOffset.offset, selectionPoint; | ||
let offsetLeft = blockOffset.offset, selectionPoint, skippedInlineObject = !1; | ||
for (const block of value) | ||
if (block._key === blockOffset.path[0]._key && isPortableTextTextBlock(block)) { | ||
for (const child of block.children) | ||
if (isPortableTextSpan(child)) { | ||
if (offsetLeft === 0) { | ||
selectionPoint = { | ||
path: [...blockOffset.path, "children", { | ||
_key: child._key | ||
}], | ||
offset: 0 | ||
}; | ||
break; | ||
} | ||
if (block._key === blockOffset.path[0]._key && isPortableTextTextBlock(block)) | ||
for (const child of block.children) { | ||
if (direction === "forward") { | ||
if (!isPortableTextSpan(child)) | ||
continue; | ||
if (offsetLeft <= child.text.length) { | ||
@@ -34,4 +28,29 @@ selectionPoint = { | ||
offsetLeft -= child.text.length; | ||
continue; | ||
} | ||
} | ||
if (!isPortableTextSpan(child)) { | ||
skippedInlineObject = !0; | ||
continue; | ||
} | ||
if (offsetLeft === 0 && selectionPoint && !skippedInlineObject) { | ||
skippedInlineObject && (selectionPoint = { | ||
path: [...blockOffset.path, "children", { | ||
_key: child._key | ||
}], | ||
offset: 0 | ||
}); | ||
break; | ||
} | ||
if (offsetLeft > child.text.length) { | ||
offsetLeft -= child.text.length; | ||
continue; | ||
} | ||
if (offsetLeft <= child.text.length && (selectionPoint = { | ||
path: [...blockOffset.path, "children", { | ||
_key: child._key | ||
}], | ||
offset: offsetLeft | ||
}, offsetLeft -= child.text.length, offsetLeft !== 0)) | ||
break; | ||
} | ||
return selectionPoint; | ||
@@ -38,0 +57,0 @@ } |
@@ -109,6 +109,8 @@ import { c } from "react-compiler-runtime"; | ||
value: context.value, | ||
blockOffset: prefixOffsets.focus | ||
blockOffset: prefixOffsets.focus, | ||
direction: "backward" | ||
}), focus = blockOffsetToSpanSelectionPoint({ | ||
value: context.value, | ||
blockOffset: suffixOffsets.anchor | ||
blockOffset: suffixOffsets.anchor, | ||
direction: "forward" | ||
}); | ||
@@ -147,6 +149,8 @@ return !anchor || !focus ? !1 : { | ||
value: context.value, | ||
blockOffset: prefixOffsets.focus | ||
blockOffset: prefixOffsets.focus, | ||
direction: "backward" | ||
}), focus = blockOffsetToSpanSelectionPoint({ | ||
value: context.value, | ||
blockOffset: suffixOffsets.anchor | ||
blockOffset: suffixOffsets.anchor, | ||
direction: "forward" | ||
}); | ||
@@ -153,0 +157,0 @@ return !anchor || !focus ? !1 : { |
@@ -51,5 +51,7 @@ import type {KeyedSegment as KeyedSegment_2} from '@portabletext/patches' | ||
blockOffset, | ||
direction, | ||
}: { | ||
value: Array<PortableTextBlock> | ||
blockOffset: BlockOffset | ||
direction: 'forward' | 'backward' | ||
}): | ||
@@ -56,0 +58,0 @@ | { |
@@ -49,5 +49,7 @@ import type { | ||
blockOffset, | ||
direction, | ||
}: { | ||
value: Array<PortableTextBlock> | ||
blockOffset: BlockOffset | ||
direction: 'forward' | 'backward' | ||
}): | ||
@@ -54,0 +56,0 @@ | { |
{ | ||
"name": "@portabletext/editor", | ||
"version": "1.33.2", | ||
"version": "1.33.3", | ||
"description": "Portable Text Editor made in React", | ||
@@ -106,3 +106,3 @@ "keywords": [ | ||
"eslint-plugin-react-compiler": "19.0.0-beta-30d8a17-20250209", | ||
"eslint-plugin-react-hooks": "^5.1.0", | ||
"eslint-plugin-react-hooks": "experimental", | ||
"jsdom": "^26.0.0", | ||
@@ -109,0 +109,0 @@ "react": "^19.0.0", |
@@ -128,2 +128,3 @@ import {useActorRef} from '@xstate/react' | ||
blockOffset: prefixOffsets.focus, | ||
direction: 'backward', | ||
}) | ||
@@ -133,2 +134,3 @@ const focus = utils.blockOffsetToSpanSelectionPoint({ | ||
blockOffset: suffixOffsets.anchor, | ||
direction: 'forward', | ||
}) | ||
@@ -174,2 +176,3 @@ | ||
blockOffset: prefixOffsets.focus, | ||
direction: 'backward', | ||
}) | ||
@@ -179,2 +182,3 @@ const focus = utils.blockOffsetToSpanSelectionPoint({ | ||
blockOffset: suffixOffsets.anchor, | ||
direction: 'forward', | ||
}) | ||
@@ -181,0 +185,0 @@ |
@@ -77,2 +77,9 @@ import type {EditorSelector} from '../editor/editor-selector' | ||
if ( | ||
(textDirectlyBefore === undefined || textDirectlyBefore === '') && | ||
(textDirectlyAfter === undefined || textDirectlyAfter === '') | ||
) { | ||
return null | ||
} | ||
const caretWordStartOffset: BlockOffset = textDirectlyBefore | ||
@@ -94,2 +101,3 @@ ? { | ||
blockOffset: caretWordStartOffset, | ||
direction: 'backward', | ||
}) | ||
@@ -99,2 +107,3 @@ const caretWordEndSelectionPoint = blockOffsetToSpanSelectionPoint({ | ||
blockOffset: caretWordEndOffset, | ||
direction: 'forward', | ||
}) | ||
@@ -101,0 +110,0 @@ |
@@ -0,16 +1,369 @@ | ||
import type {PortableTextBlock} from '@sanity/types' | ||
import {expect, test} from 'vitest' | ||
import type {EditorSchema, EditorSelection, EditorSnapshot} from '.' | ||
import type {EditorSelection, EditorSnapshot} from '.' | ||
import {compileSchemaDefinition, defineSchema} from '../editor/define-schema' | ||
import {getSelectionText} from './selector.get-selection-text' | ||
const brokenBlock = { | ||
_type: 'block', | ||
_key: 'b0', | ||
style: 'normal', | ||
markDefs: [], | ||
children: [ | ||
{ | ||
_key: 's0', | ||
_type: 'span', | ||
text: '', | ||
}, | ||
{ | ||
_key: 's1', | ||
_type: 'stock-ticker', | ||
}, | ||
{ | ||
_key: 's2', | ||
_type: 'span', | ||
text: 'b', | ||
}, | ||
{ | ||
_key: 's3', | ||
_type: 'span', | ||
text: 'a', | ||
}, | ||
{ | ||
_key: 's4', | ||
_type: 'span', | ||
text: 'r', | ||
}, | ||
{ | ||
_key: 's5', | ||
_type: 'stock-ticker', | ||
}, | ||
{ | ||
_key: 's6', | ||
_type: 'span', | ||
text: '', | ||
}, | ||
], | ||
} | ||
const bazBlock = { | ||
_type: 'block', | ||
_key: 'b1', | ||
style: 'normal', | ||
markDefs: [], | ||
children: [ | ||
{ | ||
_key: 's7', | ||
_type: 'span', | ||
text: 'baz', | ||
}, | ||
], | ||
} | ||
const imageBlock = { | ||
_type: 'image', | ||
_key: 'b2', | ||
} | ||
test(getSelectionText.name, () => { | ||
function snapshot(selection: EditorSelection): EditorSnapshot { | ||
function snapshot( | ||
value: Array<PortableTextBlock>, | ||
selection: EditorSelection, | ||
): EditorSnapshot { | ||
return { | ||
context: { | ||
converters: [], | ||
schema: {} as EditorSchema, | ||
schema: compileSchemaDefinition( | ||
defineSchema({ | ||
inlineObjects: [{name: 'stock-ticker'}], | ||
}), | ||
), | ||
keyGenerator: () => '', | ||
activeDecorators: [], | ||
value: [ | ||
value, | ||
selection, | ||
}, | ||
} | ||
} | ||
expect( | ||
getSelectionText( | ||
snapshot( | ||
[ | ||
{ | ||
_key: 'k0', | ||
_type: 'block', | ||
children: [ | ||
{ | ||
_type: 'span', | ||
_key: 'k1', | ||
text: 'f', | ||
marks: ['strong'], | ||
}, | ||
{ | ||
_type: 'span', | ||
_key: 'k2', | ||
marks: ['strong', 'em'], | ||
text: 'oo b', | ||
}, | ||
{ | ||
_type: 'span', | ||
_key: 'k3', | ||
marks: ['strong', 'em', 'underline'], | ||
text: 'a', | ||
}, | ||
{ | ||
_type: 'span', | ||
_key: 'k4', | ||
marks: ['strong', 'underline'], | ||
text: 'r ba', | ||
}, | ||
{ | ||
_type: 'span', | ||
_key: 'k5', | ||
marks: ['strong'], | ||
text: 'z', | ||
}, | ||
], | ||
}, | ||
], | ||
{ | ||
anchor: {path: [{_key: 'k0'}, 'children', {_key: 'k1'}], offset: 0}, | ||
focus: {path: [{_key: 'k0'}, 'children', {_key: 'k3'}], offset: 0}, | ||
}, | ||
), | ||
), | ||
).toBe('foo b') | ||
expect( | ||
getSelectionText( | ||
snapshot( | ||
[ | ||
{ | ||
_key: 'b0', | ||
_type: 'block', | ||
children: [{_key: 's0', _type: 'span', text: 'foo bar'}], | ||
}, | ||
], | ||
{ | ||
anchor: { | ||
path: [{_key: 'b0'}, 'children', {_key: 's0'}], | ||
offset: 0, | ||
}, | ||
focus: { | ||
path: [{_key: 'b0'}, 'children', {_key: 's0'}], | ||
offset: 3, | ||
}, | ||
}, | ||
), | ||
), | ||
).toBe('foo') | ||
expect( | ||
getSelectionText( | ||
snapshot( | ||
[ | ||
{ | ||
_key: 'b0', | ||
_type: 'block', | ||
children: [{_key: 's0', _type: 'span', text: 'foo bar'}], | ||
}, | ||
], | ||
{ | ||
anchor: { | ||
path: [{_key: 'b0'}, 'children', {_key: 's0'}], | ||
offset: 3, | ||
}, | ||
focus: { | ||
path: [{_key: 'b0'}, 'children', {_key: 's0'}], | ||
offset: 7, | ||
}, | ||
}, | ||
), | ||
), | ||
).toBe(' bar') | ||
expect( | ||
getSelectionText( | ||
snapshot([brokenBlock], { | ||
anchor: { | ||
path: [{_key: 'b0'}, 'children', {_key: 's0'}], | ||
offset: 0, | ||
}, | ||
focus: { | ||
path: [{_key: 'b0'}, 'children', {_key: 's5'}], | ||
offset: 0, | ||
}, | ||
}), | ||
), | ||
).toBe('bar') | ||
expect( | ||
getSelectionText( | ||
snapshot([brokenBlock], { | ||
anchor: { | ||
path: [{_key: 'b0'}, 'children', {_key: 's1'}], | ||
offset: 0, | ||
}, | ||
focus: { | ||
path: [{_key: 'b0'}, 'children', {_key: 's5'}], | ||
offset: 0, | ||
}, | ||
}), | ||
), | ||
).toBe('bar') | ||
expect( | ||
getSelectionText( | ||
snapshot([brokenBlock], { | ||
anchor: { | ||
path: [{_key: 'b0'}, 'children', {_key: 's2'}], | ||
offset: 0, | ||
}, | ||
focus: { | ||
path: [{_key: 'b0'}, 'children', {_key: 's5'}], | ||
offset: 0, | ||
}, | ||
}), | ||
), | ||
).toBe('bar') | ||
expect( | ||
getSelectionText( | ||
snapshot([brokenBlock], { | ||
anchor: { | ||
path: [{_key: 'b0'}, 'children', {_key: 's2'}], | ||
offset: 1, | ||
}, | ||
focus: { | ||
path: [{_key: 'b0'}, 'children', {_key: 's5'}], | ||
offset: 0, | ||
}, | ||
}), | ||
), | ||
).toBe('ar') | ||
expect( | ||
getSelectionText( | ||
snapshot([brokenBlock], { | ||
anchor: { | ||
path: [{_key: 'b0'}, 'children', {_key: 's3'}], | ||
offset: 0, | ||
}, | ||
focus: { | ||
path: [{_key: 'b0'}, 'children', {_key: 's5'}], | ||
offset: 0, | ||
}, | ||
}), | ||
), | ||
).toBe('ar') | ||
expect( | ||
getSelectionText( | ||
snapshot([brokenBlock], { | ||
anchor: { | ||
path: [{_key: 'b0'}, 'children', {_key: 's3'}], | ||
offset: 1, | ||
}, | ||
focus: { | ||
path: [{_key: 'b0'}, 'children', {_key: 's5'}], | ||
offset: 0, | ||
}, | ||
}), | ||
), | ||
).toBe('r') | ||
expect( | ||
getSelectionText( | ||
snapshot([brokenBlock], { | ||
anchor: { | ||
path: [{_key: 'b0'}, 'children', {_key: 's4'}], | ||
offset: 0, | ||
}, | ||
focus: { | ||
path: [{_key: 'b0'}, 'children', {_key: 's5'}], | ||
offset: 0, | ||
}, | ||
}), | ||
), | ||
).toBe('r') | ||
expect( | ||
getSelectionText( | ||
snapshot([brokenBlock], { | ||
anchor: { | ||
path: [{_key: 'b0'}, 'children', {_key: 's5'}], | ||
offset: 0, | ||
}, | ||
focus: { | ||
path: [{_key: 'b0'}, 'children', {_key: 's5'}], | ||
offset: 0, | ||
}, | ||
}), | ||
), | ||
).toBe('') | ||
expect( | ||
getSelectionText( | ||
snapshot([brokenBlock], { | ||
anchor: { | ||
path: [{_key: 'b0'}, 'children', {_key: 's6'}], | ||
offset: 0, | ||
}, | ||
focus: { | ||
path: [{_key: 'b0'}, 'children', {_key: 's5'}], | ||
offset: 0, | ||
}, | ||
}), | ||
), | ||
).toBe('') | ||
expect( | ||
getSelectionText( | ||
snapshot([brokenBlock, bazBlock], { | ||
anchor: { | ||
path: [{_key: 'b0'}, 'children', {_key: 's3'}], | ||
offset: 0, | ||
}, | ||
focus: { | ||
path: [{_key: 'b1'}, 'children', {_key: 's7'}], | ||
offset: 2, | ||
}, | ||
}), | ||
), | ||
).toBe('arba') | ||
expect( | ||
getSelectionText( | ||
snapshot([brokenBlock, bazBlock], { | ||
anchor: { | ||
path: [{_key: 'b0'}, 'children', {_key: 's3'}], | ||
offset: 0, | ||
}, | ||
focus: { | ||
path: [{_key: 'b1'}, 'children', {_key: 's7'}], | ||
offset: 0, | ||
}, | ||
}), | ||
), | ||
).toBe('ar') | ||
expect( | ||
getSelectionText( | ||
snapshot([brokenBlock, imageBlock, bazBlock], { | ||
anchor: { | ||
path: [{_key: imageBlock._key}], | ||
offset: 0, | ||
}, | ||
focus: { | ||
path: [{_key: 'b1'}, 'children', {_key: 's7'}], | ||
offset: 0, | ||
}, | ||
}), | ||
), | ||
).toBe('') | ||
expect( | ||
getSelectionText( | ||
snapshot([brokenBlock, imageBlock, bazBlock], { | ||
anchor: { | ||
path: [{_key: imageBlock._key}], | ||
offset: 0, | ||
}, | ||
focus: { | ||
path: [{_key: 'b1'}, 'children', {_key: 's7'}], | ||
offset: 1, | ||
}, | ||
}), | ||
), | ||
).toBe('b') | ||
expect( | ||
getSelectionText( | ||
snapshot( | ||
[ | ||
{ | ||
_type: 'block', | ||
_key: 'e0-k8', | ||
@@ -41,37 +394,31 @@ style: 'normal', | ||
], | ||
selection, | ||
}, | ||
} | ||
} | ||
expect( | ||
getSelectionText( | ||
snapshot({ | ||
anchor: { | ||
path: [ | ||
{ | ||
_key: 'e0-k8', | ||
}, | ||
'children', | ||
{ | ||
_key: 'e0-k7', | ||
}, | ||
], | ||
offset: 0, | ||
{ | ||
anchor: { | ||
path: [ | ||
{ | ||
_key: 'e0-k8', | ||
}, | ||
'children', | ||
{ | ||
_key: 'e0-k7', | ||
}, | ||
], | ||
offset: 0, | ||
}, | ||
focus: { | ||
path: [ | ||
{ | ||
_key: 'e0-k8', | ||
}, | ||
'children', | ||
{ | ||
_key: 'e0-k10', | ||
}, | ||
], | ||
offset: 1, | ||
}, | ||
}, | ||
focus: { | ||
path: [ | ||
{ | ||
_key: 'e0-k8', | ||
}, | ||
'children', | ||
{ | ||
_key: 'e0-k10', | ||
}, | ||
], | ||
offset: 1, | ||
}, | ||
}), | ||
), | ||
), | ||
).toBe(':bar') | ||
}) |
@@ -26,58 +26,73 @@ import {isPortableTextSpan, isPortableTextTextBlock} from '@sanity/types' | ||
for (const block of value) { | ||
if ( | ||
isKeyedSegment(forwardSelection.anchor.path[0]) && | ||
block._key !== forwardSelection.anchor.path[0]._key | ||
) { | ||
continue | ||
} | ||
const startBlockKey = isKeyedSegment(forwardSelection.anchor.path[0]) | ||
? forwardSelection.anchor.path[0]._key | ||
: undefined | ||
const endBlockKey = isKeyedSegment(forwardSelection.focus.path[0]) | ||
? forwardSelection.focus.path[0]._key | ||
: undefined | ||
const startChildKey = isKeyedSegment(forwardSelection.anchor.path[2]) | ||
? forwardSelection.anchor.path[2]._key | ||
: undefined | ||
const endChildKey = isKeyedSegment(forwardSelection.focus.path[2]) | ||
? forwardSelection.focus.path[2]._key | ||
: undefined | ||
let startFound = false | ||
if (!isPortableTextTextBlock(block)) { | ||
continue | ||
} | ||
if (!startBlockKey || !endBlockKey) { | ||
return text | ||
} | ||
for (const child of block.children) { | ||
if (isPortableTextSpan(child)) { | ||
if ( | ||
isKeyedSegment(forwardSelection.anchor.path[2]) && | ||
child._key === forwardSelection.anchor.path[2]._key && | ||
isKeyedSegment(forwardSelection.focus.path[2]) && | ||
child._key === forwardSelection.focus.path[2]._key | ||
) { | ||
text = | ||
text + | ||
child.text.slice( | ||
forwardSelection.anchor.offset, | ||
forwardSelection.focus.offset, | ||
) | ||
for (const block of value) { | ||
if (block._key === startBlockKey) { | ||
if (!isPortableTextTextBlock(block)) { | ||
continue | ||
} | ||
break | ||
for (const child of block.children) { | ||
if (child._key === startChildKey) { | ||
startFound = true | ||
} | ||
if ( | ||
isKeyedSegment(forwardSelection.anchor.path[2]) && | ||
child._key === forwardSelection.anchor.path[2]._key | ||
) { | ||
text = text + child.text.slice(forwardSelection.anchor.offset) | ||
if (!startFound) { | ||
continue | ||
} | ||
if ( | ||
isKeyedSegment(forwardSelection.focus.path[2]) && | ||
child._key === forwardSelection.focus.path[2]._key | ||
) { | ||
text = text + child.text.slice(0, forwardSelection.focus.offset) | ||
break | ||
if (isPortableTextSpan(child) && startChildKey) { | ||
if (child._key === startChildKey && child._key === endChildKey) { | ||
text = | ||
text + | ||
child.text.slice( | ||
forwardSelection.anchor.offset, | ||
forwardSelection.focus.offset, | ||
) | ||
} else if (child._key === startChildKey) { | ||
text = text + child.text.slice(forwardSelection.anchor.offset) | ||
} else if (child._key === endChildKey) { | ||
text = text + child.text.slice(0, forwardSelection.focus.offset) | ||
} else { | ||
text = text + child.text | ||
} | ||
} | ||
if (text.length > 0) { | ||
text = text + child.text | ||
if (child._key === endChildKey) { | ||
break | ||
} | ||
} | ||
continue | ||
} | ||
if ( | ||
isKeyedSegment(forwardSelection.focus.path[0]) && | ||
block._key === forwardSelection.focus.path[0]._key | ||
) { | ||
if (block._key === endBlockKey) { | ||
if (!isPortableTextTextBlock(block)) { | ||
continue | ||
} | ||
for (const child of block.children) { | ||
if (isPortableTextSpan(child) && endChildKey) { | ||
text = text + child.text.slice(0, forwardSelection.focus.offset) | ||
} | ||
if (child._key === endChildKey) { | ||
break | ||
} | ||
} | ||
break | ||
@@ -84,0 +99,0 @@ } |
@@ -70,2 +70,94 @@ import type {PortableTextBlock} from '@sanity/types' | ||
blockOffsetToSpanSelectionPoint({ | ||
value: [ | ||
{ | ||
_key: 'b0', | ||
_type: 'block', | ||
children: [ | ||
{_key: 's0', _type: 'span', text: 'b'}, | ||
{_key: 's1', _type: 'span', text: 'a'}, | ||
{_key: 's2', _type: 'span', text: 'r'}, | ||
], | ||
}, | ||
], | ||
blockOffset: { | ||
path: [{_key: 'b0'}], | ||
offset: 3, | ||
}, | ||
direction: 'backward', | ||
}), | ||
).toEqual({ | ||
path: [{_key: 'b0'}, 'children', {_key: 's2'}], | ||
offset: 1, | ||
}) | ||
expect( | ||
blockOffsetToSpanSelectionPoint({ | ||
value: [ | ||
{ | ||
_key: 'b0', | ||
_type: 'block', | ||
children: [ | ||
{_key: 's0', _type: 'span', text: 'b'}, | ||
{_key: 's1', _type: 'span', text: 'a'}, | ||
{_key: 's2', _type: 'span', text: 'r'}, | ||
], | ||
}, | ||
], | ||
blockOffset: { | ||
path: [{_key: 'b0'}], | ||
offset: 0, | ||
}, | ||
direction: 'backward', | ||
}), | ||
).toEqual({ | ||
path: [{_key: 'b0'}, 'children', {_key: 's0'}], | ||
offset: 0, | ||
}) | ||
expect( | ||
blockOffsetToSpanSelectionPoint({ | ||
value: [ | ||
{ | ||
_key: 'b0', | ||
_type: 'block', | ||
children: [ | ||
{_key: 's0', _type: 'span', text: 'b'}, | ||
{_key: 's1', _type: 'span', text: 'a'}, | ||
{_key: 's2', _type: 'span', text: 'r'}, | ||
], | ||
}, | ||
], | ||
blockOffset: { | ||
path: [{_key: 'b0'}], | ||
offset: 0, | ||
}, | ||
direction: 'forward', | ||
}), | ||
).toEqual({ | ||
path: [{_key: 'b0'}, 'children', {_key: 's0'}], | ||
offset: 0, | ||
}) | ||
expect( | ||
blockOffsetToSpanSelectionPoint({ | ||
value: [ | ||
{ | ||
_key: 'b0', | ||
_type: 'block', | ||
children: [ | ||
{_key: 's0', _type: 'span', text: 'b'}, | ||
{_key: 's1', _type: 'span', text: 'a'}, | ||
{_key: 's2', _type: 'span', text: 'r'}, | ||
], | ||
}, | ||
], | ||
blockOffset: { | ||
path: [{_key: 'b0'}], | ||
offset: 3, | ||
}, | ||
direction: 'forward', | ||
}), | ||
).toEqual({ | ||
path: [{_key: 'b0'}, 'children', {_key: 's2'}], | ||
offset: 1, | ||
}) | ||
expect( | ||
blockOffsetToSpanSelectionPoint({ | ||
value, | ||
@@ -76,2 +168,3 @@ blockOffset: { | ||
}, | ||
direction: 'forward', | ||
}), | ||
@@ -86,2 +179,3 @@ ).toBeUndefined() | ||
}, | ||
direction: 'forward', | ||
}), | ||
@@ -99,2 +193,3 @@ ).toEqual({ | ||
}, | ||
direction: 'forward', | ||
}), | ||
@@ -112,2 +207,3 @@ ).toEqual({ | ||
}, | ||
direction: 'forward', | ||
}), | ||
@@ -120,7 +216,221 @@ ).toEqual({ | ||
blockOffsetToSpanSelectionPoint({ | ||
value: [ | ||
{ | ||
_key: 'b0', | ||
_type: 'block', | ||
children: [ | ||
{_key: 's0', _type: 'span', text: ''}, | ||
{_key: 's1', _type: 'stock-ticker'}, | ||
{_key: 's2', _type: 'span', text: 'foo'}, | ||
{_key: 's3', _type: 'stock-ticker'}, | ||
{_key: 's4', _type: 'span', text: ''}, | ||
], | ||
}, | ||
], | ||
blockOffset: { | ||
path: [{_key: 'b0'}], | ||
offset: 0, | ||
}, | ||
direction: 'forward', | ||
}), | ||
).toEqual({ | ||
path: [{_key: 'b0'}, 'children', {_key: 's0'}], | ||
offset: 0, | ||
}) | ||
expect( | ||
blockOffsetToSpanSelectionPoint({ | ||
value: [ | ||
{ | ||
_key: 'b0', | ||
_type: 'block', | ||
children: [ | ||
{_key: 's0', _type: 'span', text: ''}, | ||
{_key: 's1', _type: 'stock-ticker'}, | ||
{_key: 's2', _type: 'span', text: 'foo'}, | ||
{_key: 's3', _type: 'stock-ticker'}, | ||
{_key: 's4', _type: 'span', text: ''}, | ||
], | ||
}, | ||
], | ||
blockOffset: { | ||
path: [{_key: 'b0'}], | ||
offset: 1, | ||
}, | ||
direction: 'forward', | ||
}), | ||
).toEqual({ | ||
path: [{_key: 'b0'}, 'children', {_key: 's2'}], | ||
offset: 1, | ||
}) | ||
expect( | ||
blockOffsetToSpanSelectionPoint({ | ||
value: [ | ||
{ | ||
_key: 'b0', | ||
_type: 'block', | ||
children: [ | ||
{_key: 's0', _type: 'span', text: ''}, | ||
{_key: 's1', _type: 'stock-ticker'}, | ||
{_key: 's2', _type: 'span', text: 'foo'}, | ||
{_key: 's3', _type: 'stock-ticker'}, | ||
{_key: 's4', _type: 'span', text: ''}, | ||
], | ||
}, | ||
], | ||
blockOffset: { | ||
path: [{_key: 'b0'}], | ||
offset: 0, | ||
}, | ||
direction: 'backward', | ||
}), | ||
).toEqual({ | ||
path: [{_key: 'b0'}, 'children', {_key: 's2'}], | ||
offset: 0, | ||
}) | ||
expect( | ||
blockOffsetToSpanSelectionPoint({ | ||
value: [ | ||
{ | ||
_key: 'b0', | ||
_type: 'block', | ||
children: [ | ||
{_key: 's0', _type: 'span', text: ''}, | ||
{_key: 's1', _type: 'stock-ticker'}, | ||
{_key: 's2', _type: 'span', text: 'foo'}, | ||
{_key: 's3', _type: 'stock-ticker'}, | ||
{_key: 's4', _type: 'span', text: ''}, | ||
], | ||
}, | ||
], | ||
blockOffset: { | ||
path: [{_key: 'b0'}], | ||
offset: 1, | ||
}, | ||
direction: 'backward', | ||
}), | ||
).toEqual({ | ||
path: [{_key: 'b0'}, 'children', {_key: 's2'}], | ||
offset: 1, | ||
}) | ||
expect( | ||
blockOffsetToSpanSelectionPoint({ | ||
value: [ | ||
{ | ||
_key: 'b0', | ||
_type: 'block', | ||
children: [ | ||
{_key: 's0', _type: 'span', text: ''}, | ||
{_key: 's1', _type: 'stock-ticker'}, | ||
{_key: 's2', _type: 'span', text: 'foo'}, | ||
{_key: 's3', _type: 'stock-ticker'}, | ||
{_key: 's4', _type: 'span', text: ''}, | ||
], | ||
}, | ||
], | ||
blockOffset: { | ||
path: [{_key: 'b0'}], | ||
offset: 3, | ||
}, | ||
direction: 'forward', | ||
}), | ||
).toEqual({ | ||
path: [{_key: 'b0'}, 'children', {_key: 's2'}], | ||
offset: 3, | ||
}) | ||
expect( | ||
blockOffsetToSpanSelectionPoint({ | ||
value: [ | ||
{ | ||
_key: 'b0', | ||
_type: 'block', | ||
children: [ | ||
{_key: 's0', _type: 'span', text: ''}, | ||
{_key: 's1', _type: 'stock-ticker'}, | ||
{_key: 's2', _type: 'span', text: 'foo'}, | ||
{_key: 's3', _type: 'stock-ticker'}, | ||
{_key: 's4', _type: 'span', text: ''}, | ||
], | ||
}, | ||
], | ||
blockOffset: { | ||
path: [{_key: 'b0'}], | ||
offset: 2, | ||
}, | ||
direction: 'forward', | ||
}), | ||
).toEqual({ | ||
path: [{_key: 'b0'}, 'children', {_key: 's2'}], | ||
offset: 2, | ||
}) | ||
expect( | ||
blockOffsetToSpanSelectionPoint({ | ||
value: [ | ||
{ | ||
_key: 'b0', | ||
_type: 'block', | ||
children: [ | ||
{_key: 's0', _type: 'span', text: ''}, | ||
{_key: 's1', _type: 'stock-ticker'}, | ||
{_key: 's2', _type: 'span', text: 'foo'}, | ||
{_key: 's3', _type: 'stock-ticker'}, | ||
{_key: 's4', _type: 'span', text: ''}, | ||
], | ||
}, | ||
], | ||
blockOffset: { | ||
path: [{_key: 'b0'}], | ||
offset: 3, | ||
}, | ||
direction: 'backward', | ||
}), | ||
).toEqual({ | ||
path: [{_key: 'b0'}, 'children', {_key: 's4'}], | ||
offset: 0, | ||
}) | ||
expect( | ||
blockOffsetToSpanSelectionPoint({ | ||
value: [ | ||
{ | ||
_key: 'b0', | ||
_type: 'block', | ||
children: [ | ||
{_key: 's0', _type: 'span', text: ''}, | ||
{_key: 's1', _type: 'stock-ticker'}, | ||
{_key: 's2', _type: 'span', text: 'foo'}, | ||
{_key: 's3', _type: 'stock-ticker'}, | ||
{_key: 's4', _type: 'span', text: ''}, | ||
], | ||
}, | ||
], | ||
blockOffset: { | ||
path: [{_key: 'b0'}], | ||
offset: 2, | ||
}, | ||
direction: 'backward', | ||
}), | ||
).toEqual({ | ||
path: [{_key: 'b0'}, 'children', {_key: 's2'}], | ||
offset: 2, | ||
}) | ||
expect( | ||
blockOffsetToSpanSelectionPoint({ | ||
value, | ||
blockOffset: { | ||
path: [{_key: 'b3'}], | ||
offset: 10, | ||
}, | ||
direction: 'backward', | ||
}), | ||
).toEqual({ | ||
path: [{_key: 'b3'}, 'children', {_key: 's5'}], | ||
offset: 0, | ||
}) | ||
expect( | ||
blockOffsetToSpanSelectionPoint({ | ||
value, | ||
blockOffset: { | ||
path: [{_key: 'b3'}], | ||
offset: 11, | ||
}, | ||
direction: 'forward', | ||
}), | ||
@@ -138,2 +448,3 @@ ).toEqual({ | ||
}, | ||
direction: 'forward', | ||
}), | ||
@@ -148,4 +459,5 @@ ).toBeUndefined() | ||
}, | ||
direction: 'forward', | ||
}), | ||
).toBeUndefined() | ||
}) |
@@ -17,5 +17,7 @@ import { | ||
blockOffset, | ||
direction, | ||
}: { | ||
value: Array<PortableTextBlock> | ||
blockOffset: BlockOffset | ||
direction: 'forward' | 'backward' | ||
}) { | ||
@@ -26,2 +28,3 @@ let offsetLeft = blockOffset.offset | ||
| undefined | ||
let skippedInlineObject = false | ||
@@ -38,10 +41,31 @@ for (const block of value) { | ||
for (const child of block.children) { | ||
if (direction === 'forward') { | ||
if (!isPortableTextSpan(child)) { | ||
continue | ||
} | ||
if (offsetLeft <= child.text.length) { | ||
selectionPoint = { | ||
path: [...blockOffset.path, 'children', {_key: child._key}], | ||
offset: offsetLeft, | ||
} | ||
break | ||
} | ||
offsetLeft -= child.text.length | ||
continue | ||
} | ||
if (!isPortableTextSpan(child)) { | ||
skippedInlineObject = true | ||
continue | ||
} | ||
if (offsetLeft === 0) { | ||
selectionPoint = { | ||
path: [...blockOffset.path, 'children', {_key: child._key}], | ||
offset: 0, | ||
if (offsetLeft === 0 && selectionPoint && !skippedInlineObject) { | ||
if (skippedInlineObject) { | ||
selectionPoint = { | ||
path: [...blockOffset.path, 'children', {_key: child._key}], | ||
offset: 0, | ||
} | ||
} | ||
@@ -51,2 +75,7 @@ break | ||
if (offsetLeft > child.text.length) { | ||
offsetLeft -= child.text.length | ||
continue | ||
} | ||
if (offsetLeft <= child.text.length) { | ||
@@ -57,6 +86,9 @@ selectionPoint = { | ||
} | ||
break | ||
offsetLeft -= child.text.length | ||
if (offsetLeft !== 0) { | ||
break | ||
} | ||
} | ||
offsetLeft -= child.text.length | ||
} | ||
@@ -63,0 +95,0 @@ } |
@@ -21,2 +21,3 @@ import type {PortableTextBlock} from '@sanity/types' | ||
blockOffset: offsets.anchor, | ||
direction: backward ? 'backward' : 'forward', | ||
}) | ||
@@ -26,2 +27,3 @@ const focus = blockOffsetToSpanSelectionPoint({ | ||
blockOffset: offsets.focus, | ||
direction: backward ? 'forward' : 'backward', | ||
}) | ||
@@ -28,0 +30,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
6137434
94643