enhance: implement dynamic modal repositioning with ResizeObserver
- Add ResizeObserver to automatically detect element height changes during live preview - Implement repositionModal() to keep modal positioned below expanded content - Combine modal repositioning with scroll-to-fit for optimal visibility - Solve UX issue where modal would overlap newly added content
This commit is contained in:
@@ -7,6 +7,8 @@ class LivePreviewManager {
|
|||||||
this.activeElement = null;
|
this.activeElement = null;
|
||||||
this.originalContent = null;
|
this.originalContent = null;
|
||||||
this.originalStyles = null;
|
this.originalStyles = null;
|
||||||
|
this.resizeObserver = null;
|
||||||
|
this.onHeightChangeCallback = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
schedulePreview(element, newValue, elementType) {
|
schedulePreview(element, newValue, elementType) {
|
||||||
@@ -33,6 +35,8 @@ class LivePreviewManager {
|
|||||||
|
|
||||||
// Apply preview styling and content
|
// Apply preview styling and content
|
||||||
this.applyPreviewContent(element, newValue, elementType);
|
this.applyPreviewContent(element, newValue, elementType);
|
||||||
|
|
||||||
|
// ResizeObserver will automatically detect height changes
|
||||||
}
|
}
|
||||||
|
|
||||||
extractOriginalContent(element, elementType) {
|
extractOriginalContent(element, elementType) {
|
||||||
@@ -101,6 +105,9 @@ class LivePreviewManager {
|
|||||||
this.previewTimeouts.delete(elementId);
|
this.previewTimeouts.delete(elementId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stop ResizeObserver
|
||||||
|
this.stopResizeObserver();
|
||||||
|
|
||||||
// Restore original content
|
// Restore original content
|
||||||
if (this.originalContent && element === this.activeElement) {
|
if (this.originalContent && element === this.activeElement) {
|
||||||
this.restoreOriginalContent(element);
|
this.restoreOriginalContent(element);
|
||||||
@@ -138,6 +145,36 @@ class LivePreviewManager {
|
|||||||
setActiveElement(element) {
|
setActiveElement(element) {
|
||||||
this.activeElement = element;
|
this.activeElement = element;
|
||||||
this.originalContent = null;
|
this.originalContent = null;
|
||||||
|
this.startResizeObserver(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
setHeightChangeCallback(callback) {
|
||||||
|
this.onHeightChangeCallback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
startResizeObserver(element) {
|
||||||
|
// Clean up existing observer
|
||||||
|
this.stopResizeObserver();
|
||||||
|
|
||||||
|
// Create new ResizeObserver for this element
|
||||||
|
this.resizeObserver = new ResizeObserver(entries => {
|
||||||
|
// Use requestAnimationFrame to ensure smooth updates
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
if (this.onHeightChangeCallback && element === this.activeElement) {
|
||||||
|
this.onHeightChangeCallback(element);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start observing the element
|
||||||
|
this.resizeObserver.observe(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
stopResizeObserver() {
|
||||||
|
if (this.resizeObserver) {
|
||||||
|
this.resizeObserver.disconnect();
|
||||||
|
this.resizeObserver = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,6 +206,11 @@ export class InsertrFormRenderer {
|
|||||||
// Initialize preview manager for this element
|
// Initialize preview manager for this element
|
||||||
this.previewManager.setActiveElement(element);
|
this.previewManager.setActiveElement(element);
|
||||||
|
|
||||||
|
// Set up height change callback to reposition modal based on new element size
|
||||||
|
this.previewManager.setHeightChangeCallback((changedElement) => {
|
||||||
|
this.repositionModal(changedElement, overlay);
|
||||||
|
});
|
||||||
|
|
||||||
// Create form
|
// Create form
|
||||||
const form = this.createEditForm(contentId, config, currentContent);
|
const form = this.createEditForm(contentId, config, currentContent);
|
||||||
|
|
||||||
@@ -357,7 +399,7 @@ export class InsertrFormRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Position form relative to element
|
* Position form relative to element and ensure visibility with scroll-to-fit
|
||||||
*/
|
*/
|
||||||
positionForm(element, overlay) {
|
positionForm(element, overlay) {
|
||||||
const rect = element.getBoundingClientRect();
|
const rect = element.getBoundingClientRect();
|
||||||
@@ -398,6 +440,55 @@ export class InsertrFormRenderer {
|
|||||||
overlay.style.top = `${top}px`;
|
overlay.style.top = `${top}px`;
|
||||||
overlay.style.left = `${left}px`;
|
overlay.style.left = `${left}px`;
|
||||||
overlay.style.zIndex = '10000';
|
overlay.style.zIndex = '10000';
|
||||||
|
|
||||||
|
// Ensure modal is fully visible after positioning
|
||||||
|
this.ensureModalVisible(element, overlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reposition modal based on current element size and ensure visibility
|
||||||
|
*/
|
||||||
|
repositionModal(element, overlay) {
|
||||||
|
// Wait for next frame to ensure DOM is updated
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
const rect = element.getBoundingClientRect();
|
||||||
|
const form = overlay.querySelector('.insertr-edit-form');
|
||||||
|
|
||||||
|
// Calculate new position below the current element boundaries
|
||||||
|
const newTop = rect.bottom + window.scrollY + 10;
|
||||||
|
|
||||||
|
// Update modal position
|
||||||
|
overlay.style.top = `${newTop}px`;
|
||||||
|
|
||||||
|
// After repositioning, ensure modal is still visible
|
||||||
|
this.ensureModalVisible(element, overlay);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure modal is fully visible by scrolling viewport if necessary
|
||||||
|
*/
|
||||||
|
ensureModalVisible(element, overlay) {
|
||||||
|
// Wait for next frame to ensure DOM is updated
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
const modal = overlay.querySelector('.insertr-edit-form');
|
||||||
|
const modalRect = modal.getBoundingClientRect();
|
||||||
|
const viewportHeight = window.innerHeight;
|
||||||
|
|
||||||
|
// Calculate if modal extends below viewport
|
||||||
|
const modalBottom = modalRect.bottom;
|
||||||
|
const viewportBottom = viewportHeight;
|
||||||
|
|
||||||
|
if (modalBottom > viewportBottom) {
|
||||||
|
// Calculate scroll amount needed with some padding
|
||||||
|
const scrollAmount = modalBottom - viewportBottom + 20;
|
||||||
|
|
||||||
|
window.scrollBy({
|
||||||
|
top: scrollAmount,
|
||||||
|
behavior: 'smooth'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user