-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Open
Labels
Description
Bug: Selection/Cursor jumps to beginning after applying color with custom color syntax plugin
Description
When applying text color using a custom color syntax plugin wrapper, the cursor/selection position resets unexpectedly. Additionally, when changing text to the same color, the highlight background still displays.
Environment
- Toast UI Editor Version: 3.x
- @toast-ui/editor-plugin-color-syntax Version: 3.x
- Browser: Chrome/Firefox/Safari (all browsers affected)
- OS: Windows/macOS/Linux
Steps to Reproduce
- Create an editor instance with the color syntax plugin
- Select a single character or text in the middle of a line
- Click a color from the color preset palette
- Expected: Cursor stays at the end of the selection
- Actual: Cursor jumps to the beginning of the line (first time only)
const editorNode = new Editor({
el: document.getElementById('editor'),
plugins: [colorSyntax],
toolbarItems: [['color']],
});
// Select "test" text and apply color
// Cursor jumps to start of line instead of staying after "test"Current Behavior
Issue 1 - Cursor Jumps:
- When color is applied to selected text, cursor position resets
- Occurs only on the first color change in a session
- Happens regardless of selection position (beginning, middle, or end of line)
- Using
tr.setSelection()before dispatch does not prevent this
Issue 2 - Highlight Remains:
- When changing text to the same color already applied
- Or when changing part of colored text to a different color
- The highlight/background color still displays even after applying new color
- Example: Text with
color: #000000;→ change tocolor: #ffc90e;→ still shows background highlight
Minimal Code Example
// Custom wrapper (attempting to override)
function colorSyntax(context, options) {
const pluginInfo = originalColorSyntax(context, options);
if (pluginInfo.wysiwygCommands?.color) {
const originalCommand = pluginInfo.wysiwygCommands.color;
pluginInfo.wysiwygCommands.color = (value, state, dispatch) => {
const { selectedColor } = value;
if (!selectedColor) return false;
const { tr, selection, schema } = state;
const from = Math.min(selection.anchor, selection.head);
const to = Math.max(selection.anchor, selection.head);
// Save selection
const savedAnchor = selection.anchor;
const savedHead = selection.head;
// Process color
let pos = from;
while (pos < to) {
const node = tr.doc.nodeAt(pos);
if (node?.isText) {
const spanMark = node.marks?.find(m => m.type === schema.marks.span);
const currentStyle = spanMark?.attrs.htmlAttrs?.style || '';
// Remove old color, add new color
const newStyle = currentStyle
.replace(/color\s*:\s*[^;]+;?/gi, '')
.trim() + `;color: ${selectedColor};`;
tr.removeMark(pos, pos + node.nodeSize, schema.marks.span);
tr.addMark(pos, pos + node.nodeSize, schema.marks.span.create({
htmlAttrs: { style: newStyle },
htmlInline: true
}));
pos += node.nodeSize;
} else {
pos += 1;
}
}
// Try to restore selection - DOES NOT WORK
tr.setSelection(state.selection.constructor.create(tr.doc, savedAnchor, savedHead));
dispatch(tr);
return true;
};
}
return pluginInfo;
}Expected Behavior
- After applying color, cursor should remain at the end of the selection (anchor: 40, head: 41 → should stay the same)
- Changing to the same color should not display highlight
- Selection state should be preserved through the transaction
Actual Behavior
- Cursor jumps to beginning of line
- Selection is lost after color is applied
- Highlight background persists even after color change
Attempted Solutions
- ✗ Saving/restoring selection with
tr.setSelection()before dispatch - ✗ Using
editorNode.setSelection(savedSelection)after dispatch - ✗ Wrapping dispatch with
setTimeout() - ✗ Not overriding command and calling original - cursor still jumps
- ✗ Intercepting UI events at color button level
Questions
- Is there a known issue with selection preservation in color syntax plugin?
- Should we be using a different approach (hooks, events) instead of command override?
- Is the cursor jump behavior expected when modifying marks?
- How can we properly preserve selection state in custom plugin wrappers?
Additional Context
Document structure (from state.doc):
{
"type": "paragraph",
"content": [
{
"type": "text",
"marks": [
{
"type": "span",
"attrs": {
"htmlAttrs": {
"style": "font-size: 22px;color: #000000;"
},
"htmlInline": true
}
}
],
"text": "フォームからデータの登録ができます。"
}
]
}Selection state (before color apply):
anchor: 40
head: 41
Selection state (after color apply):
anchor: 0 // ❌ Should still be 40
head: 0 // ❌ Should still be 41
Related Issues
- Similar to selection management issues in ProseMirror plugins
- Toast UI Editor uses ProseMirror internally for WYSIWYG editing
Suggested Fix
- Provide documentation on proper selection handling in custom plugins
- Or: Ensure color command preserves selection state by default
- Consider adding selection restoration utility function for plugin developers
Reactions are currently unavailable