UNPKG

@atlaskit/navigation-system

Version:

The latest navigation system for Atlassian apps.

162 lines • 12.9 kB
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; }