Skip to content
Open
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
38 changes: 29 additions & 9 deletions packages/page-controller/src/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,16 @@ export async function inputTextElement(element: HTMLElement, text: string) {
// - Monaco/CodeMirror: Require direct JS instance access. No universal way to obtain.
// - Draft.js: Not responsive to synthetic/execCommand/Range/DataTransfer. Unmaintained.
//
// Strategy: Try Plan A (synthetic events) first, then verify and fall back
// to Plan B (execCommand) if the text wasn't actually inserted.
//
// Plan A: Dispatch synthetic events
// Works: LinkedIn, React contenteditable, Quill.
// Fails: Slate.js
// Works: React contenteditable, Quill, some LinkedIn versions.
// Fails: Slate.js, some contenteditable editors that ignore synthetic events.
// Sequence: beforeinput -> mutation -> input -> change -> blur

let planASucceeded = false

// Dispatch beforeinput + mutation + input for clearing
if (
element.dispatchEvent(
Expand Down Expand Up @@ -164,18 +169,33 @@ export async function inputTextElement(element: HTMLElement, text: string) {
)
}

// Verify Plan A worked by checking if the text was actually inserted
const currentText = element.innerText.trim()
planASucceeded = currentText === text.trim()

if (!planASucceeded) {
// Plan B: execCommand fallback (deprecated but widely supported)
// Works: LinkedIn, Quill, Slate.js, react contenteditable components.
// This approach integrates with the browser's undo stack and is handled
// natively by most rich-text editors.
element.focus()

// Select all existing content and delete it
const selection = window.getSelection()
const range = document.createRange()
range.selectNodeContents(element)
selection?.removeAllRanges()
selection?.addRange(range)

document.execCommand('delete', false)
document.execCommand('insertText', false, text)
}

// Dispatch change event (for good measure)
element.dispatchEvent(new Event('change', { bubbles: true }))

// Trigger blur for validation
element.blur()

// Plan B: execCommand (deprecated but works better for some editors)
// Works: LinkedIn, Quill, Slate.js, react contenteditable components
//
// document.execCommand('selectAll')
// document.execCommand('delete')
// document.execCommand('insertText', false, text)
} else if (element instanceof HTMLTextAreaElement) {
nativeTextAreaValueSetter.call(element, text)
} else {
Expand Down