@atlaskit/navigation-system
Version:
The latest navigation system for Atlassian apps.
162 lines • 12.9 kB
JavaScript
import { resolveLayoutWidth } from './resolve-layout-width';
function clampTargetWidth({
target,
min,
max
}) {
return Math.max(min, Math.min(target, max));
}
function getTargetWidth(area, isSideNavOverlay) {
var _area$compactDefaultW, _area$liveResize, _ref, _area$requestedWidth;
const defaultWidth = isSideNavOverlay ? (_area$compactDefaultW = area.compactDefaultWidth) !== null && _area$compactDefaultW !== void 0 ? _area$compactDefaultW : area.defaultWidth : area.defaultWidth;
return ((_area$liveResize = area.liveResize) === null || _area$liveResize === void 0 ? void 0 : _area$liveResize.mode) === 'inline' ? area.liveResize.width : (_ref = (_area$requestedWidth = area.requestedWidth) !== null && _area$requestedWidth !== void 0 ? _area$requestedWidth : area.preferredWidth) !== null && _ref !== void 0 ? _ref : defaultWidth;
}
function getOverlayTargetWidth(area, isSideNavOverlay, areaName) {
var _area$compactDefaultW2, _area$liveResize2, _ref2, _area$requestedWidth2;
const defaultWidth = isSideNavOverlay ? (_area$compactDefaultW2 = area.compactDefaultWidth) !== null && _area$compactDefaultW2 !== void 0 ? _area$compactDefaultW2 : area.defaultWidth : area.defaultWidth;
return ((_area$liveResize2 = area.liveResize) === null || _area$liveResize2 === void 0 ? void 0 : _area$liveResize2.mode) === 'overlay' ? area.liveResize.width : (_ref2 = (_area$requestedWidth2 = area.requestedWidth) !== null && _area$requestedWidth2 !== void 0 ? _area$requestedWidth2 : areaName === 'panel' ? area.preferredWidth : area.preferredOverlayWidth) !== null && _ref2 !== void 0 ? _ref2 : defaultWidth;
}
export function calculateLayoutAreaStates({
viewportWidth,
layoutWidth = viewportWidth,
isChatPanelOverlay,
isSideNavOverlay,
occupiedWidth = 0,
mainMinWidth: mainMinWidthBound,
areas,
resizeSession,
initialModes
}) {
var _ref3, _resizeSession$overla, _baselineWidths$chat, _panel$liveResize, _states$sideNav$widt, _states$sideNav, _states$chatPanel$wi, _states$chatPanel;
const mainMinWidth = resolveLayoutWidth(mainMinWidthBound, viewportWidth);
const availableWidth = Math.max(0, layoutWidth - occupiedWidth);
const sideNav = areas['side-nav'];
const panel = areas.panel;
const chatPanel = areas['chat-panel'];
const sideNavMinWidth = sideNav ? resolveLayoutWidth(sideNav.minWidth, viewportWidth) : 0;
const panelMinWidth = panel ? resolveLayoutWidth(panel.minWidth, viewportWidth) : 0;
const chatPanelMinWidth = chatPanel ? resolveLayoutWidth(chatPanel.minWidth, viewportWidth) : 0;
const resizingArea = resizeSession && areas[resizeSession.area];
const isAcknowledged = (resizeSession === null || resizeSession === void 0 ? void 0 : resizeSession.completedWidth) !== undefined && ((resizingArea === null || resizingArea === void 0 ? void 0 : resizingArea.requestedWidth) === undefined || resizingArea.requestedWidth === resizeSession.completedWidth);
const baselineWidths = (_ref3 = isAcknowledged ? resizeSession === null || resizeSession === void 0 ? void 0 : resizeSession.completedWidths : undefined) !== null && _ref3 !== void 0 ? _ref3 : resizeSession === null || resizeSession === void 0 ? void 0 : resizeSession.baselineWidths;
const layoutModes = (_resizeSession$overla = resizeSession === null || resizeSession === void 0 ? void 0 : resizeSession.overlayModes) !== null && _resizeSession$overla !== void 0 ? _resizeSession$overla : initialModes;
// A growing panel cascades towards inline-start. Chat is on the other side
// of the local rail, so local growth must reserve its full starting width.
const shouldCollapseSideNav = Boolean(resizeSession && resizeSession.area !== 'side-nav' && resizeSession.baselineWidths['side-nav'] !== undefined && resizeSession.baselineWidths[resizeSession.area] !== undefined && resizingArea && !isSideNavOverlay && clampTargetWidth({
target: getTargetWidth(resizingArea, isSideNavOverlay),
min: resolveLayoutWidth(resizingArea.minWidth, viewportWidth),
max: resizingArea.maxWidth !== undefined ? resolveLayoutWidth(resizingArea.maxWidth, viewportWidth) : Number.POSITIVE_INFINITY
}) > availableWidth - mainMinWidth - sideNavMinWidth - (resizeSession.area === 'panel' ? resizeSession.baselineWidths['chat-panel'] !== undefined ? (_baselineWidths$chat = baselineWidths === null || baselineWidths === void 0 ? void 0 : baselineWidths['chat-panel']) !== null && _baselineWidths$chat !== void 0 ? _baselineWidths$chat : chatPanelMinWidth : 0 : resizeSession.baselineWidths.panel !== undefined ? panelMinWidth : 0));
const isSideNavOpenInline = Boolean((sideNav === null || sideNav === void 0 ? void 0 : sideNav.isOpen) && !shouldCollapseSideNav && (layoutModes ? layoutModes['side-nav'] === 'inline' : !isSideNavOverlay && availableWidth >= mainMinWidth + sideNavMinWidth));
const isChatPanelOpenInline = Boolean((chatPanel === null || chatPanel === void 0 ? void 0 : chatPanel.isOpen) && (!resizeSession || resizeSession.baselineWidths['chat-panel'] !== undefined) && (layoutModes ? layoutModes['chat-panel'] === 'inline' : !isChatPanelOverlay && availableWidth >= mainMinWidth + chatPanelMinWidth + (isSideNavOpenInline ? sideNavMinWidth : 0)));
const isPanelInline = Boolean((panel === null || panel === void 0 ? void 0 : panel.isOpen) && (!resizeSession || resizeSession.baselineWidths.panel !== undefined) && (layoutModes ? layoutModes.panel === 'inline' : availableWidth >= mainMinWidth + panelMinWidth + (isChatPanelOpenInline ? chatPanelMinWidth : 0) + (isSideNavOpenInline ? sideNavMinWidth : 0)));
const inlineAreas = [isSideNavOpenInline && 'side-nav', isPanelInline && 'panel', isChatPanelOpenInline && 'chat-panel'].filter(area => Boolean(area));
const widths = new Map();
const minimums = new Map();
for (const areaName of inlineAreas) {
const area = areas[areaName];
if (!area) {
continue;
}
const min = resolveLayoutWidth(area.minWidth, viewportWidth);
const max = area.maxWidth ? resolveLayoutWidth(area.maxWidth, viewportWidth) : Number.POSITIVE_INFINITY;
const baselineWidth = baselineWidths === null || baselineWidths === void 0 ? void 0 : baselineWidths[areaName];
const requestedTarget = resizeSession && areaName !== resizeSession.area ? shouldCollapseSideNav && resizeSession.area === 'chat-panel' ? min : baselineWidth !== null && baselineWidth !== void 0 ? baselineWidth : getTargetWidth(area, isSideNavOverlay) : getTargetWidth(area, isSideNavOverlay);
const target = clampTargetWidth({
target: requestedTarget,
min,
max
});
minimums.set(areaName, min);
widths.set(areaName, target);
}
// Narrowing Chat moves the shared rail towards inline-end. Give the released
// space to its inline neighbour so local grows instead of sliding across Main.
// Use effective (clamped) widths and the session baseline, not each drag frame,
// so reversing direction and completing a controlled resize do not add it twice.
if ((resizeSession === null || resizeSession === void 0 ? void 0 : resizeSession.area) === 'chat-panel' && isChatPanelOpenInline && isPanelInline && panel) {
var _widths$get, _baselineWidths$chat2, _widths$get2;
const chatWidth = (_widths$get = widths.get('chat-panel')) !== null && _widths$get !== void 0 ? _widths$get : 0;
const releasedWidth = Math.max(0, ((_baselineWidths$chat2 = baselineWidths === null || baselineWidths === void 0 ? void 0 : baselineWidths['chat-panel']) !== null && _baselineWidths$chat2 !== void 0 ? _baselineWidths$chat2 : chatWidth) - chatWidth);
const panelMaxWidth = panel.maxWidth !== undefined ? Math.max(panelMinWidth, resolveLayoutWidth(panel.maxWidth, viewportWidth)) : Number.POSITIVE_INFINITY;
widths.set('panel', Math.min(panelMaxWidth, ((_widths$get2 = widths.get('panel')) !== null && _widths$get2 !== void 0 ? _widths$get2 : 0) + releasedWidth));
}
// Continuing to pull the local rail towards Chat after local reaches its
// minimum gives Main the remaining space available in inline Chat.
if ((resizeSession === null || resizeSession === void 0 ? void 0 : resizeSession.area) === 'panel' && (panel === null || panel === void 0 ? void 0 : (_panel$liveResize = panel.liveResize) === null || _panel$liveResize === void 0 ? void 0 : _panel$liveResize.mode) === 'inline' && isChatPanelOpenInline) {
var _widths$get3;
const remainingDrag = Math.max(0, panelMinWidth - panel.liveResize.width);
const chatWidth = (_widths$get3 = widths.get('chat-panel')) !== null && _widths$get3 !== void 0 ? _widths$get3 : 0;
widths.set('chat-panel', Math.max(chatPanelMinWidth, chatWidth - remainingDrag));
}
let overflow = Math.max(0, Array.from(widths.values()).reduce((total, width) => total + width, 0) - (availableWidth - mainMinWidth));
const compressionOrder = ['panel', 'chat-panel', 'side-nav'];
// Main gives up spare space first. Only regions beyond the moving rail may
// then compress: local growth moves towards SideNav, never towards Chat.
// SideNav and Chat growth can reach both other regions in their direction.
const shrinkOrder = resizeSession ? resizeSession.area === 'panel' ? ['side-nav', 'panel'] : [...compressionOrder.filter(area => area !== resizeSession.area), resizeSession.area] : compressionOrder;
for (const areaName of shrinkOrder) {
var _widths$get4, _minimums$get;
if (overflow <= 0) {
break;
}
const width = (_widths$get4 = widths.get(areaName)) !== null && _widths$get4 !== void 0 ? _widths$get4 : 0;
const min = (_minimums$get = minimums.get(areaName)) !== null && _minimums$get !== void 0 ? _minimums$get : 0;
const reduction = Math.min(overflow, Math.max(0, width - min));
widths.set(areaName, width - reduction);
overflow -= reduction;
}
const states = {};
for (const areaName of inlineAreas) {
var _widths$get5, _minimums$get2, _areas$areaName;
const width = (_widths$get5 = widths.get(areaName)) !== null && _widths$get5 !== void 0 ? _widths$get5 : 0;
const min = (_minimums$get2 = minimums.get(areaName)) !== null && _minimums$get2 !== void 0 ? _minimums$get2 : 0;
const maxWidth = (_areas$areaName = areas[areaName]) === null || _areas$areaName === void 0 ? void 0 : _areas$areaName.maxWidth;
const configuredMax = maxWidth !== undefined ? resolveLayoutWidth(maxWidth, viewportWidth) : Number.POSITIVE_INFINITY;
states[areaName] = {
mode: 'inline',
width,
minWidth: min,
resizeBounds: {
min,
max: Math.max(min, Math.min(configuredMax, availableWidth - mainMinWidth - inlineAreas.reduce((total, sibling) => {
var _widths$get6, _minimums$get3;
return sibling === areaName || sibling === 'side-nav' && areaName !== 'side-nav' ? total : total + (areaName === 'panel' && sibling === 'chat-panel' ? (_widths$get6 = widths.get(sibling)) !== null && _widths$get6 !== void 0 ? _widths$get6 : 0 : (_minimums$get3 = minimums.get(sibling)) !== null && _minimums$get3 !== void 0 ? _minimums$get3 : 0);
}, 0)))
}
};
}
const inlineSideNavWidth = (_states$sideNav$widt = (_states$sideNav = states['side-nav']) === null || _states$sideNav === void 0 ? void 0 : _states$sideNav.width) !== null && _states$sideNav$widt !== void 0 ? _states$sideNav$widt : 0;
const inlineChatPanelWidth = (_states$chatPanel$wi = (_states$chatPanel = states['chat-panel']) === null || _states$chatPanel === void 0 ? void 0 : _states$chatPanel.width) !== null && _states$chatPanel$wi !== void 0 ? _states$chatPanel$wi : 0;
const mainAreaWidth = Math.max(0, availableWidth - inlineSideNavWidth - inlineChatPanelWidth);
for (const areaName of ['side-nav', 'panel', 'chat-panel']) {
var _states$areaName;
const area = areas[areaName];
if (!(area !== null && area !== void 0 && area.isOpen) || ((_states$areaName = states[areaName]) === null || _states$areaName === void 0 ? void 0 : _states$areaName.mode) === 'inline' || areaName === 'side-nav' && shouldCollapseSideNav) {
continue;
}
const min = resolveLayoutWidth(area.minWidth, viewportWidth);
const configuredMax = area.maxWidth ? resolveLayoutWidth(area.maxWidth, viewportWidth) : Number.POSITIVE_INFINITY;
// Chat covers Root; SideNav and the local panel overlay Main's grid track.
// Occupied tracks and inline chat are not part of that covered region.
const layoutMax = (areaName === 'chat-panel' ? layoutWidth : mainAreaWidth) * 0.9;
const max = Math.max(0, Math.min(configuredMax, layoutMax));
const effectiveMin = Math.min(min, max);
const target = getOverlayTargetWidth(area, isSideNavOverlay, areaName);
const width = clampTargetWidth({
target,
min: effectiveMin,
max
});
states[areaName] = {
mode: 'overlay',
width,
minWidth: effectiveMin,
resizeBounds: {
min: effectiveMin,
max
}
};
}
return states;
}