fix: DockerSecretsResolver - don't normalize absolute paths like /var/www/html/...
Some checks failed
Deploy Application / deploy (push) Has been cancelled

This commit is contained in:
2025-11-24 21:28:25 +01:00
parent 4eb7134853
commit 77abc65cd7
1327 changed files with 91915 additions and 9909 deletions

View File

@@ -0,0 +1,315 @@
/**
* Drawer Manager for LiveComponents
*
* Manages drawer/sidebar components with:
* - Slide-in animation (CSS Transitions)
* - Overlay backdrop management
* - ESC key handling
* - Focus management (focus trapping)
* - Stack management for multiple drawers
*/
export class DrawerManager {
constructor() {
this.drawerStack = []; // Array of { componentId, drawerElement, overlayElement, zIndex, closeOnEscape, closeOnOverlay }
this.baseZIndex = 1040;
this.zIndexIncrement = 10;
this.escapeHandler = null;
}
/**
* Open drawer and add to stack
* @param {string} componentId - Component ID
* @param {Object} options - Drawer options
* @returns {Object} Drawer instance
*/
open(componentId, options = {}) {
const {
title = '',
content = '',
position = 'left',
width = '400px',
showOverlay = true,
closeOnOverlay = true,
closeOnEscape = true,
animation = 'slide'
} = options;
// Calculate z-index
const zIndex = this.baseZIndex + (this.drawerStack.length * this.zIndexIncrement);
// Create overlay if needed
let overlay = null;
if (showOverlay) {
overlay = this.createOverlay(zIndex, closeOnOverlay, componentId);
document.body.appendChild(overlay);
}
// Create drawer element
const drawer = this.createDrawerElement(componentId, title, content, position, width, animation, zIndex + 1);
document.body.appendChild(drawer);
// Track in stack
const stackItem = {
componentId,
drawerElement: drawer,
overlayElement: overlay,
zIndex: zIndex + 1,
closeOnEscape,
closeOnOverlay
};
this.drawerStack.push(stackItem);
// Setup event handlers
this.setupDrawerHandlers(drawer, overlay, componentId, closeOnOverlay, closeOnEscape);
// Update ESC handler
this.updateEscapeHandler();
// Animate in
requestAnimationFrame(() => {
if (overlay) {
overlay.classList.add('drawer-overlay--show');
}
drawer.classList.add('drawer--show');
});
// Focus management
this.focusDrawer(drawer);
return {
drawer: drawer,
overlay: overlay,
close: () => this.close(componentId),
isOpen: () => drawer.classList.contains('drawer--show')
};
}
/**
* Close drawer and remove from stack
* @param {string} componentId - Component ID
*/
close(componentId) {
const index = this.drawerStack.findIndex(item => item.componentId === componentId);
if (index === -1) {
return;
}
const { drawerElement, overlayElement } = this.drawerStack[index];
// Animate out
drawerElement.classList.remove('drawer--show');
if (overlayElement) {
overlayElement.classList.remove('drawer-overlay--show');
}
// Remove from DOM after animation
setTimeout(() => {
if (drawerElement.parentNode) {
drawerElement.parentNode.removeChild(drawerElement);
}
if (overlayElement && overlayElement.parentNode) {
overlayElement.parentNode.removeChild(overlayElement);
}
}, 300); // Match CSS transition duration
// Remove from stack
this.drawerStack.splice(index, 1);
// Update ESC handler
this.updateEscapeHandler();
// Focus previous drawer or return focus to body
if (this.drawerStack.length > 0) {
const previousDrawer = this.drawerStack[this.drawerStack.length - 1];
this.focusDrawer(previousDrawer.drawerElement);
} else {
// Return focus to previously focused element
const activeElement = document.activeElement;
if (activeElement && activeElement !== document.body) {
activeElement.blur();
}
document.body.focus();
}
}
/**
* Close all drawers
*/
closeAll() {
while (this.drawerStack.length > 0) {
const { componentId } = this.drawerStack[this.drawerStack.length - 1];
this.close(componentId);
}
}
/**
* Get topmost drawer
* @returns {Object|null}
*/
getTopDrawer() {
if (this.drawerStack.length === 0) {
return null;
}
return this.drawerStack[this.drawerStack.length - 1];
}
/**
* Check if drawer is open
* @param {string} componentId - Component ID
* @returns {boolean}
*/
isOpen(componentId) {
return this.drawerStack.some(item => item.componentId === componentId);
}
/**
* Create overlay element
* @param {number} zIndex - Z-index value
* @param {boolean} closeOnOverlay - Whether to close on overlay click
* @param {string} componentId - Component ID
* @returns {HTMLElement}
*/
createOverlay(zIndex, closeOnOverlay, componentId) {
const overlay = document.createElement('div');
overlay.className = 'drawer-overlay';
overlay.style.zIndex = zIndex.toString();
overlay.setAttribute('role', 'presentation');
overlay.setAttribute('aria-hidden', 'true');
if (closeOnOverlay) {
overlay.addEventListener('click', () => {
this.close(componentId);
});
}
return overlay;
}
/**
* Create drawer element
* @param {string} componentId - Component ID
* @param {string} title - Drawer title
* @param {string} content - Drawer content
* @param {string} position - Position (left/right)
* @param {string} width - Drawer width
* @param {string} animation - Animation type
* @param {number} zIndex - Z-index value
* @returns {HTMLElement}
*/
createDrawerElement(componentId, title, content, position, width, animation, zIndex) {
const drawer = document.createElement('div');
drawer.className = `drawer drawer--${position} drawer-animation-${animation}`;
drawer.id = `drawer-${componentId}`;
drawer.style.zIndex = zIndex.toString();
drawer.style.width = width;
drawer.setAttribute('role', 'dialog');
drawer.setAttribute('aria-modal', 'true');
drawer.setAttribute('aria-labelledby', title ? `drawer-title-${componentId}` : '');
drawer.innerHTML = `
<div class="drawer-content">
${title ? `
<div class="drawer-header">
<h3 class="drawer-title" id="drawer-title-${componentId}">${title}</h3>
<button class="drawer-close" aria-label="Close drawer">×</button>
</div>
` : ''}
<div class="drawer-body">${content}</div>
</div>
`;
return drawer;
}
/**
* Setup event handlers for drawer
* @param {HTMLElement} drawer - Drawer element
* @param {HTMLElement|null} overlay - Overlay element
* @param {string} componentId - Component ID
* @param {boolean} closeOnOverlay - Whether to close on overlay click
* @param {boolean} closeOnEscape - Whether to close on ESC key
*/
setupDrawerHandlers(drawer, overlay, componentId, closeOnOverlay, closeOnEscape) {
// Close button
const closeBtn = drawer.querySelector('.drawer-close');
if (closeBtn) {
closeBtn.addEventListener('click', () => {
this.close(componentId);
});
}
// Overlay click (already handled in createOverlay, but ensure it's set)
if (overlay && closeOnOverlay) {
// Already set in createOverlay
}
}
/**
* Update ESC key handler
*/
updateEscapeHandler() {
// Remove existing handler
if (this.escapeHandler) {
document.removeEventListener('keydown', this.escapeHandler);
this.escapeHandler = null;
}
// Add handler if there are drawers
if (this.drawerStack.length > 0) {
const topDrawer = this.getTopDrawer();
if (topDrawer && topDrawer.closeOnEscape) {
this.escapeHandler = (e) => {
if (e.key === 'Escape' && !e.defaultPrevented) {
e.preventDefault();
this.close(topDrawer.componentId);
}
};
document.addEventListener('keydown', this.escapeHandler);
}
}
}
/**
* Focus drawer element
* @param {HTMLElement} drawer - Drawer element
*/
focusDrawer(drawer) {
if (!drawer) {
return;
}
// Find first focusable element
const focusableSelectors = [
'button:not([disabled])',
'[href]',
'input:not([disabled])',
'select:not([disabled])',
'textarea:not([disabled])',
'[tabindex]:not([tabindex="-1"])'
].join(', ');
const focusableElement = drawer.querySelector(focusableSelectors);
if (focusableElement) {
requestAnimationFrame(() => {
focusableElement.focus();
});
} else {
requestAnimationFrame(() => {
drawer.focus();
});
}
}
/**
* Cleanup all drawers
*/
destroy() {
this.closeAll();
if (this.escapeHandler) {
document.removeEventListener('keydown', this.escapeHandler);
this.escapeHandler = null;
}
}
}