The Selection API in SquibView allows you to detect, examine, and manipulate text selections in both the source and rendered panels. This guide explains how to use these powerful features in your applications.
SquibView’s Selection API enables you to:
When text is selected, SquibView creates a selection data object with the following structure:
For the source panel:
{
panel: 'source',
text: 'selected text',
range: {
start: 10, // Character positionend: 22// Character position
}
}
For the rendered panel:
{
panel: 'rendered',
text: 'selected text',
range: DOMRange, // DOM Range objectelement: HTMLElement// The contenteditable element
}
There are two main ways to detect and respond to text selections:
// Register a callback for selection eventsconst unsubscribe = editor.onTextSelected(selectionData => {
console.log(`Selected text: ${selectionData.text}`);
console.log(`In panel: ${selectionData.panel}`);
// Do something with the selectionif (selectionData.text.includes('TODO')) {
handleTodo(selectionData);
}
});
// Later, to stop listening for selections:unsubscribe();
// Get current selection at any timeconst button = document.getElementById('checkSelection');
button.addEventListener('click', () => {
const selection = editor.getCurrentSelection();
if (selection) {
console.log(`Currently selected: ${selection.text}`);
} else {
console.log('Nothing currently selected');
}
});
Once you have a selection, you can manipulate it in various ways:
// Replace selected text with new content
editor.replaceSelectedText('replacement text', selectionData);
// Add formatting based on panelif (selectionData.panel === 'source') {
editor.replaceSelectedText(`**${selectionData.text}**`, selectionData); // Bold in Markdown
} else {
editor.replaceSelectedText(`<strong>${selectionData.text}</strong>`, selectionData); // Bold in HTML
}
You can make text in the rendered panel non-editable (locked) or editable (unlocked):
// Make selected text non-editable (only works in rendered panel)if (selectionData.panel === 'rendered') {
editor.setSelectionEditable(false, selectionData); // Lock (make non-editable)// Or make it editable
editor.setSelectionEditable(true, selectionData); // Unlock (make editable)// Or use the smart toggle that automatically detects current stateconst newState = editor.toggleSelectionLock(selectionData);
console.log(newState ? "Now editable" : "Now locked");
}
Locked content is visually indicated with:
Unlocked content (after previously being locked) shows an unlock icon (🔓).
// Clear current selection
editor.clearSelection();
The onReplaceSelectedText
handler provides a powerful way to automatically process selections and optionally replace them:
// Set up a selection handler
editor.onReplaceSelectedText = function(selectionData) {
// Process selectionif (selectionData.text === 'TODO') {
return'✅ DONE'; // Return text to replace the selection
}
if (selectionData.text.match(/^[a-z]/)) {
// Capitalize first letterreturn selectionData.text.charAt(0).toUpperCase() +
selectionData.text.slice(1);
}
// Return undefined or null to not replace anything// This allows processing selections without modifying themlogSelectionToAnalytics(selectionData);
};
// Remove the handler
editor.onReplaceSelectedText = null;
You can set up the selection handler when creating the SquibView instance:
const editor = newSquibView('#editor', {
initialContent: '# Hello World',
inputContentType: 'md',
onReplaceSelectedText: function(selectionData) {
// Process selections from the beginningif (selectionData.text === 'hello') {
return'Hello, world!';
}
}
});
editor.onTextSelected(selectionData => {
// Always add exclamation marks to selected textconst newText = `${selectionData.text}!!!`;
editor.replaceSelectedText(newText, selectionData);
});
editor.onReplaceSelectedText = function(selectionData) {
// Example: Smart formatting based on text contentif (selectionData.text.match(/^\d+$/)) {
// Format numbers with commasreturnNumber(selectionData.text).toLocaleString();
}
if (selectionData.text.toLowerCase() === 'date') {
// Replace "date" with current datereturnnewDate().toLocaleDateString();
}
};
editor.onTextSelected(selectionData => {
// Analyze text without replacing itconst wordCount = selectionData.text.split(/\s+/).filter(Boolean).length;
const charCount = selectionData.text.length;
document.getElementById('stats').innerHTML = `
<p>Selection contains ${wordCount} words and ${charCount} characters</p>
`;
});
editor.onReplaceSelectedText = asyncfunction(selectionData) {
// Only process single wordsif (selectionData.text.trim().split(/\s+/).length === 1) {
const word = selectionData.text.trim();
// Show definition in a tooltip (without replacing)try {
const response = awaitfetch(`https://api.dictionaryapi.dev/api/v2/entries/en/${word}`);
const data = await response.json();
if (data && data[0] && data[0].meanings) {
const definition = data[0].meanings[0].definitions[0].definition;
showTooltip(definition, selectionData);
}
} catch (err) {
console.error('Dictionary lookup failed', err);
}
}
// Return undefined to not replace the text
};
Consider Context: Selection behavior may differ between source and rendered panels
if (selectionData.panel === 'source') {
// Handle source panel selection (plain text)
} else {
// Handle rendered panel selection (may contain HTML)
}
Clean Up Listeners: Always store and use the unsubscribe function to prevent memory leaks
const unsubscribe = editor.onTextSelected(handler);
// Later:unsubscribe();
Respect User Intent: Avoid aggressive text replacement that might disrupt the user’s workflow
// Good: Only replace when there's a clear pattern or intentif (selectionData.text.match(/^todo:/i)) {
return'✅ ' + selectionData.text.substring(5);
}
Improve UX With Visual Feedback: Indicate when selection handling is active
editor.onTextSelected(selectionData => {
// Show UI indicator when text is selecteddocument.getElementById('selection-active').style.display = 'block';
// Hide when selection is clearedwindow.setTimeout(() => {
if (!editor.getCurrentSelection()) {
document.getElementById('selection-active').style.display = 'none';
}
}, 100);
});
Combine With Other Events: Create powerful workflows by combining selection with other events
// Track selectionlet lastSelection = null;
editor.onTextSelected(selectionData => {
lastSelection = selectionData;
});
// Use with keyboard shortcutsdocument.addEventListener('keydown', e => {
if (e.ctrlKey && e.key === 'b' && lastSelection) {
// Bold on Ctrl+Bif (lastSelection.panel === 'source') {
editor.replaceSelectedText(`**${lastSelection.text}**`, lastSelection);
} else {
editor.replaceSelectedText(`<strong>${lastSelection.text}</strong>`, lastSelection);
}
e.preventDefault();
}
});