fix: DockerSecretsResolver - don't normalize absolute paths like /var/www/html/...
Some checks failed
Deploy Application / deploy (push) Has been cancelled
Some checks failed
Deploy Application / deploy (push) Has been cancelled
This commit is contained in:
@@ -280,6 +280,214 @@ export class DomPatcher {
|
||||
// Fallback - not guaranteed to be unique
|
||||
return element.tagName.toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Swap element content using different strategies
|
||||
*
|
||||
* Supports multiple swap strategies similar to htmx:
|
||||
* - innerHTML: Replace inner content (default)
|
||||
* - outerHTML: Replace element itself
|
||||
* - beforebegin: Insert before element
|
||||
* - afterbegin: Insert at start of element
|
||||
* - afterend: Insert after element
|
||||
* - beforeend: Insert at end of element
|
||||
* - none: No DOM update (only events/state)
|
||||
*
|
||||
* @param {HTMLElement} target - Target element to swap
|
||||
* @param {string} html - HTML content to swap
|
||||
* @param {string} strategy - Swap strategy (default: 'innerHTML')
|
||||
* @param {string} transition - Optional transition type (fade, slide, none)
|
||||
* @param {Object} scrollOptions - Optional scroll options { enabled, target, behavior }
|
||||
* @returns {boolean} - True if swap was successful
|
||||
*/
|
||||
swapElement(target, html, strategy = 'innerHTML', transition = null, scrollOptions = null) {
|
||||
if (!target || !target.parentNode) {
|
||||
console.warn('[DomPatcher] Invalid target element for swap');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Preserve focus state before swap
|
||||
const restoreFocus = this.preserveFocus(target);
|
||||
|
||||
// Apply transition if specified
|
||||
if (transition && transition !== 'none') {
|
||||
this.applyTransition(target, transition, strategy);
|
||||
}
|
||||
|
||||
// Parse HTML into document fragment
|
||||
const temp = document.createElement('div');
|
||||
temp.innerHTML = html;
|
||||
const newContent = temp.firstElementChild || temp;
|
||||
|
||||
try {
|
||||
switch (strategy) {
|
||||
case 'innerHTML':
|
||||
// Replace inner content (default behavior)
|
||||
target.innerHTML = html;
|
||||
break;
|
||||
|
||||
case 'outerHTML':
|
||||
// Replace element itself
|
||||
if (newContent.nodeType === Node.ELEMENT_NODE) {
|
||||
target.parentNode.replaceChild(newContent.cloneNode(true), target);
|
||||
} else {
|
||||
// If HTML doesn't have a single root element, wrap it
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.innerHTML = html;
|
||||
while (wrapper.firstChild) {
|
||||
target.parentNode.insertBefore(wrapper.firstChild, target);
|
||||
}
|
||||
target.parentNode.removeChild(target);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'beforebegin':
|
||||
// Insert before target element
|
||||
if (newContent.nodeType === Node.ELEMENT_NODE) {
|
||||
target.parentNode.insertBefore(newContent.cloneNode(true), target);
|
||||
} else {
|
||||
// Multiple nodes - insert all
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.innerHTML = html;
|
||||
while (wrapper.firstChild) {
|
||||
target.parentNode.insertBefore(wrapper.firstChild, target);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'afterbegin':
|
||||
// Insert at start of target element
|
||||
if (newContent.nodeType === Node.ELEMENT_NODE) {
|
||||
target.insertBefore(newContent.cloneNode(true), target.firstChild);
|
||||
} else {
|
||||
// Multiple nodes - insert all at start
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.innerHTML = html;
|
||||
while (wrapper.firstChild) {
|
||||
target.insertBefore(wrapper.firstChild, target.firstChild);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'afterend':
|
||||
// Insert after target element
|
||||
if (newContent.nodeType === Node.ELEMENT_NODE) {
|
||||
target.parentNode.insertBefore(newContent.cloneNode(true), target.nextSibling);
|
||||
} else {
|
||||
// Multiple nodes - insert all after
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.innerHTML = html;
|
||||
const nextSibling = target.nextSibling;
|
||||
while (wrapper.firstChild) {
|
||||
target.parentNode.insertBefore(wrapper.firstChild, nextSibling);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'beforeend':
|
||||
// Insert at end of target element
|
||||
if (newContent.nodeType === Node.ELEMENT_NODE) {
|
||||
target.appendChild(newContent.cloneNode(true));
|
||||
} else {
|
||||
// Multiple nodes - append all
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.innerHTML = html;
|
||||
while (wrapper.firstChild) {
|
||||
target.appendChild(wrapper.firstChild);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'none':
|
||||
// No DOM update - only events/state will be handled
|
||||
// This is handled by the caller, so we just return success
|
||||
return true;
|
||||
|
||||
default:
|
||||
console.warn(`[DomPatcher] Unknown swap strategy: ${strategy}, falling back to innerHTML`);
|
||||
target.innerHTML = html;
|
||||
break;
|
||||
}
|
||||
|
||||
// Restore focus after swap (only for strategies that don't remove the target)
|
||||
if (strategy !== 'outerHTML' && strategy !== 'none') {
|
||||
restoreFocus();
|
||||
}
|
||||
|
||||
// Handle scroll behavior if specified
|
||||
if (scrollOptions && scrollOptions.enabled) {
|
||||
this.scrollToTarget(scrollOptions.target || target, scrollOptions.behavior || 'smooth');
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('[DomPatcher] Error during swap:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply CSS transition to element
|
||||
*
|
||||
* @param {HTMLElement} element - Element to apply transition to
|
||||
* @param {string} transition - Transition type (fade, slide)
|
||||
* @param {string} strategy - Swap strategy
|
||||
*/
|
||||
applyTransition(element, transition, strategy) {
|
||||
// Add transition class
|
||||
const transitionClass = `lc-transition-${transition}`;
|
||||
|
||||
// For fade/slide transitions, we need to apply the transition class
|
||||
// and then trigger the transition by adding an active class
|
||||
element.classList.add(transitionClass);
|
||||
|
||||
// Force reflow to ensure class is applied
|
||||
void element.offsetWidth;
|
||||
|
||||
// Add active class to trigger transition
|
||||
requestAnimationFrame(() => {
|
||||
element.classList.add('lc-transition-active');
|
||||
});
|
||||
|
||||
// Remove transition classes after animation completes
|
||||
const handleTransitionEnd = (e) => {
|
||||
// Only handle transition end for this element
|
||||
if (e.target === element) {
|
||||
element.classList.remove(transitionClass);
|
||||
element.classList.remove('lc-transition-active');
|
||||
element.removeEventListener('transitionend', handleTransitionEnd);
|
||||
}
|
||||
};
|
||||
|
||||
element.addEventListener('transitionend', handleTransitionEnd, { once: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll to target element
|
||||
*
|
||||
* @param {HTMLElement|string} target - Target element or selector
|
||||
* @param {string} behavior - Scroll behavior (smooth, instant)
|
||||
*/
|
||||
scrollToTarget(target, behavior = 'smooth') {
|
||||
let targetElement = target;
|
||||
|
||||
// If target is a string, try to find element
|
||||
if (typeof target === 'string') {
|
||||
targetElement = document.querySelector(target);
|
||||
}
|
||||
|
||||
if (!targetElement || !(targetElement instanceof HTMLElement)) {
|
||||
console.warn('[DomPatcher] Scroll target not found:', target);
|
||||
return;
|
||||
}
|
||||
|
||||
// Scroll to element
|
||||
targetElement.scrollIntoView({
|
||||
behavior: behavior === 'smooth' ? 'smooth' : 'auto',
|
||||
block: 'start',
|
||||
inline: 'nearest'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Create singleton instance
|
||||
|
||||
Reference in New Issue
Block a user