update
This commit is contained in:
14
changelog.md
14
changelog.md
@ -1,8 +1,9 @@
|
||||
# Changelog
|
||||
|
||||
## 2025-06-28 - 1.10.10 - improve(dees-dashboardgrid)
|
||||
## 2025-06-29 - 1.10.10 - improve(dees-dashboardgrid, dees-input-wysiwyg)
|
||||
Enhanced dashboard grid component with advanced spacing and layout features inspired by gridstack.js
|
||||
|
||||
Dashboard Grid improvements:
|
||||
- Improved margin system supporting uniform or individual margins (top, right, bottom, left)
|
||||
- Added collision detection to prevent widget overlap during drag operations
|
||||
- Implemented auto-positioning for new widgets to find first available space
|
||||
@ -18,6 +19,17 @@ Enhanced dashboard grid component with advanced spacing and layout features insp
|
||||
- Added findAvailablePosition() for intelligent widget placement
|
||||
- Improved drag and resize calculations for pixel-perfect positioning
|
||||
|
||||
WYSIWYG editor drag and drop fixes:
|
||||
- Fixed drop indicator positioning to properly account for block margins
|
||||
- Added defensive checks in drag event handlers to prevent potential crashes
|
||||
- Improved updateBlockPositions with null checks and error handling
|
||||
- Updated drop indicator calculation to use simplified margin approach
|
||||
- Fixed drop indicator height to match the exact space occupied by dragged blocks
|
||||
- Improved drop indicator positioning algorithm to accurately show where blocks will land
|
||||
- Simplified visual block position calculations accounting for CSS transforms
|
||||
- Enhanced margin calculation to use correct values based on block type (16px for paragraphs, 24px for headings, 20px for code/quotes)
|
||||
- Fixed index calculation issue when dragging blocks downward by adjusting target index for excluded dragged block
|
||||
|
||||
## 2025-06-28 - 1.10.9 - feat(dees-dashboardgrid)
|
||||
Add new dashboard grid component with drag-and-drop and resize capabilities
|
||||
|
||||
|
85
test/test.wysiwyg-blockmovement.browser.ts
Normal file
85
test/test.wysiwyg-blockmovement.browser.ts
Normal file
@ -0,0 +1,85 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
|
||||
|
||||
// Initialize the element
|
||||
DeesInputWysiwyg;
|
||||
|
||||
tap.test('wysiwyg block movement during drag', async () => {
|
||||
const element = document.createElement('dees-input-wysiwyg');
|
||||
document.body.appendChild(element);
|
||||
|
||||
await element.updateComplete;
|
||||
|
||||
// Set initial content
|
||||
element.blocks = [
|
||||
{ id: 'block1', type: 'paragraph', content: 'Block 1' },
|
||||
{ id: 'block2', type: 'paragraph', content: 'Block 2' },
|
||||
{ id: 'block3', type: 'paragraph', content: 'Block 3' },
|
||||
];
|
||||
element.renderBlocksProgrammatically();
|
||||
|
||||
await element.updateComplete;
|
||||
|
||||
const editorContent = element.shadowRoot!.querySelector('.editor-content') as HTMLDivElement;
|
||||
const block1 = editorContent.querySelector('[data-block-id="block1"]') as HTMLElement;
|
||||
|
||||
// Start dragging block 1
|
||||
const mockDragEvent = {
|
||||
dataTransfer: {
|
||||
effectAllowed: '',
|
||||
setData: () => {},
|
||||
setDragImage: () => {}
|
||||
},
|
||||
clientY: 50,
|
||||
preventDefault: () => {},
|
||||
} as any;
|
||||
|
||||
element.dragDropHandler.handleDragStart(mockDragEvent, element.blocks[0]);
|
||||
|
||||
// Wait for dragging class
|
||||
await new Promise(resolve => setTimeout(resolve, 20));
|
||||
|
||||
// Verify drag state
|
||||
expect(element.dragDropHandler.dragState.draggedBlockId).toEqual('block1');
|
||||
|
||||
// Check that drag height was calculated
|
||||
console.log('Checking drag height...');
|
||||
const dragHandler = element.dragDropHandler as any;
|
||||
console.log('draggedBlockHeight:', dragHandler.draggedBlockHeight);
|
||||
console.log('draggedBlockContentHeight:', dragHandler.draggedBlockContentHeight);
|
||||
|
||||
// Manually call updateBlockPositions to simulate drag movement
|
||||
console.log('Simulating drag movement...');
|
||||
const updateBlockPositions = dragHandler.updateBlockPositions.bind(dragHandler);
|
||||
|
||||
// Simulate dragging down past block 2
|
||||
const block2 = editorContent.querySelector('[data-block-id="block2"]') as HTMLElement;
|
||||
const block2Rect = block2.getBoundingClientRect();
|
||||
const dragToY = block2Rect.bottom + 10;
|
||||
|
||||
console.log('Dragging to Y position:', dragToY);
|
||||
updateBlockPositions(dragToY);
|
||||
|
||||
// Check if blocks have moved
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
|
||||
const blocks = Array.from(editorContent.querySelectorAll('.block-wrapper'));
|
||||
console.log('Block states after drag:');
|
||||
blocks.forEach((block, i) => {
|
||||
const classes = block.className;
|
||||
const offset = (block as HTMLElement).style.getPropertyValue('--drag-offset');
|
||||
console.log(`Block ${i}: classes="${classes}", offset="${offset}"`);
|
||||
});
|
||||
|
||||
// Check that at least one block has move class
|
||||
const movedUpBlocks = editorContent.querySelectorAll('.block-wrapper.move-up');
|
||||
const movedDownBlocks = editorContent.querySelectorAll('.block-wrapper.move-down');
|
||||
console.log('Moved up blocks:', movedUpBlocks.length);
|
||||
console.log('Moved down blocks:', movedDownBlocks.length);
|
||||
|
||||
// Clean up
|
||||
element.dragDropHandler.handleDragEnd();
|
||||
document.body.removeChild(element);
|
||||
});
|
||||
|
||||
tap.start();
|
95
test/test.wysiwyg-dragdrop-simple.browser.ts
Normal file
95
test/test.wysiwyg-dragdrop-simple.browser.ts
Normal file
@ -0,0 +1,95 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
|
||||
|
||||
// Initialize the element
|
||||
DeesInputWysiwyg;
|
||||
|
||||
tap.test('wysiwyg drag handler initialization', async () => {
|
||||
const element = document.createElement('dees-input-wysiwyg');
|
||||
document.body.appendChild(element);
|
||||
|
||||
// Wait for element to be ready
|
||||
await element.updateComplete;
|
||||
|
||||
// Check that drag handler is initialized
|
||||
expect(element.dragDropHandler).toBeTruthy();
|
||||
|
||||
// Set initial content with multiple blocks
|
||||
element.blocks = [
|
||||
{ id: 'block1', type: 'paragraph', content: 'First paragraph' },
|
||||
{ id: 'block2', type: 'paragraph', content: 'Second paragraph' },
|
||||
];
|
||||
element.renderBlocksProgrammatically();
|
||||
|
||||
await element.updateComplete;
|
||||
|
||||
// Check that editor content ref exists
|
||||
console.log('editorContentRef:', element.editorContentRef);
|
||||
expect(element.editorContentRef).toBeTruthy();
|
||||
|
||||
// Check that blocks are rendered
|
||||
const blockWrappers = element.shadowRoot!.querySelectorAll('.block-wrapper');
|
||||
console.log('Number of block wrappers:', blockWrappers.length);
|
||||
expect(blockWrappers.length).toEqual(2);
|
||||
|
||||
// Check drag handles
|
||||
const dragHandles = element.shadowRoot!.querySelectorAll('.drag-handle');
|
||||
console.log('Number of drag handles:', dragHandles.length);
|
||||
expect(dragHandles.length).toEqual(2);
|
||||
|
||||
// Clean up
|
||||
document.body.removeChild(element);
|
||||
});
|
||||
|
||||
tap.test('wysiwyg drag start behavior', async () => {
|
||||
const element = document.createElement('dees-input-wysiwyg');
|
||||
document.body.appendChild(element);
|
||||
|
||||
await element.updateComplete;
|
||||
|
||||
// Set initial content
|
||||
element.blocks = [
|
||||
{ id: 'block1', type: 'paragraph', content: 'Test block' },
|
||||
];
|
||||
element.renderBlocksProgrammatically();
|
||||
|
||||
await element.updateComplete;
|
||||
|
||||
const dragHandle = element.shadowRoot!.querySelector('.drag-handle') as HTMLElement;
|
||||
expect(dragHandle).toBeTruthy();
|
||||
|
||||
// Check that drag handle has draggable attribute
|
||||
console.log('Drag handle draggable:', dragHandle.draggable);
|
||||
expect(dragHandle.draggable).toBeTrue();
|
||||
|
||||
// Test drag handler state before drag
|
||||
console.log('Initial drag state:', element.dragDropHandler.dragState);
|
||||
expect(element.dragDropHandler.dragState.draggedBlockId).toBeNull();
|
||||
|
||||
// Try to manually call handleDragStart
|
||||
const mockDragEvent = {
|
||||
dataTransfer: {
|
||||
effectAllowed: '',
|
||||
setData: (type: string, data: string) => {
|
||||
console.log('setData called with:', type, data);
|
||||
},
|
||||
setDragImage: (img: any, x: number, y: number) => {
|
||||
console.log('setDragImage called');
|
||||
}
|
||||
},
|
||||
clientY: 100,
|
||||
preventDefault: () => {},
|
||||
} as any;
|
||||
|
||||
element.dragDropHandler.handleDragStart(mockDragEvent, element.blocks[0]);
|
||||
|
||||
// Check drag state after drag start
|
||||
console.log('Drag state after start:', element.dragDropHandler.dragState);
|
||||
expect(element.dragDropHandler.dragState.draggedBlockId).toEqual('block1');
|
||||
|
||||
// Clean up
|
||||
element.dragDropHandler.handleDragEnd();
|
||||
document.body.removeChild(element);
|
||||
});
|
||||
|
||||
tap.start();
|
133
test/test.wysiwyg-dragdrop-visual.browser.ts
Normal file
133
test/test.wysiwyg-dragdrop-visual.browser.ts
Normal file
@ -0,0 +1,133 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
|
||||
|
||||
// Initialize the element
|
||||
DeesInputWysiwyg;
|
||||
|
||||
tap.test('wysiwyg drag visual feedback - block movement', async () => {
|
||||
const element = document.createElement('dees-input-wysiwyg');
|
||||
document.body.appendChild(element);
|
||||
|
||||
await element.updateComplete;
|
||||
|
||||
// Set initial content
|
||||
element.blocks = [
|
||||
{ id: 'block1', type: 'paragraph', content: 'Block 1' },
|
||||
{ id: 'block2', type: 'paragraph', content: 'Block 2' },
|
||||
{ id: 'block3', type: 'paragraph', content: 'Block 3' },
|
||||
];
|
||||
element.renderBlocksProgrammatically();
|
||||
|
||||
await element.updateComplete;
|
||||
|
||||
const editorContent = element.shadowRoot!.querySelector('.editor-content') as HTMLDivElement;
|
||||
const block1 = editorContent.querySelector('[data-block-id="block1"]') as HTMLElement;
|
||||
|
||||
// Manually start drag
|
||||
const mockDragEvent = {
|
||||
dataTransfer: {
|
||||
effectAllowed: '',
|
||||
setData: (type: string, data: string) => {},
|
||||
setDragImage: (img: any, x: number, y: number) => {}
|
||||
},
|
||||
clientY: 50,
|
||||
preventDefault: () => {},
|
||||
} as any;
|
||||
|
||||
element.dragDropHandler.handleDragStart(mockDragEvent, element.blocks[0]);
|
||||
|
||||
// Wait for dragging class
|
||||
await new Promise(resolve => setTimeout(resolve, 20));
|
||||
|
||||
// Check dragging state
|
||||
console.log('Block 1 classes:', block1.className);
|
||||
console.log('Editor content classes:', editorContent.className);
|
||||
expect(block1.classList.contains('dragging')).toBeTrue();
|
||||
expect(editorContent.classList.contains('dragging')).toBeTrue();
|
||||
|
||||
// Check drop indicator exists
|
||||
const dropIndicator = editorContent.querySelector('.drop-indicator') as HTMLElement;
|
||||
console.log('Drop indicator:', dropIndicator);
|
||||
expect(dropIndicator).toBeTruthy();
|
||||
|
||||
// Test block movement calculation
|
||||
console.log('Testing updateBlockPositions...');
|
||||
|
||||
// Access private method for testing
|
||||
const updateBlockPositions = element.dragDropHandler['updateBlockPositions'].bind(element.dragDropHandler);
|
||||
|
||||
// Simulate dragging to different position
|
||||
updateBlockPositions(150); // Move down
|
||||
|
||||
// Check if blocks have move classes
|
||||
const blocks = Array.from(editorContent.querySelectorAll('.block-wrapper'));
|
||||
console.log('Block classes after move:');
|
||||
blocks.forEach((block, i) => {
|
||||
console.log(`Block ${i}:`, block.className, 'transform:', (block as HTMLElement).style.getPropertyValue('--drag-offset'));
|
||||
});
|
||||
|
||||
// Clean up
|
||||
element.dragDropHandler.handleDragEnd();
|
||||
document.body.removeChild(element);
|
||||
});
|
||||
|
||||
tap.test('wysiwyg drop indicator positioning', async () => {
|
||||
const element = document.createElement('dees-input-wysiwyg');
|
||||
document.body.appendChild(element);
|
||||
|
||||
await element.updateComplete;
|
||||
|
||||
// Set initial content
|
||||
element.blocks = [
|
||||
{ id: 'block1', type: 'paragraph', content: 'Paragraph 1' },
|
||||
{ id: 'block2', type: 'heading-2', content: 'Heading 2' },
|
||||
];
|
||||
element.renderBlocksProgrammatically();
|
||||
|
||||
await element.updateComplete;
|
||||
|
||||
const editorContent = element.shadowRoot!.querySelector('.editor-content') as HTMLDivElement;
|
||||
|
||||
// Start dragging first block
|
||||
const mockDragEvent = {
|
||||
dataTransfer: {
|
||||
effectAllowed: '',
|
||||
setData: (type: string, data: string) => {},
|
||||
setDragImage: (img: any, x: number, y: number) => {}
|
||||
},
|
||||
clientY: 50,
|
||||
preventDefault: () => {},
|
||||
} as any;
|
||||
|
||||
element.dragDropHandler.handleDragStart(mockDragEvent, element.blocks[0]);
|
||||
|
||||
// Wait for initialization
|
||||
await new Promise(resolve => setTimeout(resolve, 20));
|
||||
|
||||
// Get drop indicator
|
||||
const dropIndicator = editorContent.querySelector('.drop-indicator') as HTMLElement;
|
||||
expect(dropIndicator).toBeTruthy();
|
||||
|
||||
// Check initial display state
|
||||
console.log('Drop indicator initial display:', dropIndicator.style.display);
|
||||
|
||||
// Trigger updateBlockPositions to see drop indicator
|
||||
const updateBlockPositions = element.dragDropHandler['updateBlockPositions'].bind(element.dragDropHandler);
|
||||
updateBlockPositions(100);
|
||||
|
||||
// Check drop indicator position
|
||||
console.log('Drop indicator after update:');
|
||||
console.log('- display:', dropIndicator.style.display);
|
||||
console.log('- top:', dropIndicator.style.top);
|
||||
console.log('- height:', dropIndicator.style.height);
|
||||
|
||||
expect(dropIndicator.style.display).toEqual('block');
|
||||
expect(dropIndicator.style.top).toBeTruthy();
|
||||
expect(dropIndicator.style.height).toBeTruthy();
|
||||
|
||||
// Clean up
|
||||
element.dragDropHandler.handleDragEnd();
|
||||
document.body.removeChild(element);
|
||||
});
|
||||
|
||||
tap.start();
|
172
test/test.wysiwyg-dragdrop.browser.ts
Normal file
172
test/test.wysiwyg-dragdrop.browser.ts
Normal file
@ -0,0 +1,172 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
|
||||
|
||||
// Initialize the element
|
||||
DeesInputWysiwyg;
|
||||
|
||||
tap.test('wysiwyg drag and drop should work correctly', async () => {
|
||||
const element = document.createElement('dees-input-wysiwyg');
|
||||
document.body.appendChild(element);
|
||||
|
||||
// Wait for element to be ready
|
||||
await element.updateComplete;
|
||||
|
||||
// Set initial content with multiple blocks
|
||||
element.blocks = [
|
||||
{ id: 'block1', type: 'paragraph', content: 'First paragraph' },
|
||||
{ id: 'block2', type: 'heading-2', content: 'Test Heading' },
|
||||
{ id: 'block3', type: 'paragraph', content: 'Second paragraph' },
|
||||
];
|
||||
element.renderBlocksProgrammatically();
|
||||
|
||||
await element.updateComplete;
|
||||
|
||||
// Check that blocks are rendered
|
||||
const editorContent = element.shadowRoot!.querySelector('.editor-content') as HTMLDivElement;
|
||||
expect(editorContent).toBeTruthy();
|
||||
|
||||
const blockWrappers = editorContent.querySelectorAll('.block-wrapper');
|
||||
expect(blockWrappers.length).toEqual(3);
|
||||
|
||||
// Test drag handles exist for non-divider blocks
|
||||
const dragHandles = editorContent.querySelectorAll('.drag-handle');
|
||||
expect(dragHandles.length).toEqual(3);
|
||||
|
||||
// Get references to specific blocks
|
||||
const firstBlock = editorContent.querySelector('[data-block-id="block1"]') as HTMLElement;
|
||||
const secondBlock = editorContent.querySelector('[data-block-id="block2"]') as HTMLElement;
|
||||
const firstDragHandle = firstBlock.querySelector('.drag-handle') as HTMLElement;
|
||||
|
||||
expect(firstBlock).toBeTruthy();
|
||||
expect(secondBlock).toBeTruthy();
|
||||
expect(firstDragHandle).toBeTruthy();
|
||||
|
||||
// Test drag initialization
|
||||
console.log('Testing drag initialization...');
|
||||
|
||||
// Create drag event
|
||||
const dragStartEvent = new DragEvent('dragstart', {
|
||||
dataTransfer: new DataTransfer(),
|
||||
clientY: 100,
|
||||
bubbles: true
|
||||
});
|
||||
|
||||
// Simulate drag start
|
||||
firstDragHandle.dispatchEvent(dragStartEvent);
|
||||
|
||||
// Check that drag state is initialized
|
||||
expect(element.dragDropHandler.dragState.draggedBlockId).toEqual('block1');
|
||||
|
||||
// Check that dragging class is applied
|
||||
await new Promise(resolve => setTimeout(resolve, 20)); // Wait for setTimeout in drag start
|
||||
expect(firstBlock.classList.contains('dragging')).toBeTrue();
|
||||
expect(editorContent.classList.contains('dragging')).toBeTrue();
|
||||
|
||||
// Test drop indicator creation
|
||||
const dropIndicator = editorContent.querySelector('.drop-indicator');
|
||||
expect(dropIndicator).toBeTruthy();
|
||||
|
||||
// Simulate drag over
|
||||
const dragOverEvent = new DragEvent('dragover', {
|
||||
dataTransfer: new DataTransfer(),
|
||||
clientY: 200,
|
||||
bubbles: true,
|
||||
cancelable: true
|
||||
});
|
||||
|
||||
document.dispatchEvent(dragOverEvent);
|
||||
|
||||
// Check that blocks move out of the way
|
||||
console.log('Checking block movements...');
|
||||
const blocks = Array.from(editorContent.querySelectorAll('.block-wrapper'));
|
||||
const hasMovedBlocks = blocks.some(block =>
|
||||
block.classList.contains('move-up') || block.classList.contains('move-down')
|
||||
);
|
||||
|
||||
console.log('Blocks with move classes:', blocks.filter(block =>
|
||||
block.classList.contains('move-up') || block.classList.contains('move-down')
|
||||
).length);
|
||||
|
||||
// Test drag end
|
||||
const dragEndEvent = new DragEvent('dragend', {
|
||||
bubbles: true
|
||||
});
|
||||
|
||||
document.dispatchEvent(dragEndEvent);
|
||||
|
||||
// Wait for cleanup
|
||||
await new Promise(resolve => setTimeout(resolve, 150));
|
||||
|
||||
// Check that drag state is cleaned up
|
||||
expect(element.dragDropHandler.dragState.draggedBlockId).toBeNull();
|
||||
expect(firstBlock.classList.contains('dragging')).toBeFalse();
|
||||
expect(editorContent.classList.contains('dragging')).toBeFalse();
|
||||
|
||||
// Check that drop indicator is removed
|
||||
const dropIndicatorAfter = editorContent.querySelector('.drop-indicator');
|
||||
expect(dropIndicatorAfter).toBeFalsy();
|
||||
|
||||
// Clean up
|
||||
document.body.removeChild(element);
|
||||
});
|
||||
|
||||
tap.test('wysiwyg drag and drop visual feedback', async () => {
|
||||
const element = document.createElement('dees-input-wysiwyg');
|
||||
document.body.appendChild(element);
|
||||
|
||||
await element.updateComplete;
|
||||
|
||||
// Set initial content
|
||||
element.blocks = [
|
||||
{ id: 'block1', type: 'paragraph', content: 'Block 1' },
|
||||
{ id: 'block2', type: 'paragraph', content: 'Block 2' },
|
||||
{ id: 'block3', type: 'paragraph', content: 'Block 3' },
|
||||
];
|
||||
element.renderBlocksProgrammatically();
|
||||
|
||||
await element.updateComplete;
|
||||
|
||||
const editorContent = element.shadowRoot!.querySelector('.editor-content') as HTMLDivElement;
|
||||
const block1 = editorContent.querySelector('[data-block-id="block1"]') as HTMLElement;
|
||||
const dragHandle1 = block1.querySelector('.drag-handle') as HTMLElement;
|
||||
|
||||
// Start dragging block 1
|
||||
const dragStartEvent = new DragEvent('dragstart', {
|
||||
dataTransfer: new DataTransfer(),
|
||||
clientY: 50,
|
||||
bubbles: true
|
||||
});
|
||||
|
||||
dragHandle1.dispatchEvent(dragStartEvent);
|
||||
|
||||
// Wait for dragging class
|
||||
await new Promise(resolve => setTimeout(resolve, 20));
|
||||
|
||||
// Simulate dragging down
|
||||
const dragOverEvent = new DragEvent('dragover', {
|
||||
dataTransfer: new DataTransfer(),
|
||||
clientY: 150, // Move down past block 2
|
||||
bubbles: true,
|
||||
cancelable: true
|
||||
});
|
||||
|
||||
// Trigger the global drag over handler
|
||||
element.dragDropHandler['handleGlobalDragOver'](dragOverEvent);
|
||||
|
||||
// Check that transform is applied to dragged block
|
||||
const transform = block1.style.transform;
|
||||
console.log('Dragged block transform:', transform);
|
||||
expect(transform).toContain('translateY');
|
||||
|
||||
// Check drop indicator position
|
||||
const dropIndicator = editorContent.querySelector('.drop-indicator') as HTMLElement;
|
||||
if (dropIndicator) {
|
||||
const indicatorStyle = dropIndicator.style;
|
||||
console.log('Drop indicator position:', indicatorStyle.top, 'display:', indicatorStyle.display);
|
||||
}
|
||||
|
||||
// Clean up
|
||||
document.body.removeChild(element);
|
||||
});
|
||||
|
||||
tap.start();
|
124
test/test.wysiwyg-dragissue.browser.ts
Normal file
124
test/test.wysiwyg-dragissue.browser.ts
Normal file
@ -0,0 +1,124 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
|
||||
|
||||
// Initialize the element
|
||||
DeesInputWysiwyg;
|
||||
|
||||
tap.test('wysiwyg drag full flow without await', async () => {
|
||||
const element = document.createElement('dees-input-wysiwyg');
|
||||
document.body.appendChild(element);
|
||||
|
||||
await element.updateComplete;
|
||||
|
||||
// Set initial content
|
||||
element.blocks = [
|
||||
{ id: 'block1', type: 'paragraph', content: 'Test block' },
|
||||
];
|
||||
element.renderBlocksProgrammatically();
|
||||
|
||||
await element.updateComplete;
|
||||
|
||||
// Mock drag event
|
||||
const mockDragEvent = {
|
||||
dataTransfer: {
|
||||
effectAllowed: '',
|
||||
setData: (type: string, data: string) => {
|
||||
console.log('setData:', type, data);
|
||||
},
|
||||
setDragImage: (img: any, x: number, y: number) => {
|
||||
console.log('setDragImage');
|
||||
}
|
||||
},
|
||||
clientY: 100,
|
||||
preventDefault: () => {},
|
||||
} as any;
|
||||
|
||||
console.log('Starting drag...');
|
||||
element.dragDropHandler.handleDragStart(mockDragEvent, element.blocks[0]);
|
||||
console.log('Drag started');
|
||||
|
||||
// Check immediate state
|
||||
expect(element.dragDropHandler.dragState.draggedBlockId).toEqual('block1');
|
||||
|
||||
// Instead of await with setTimeout, use a done callback
|
||||
return new Promise<void>((resolve) => {
|
||||
console.log('Setting up delayed check...');
|
||||
|
||||
// Use regular setTimeout
|
||||
setTimeout(() => {
|
||||
console.log('In setTimeout callback');
|
||||
|
||||
try {
|
||||
const block1 = element.shadowRoot!.querySelector('[data-block-id="block1"]') as HTMLElement;
|
||||
const editorContent = element.shadowRoot!.querySelector('.editor-content') as HTMLDivElement;
|
||||
|
||||
console.log('Block has dragging class:', block1?.classList.contains('dragging'));
|
||||
console.log('Editor has dragging class:', editorContent?.classList.contains('dragging'));
|
||||
|
||||
// Clean up
|
||||
element.dragDropHandler.handleDragEnd();
|
||||
document.body.removeChild(element);
|
||||
|
||||
resolve();
|
||||
} catch (error) {
|
||||
console.error('Error in setTimeout:', error);
|
||||
throw error;
|
||||
}
|
||||
}, 50);
|
||||
});
|
||||
});
|
||||
|
||||
tap.test('identify the crash point', async () => {
|
||||
console.log('Test started');
|
||||
|
||||
const element = document.createElement('dees-input-wysiwyg');
|
||||
document.body.appendChild(element);
|
||||
|
||||
console.log('Element created');
|
||||
await element.updateComplete;
|
||||
|
||||
console.log('Setting blocks');
|
||||
element.blocks = [{ id: 'block1', type: 'paragraph', content: 'Test' }];
|
||||
element.renderBlocksProgrammatically();
|
||||
|
||||
console.log('Waiting for update');
|
||||
await element.updateComplete;
|
||||
|
||||
console.log('Creating mock event');
|
||||
const mockDragEvent = {
|
||||
dataTransfer: {
|
||||
effectAllowed: '',
|
||||
setData: () => {},
|
||||
setDragImage: () => {}
|
||||
},
|
||||
clientY: 100,
|
||||
preventDefault: () => {},
|
||||
} as any;
|
||||
|
||||
console.log('Calling handleDragStart');
|
||||
element.dragDropHandler.handleDragStart(mockDragEvent, element.blocks[0]);
|
||||
|
||||
console.log('handleDragStart completed');
|
||||
|
||||
// Try different wait methods
|
||||
console.log('About to wait...');
|
||||
|
||||
// Method 1: Direct promise
|
||||
await Promise.resolve();
|
||||
console.log('Promise.resolve completed');
|
||||
|
||||
// Method 2: setTimeout 0
|
||||
await new Promise(resolve => setTimeout(resolve, 0));
|
||||
console.log('setTimeout 0 completed');
|
||||
|
||||
// Method 3: requestAnimationFrame
|
||||
await new Promise(resolve => requestAnimationFrame(() => resolve(undefined)));
|
||||
console.log('requestAnimationFrame completed');
|
||||
|
||||
// Clean up
|
||||
element.dragDropHandler.handleDragEnd();
|
||||
document.body.removeChild(element);
|
||||
console.log('Cleanup completed');
|
||||
});
|
||||
|
||||
tap.start();
|
108
test/test.wysiwyg-dropindicator.browser.ts
Normal file
108
test/test.wysiwyg-dropindicator.browser.ts
Normal file
@ -0,0 +1,108 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
|
||||
|
||||
// Initialize the element
|
||||
DeesInputWysiwyg;
|
||||
|
||||
tap.test('wysiwyg drop indicator creation', async () => {
|
||||
const element = document.createElement('dees-input-wysiwyg');
|
||||
document.body.appendChild(element);
|
||||
|
||||
await element.updateComplete;
|
||||
|
||||
// Set initial content
|
||||
element.blocks = [
|
||||
{ id: 'block1', type: 'paragraph', content: 'Test block' },
|
||||
];
|
||||
element.renderBlocksProgrammatically();
|
||||
|
||||
await element.updateComplete;
|
||||
|
||||
// Check editorContentRef
|
||||
console.log('editorContentRef exists:', !!element.editorContentRef);
|
||||
console.log('editorContentRef tagName:', element.editorContentRef?.tagName);
|
||||
expect(element.editorContentRef).toBeTruthy();
|
||||
|
||||
// Check initial state - no drop indicator
|
||||
let dropIndicator = element.shadowRoot!.querySelector('.drop-indicator');
|
||||
console.log('Drop indicator before drag:', dropIndicator);
|
||||
expect(dropIndicator).toBeFalsy();
|
||||
|
||||
// Manually call createDropIndicator
|
||||
try {
|
||||
console.log('Calling createDropIndicator...');
|
||||
element.dragDropHandler['createDropIndicator']();
|
||||
console.log('createDropIndicator succeeded');
|
||||
} catch (error) {
|
||||
console.error('Error creating drop indicator:', error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Check drop indicator was created
|
||||
dropIndicator = element.shadowRoot!.querySelector('.drop-indicator');
|
||||
console.log('Drop indicator after creation:', dropIndicator);
|
||||
console.log('Drop indicator parent:', dropIndicator?.parentElement?.className);
|
||||
expect(dropIndicator).toBeTruthy();
|
||||
expect(dropIndicator!.style.display).toEqual('none');
|
||||
|
||||
// Clean up
|
||||
document.body.removeChild(element);
|
||||
});
|
||||
|
||||
tap.test('wysiwyg drag initialization with drop indicator', async () => {
|
||||
const element = document.createElement('dees-input-wysiwyg');
|
||||
document.body.appendChild(element);
|
||||
|
||||
await element.updateComplete;
|
||||
|
||||
// Set initial content
|
||||
element.blocks = [
|
||||
{ id: 'block1', type: 'paragraph', content: 'Test block' },
|
||||
];
|
||||
element.renderBlocksProgrammatically();
|
||||
|
||||
await element.updateComplete;
|
||||
|
||||
// Mock drag event
|
||||
const mockDragEvent = {
|
||||
dataTransfer: {
|
||||
effectAllowed: '',
|
||||
setData: (type: string, data: string) => {
|
||||
console.log('setData:', type, data);
|
||||
},
|
||||
setDragImage: (img: any, x: number, y: number) => {
|
||||
console.log('setDragImage');
|
||||
}
|
||||
},
|
||||
clientY: 100,
|
||||
preventDefault: () => {},
|
||||
} as any;
|
||||
|
||||
console.log('Starting drag...');
|
||||
|
||||
try {
|
||||
element.dragDropHandler.handleDragStart(mockDragEvent, element.blocks[0]);
|
||||
console.log('Drag start succeeded');
|
||||
} catch (error) {
|
||||
console.error('Error during drag start:', error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Wait for async operations
|
||||
await new Promise(resolve => setTimeout(resolve, 20));
|
||||
|
||||
// Check drop indicator exists
|
||||
const dropIndicator = element.shadowRoot!.querySelector('.drop-indicator');
|
||||
console.log('Drop indicator after drag start:', dropIndicator);
|
||||
expect(dropIndicator).toBeTruthy();
|
||||
|
||||
// Check drag state
|
||||
console.log('Drag state:', element.dragDropHandler.dragState);
|
||||
expect(element.dragDropHandler.dragState.draggedBlockId).toEqual('block1');
|
||||
|
||||
// Clean up
|
||||
element.dragDropHandler.handleDragEnd();
|
||||
document.body.removeChild(element);
|
||||
});
|
||||
|
||||
tap.start();
|
114
test/test.wysiwyg-eventlisteners.browser.ts
Normal file
114
test/test.wysiwyg-eventlisteners.browser.ts
Normal file
@ -0,0 +1,114 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
|
||||
|
||||
// Initialize the element
|
||||
DeesInputWysiwyg;
|
||||
|
||||
tap.test('wysiwyg global event listeners', async () => {
|
||||
const element = document.createElement('dees-input-wysiwyg');
|
||||
document.body.appendChild(element);
|
||||
|
||||
await element.updateComplete;
|
||||
|
||||
// Set initial content
|
||||
element.blocks = [
|
||||
{ id: 'block1', type: 'paragraph', content: 'Test block' },
|
||||
];
|
||||
element.renderBlocksProgrammatically();
|
||||
|
||||
await element.updateComplete;
|
||||
|
||||
const block1 = element.shadowRoot!.querySelector('[data-block-id="block1"]') as HTMLElement;
|
||||
console.log('Block 1 found:', !!block1);
|
||||
|
||||
// Set up drag state manually without using handleDragStart
|
||||
element.dragDropHandler['draggedBlockId'] = 'block1';
|
||||
element.dragDropHandler['draggedBlockElement'] = block1;
|
||||
element.dragDropHandler['initialMouseY'] = 100;
|
||||
|
||||
// Create drop indicator manually
|
||||
element.dragDropHandler['createDropIndicator']();
|
||||
|
||||
// Test adding global event listeners
|
||||
console.log('Adding event listeners...');
|
||||
const handleGlobalDragOver = element.dragDropHandler['handleGlobalDragOver'];
|
||||
const handleGlobalDragEnd = element.dragDropHandler['handleGlobalDragEnd'];
|
||||
|
||||
try {
|
||||
document.addEventListener('dragover', handleGlobalDragOver);
|
||||
console.log('dragover listener added');
|
||||
|
||||
document.addEventListener('dragend', handleGlobalDragEnd);
|
||||
console.log('dragend listener added');
|
||||
} catch (error) {
|
||||
console.error('Error adding event listeners:', error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Test firing a dragover event
|
||||
console.log('Creating dragover event...');
|
||||
const dragOverEvent = new Event('dragover', {
|
||||
bubbles: true,
|
||||
cancelable: true
|
||||
});
|
||||
Object.defineProperty(dragOverEvent, 'clientY', { value: 150 });
|
||||
|
||||
console.log('Dispatching dragover event...');
|
||||
document.dispatchEvent(dragOverEvent);
|
||||
console.log('dragover event dispatched');
|
||||
|
||||
// Clean up
|
||||
document.removeEventListener('dragover', handleGlobalDragOver);
|
||||
document.removeEventListener('dragend', handleGlobalDragEnd);
|
||||
|
||||
document.body.removeChild(element);
|
||||
});
|
||||
|
||||
tap.test('wysiwyg setTimeout in drag start', async () => {
|
||||
const element = document.createElement('dees-input-wysiwyg');
|
||||
document.body.appendChild(element);
|
||||
|
||||
await element.updateComplete;
|
||||
|
||||
// Set initial content
|
||||
element.blocks = [
|
||||
{ id: 'block1', type: 'paragraph', content: 'Test block' },
|
||||
];
|
||||
element.renderBlocksProgrammatically();
|
||||
|
||||
await element.updateComplete;
|
||||
|
||||
const block1 = element.shadowRoot!.querySelector('[data-block-id="block1"]') as HTMLElement;
|
||||
const editorContent = element.shadowRoot!.querySelector('.editor-content') as HTMLDivElement;
|
||||
|
||||
// Set drag state
|
||||
element.dragDropHandler['draggedBlockId'] = 'block1';
|
||||
element.dragDropHandler['draggedBlockElement'] = block1;
|
||||
|
||||
console.log('Testing setTimeout callback...');
|
||||
|
||||
// Test the setTimeout callback directly
|
||||
try {
|
||||
if (block1) {
|
||||
console.log('Adding dragging class to block...');
|
||||
block1.classList.add('dragging');
|
||||
console.log('Block classes:', block1.className);
|
||||
}
|
||||
if (editorContent) {
|
||||
console.log('Adding dragging class to editor...');
|
||||
editorContent.classList.add('dragging');
|
||||
console.log('Editor classes:', editorContent.className);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error in setTimeout callback:', error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
expect(block1.classList.contains('dragging')).toBeTrue();
|
||||
expect(editorContent.classList.contains('dragging')).toBeTrue();
|
||||
|
||||
// Clean up
|
||||
document.body.removeChild(element);
|
||||
});
|
||||
|
||||
tap.start();
|
@ -11,6 +11,8 @@ export class WysiwygDragDropHandler {
|
||||
private initialBlockY: number = 0;
|
||||
private draggedBlockElement: HTMLElement | null = null;
|
||||
private draggedBlockHeight: number = 0;
|
||||
private draggedBlockContentHeight: number = 0;
|
||||
private draggedBlockMarginTop: number = 0;
|
||||
private lastUpdateTime: number = 0;
|
||||
private updateThrottle: number = 80; // milliseconds
|
||||
|
||||
@ -48,11 +50,33 @@ export class WysiwygDragDropHandler {
|
||||
this.initialMouseY = e.clientY;
|
||||
this.draggedBlockElement = this.component.editorContentRef.querySelector(`[data-block-id="${block.id}"]`);
|
||||
|
||||
|
||||
if (this.draggedBlockElement) {
|
||||
// Get the wrapper rect for measurements
|
||||
const rect = this.draggedBlockElement.getBoundingClientRect();
|
||||
this.draggedBlockHeight = rect.height;
|
||||
this.initialBlockY = rect.top;
|
||||
|
||||
// Get the inner block element for proper measurements
|
||||
const innerBlock = this.draggedBlockElement.querySelector('.block');
|
||||
if (innerBlock) {
|
||||
const innerRect = innerBlock.getBoundingClientRect();
|
||||
const computedStyle = window.getComputedStyle(innerBlock);
|
||||
this.draggedBlockMarginTop = parseInt(computedStyle.marginTop) || 0;
|
||||
this.draggedBlockContentHeight = innerRect.height;
|
||||
}
|
||||
|
||||
// The drop indicator should match the wrapper height exactly
|
||||
// The wrapper already includes all the space the block occupies
|
||||
this.draggedBlockHeight = rect.height;
|
||||
|
||||
console.log('Drag measurements:', {
|
||||
wrapperHeight: rect.height,
|
||||
marginTop: this.draggedBlockMarginTop,
|
||||
dropIndicatorHeight: this.draggedBlockHeight,
|
||||
contentHeight: this.draggedBlockContentHeight,
|
||||
blockId: block.id
|
||||
});
|
||||
|
||||
// Create drop indicator
|
||||
this.createDropIndicator();
|
||||
|
||||
@ -98,6 +122,8 @@ export class WysiwygDragDropHandler {
|
||||
this.dragOverPosition = null;
|
||||
this.draggedBlockElement = null;
|
||||
this.draggedBlockHeight = 0;
|
||||
this.draggedBlockContentHeight = 0;
|
||||
this.draggedBlockMarginTop = 0;
|
||||
this.initialBlockY = 0;
|
||||
|
||||
// Update component state
|
||||
@ -284,34 +310,93 @@ export class WysiwygDragDropHandler {
|
||||
if (!this.dropIndicator || !this.draggedBlockElement) return;
|
||||
|
||||
this.dropIndicator.style.display = 'block';
|
||||
this.dropIndicator.style.height = `${this.draggedBlockHeight}px`;
|
||||
|
||||
const containerRect = this.component.editorContentRef.getBoundingClientRect();
|
||||
// Calculate where the block will actually land
|
||||
let topPosition = 0;
|
||||
|
||||
if (targetIndex === 0) {
|
||||
// Before first block
|
||||
topPosition = 0;
|
||||
} else {
|
||||
// After a specific block
|
||||
const prevIndex = targetIndex - 1;
|
||||
let blockCount = 0;
|
||||
// Build array of visual block positions (excluding dragged block)
|
||||
const visualBlocks: { index: number, top: number, bottom: number }[] = [];
|
||||
|
||||
for (let i = 0; i < blocks.length; i++) {
|
||||
if (i === draggedIndex) continue; // Skip the dragged block
|
||||
|
||||
// Find the visual position of the block that will be before our dropped block
|
||||
for (let i = 0; i < blocks.length; i++) {
|
||||
if (i === draggedIndex) continue; // Skip the dragged block
|
||||
|
||||
if (blockCount === prevIndex) {
|
||||
const rect = blocks[i].getBoundingClientRect();
|
||||
topPosition = rect.bottom - containerRect.top + 16; // 16px gap
|
||||
break;
|
||||
const block = blocks[i];
|
||||
const rect = block.getBoundingClientRect();
|
||||
let top = rect.top - containerRect.top;
|
||||
let bottom = rect.bottom - containerRect.top;
|
||||
|
||||
// Account for any transforms
|
||||
const transform = window.getComputedStyle(block).transform;
|
||||
if (transform && transform !== 'none') {
|
||||
const matrix = new DOMMatrix(transform);
|
||||
const yOffset = matrix.m42;
|
||||
top += yOffset;
|
||||
bottom += yOffset;
|
||||
}
|
||||
|
||||
visualBlocks.push({ index: i, top, bottom });
|
||||
}
|
||||
|
||||
// Sort by visual position
|
||||
visualBlocks.sort((a, b) => a.top - b.top);
|
||||
|
||||
// Adjust targetIndex to account for excluded dragged block
|
||||
let adjustedTargetIndex = targetIndex;
|
||||
if (targetIndex > draggedIndex) {
|
||||
adjustedTargetIndex--; // Reduce by 1 since dragged block is not in visualBlocks
|
||||
}
|
||||
|
||||
// Calculate drop position
|
||||
// Get the margin that will be applied based on the dragged block type
|
||||
let blockMargin = 16; // default margin
|
||||
if (this.draggedBlockElement) {
|
||||
const draggedBlock = this.component.blocks.find(b => b.id === this.draggedBlockId);
|
||||
if (draggedBlock) {
|
||||
const blockType = draggedBlock.type;
|
||||
if (blockType === 'heading-1' || blockType === 'heading-2' || blockType === 'heading-3') {
|
||||
blockMargin = 24;
|
||||
} else if (blockType === 'code' || blockType === 'quote') {
|
||||
blockMargin = 20;
|
||||
}
|
||||
blockCount++;
|
||||
}
|
||||
}
|
||||
|
||||
this.dropIndicator.style.top = `${topPosition}px`;
|
||||
if (adjustedTargetIndex === 0) {
|
||||
// Insert at the very top - no margin needed for first block
|
||||
topPosition = 0;
|
||||
} else if (adjustedTargetIndex >= visualBlocks.length) {
|
||||
// Insert at the end
|
||||
const lastBlock = visualBlocks[visualBlocks.length - 1];
|
||||
if (lastBlock) {
|
||||
topPosition = lastBlock.bottom;
|
||||
// Add margin that will be applied to the dropped block
|
||||
topPosition += blockMargin;
|
||||
}
|
||||
} else {
|
||||
// Insert between blocks
|
||||
const blockBefore = visualBlocks[adjustedTargetIndex - 1];
|
||||
if (blockBefore) {
|
||||
topPosition = blockBefore.bottom;
|
||||
// Add margin that will be applied to the dropped block
|
||||
topPosition += blockMargin;
|
||||
}
|
||||
}
|
||||
|
||||
// Set the indicator height to match the dragged block
|
||||
this.dropIndicator.style.height = `${this.draggedBlockHeight}px`;
|
||||
|
||||
// Set position
|
||||
this.dropIndicator.style.top = `${Math.max(0, topPosition)}px`;
|
||||
|
||||
console.log('Drop indicator update:', {
|
||||
targetIndex,
|
||||
adjustedTargetIndex,
|
||||
draggedIndex,
|
||||
topPosition,
|
||||
height: this.draggedBlockHeight,
|
||||
blockMargin,
|
||||
visualBlocks: visualBlocks.map(b => ({ index: b.index, top: b.top, bottom: b.bottom }))
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
Reference in New Issue
Block a user