Skip to content
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
1 change: 1 addition & 0 deletions packages/volto-slate/news/8402.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed cursor position lost when clicking on a Slate block in edit, causing the cursor to jump to the start of the text. @wesleybl
32 changes: 31 additions & 1 deletion packages/volto-slate/src/editor/SlateEditor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ class SlateEditor extends Component {
this.createEditor = this.createEditor.bind(this);
this.multiDecorator = this.multiDecorator.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handlePointerDown = this.handlePointerDown.bind(this);
this.getSavedSelection = this.getSavedSelection.bind(this);
this.setSavedSelection = this.setSavedSelection.bind(this);
this.scheduleFocus = this.scheduleFocus.bind(this);
Expand All @@ -73,6 +74,31 @@ class SlateEditor extends Component {

this.editor = null;
this.selectionTimeout = null;
this.pendingPointerSelection = null;
}

handlePointerDown(event) {
if (this.props.selected) return;

const nativeEvent = event.nativeEvent || event;
const domPoint = document.caretPositionFromPoint?.(
nativeEvent.clientX,
nativeEvent.clientY,
);
if (!domPoint) return;

try {
const point = ReactEditor.toSlatePoint(
this.state.editor,
[domPoint.offsetNode, domPoint.offset],
{ exactMatch: false, suppressThrow: true },
);
if (point) {
this.pendingPointerSelection = { anchor: point, focus: point };
}
} catch {
this.pendingPointerSelection = null;
}
}

getSavedSelection() {
Expand Down Expand Up @@ -185,7 +211,10 @@ class SlateEditor extends Component {
if (!prevProps.selected && this.props.selected) {
// if the SlateEditor becomes selected from unselected

if (window.getSelection().type === 'None') {
if (this.pendingPointerSelection) {
Transforms.select(this.state.editor, this.pendingPointerSelection);
this.pendingPointerSelection = null;
} else if (window.getSelection().type === 'None') {
// TODO: why is this condition checked?
Transforms.select(
this.state.editor,
Expand Down Expand Up @@ -326,6 +355,7 @@ class SlateEditor extends Component {
return null;
}}
onClick={this.props.onClick}
onPointerDown={this.handlePointerDown}
onSelect={(e) => {
if (!selected && this.props.onFocus) {
// we can't overwrite the onFocus of Editable, as the onFocus
Expand Down
94 changes: 94 additions & 0 deletions packages/volto/cypress/tests/core/blocks/blocks-slate-cursor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
describe('Slate cursor position Tests', () => {
const EDITABLE = '.content-area .slate-editor [contenteditable=true]';
const BLOCK = '.block.slate';
const TEXT = 'The quick brown fox jumps over the lazy dog';

beforeEach(() => {
cy.intercept('GET', `/**/*?expand*`).as('content');
cy.intercept('GET', '/**/Document').as('schema');
// given a logged in editor and a page in edit mode
cy.autologin();
cy.createContent({
contentType: 'Document',
contentId: 'my-page',
contentTitle: 'My Page',
bodyModifier: (body) => ({
...body,
blocks: {
'block-1': {
'@type': 'slate',
value: [{ type: 'p', children: [{ text: 'ab' }] }],
},
'block-2': {
'@type': 'slate',
value: [{ type: 'p', children: [{ text: 'abc' }] }],
},
'block-3': {
'@type': 'slate',
value: [{ type: 'p', children: [{ text: 'ab' }] }],
},
'block-4': {
'@type': 'slate',
value: [{ type: 'p', children: [{ text: TEXT }] }],
},
},
blocks_layout: {
items: ['block-1', 'block-2', 'block-3', 'block-4'],
},
}),
});
cy.visit('/');
cy.wait('@content');

cy.navigate('/my-page/edit');
cy.wait('@schema');
});

const getEndOfTextCoordinates = (editable) => {
const walker = document.createTreeWalker(editable, NodeFilter.SHOW_TEXT);
let textNode;
while (walker.nextNode()) {
if (walker.currentNode.textContent === TEXT) {
textNode = walker.currentNode;
break;
}
}
expect(
Boolean(textNode),
'text node of the last block to be found',
).to.equal(true);
const range = document.createRange();
range.setStart(textNode, textNode.textContent.length - 1);
range.setEnd(textNode, textNode.textContent.length);
const rect = range.getClientRects()[0];
const editableRect = editable.getBoundingClientRect();
return {
x: rect.right - editableRect.left - 1,
y: rect.top - editableRect.top + rect.height / 2,
};
};

it('keeps the cursor at the click position when clicking at the end of a slate block', () => {
// when I click at the very end of the text
cy.get(EDITABLE)
.last()
.scrollIntoView({ block: 'center' })
.then(($editable) => {
const { x, y } = getEndOfTextCoordinates($editable[0]);
cy.wrap($editable).click(x, y);
});

// then the block should be selected and the cursor should stay at the end
cy.get(BLOCK).last().should('have.class', 'selected');

cy.window().then((win) => {
const range = win.getSelection().getRangeAt(0);
const anchor = range.startContainer;
const offset =
anchor.nodeType === Node.TEXT_NODE ? range.startOffset : -1;
expect(offset, `cursor offset ${offset} to stay at the end`).to.equal(
TEXT.length,
);
});
});
});
1 change: 1 addition & 0 deletions packages/volto/news/8402.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed cursor position lost when clicking on a Slate block in edit, causing the cursor to jump to the start of the text. @wesleybl
Loading