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.originalContent = null;
|
||||
this.originalStyles = null;
|
||||
this.resizeObserver = null;
|
||||
this.onHeightChangeCallback = null;
|
||||
}
|
||||
|
||||
schedulePreview(element, newValue, elementType) {
|
||||
@@ -33,6 +35,8 @@ class LivePreviewManager {
|
||||
|
||||
// Apply preview styling and content
|
||||
this.applyPreviewContent(element, newValue, elementType);
|
||||
|
||||
// ResizeObserver will automatically detect height changes
|
||||
}
|
||||
|
||||
extractOriginalContent(element, elementType) {
|
||||
@@ -101,6 +105,9 @@ class LivePreviewManager {
|
||||
this.previewTimeouts.delete(elementId);
|
||||
}
|
||||
|
||||
// Stop ResizeObserver
|
||||
this.stopResizeObserver();
|
||||
|
||||
// Restore original content
|
||||
if (this.originalContent && element === this.activeElement) {
|
||||
this.restoreOriginalContent(element);
|
||||
@@ -138,6 +145,36 @@ class LivePreviewManager {
|
||||
setActiveElement(element) {
|
||||
this.activeElement = element;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,6 +205,11 @@ export class InsertrFormRenderer {
|
||||
|
||||
// Initialize preview manager for this 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
|
||||
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) {
|
||||
const rect = element.getBoundingClientRect();
|
||||
@@ -398,6 +440,55 @@ export class InsertrFormRenderer {
|
||||
overlay.style.top = `${top}px`;
|
||||
overlay.style.left = `${left}px`;
|
||||
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