This commit is contained in:
Matyas Gosztonyi 2026-08-04 14:15:59 +02:00 committed by GitHub
commit e3a27344ba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 407 additions and 59 deletions

View file

@ -23,6 +23,7 @@ const MIN_CHAT_WIDTH = 380;
const EMAIL_DOC_SPLIT_WIDTH_KEY = 'odysseus-email-doc-split-width'; const EMAIL_DOC_SPLIT_WIDTH_KEY = 'odysseus-email-doc-split-width';
const EDGE_DOCK_WIDTH_KEY_PREFIX = 'odysseus-edge-dock-width'; const EDGE_DOCK_WIDTH_KEY_PREFIX = 'odysseus-edge-dock-width';
const MIN_EDGE_DOCK_WIDTH = 320; const MIN_EDGE_DOCK_WIDTH = 320;
const EDGE_DOCK_SWITCHER_FLAG = 'odysseus-edge-dock-switcher';
let _edgeDockHandlePositioner = null; let _edgeDockHandlePositioner = null;
@ -30,10 +31,111 @@ function _positionEdgeDockResizeHandles() {
try { _edgeDockHandlePositioner && _edgeDockHandlePositioner(); } catch (_) {} try { _edgeDockHandlePositioner && _edgeDockHandlePositioner(); } catch (_) {}
} }
function _notifyEdgeDockChanged(side, owner = null) {
try {
window.dispatchEvent(new CustomEvent('odysseus:edge-dock-changed', {
detail: { side, owner },
}));
} catch (_) {}
}
function _dockClassForSide(side) { function _dockClassForSide(side) {
return side === 'left' ? 'modal-left-docked' : 'modal-right-docked'; return side === 'left' ? 'modal-left-docked' : 'modal-right-docked';
} }
function _dockOwnerSurface(owner) {
return owner?.closest?.('.notes-pane-backdrop') || owner;
}
function _zIndexForElement(el, fallback = 250) {
const raw = el ? window.getComputedStyle(el).zIndex : '';
const n = parseInt(raw, 10);
return Number.isFinite(n) ? n : fallback;
}
function _isUsableDockOwner(owner) {
if (!owner || !owner.isConnected) return false;
if (owner.classList?.contains('hidden')) return false;
if (owner.classList?.contains('modal-minimized')) return false;
if (owner.style?.display === 'none') return false;
const content = _resolveDockNodes(owner)?.content;
if (!content || !content.isConnected) return false;
if (content.classList?.contains('hidden')) return false;
if (content.style?.display === 'none') return false;
const r = content.getBoundingClientRect?.();
return !!r && r.width > 0 && r.height > 0;
}
function _dockOwnersForSide(side) {
if (side !== 'left' && side !== 'right') return [];
return Array.from(document.querySelectorAll(`.${_dockClassForSide(side)}`)).filter(_isUsableDockOwner);
}
function _activeDockOwnerForSide(side) {
let best = null;
let bestZ = -Infinity;
_dockOwnersForSide(side).forEach((owner) => {
const z = _zIndexForElement(_dockOwnerSurface(owner), 250);
if (z >= bestZ) {
best = owner;
bestZ = z;
}
});
return best;
}
function _applyDockWidthToOwner(owner, side, width, left = _leftNavRight()) {
const content = _resolveDockNodes(owner)?.content;
if (!content || !width) return;
const w = Math.round(width);
content._userDockWidth = w;
if (side === 'right') {
content.style.left = 'auto';
content.style.right = '0';
content.style.width = w + 'px';
content.style.maxWidth = w + 'px';
if (_shouldAutoCollapseSidebar(w)) {
_collapseSidebarToRail();
if (content._preDockSnapshot) content._preDockSnapshot.collapsedSidebar = true;
}
} else {
content._emailDocSplitUserW = w;
content.style.left = left + 'px';
content.style.right = 'auto';
content.style.width = w + 'px';
content.style.maxWidth = w + 'px';
}
}
function _syncDockSideWidth(side, requestedWidth, owners = _dockOwnersForSide(side)) {
if (side !== 'left' && side !== 'right') return 0;
if (!owners.length) return 0;
if (side === 'left'
&& document.body.classList.contains('email-doc-split-active')
&& document.body.classList.contains('doc-view')) {
_positionEdgeDockResizeHandles();
return requestedWidth || _activeDockWidth(side);
}
let w = 0;
if (side === 'right') {
w = _clampRightDockWidth(requestedWidth || _activeDockWidth('right') || _defaultDockWidth());
document.body.classList.add('right-dock-active');
document.documentElement.style.setProperty('--right-dock-w', w + 'px');
owners.forEach((owner) => _applyDockWidthToOwner(owner, side, w));
} else {
const left = _leftNavRight();
w = _clampLeftDockWidth(requestedWidth || _activeDockWidth('left') || _defaultDockWidth(), left);
document.body.classList.add('left-dock-active');
document.documentElement.style.setProperty(
'--left-dock-w',
document.body.classList.contains('email-doc-split-active') ? '0px' : w + 'px',
);
owners.forEach((owner) => _applyDockWidthToOwner(owner, side, w, left));
}
_positionEdgeDockResizeHandles();
return w;
}
function _hasOtherDockedWindow(side, owner) { function _hasOtherDockedWindow(side, owner) {
const cls = _dockClassForSide(side); const cls = _dockClassForSide(side);
return Array.from(document.querySelectorAll(`.${cls}`)).some((el) => { return Array.from(document.querySelectorAll(`.${cls}`)).some((el) => {
@ -347,6 +449,7 @@ function _applyDockInternal(modal, side, dockClass) {
if (!nodes) return 0; if (!nodes) return 0;
const content = nodes.content; const content = nodes.content;
if (!content) return 0; if (!content) return 0;
const existingSideWidth = _hasOtherDockedWindow(side, modal) ? _activeDockWidth(side) : 0;
// If the modal is currently docked on the OTHER side (e.g. the user // If the modal is currently docked on the OTHER side (e.g. the user
// manually docked it right, then a reply re-docks it left), clear that // manually docked it right, then a reply re-docks it left), clear that
// side's class + body push first. Otherwise both sides' state coexist — // side's class + body push first. Otherwise both sides' state coexist —
@ -509,7 +612,13 @@ function _applyDockInternal(modal, side, dockClass) {
} }
content._dockSide = side; content._dockSide = side;
content._dockOwner = modal; content._dockOwner = modal;
_positionEdgeDockResizeHandles(); const sideOwners = _dockOwnersForSide(side);
if (sideOwners.length > 1) {
const syncedWidth = _syncDockSideWidth(side, existingSideWidth || w, sideOwners);
if (syncedWidth) w = syncedWidth;
} else {
_positionEdgeDockResizeHandles();
}
// Watch for the docked modal disappearing (removed from DOM or hidden // Watch for the docked modal disappearing (removed from DOM or hidden
// via .hidden class) and clean up the body padding + sidebar in that // via .hidden class) and clean up the body padding + sidebar in that
// case. Without this, closing a docked window leaves a phantom strip // case. Without this, closing a docked window leaves a phantom strip
@ -539,6 +648,7 @@ function _applyDockInternal(modal, side, dockClass) {
modal._dockCloseWatcher = { obs }; modal._dockCloseWatcher = { obs };
} }
} }
_notifyEdgeDockChanged(side, modal);
return w; return w;
} }
@ -571,6 +681,8 @@ function _onDockedModalGone(modal, dockClass) {
} }
modal.classList.remove('modal-right-docked'); modal.classList.remove('modal-right-docked');
modal.classList.remove('modal-left-docked'); modal.classList.remove('modal-left-docked');
if (hadRight) _notifyEdgeDockChanged('right', modal);
if (hadLeft) _notifyEdgeDockChanged('left', modal);
// Clear the content's docked inline geometry. Singleton modals (plan, // Clear the content's docked inline geometry. Singleton modals (plan,
// workspace, calendar, …) reuse the same element across open/close, so if we // workspace, calendar, …) reuse the same element across open/close, so if we
// only drop the body push the element stays positioned (position:fixed; // only drop the body push the element stays positioned (position:fixed;
@ -666,6 +778,7 @@ export function clearRightDock(modal, cx, cy, dockClass) {
delete content._preDockSnapshot; delete content._preDockSnapshot;
delete content._dockSuspended; delete content._dockSuspended;
_positionEdgeDockResizeHandles(); _positionEdgeDockResizeHandles();
_notifyEdgeDockChanged(side, modal);
} }
// Temporarily release a docked modal's body push (chat returns to full // Temporarily release a docked modal's body push (chat returns to full
@ -711,6 +824,7 @@ export function suspendDock(modal) {
} }
content._dockSuspended = side; content._dockSuspended = side;
_positionEdgeDockResizeHandles(); _positionEdgeDockResizeHandles();
_notifyEdgeDockChanged(side, modal);
return side; return side;
} }
@ -785,6 +899,200 @@ export function makeEdgeDockController(modal, side = 'right', dockClass) {
}; };
} }
(function _initEdgeDockSwitcher() {
if (typeof document === 'undefined' || typeof window === 'undefined') return;
const bars = { left: null, right: null };
let refreshTimer = 0;
let refreshInterval = 0;
const _enabled = () => {
try { return localStorage.getItem(EDGE_DOCK_SWITCHER_FLAG) !== '0'; }
catch (_) { return true; }
};
const _ownerSurface = _dockOwnerSurface;
const _topToolZ = (exclude = null) => {
const skip = new Set([exclude, _ownerSurface(exclude)].filter(Boolean));
let top = 300;
document.querySelectorAll('.modal, .research-overlay, .notes-pane-backdrop').forEach((el) => {
if (!el || skip.has(el)) return;
if (el.classList?.contains('hidden')) return;
if (el.style?.display === 'none') return;
top = Math.max(top, _zIndexForElement(el, 250));
});
return top;
};
const _bringOwnerToFront = (owner) => {
const surface = _ownerSurface(owner);
if (!surface) return;
surface.style.setProperty('z-index', String(_topToolZ(owner) + 1), 'important');
try {
window.dispatchEvent(new CustomEvent('odysseus:modal-opened', {
detail: { id: owner?.id || '', modal: owner },
}));
} catch (_) {}
};
const _simpleTitleForOwner = (owner) => {
const id = owner?.id || '';
const labels = {
'assistant-settings-modal': 'Assistant',
'calendar-modal': 'Calendar',
'compare-model-overlay': 'Compare',
'cookbook-modal': 'Cookbook',
'doc-editor-pane': 'Document',
'doclib-modal': 'Library',
'email-lib-modal': 'Email',
'gallery-modal': 'Gallery',
'library-modal': 'Library',
'notes-pane': 'Notes',
'research-overlay': 'Deep Research',
'research-pane': 'Deep Research',
'settings-modal': 'Settings',
'tasks-modal': 'Tasks',
};
return labels[id] || '';
};
const _cleanTabLabel = (label) => (label || '')
.replace(/\s+/g, ' ')
.replace(/\s+\d+\s+(photos?|research|tasks?|notes?|documents?|docs?|items?|emails?|chats?|sessions?|models?)$/i, '')
.trim();
const _labelForOwner = (owner) => {
const simple = _simpleTitleForOwner(owner);
if (simple) return simple;
const content = _resolveDockNodes(owner)?.content || owner;
const titleEl = content?.querySelector?.('.modal-header h4, .notes-pane-title, .modal-title, [data-window-title]');
const text = _cleanTabLabel(titleEl?.textContent || owner?.getAttribute?.('aria-label') || owner?.id || 'Window');
return text || 'Window';
};
const _activeOwner = (owners) => {
let best = null;
let bestZ = -Infinity;
owners.forEach((owner) => {
const z = _zIndexForElement(_ownerSurface(owner), 250);
if (z >= bestZ) {
best = owner;
bestZ = z;
}
});
return best || owners[owners.length - 1] || null;
};
const _hideBar = (side) => {
const bar = bars[side];
if (!bar) return;
bar.replaceChildren();
bar.hidden = true;
bar.parentElement?.classList?.remove('edge-dock-switcher-host');
};
const _ensureBar = (side) => {
if (!bars[side]?.isConnected) {
const bar = document.createElement('div');
bar.className = `edge-dock-switcher edge-dock-switcher-${side}`;
bar.hidden = true;
bar.setAttribute('role', 'tablist');
bar.setAttribute('aria-label', `${side} docked windows`);
bar.addEventListener('pointerdown', (event) => event.stopPropagation());
bar.addEventListener('mousedown', (event) => event.stopPropagation());
bar.addEventListener('touchstart', (event) => event.stopPropagation(), { passive: true });
bars[side] = bar;
}
const oldParent = bars[side]?.parentElement;
if (oldParent && oldParent !== document.body) oldParent.classList.remove('edge-dock-switcher-host');
if (bars[side].parentElement !== document.body) document.body.appendChild(bars[side]);
return bars[side];
};
const _renderSide = (side) => {
const owners = _enabled() && window.innerWidth > 768 ? _dockOwnersForSide(side) : [];
if (owners.length < 2) {
_hideBar(side);
return;
}
const active = _activeOwner(owners);
const bar = _ensureBar(side);
bar.replaceChildren();
owners.forEach((owner) => {
const label = _labelForOwner(owner);
const btn = document.createElement('button');
btn.type = 'button';
btn.className = `edge-dock-switcher-tab${owner === active ? ' active' : ''}`;
btn.title = label;
btn.setAttribute('role', 'tab');
btn.setAttribute('aria-selected', owner === active ? 'true' : 'false');
btn.addEventListener('pointerdown', (event) => event.stopPropagation());
btn.addEventListener('click', (event) => {
event.preventDefault();
event.stopPropagation();
_bringOwnerToFront(owner);
_scheduleRefresh(40);
});
const labelEl = document.createElement('span');
labelEl.className = 'edge-dock-switcher-tab-label';
labelEl.textContent = label;
btn.appendChild(labelEl);
bar.appendChild(btn);
});
bar.hidden = false;
};
function _refresh() {
if (!document.body) return;
_renderSide('left');
_renderSide('right');
}
function _scheduleRefresh(delay = 180) {
if (refreshTimer) clearTimeout(refreshTimer);
refreshTimer = setTimeout(() => {
refreshTimer = 0;
requestAnimationFrame(_refresh);
}, delay);
}
const _start = () => {
_scheduleRefresh(0);
window.addEventListener('resize', () => _scheduleRefresh(120));
window.addEventListener('odysseus:modal-opened', () => _scheduleRefresh(120));
window.addEventListener('odysseus:edge-dock-changed', () => _scheduleRefresh(320));
document.addEventListener('keyup', (event) => {
if (event.key === 'Escape') _scheduleRefresh(120);
}, true);
refreshInterval = window.setInterval(() => {
if (document.visibilityState !== 'hidden') _refresh();
}, 1500);
window.odysseusEdgeDockSwitcher = {
refresh: _refresh,
disable() {
try { localStorage.setItem(EDGE_DOCK_SWITCHER_FLAG, '0'); } catch (_) {}
_refresh();
},
enable() {
try { localStorage.removeItem(EDGE_DOCK_SWITCHER_FLAG); } catch (_) {}
_refresh();
},
stop() {
if (refreshInterval) window.clearInterval(refreshInterval);
refreshInterval = 0;
bars.left?.remove();
bars.right?.remove();
bars.left = null;
bars.right = null;
},
};
};
if (document.body) _start();
else document.addEventListener('DOMContentLoaded', _start, { once: true });
})();
(function _initEdgeDockResizeHandles() { (function _initEdgeDockResizeHandles() {
if (typeof document === 'undefined') return; if (typeof document === 'undefined') return;
if (!document.body) { if (!document.body) {
@ -820,33 +1128,7 @@ export function makeEdgeDockController(modal, side = 'right', dockClass) {
document.body.appendChild(handle); document.body.appendChild(handle);
} }
const _isUsableDockOwner = (owner) => { const _activeDockOwner = (side) => _activeDockOwnerForSide(side);
if (!owner || !owner.isConnected) return false;
if (owner.classList?.contains('hidden')) return false;
if (owner.style?.display === 'none') return false;
const nodes = _resolveDockNodes(owner);
const content = nodes?.content;
if (!content || !content.isConnected) return false;
if (content.classList?.contains('hidden')) return false;
if (content.style?.display === 'none') return false;
const r = content.getBoundingClientRect();
return r.width > 0 && r.height > 0;
};
const _activeDockOwner = (side) => {
const cls = _dockClassForSide(side);
const all = Array.from(document.querySelectorAll(`.${cls}`));
for (const owner of all.reverse()) {
if (_isUsableDockOwner(owner)) return owner;
}
return null;
};
const _zIndexFor = (el, fallback = 250) => {
const raw = el ? window.getComputedStyle(el).zIndex : '';
const n = parseInt(raw, 10);
return Number.isFinite(n) ? n : fallback;
};
const _hasVisibleFloatingModal = (owner) => { const _hasVisibleFloatingModal = (owner) => {
const all = Array.from(document.querySelectorAll('.modal:not(.hidden):not(.modal-minimized)')); const all = Array.from(document.querySelectorAll('.modal:not(.hidden):not(.modal-minimized)'));
@ -863,40 +1145,18 @@ export function makeEdgeDockController(modal, side = 'right', dockClass) {
}); });
}; };
const _setWidth = (owner, side, clientX) => { const _setWidth = (owner, side, clientX, resizeOwners = null) => {
const nodes = _resolveDockNodes(owner); const targets = (resizeOwners && resizeOwners.length) ? resizeOwners : [owner].filter(Boolean);
const content = nodes?.content; if (!targets.length) return 0;
if (!content) return 0;
let w = 0; let w = 0;
if (side === 'right') { if (side === 'right') {
w = _clampRightDockWidth(window.innerWidth - clientX); w = _clampRightDockWidth(window.innerWidth - clientX);
content._userDockWidth = w; _syncDockSideWidth(side, w, targets);
content.style.left = 'auto';
content.style.right = '0';
content.style.width = w + 'px';
content.style.maxWidth = w + 'px';
document.body.classList.add('right-dock-active');
document.documentElement.style.setProperty('--right-dock-w', w + 'px');
if (_shouldAutoCollapseSidebar(w)) {
_collapseSidebarToRail();
if (content._preDockSnapshot) content._preDockSnapshot.collapsedSidebar = true;
}
} else { } else {
const left = _leftNavRight(); const left = _leftNavRight();
w = _clampLeftDockWidth(clientX - left, left); w = _clampLeftDockWidth(clientX - left, left);
content._userDockWidth = w; _syncDockSideWidth(side, w, targets);
content._emailDocSplitUserW = w;
content.style.left = left + 'px';
content.style.right = 'auto';
content.style.width = w + 'px';
content.style.maxWidth = w + 'px';
document.body.classList.add('left-dock-active');
document.documentElement.style.setProperty(
'--left-dock-w',
document.body.classList.contains('email-doc-split-active') ? '0px' : w + 'px',
);
} }
_positionEdgeDockResizeHandles();
return w; return w;
}; };
@ -928,7 +1188,7 @@ export function makeEdgeDockController(modal, side = 'right', dockClass) {
} }
_setStyle(handle, 'display', 'block'); _setStyle(handle, 'display', 'block');
_setStyle(handle, 'left', (x - 5) + 'px'); _setStyle(handle, 'left', (x - 5) + 'px');
_setStyle(handle, 'zIndex', String(_zIndexFor(owner) + 1)); _setStyle(handle, 'zIndex', String(_zIndexForElement(_dockOwnerSurface(owner), 250) + 1));
} }
}; };
@ -943,15 +1203,17 @@ export function makeEdgeDockController(modal, side = 'right', dockClass) {
handle.setPointerCapture?.(e.pointerId); handle.setPointerCapture?.(e.pointerId);
const nodes = _resolveDockNodes(owner); const nodes = _resolveDockNodes(owner);
const content = nodes?.content; const content = nodes?.content;
const resizeOwners = _dockOwnersForSide(side);
if (!resizeOwners.includes(owner)) resizeOwners.push(owner);
const prevCursor = document.body.style.cursor; const prevCursor = document.body.style.cursor;
const prevUserSelect = document.body.style.userSelect; const prevUserSelect = document.body.style.userSelect;
document.body.style.cursor = 'col-resize'; document.body.style.cursor = 'col-resize';
document.body.style.userSelect = 'none'; document.body.style.userSelect = 'none';
document.body.classList.add('edge-dock-resizing'); document.body.classList.add('edge-dock-resizing');
_setWidth(owner, side, e.clientX); _setWidth(owner, side, e.clientX, resizeOwners);
const onMove = (ev) => { const onMove = (ev) => {
ev.preventDefault(); ev.preventDefault();
_setWidth(owner, side, ev.clientX); _setWidth(owner, side, ev.clientX, resizeOwners);
}; };
const onUp = (ev) => { const onUp = (ev) => {
try { handle.releasePointerCapture?.(e.pointerId); } catch (_) {} try { handle.releasePointerCapture?.(e.pointerId); } catch (_) {}
@ -964,7 +1226,12 @@ export function makeEdgeDockController(modal, side = 'right', dockClass) {
const finalW = side === 'right' const finalW = side === 'right'
? parseFloat(document.documentElement.style.getPropertyValue('--right-dock-w')) || content?.getBoundingClientRect?.().width || 0 ? parseFloat(document.documentElement.style.getPropertyValue('--right-dock-w')) || content?.getBoundingClientRect?.().width || 0
: content?.getBoundingClientRect?.().width || 0; : content?.getBoundingClientRect?.().width || 0;
if (finalW) _saveDockWidth(owner, content, side, finalW); if (finalW) {
resizeOwners.forEach((dockOwner) => {
const dockContent = _resolveDockNodes(dockOwner)?.content;
if (dockContent) _saveDockWidth(dockOwner, dockContent, side, finalW);
});
}
ev.preventDefault(); ev.preventDefault();
}; };
document.addEventListener('pointermove', onMove, true); document.addEventListener('pointermove', onMove, true);

View file

@ -16366,6 +16366,87 @@ body.right-dock-active .chat-container,
body.right-dock-active:not(.email-doc-split-active) .doc-editor-pane { body.right-dock-active:not(.email-doc-split-active) .doc-editor-pane {
margin-right: var(--right-dock-w, 0px); margin-right: var(--right-dock-w, 0px);
} }
.edge-dock-switcher {
position: fixed;
top: 18px;
z-index: 1200;
display: flex;
flex-direction: column;
align-items: stretch;
gap: 4px;
min-width: 0;
max-height: calc(100dvh - 36px);
margin: 0;
padding: 0;
overflow: visible;
pointer-events: auto;
}
.edge-dock-switcher[hidden] {
display: none !important;
}
.edge-dock-switcher-right {
right: calc(var(--right-dock-w, 0px) - 1px);
}
.edge-dock-switcher-left {
left: calc(48px + var(--left-dock-w, 0px) - 1px);
}
.edge-dock-switcher-tab {
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 124px;
overflow: hidden;
padding: 0;
background: color-mix(in srgb, var(--panel) 92%, var(--bg));
border: 1px solid color-mix(in srgb, var(--border) 82%, transparent);
color: color-mix(in srgb, var(--fg) 78%, transparent);
font: inherit;
font-size: 10px;
line-height: 1;
text-align: center;
cursor: pointer;
}
.edge-dock-switcher-right .edge-dock-switcher-tab {
border-right-color: color-mix(in srgb, var(--panel) 85%, var(--bg));
border-radius: 6px 0 0 6px;
}
.edge-dock-switcher-left .edge-dock-switcher-tab {
border-left-color: color-mix(in srgb, var(--panel) 85%, var(--bg));
border-radius: 0 6px 6px 0;
}
.edge-dock-switcher-tab-label {
display: block;
flex: 0 0 auto;
width: max-content;
max-width: none;
overflow: visible;
text-overflow: clip;
white-space: nowrap;
pointer-events: none;
}
.edge-dock-switcher-right .edge-dock-switcher-tab-label {
transform: rotate(-90deg);
}
.edge-dock-switcher-left .edge-dock-switcher-tab-label {
transform: rotate(90deg);
}
.edge-dock-switcher-tab:hover {
color: var(--fg);
border-color: color-mix(in srgb, var(--accent, var(--red)) 48%, var(--border));
background: color-mix(in srgb, var(--accent, var(--red)) 9%, var(--panel));
}
.edge-dock-switcher-tab.active {
color: var(--fg);
border-color: color-mix(in srgb, var(--accent, var(--red)) 65%, var(--border));
background: color-mix(in srgb, var(--accent, var(--red)) 16%, var(--panel));
}
.edge-dock-switcher-right .edge-dock-switcher-tab.active {
border-right-color: color-mix(in srgb, var(--panel) 85%, var(--bg));
}
.edge-dock-switcher-left .edge-dock-switcher-tab.active {
border-left-color: color-mix(in srgb, var(--panel) 85%, var(--bg));
}
.modal.modal-right-docked { .modal.modal-right-docked {
align-items: stretch; align-items: stretch;
justify-content: flex-end; justify-content: flex-end;