fix(wysiwyg): Implement programmatic rendering to eliminate focus loss during typing
- Convert parent component to use static rendering with programmatic DOM manipulation - Remove all reactive state that could trigger re-renders during editing - Delay content sync to avoid interference with typing (2s auto-save) - Update all block operations to use manual DOM updates instead of Lit re-renders - Fix specific issue where typing + arrow keys caused focus loss - Add comprehensive focus management documentation
This commit is contained in:
@ -2,6 +2,7 @@ import { type IBlock } from './wysiwyg.types.js';
|
||||
import { WysiwygShortcuts } from './wysiwyg.shortcuts.js';
|
||||
import { WysiwygBlocks } from './wysiwyg.blocks.js';
|
||||
import { WysiwygBlockOperations } from './wysiwyg.blockoperations.js';
|
||||
import { WysiwygModalManager } from './wysiwyg.modalmanager.js';
|
||||
|
||||
export class WysiwygInputHandler {
|
||||
private component: any;
|
||||
@ -20,10 +21,7 @@ export class WysiwygInputHandler {
|
||||
const target = e.target as HTMLDivElement;
|
||||
const textContent = target.textContent || '';
|
||||
|
||||
// Update block content based on type
|
||||
this.updateBlockContent(block, target);
|
||||
|
||||
// Check for block type transformations
|
||||
// Check for block type transformations BEFORE updating content
|
||||
const detectedType = this.detectBlockTypeIntent(textContent);
|
||||
if (detectedType && detectedType.type !== block.type) {
|
||||
e.preventDefault();
|
||||
@ -34,7 +32,10 @@ export class WysiwygInputHandler {
|
||||
// Handle slash commands
|
||||
this.handleSlashCommand(textContent, target);
|
||||
|
||||
// Schedule auto-save
|
||||
// Don't update block content immediately - let the block handle its own content
|
||||
// This prevents re-renders during typing
|
||||
|
||||
// Schedule auto-save (which will sync content later)
|
||||
this.scheduleAutoSave();
|
||||
}
|
||||
|
||||
@ -48,7 +49,11 @@ export class WysiwygInputHandler {
|
||||
|
||||
if (blockComponent) {
|
||||
// Use the block component's getContent method for consistency
|
||||
block.content = blockComponent.getContent();
|
||||
const newContent = blockComponent.getContent();
|
||||
// Only update if content actually changed to avoid unnecessary updates
|
||||
if (block.content !== newContent) {
|
||||
block.content = newContent;
|
||||
}
|
||||
|
||||
// Update list metadata if needed
|
||||
if (block.type === 'list') {
|
||||
@ -133,7 +138,11 @@ export class WysiwygInputHandler {
|
||||
target.innerHTML = `<${listTag}><li></li></${listTag}>`;
|
||||
|
||||
this.component.updateValue();
|
||||
this.component.requestUpdate();
|
||||
|
||||
// Update the block element programmatically
|
||||
if (this.component.editorContentRef) {
|
||||
this.component.updateBlockElement(block.id);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
WysiwygBlocks.focusListItem(target);
|
||||
@ -142,13 +151,17 @@ export class WysiwygInputHandler {
|
||||
block.type = 'divider';
|
||||
block.content = ' ';
|
||||
|
||||
// Update the block element programmatically
|
||||
if (this.component.editorContentRef) {
|
||||
this.component.updateBlockElement(block.id);
|
||||
}
|
||||
|
||||
const newBlock = blockOps.createBlock();
|
||||
blockOps.insertBlockAfter(block, newBlock);
|
||||
|
||||
this.component.updateValue();
|
||||
this.component.requestUpdate();
|
||||
} else if (detectedType.type === 'code') {
|
||||
const language = await this.component.showLanguageSelectionModal();
|
||||
const language = await WysiwygModalManager.showLanguageSelectionModal();
|
||||
if (language) {
|
||||
block.type = 'code';
|
||||
block.content = '';
|
||||
@ -156,7 +169,11 @@ export class WysiwygInputHandler {
|
||||
target.textContent = '';
|
||||
|
||||
this.component.updateValue();
|
||||
this.component.requestUpdate();
|
||||
|
||||
// Update the block element programmatically
|
||||
if (this.component.editorContentRef) {
|
||||
this.component.updateBlockElement(block.id);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
block.type = detectedType.type;
|
||||
@ -164,7 +181,11 @@ export class WysiwygInputHandler {
|
||||
target.textContent = '';
|
||||
|
||||
this.component.updateValue();
|
||||
this.component.requestUpdate();
|
||||
|
||||
// Update the block element programmatically
|
||||
if (this.component.editorContentRef) {
|
||||
this.component.updateBlockElement(block.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -233,8 +254,30 @@ export class WysiwygInputHandler {
|
||||
return;
|
||||
}
|
||||
this.saveTimeout = setTimeout(() => {
|
||||
// Sync all block content from DOM before saving
|
||||
this.syncAllBlockContent();
|
||||
// Only update value, don't trigger any re-renders
|
||||
this.component.updateValue();
|
||||
}, 1000);
|
||||
// Don't call requestUpdate() as it's not needed
|
||||
}, 2000); // Increased delay to reduce interference with typing
|
||||
}
|
||||
|
||||
/**
|
||||
* Syncs content from all block DOMs to the data model
|
||||
*/
|
||||
private syncAllBlockContent(): void {
|
||||
this.component.blocks.forEach((block: IBlock) => {
|
||||
const wrapperElement = this.component.shadowRoot?.querySelector(`[data-block-id="${block.id}"]`);
|
||||
const blockComponent = wrapperElement?.querySelector('dees-wysiwyg-block') as any;
|
||||
|
||||
if (blockComponent && blockComponent.getContent) {
|
||||
const newContent = blockComponent.getContent();
|
||||
// Only update if content actually changed
|
||||
if (block.content !== newContent) {
|
||||
block.content = newContent;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
Reference in New Issue
Block a user