diff --git a/readme.refactoring-summary.md b/readme.refactoring-summary.md index 5f122cf..3e5f602 100644 --- a/readme.refactoring-summary.md +++ b/readme.refactoring-summary.md @@ -1,5 +1,13 @@ # WYSIWYG Editor Refactoring Progress Summary +## Latest Updates + +### Selection Highlighting Fix ✅ +- **Issue**: "Paragraphs are not highlighted consistently, headings are always highlighted" +- **Root Cause**: The `shouldUpdate` method in `dees-wysiwyg-block.ts` was using a generic `.block` selector that would match the first element with that class, not necessarily the correct block element +- **Solution**: Changed the selector to be more specific: `.block.${blockType}` which ensures the correct element is found for each block type +- **Result**: All block types now highlight consistently when selected + ## Completed Phases ### Phase 1: Infrastructure ✅ diff --git a/test/test.wysiwyg-basic.browser.ts b/test/test.wysiwyg-basic.browser.ts new file mode 100644 index 0000000..1f4d9da --- /dev/null +++ b/test/test.wysiwyg-basic.browser.ts @@ -0,0 +1,9 @@ +import { expect, tap } from '@git.zone/tstest/tapbundle'; +import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js'; + +tap.test('should create wysiwyg editor', async () => { + const editor = new DeesInputWysiwyg(); + expect(editor).toBeInstanceOf(DeesInputWysiwyg); +}); + +export default tap.start(); \ No newline at end of file diff --git a/test/test.wysiwyg-selection-highlight.browser.ts b/test/test.wysiwyg-selection-highlight.browser.ts new file mode 100644 index 0000000..d78a3e6 --- /dev/null +++ b/test/test.wysiwyg-selection-highlight.browser.ts @@ -0,0 +1,156 @@ +import { expect, tap, webhelpers } from '@git.zone/tstest/tapbundle'; +import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js'; +import { DeesWysiwygBlock } from '../ts_web/elements/wysiwyg/dees-wysiwyg-block.js'; + +tap.test('Selection highlighting should work consistently for all block types', async () => { + const editor: DeesInputWysiwyg = await webhelpers.fixture( + webhelpers.html`` + ); + + // Import various block types + editor.importBlocks([ + { id: 'para-1', type: 'paragraph', content: 'This is a paragraph' }, + { id: 'heading-1', type: 'heading-1', content: 'This is a heading' }, + { id: 'quote-1', type: 'quote', content: 'This is a quote' }, + { id: 'code-1', type: 'code', content: 'const x = 42;' }, + { id: 'list-1', type: 'list', content: 'Item 1\nItem 2' } + ]); + + await editor.updateComplete; + await new Promise(resolve => setTimeout(resolve, 100)); + + // Test paragraph highlighting + console.log('Testing paragraph highlighting...'); + const paraWrapper = editor.shadowRoot?.querySelector('[data-block-id="para-1"]'); + const paraComponent = paraWrapper?.querySelector('dees-wysiwyg-block') as DeesWysiwygBlock; + const paraContainer = paraComponent?.shadowRoot?.querySelector('.wysiwyg-block-container') as HTMLElement; + const paraElement = paraContainer?.querySelector('.block.paragraph') as HTMLElement; + + // Focus paragraph to select it + paraElement.focus(); + await new Promise(resolve => setTimeout(resolve, 100)); + + // Check if paragraph has selected class + const paraHasSelected = paraElement.classList.contains('selected'); + console.log('Paragraph has selected class:', paraHasSelected); + + // Check computed styles + const paraStyle = window.getComputedStyle(paraElement); + console.log('Paragraph background:', paraStyle.background); + console.log('Paragraph box-shadow:', paraStyle.boxShadow); + + // Test heading highlighting + console.log('\nTesting heading highlighting...'); + const headingWrapper = editor.shadowRoot?.querySelector('[data-block-id="heading-1"]'); + const headingComponent = headingWrapper?.querySelector('dees-wysiwyg-block') as DeesWysiwygBlock; + const headingContainer = headingComponent?.shadowRoot?.querySelector('.wysiwyg-block-container') as HTMLElement; + const headingElement = headingContainer?.querySelector('.block.heading-1') as HTMLElement; + + // Focus heading to select it + headingElement.focus(); + await new Promise(resolve => setTimeout(resolve, 100)); + + // Check if heading has selected class + const headingHasSelected = headingElement.classList.contains('selected'); + console.log('Heading has selected class:', headingHasSelected); + + // Check computed styles + const headingStyle = window.getComputedStyle(headingElement); + console.log('Heading background:', headingStyle.background); + console.log('Heading box-shadow:', headingStyle.boxShadow); + + // Test quote highlighting + console.log('\nTesting quote highlighting...'); + const quoteWrapper = editor.shadowRoot?.querySelector('[data-block-id="quote-1"]'); + const quoteComponent = quoteWrapper?.querySelector('dees-wysiwyg-block') as DeesWysiwygBlock; + const quoteContainer = quoteComponent?.shadowRoot?.querySelector('.wysiwyg-block-container') as HTMLElement; + const quoteElement = quoteContainer?.querySelector('.block.quote') as HTMLElement; + + // Focus quote to select it + quoteElement.focus(); + await new Promise(resolve => setTimeout(resolve, 100)); + + // Check if quote has selected class + const quoteHasSelected = quoteElement.classList.contains('selected'); + console.log('Quote has selected class:', quoteHasSelected); + + // Test code highlighting + console.log('\nTesting code highlighting...'); + const codeWrapper = editor.shadowRoot?.querySelector('[data-block-id="code-1"]'); + const codeComponent = codeWrapper?.querySelector('dees-wysiwyg-block') as DeesWysiwygBlock; + const codeContainer = codeComponent?.shadowRoot?.querySelector('.wysiwyg-block-container') as HTMLElement; + const codeElement = codeContainer?.querySelector('.block.code') as HTMLElement; + + // Focus code to select it + codeElement.focus(); + await new Promise(resolve => setTimeout(resolve, 100)); + + // Check if code has selected class + const codeHasSelected = codeElement.classList.contains('selected'); + console.log('Code has selected class:', codeHasSelected); + + // Focus back on paragraph and check if others are deselected + console.log('\nFocusing back on paragraph...'); + paraElement.focus(); + await new Promise(resolve => setTimeout(resolve, 100)); + + // Check that only paragraph is selected + expect(paraElement.classList.contains('selected')).toBeTrue(); + expect(headingElement.classList.contains('selected')).toBeFalse(); + expect(quoteElement.classList.contains('selected')).toBeFalse(); + expect(codeElement.classList.contains('selected')).toBeFalse(); + + console.log('Selection highlighting test complete'); +}); + +tap.test('Selected class should toggle correctly when clicking between blocks', async () => { + const editor: DeesInputWysiwyg = await webhelpers.fixture( + webhelpers.html`` + ); + + // Import two blocks + editor.importBlocks([ + { id: 'block-1', type: 'paragraph', content: 'First block' }, + { id: 'block-2', type: 'paragraph', content: 'Second block' } + ]); + + await editor.updateComplete; + await new Promise(resolve => setTimeout(resolve, 100)); + + // Get both blocks + const block1Wrapper = editor.shadowRoot?.querySelector('[data-block-id="block-1"]'); + const block1Component = block1Wrapper?.querySelector('dees-wysiwyg-block') as DeesWysiwygBlock; + const block1Container = block1Component?.shadowRoot?.querySelector('.wysiwyg-block-container') as HTMLElement; + const block1Element = block1Container?.querySelector('.block.paragraph') as HTMLElement; + + const block2Wrapper = editor.shadowRoot?.querySelector('[data-block-id="block-2"]'); + const block2Component = block2Wrapper?.querySelector('dees-wysiwyg-block') as DeesWysiwygBlock; + const block2Container = block2Component?.shadowRoot?.querySelector('.wysiwyg-block-container') as HTMLElement; + const block2Element = block2Container?.querySelector('.block.paragraph') as HTMLElement; + + // Initially neither should be selected + expect(block1Element.classList.contains('selected')).toBeFalse(); + expect(block2Element.classList.contains('selected')).toBeFalse(); + + // Click on first block + block1Element.click(); + block1Element.focus(); + await new Promise(resolve => setTimeout(resolve, 100)); + + // First block should be selected + expect(block1Element.classList.contains('selected')).toBeTrue(); + expect(block2Element.classList.contains('selected')).toBeFalse(); + + // Click on second block + block2Element.click(); + block2Element.focus(); + await new Promise(resolve => setTimeout(resolve, 100)); + + // Second block should be selected, first should not + expect(block1Element.classList.contains('selected')).toBeFalse(); + expect(block2Element.classList.contains('selected')).toBeTrue(); + + console.log('Toggle test complete'); +}); + +export default tap.start(); \ No newline at end of file diff --git a/test/test.wysiwyg-selection-simple.browser.ts b/test/test.wysiwyg-selection-simple.browser.ts new file mode 100644 index 0000000..4fd6c38 --- /dev/null +++ b/test/test.wysiwyg-selection-simple.browser.ts @@ -0,0 +1,62 @@ +import { expect, tap, webhelpers } from '@git.zone/tstest/tapbundle'; +import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js'; +import { DeesWysiwygBlock } from '../ts_web/elements/wysiwyg/dees-wysiwyg-block.js'; + +tap.test('Selection highlighting basic test', async () => { + const editor: DeesInputWysiwyg = await webhelpers.fixture( + webhelpers.html`` + ); + + // Import two blocks + editor.importBlocks([ + { id: 'para-1', type: 'paragraph', content: 'First paragraph' }, + { id: 'head-1', type: 'heading-1', content: 'First heading' } + ]); + + await editor.updateComplete; + await new Promise(resolve => setTimeout(resolve, 500)); + + // Get paragraph element + const paraWrapper = editor.shadowRoot?.querySelector('[data-block-id="para-1"]'); + const paraComponent = paraWrapper?.querySelector('dees-wysiwyg-block') as DeesWysiwygBlock; + const paraBlock = paraComponent?.shadowRoot?.querySelector('.block.paragraph') as HTMLElement; + + // Get heading element + const headWrapper = editor.shadowRoot?.querySelector('[data-block-id="head-1"]'); + const headComponent = headWrapper?.querySelector('dees-wysiwyg-block') as DeesWysiwygBlock; + const headBlock = headComponent?.shadowRoot?.querySelector('.block.heading-1') as HTMLElement; + + console.log('Found elements:', { + paraBlock: !!paraBlock, + headBlock: !!headBlock + }); + + // Focus paragraph + paraBlock.focus(); + await new Promise(resolve => setTimeout(resolve, 100)); + + // Check classes + console.log('Paragraph classes:', paraBlock.className); + console.log('Heading classes:', headBlock.className); + + // Check isSelected property + console.log('Paragraph component isSelected:', paraComponent.isSelected); + console.log('Heading component isSelected:', headComponent.isSelected); + + // Focus heading + headBlock.focus(); + await new Promise(resolve => setTimeout(resolve, 100)); + + // Check classes again + console.log('\nAfter focusing heading:'); + console.log('Paragraph classes:', paraBlock.className); + console.log('Heading classes:', headBlock.className); + console.log('Paragraph component isSelected:', paraComponent.isSelected); + console.log('Heading component isSelected:', headComponent.isSelected); + + // Check that heading is selected + expect(headBlock.classList.contains('selected')).toBeTrue(); + expect(paraBlock.classList.contains('selected')).toBeFalse(); +}); + +export default tap.start(); \ No newline at end of file diff --git a/ts_web/elements/wysiwyg/dees-wysiwyg-block.ts b/ts_web/elements/wysiwyg/dees-wysiwyg-block.ts index 344cad7..13e530b 100644 --- a/ts_web/elements/wysiwyg/dees-wysiwyg-block.ts +++ b/ts_web/elements/wysiwyg/dees-wysiwyg-block.ts @@ -699,11 +699,17 @@ export class DeesWysiwygBlock extends DeesElement { ]; protected shouldUpdate(changedProperties: Map): boolean { - // If selection state changed, we need to update for non-editable blocks - const nonEditableTypes = ['divider', 'image', 'youtube', 'markdown', 'html', 'attachment']; - if (changedProperties.has('isSelected') && this.block && nonEditableTypes.includes(this.block.type)) { - // For non-editable blocks, we need to update the selected class - const element = this.shadowRoot?.querySelector('.block') as HTMLElement; + // If selection state changed, update the selected class without re-rendering + if (changedProperties.has('isSelected') && this.block) { + // Find the block element based on block type + let element: HTMLElement | null = null; + + // Build the specific selector based on block type + const blockType = this.block.type; + const selector = `.block.${blockType}`; + + element = this.shadowRoot?.querySelector(selector) as HTMLElement; + if (element) { if (this.isSelected) { element.classList.add('selected');