UNPKG

@atlaskit/navigation-system

Version:

The latest navigation system for Atlassian apps.

469 lines (466 loc) • 25.7 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _typeof = require("@babel/runtime/helpers/typeof"); Object.defineProperty(exports, "__esModule", { value: true }); exports.LayoutAreaSizingRuntime = LayoutAreaSizingRuntime; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray")); var _react = _interopRequireWildcard(require("react")); var _bindEventListener = require("bind-event-listener"); var _calculateLayoutAreaStates = require("./calculate-layout-area-states"); var _constants = require("./constants"); var _layoutAreaSizingContext = require("./layout-area-sizing-context"); var _layoutAreaStateContexts = require("./layout-area-state-contexts"); var _resolveLayoutWidth = require("./resolve-layout-width"); var _useOccupiedLayoutWidth = require("./use-occupied-layout-width"); function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); } function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } function useStableAreaState(state) { var _ref = state !== null && state !== void 0 ? state : {}, mode = _ref.mode, width = _ref.width, minWidth = _ref.minWidth; return (0, _react.useMemo)(function () { return mode !== undefined && width !== undefined && minWidth !== undefined ? { mode: mode, width: width, minWidth: minWidth } : undefined; }, [mode, width, minWidth]); } function LayoutAreaSizingRuntime(_ref2) { var children = _ref2.children, layoutRef = _ref2.layoutRef, onReady = _ref2.onReady, onUpdate = _ref2.onUpdate; var occupiedWidth = (0, _useOccupiedLayoutWidth.useOccupiedLayoutWidth)(layoutRef); var _useState = (0, _react.useState)(null), _useState2 = (0, _slicedToArray2.default)(_useState, 2), dimensions = _useState2[0], setDimensions = _useState2[1]; var _useState3 = (0, _react.useState)(_constants.defaultMainMinWidth), _useState4 = (0, _slicedToArray2.default)(_useState3, 2), mainMinWidth = _useState4[0], setMainMinWidth = _useState4[1]; var _useState5 = (0, _react.useState)({}), _useState6 = (0, _slicedToArray2.default)(_useState5, 2), areas = _useState6[0], setAreas = _useState6[1]; var _useState7 = (0, _react.useState)(null), _useState8 = (0, _slicedToArray2.default)(_useState7, 2), resizeSession = _useState8[0], setResizeSession = _useState8[1]; var areaStatesRef = _react.default.useRef({}); var configsRef = _react.default.useRef({}); var _useState9 = (0, _react.useState)(false), _useState0 = (0, _slicedToArray2.default)(_useState9, 2), hasInteraction = _useState0[0], setHasInteraction = _useState0[1]; var adoptedConstraints = _react.default.useRef(null); // Preserve the CSS handoff only while its available space is unchanged. var isPreservingInitialLayout = !hasInteraction && (!adoptedConstraints.current || adoptedConstraints.current.occupiedWidth === occupiedWidth && adoptedConstraints.current.mainMinWidth === mainMinWidth); var _useState1 = (0, _react.useState)({}), _useState10 = (0, _slicedToArray2.default)(_useState1, 2), initialStates = _useState10[0], setInitialStates = _useState10[1]; var inputsRef = _react.default.useRef({ dimensions: dimensions, occupiedWidth: occupiedWidth, mainMinWidth: mainMinWidth, areas: areas, resizeSession: resizeSession }); (0, _react.useEffect)(function () { inputsRef.current = { dimensions: dimensions, occupiedWidth: occupiedWidth, mainMinWidth: mainMinWidth, areas: areas, resizeSession: resizeSession }; }, [dimensions, occupiedWidth, mainMinWidth, areas, resizeSession]); (0, _react.useEffect)(function () { if (adoptedConstraints.current && (adoptedConstraints.current.occupiedWidth !== occupiedWidth || adoptedConstraints.current.mainMinWidth !== mainMinWidth)) { setHasInteraction(true); } setResizeSession(null); }, [mainMinWidth, occupiedWidth]); (0, _react.useEffect)(function () { var root = layoutRef === null || layoutRef === void 0 ? void 0 : layoutRef.current; var previous = null; // Match the actual rem-based grid breakpoint, including browser font settings. var inlineChatQuery = window.matchMedia('(min-width: 40rem)'); var inlineSideNavQuery = window.matchMedia('(min-width: 64rem)'); var updateDimensions = function updateDimensions(contentWidth) { var _style$width, _style$paddingLeft, _style$paddingRight, _style$borderLeftWidt, _style$borderRightWid, _previous; var style = root && contentWidth === undefined ? getComputedStyle(root) : null; var computedWidth = Number.parseFloat((_style$width = style === null || style === void 0 ? void 0 : style.width) !== null && _style$width !== void 0 ? _style$width : ''); var padding = (parseFloat((_style$paddingLeft = style === null || style === void 0 ? void 0 : style.paddingLeft) !== null && _style$paddingLeft !== void 0 ? _style$paddingLeft : '') || 0) + (parseFloat((_style$paddingRight = style === null || style === void 0 ? void 0 : style.paddingRight) !== null && _style$paddingRight !== void 0 ? _style$paddingRight : '') || 0); var border = (parseFloat((_style$borderLeftWidt = style === null || style === void 0 ? void 0 : style.borderLeftWidth) !== null && _style$borderLeftWidt !== void 0 ? _style$borderLeftWidt : '') || 0) + (parseFloat((_style$borderRightWid = style === null || style === void 0 ? void 0 : style.borderRightWidth) !== null && _style$borderRightWid !== void 0 ? _style$borderRightWid : '') || 0); var layoutWidth = contentWidth !== null && contentWidth !== void 0 ? contentWidth : root ? // Keep fractional CSS pixels, just as ResizeObserver does. Otherwise // its initial delivery could be mistaken for a viewport resize. Math.max(0, Number.isFinite(computedWidth) ? computedWidth - ((style === null || style === void 0 ? void 0 : style.boxSizing) === 'border-box' ? padding + border : 0) : root.clientWidth - padding) : window.innerWidth; var next = { viewportWidth: window.innerWidth, layoutWidth: layoutWidth, isChatPanelOverlay: !inlineChatQuery.matches, isSideNavOverlay: !inlineSideNavQuery.matches }; if (((_previous = previous) === null || _previous === void 0 ? void 0 : _previous.viewportWidth) === next.viewportWidth && previous.layoutWidth === next.layoutWidth && previous.isChatPanelOverlay === next.isChatPanelOverlay && previous.isSideNavOverlay === next.isSideNavOverlay) { return; } if (previous) { setHasInteraction(true); } previous = next; setDimensions(next); setResizeSession(null); }; updateDimensions(); var observer = new ResizeObserver(function (entries) { var entry = entries.find(function (entry) { return entry.target === root; }); if (entry) { updateDimensions(entry.contentRect.width); } }); if (root) { observer.observe(root); } var unbindResize = (0, _bindEventListener.bind)(window, { type: 'resize', listener: function listener() { return updateDimensions(); } }); var unbindQuery = (0, _bindEventListener.bind)(inlineChatQuery, { type: 'change', listener: function listener() { return updateDimensions(); } }); var unbindSideNavQuery = (0, _bindEventListener.bind)(inlineSideNavQuery, { type: 'change', listener: function listener() { return updateDimensions(); } }); return function () { observer.disconnect(); unbindResize(); unbindQuery(); unbindSideNavQuery(); }; }, [layoutRef]); var registerArea = (0, _react.useCallback)(function (area, config) { var previous = configsRef.current[area]; if (adoptedConstraints.current && (!previous || previous.isOpen !== config.isOpen || previous.requestedWidth !== config.requestedWidth || previous.minWidth !== config.minWidth || previous.maxWidth !== config.maxWidth || previous.defaultWidth !== config.defaultWidth || previous.compactDefaultWidth !== config.compactDefaultWidth)) { setHasInteraction(true); } configsRef.current[area] = config; setResizeSession(function (current) { // A changed layout/configuration needs a fresh allocation. A controlled // acknowledgement for the resized area keeps its siblings fixed. return previous && (previous.isOpen === config.isOpen || area === 'side-nav' && !config.isOpen && (current === null || current === void 0 ? void 0 : current.area) !== 'side-nav') && previous.minWidth === config.minWidth && previous.maxWidth === config.maxWidth && previous.defaultWidth === config.defaultWidth && previous.compactDefaultWidth === config.compactDefaultWidth && (previous.requestedWidth === config.requestedWidth || (current === null || current === void 0 ? void 0 : current.area) === area && current.completedWidth === config.requestedWidth || area === 'side-nav' && !config.isOpen && (current === null || current === void 0 ? void 0 : current.area) !== 'side-nav') ? current : null; }); setAreas(function (current) { var existing = current[area]; return _objectSpread(_objectSpread({}, current), {}, (0, _defineProperty2.default)({}, area, _objectSpread(_objectSpread(_objectSpread({}, existing), config), {}, { liveResize: config.isOpen ? existing === null || existing === void 0 ? void 0 : existing.liveResize : undefined }))); }); }, []); var unregisterArea = (0, _react.useCallback)(function (area) { if (adoptedConstraints.current) { setHasInteraction(true); } delete configsRef.current[area]; setResizeSession(null); setAreas(function (current) { if (!current[area]) { return current; } // Closing uses isOpen: false and retains preferences. Unregistering means // the owning component unmounted; a replacement must start with its own defaults. var next = _objectSpread({}, current); delete next[area]; return next; }); }, []); var captureSiblingWidths = (0, _react.useCallback)(function (area) { var mode = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'inline'; var baselineWidths = {}; var overlayModes = {}; var _iterator = _createForOfIteratorHelper(['side-nav', 'panel', 'chat-panel']), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var areaName = _step.value; var state = areaStatesRef.current[areaName]; if (state) { overlayModes[areaName] = state.mode; } if ((state === null || state === void 0 ? void 0 : state.mode) === 'inline') { baselineWidths[areaName] = state.width; } } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } return { area: area, baselineWidths: baselineWidths, overlayModes: mode === 'overlay' ? overlayModes : undefined }; }, []); var startResize = (0, _react.useCallback)(function (area, mode) { setHasInteraction(true); setResizeSession(captureSiblingWidths(area, mode)); // Start from the effective width, which may already be compressed below the preferred width. // Reapplying the preferred width here would move the splitter away from the pointer. var renderedState = areaStatesRef.current[area]; if ((renderedState === null || renderedState === void 0 ? void 0 : renderedState.mode) === mode) { setAreas(function (current) { var existing = current[area]; return existing ? _objectSpread(_objectSpread({}, current), {}, (0, _defineProperty2.default)({}, area, _objectSpread(_objectSpread({}, existing), {}, { liveResize: { mode: mode, width: renderedState.width } }))) : current; }); } }, [captureSiblingWidths]); var resize = (0, _react.useCallback)(function (area, mode, width) { setAreas(function (current) { var _existing$liveResize; var existing = current[area]; if ((existing === null || existing === void 0 || (_existing$liveResize = existing.liveResize) === null || _existing$liveResize === void 0 ? void 0 : _existing$liveResize.mode) === mode && existing.liveResize.width === width) { return current; } return existing ? _objectSpread(_objectSpread({}, current), {}, (0, _defineProperty2.default)({}, area, _objectSpread(_objectSpread({}, existing), {}, { liveResize: { mode: mode, width: width } }))) : current; }); }, []); var completeResize = (0, _react.useCallback)(function (area, mode, width) { setHasInteraction(true); if (mode === 'overlay') { var session = inputsRef.current.resizeSession; setResizeSession(_objectSpread(_objectSpread({}, (session === null || session === void 0 ? void 0 : session.area) === area && session.overlayModes ? session : captureSiblingWidths(area, mode)), {}, { completedWidth: width })); } if (mode === 'inline') { var _inputs$resizeSession, _inputs$areas$area; var inputs = inputsRef.current; var _session = ((_inputs$resizeSession = inputs.resizeSession) === null || _inputs$resizeSession === void 0 ? void 0 : _inputs$resizeSession.area) === area && (_inputs$areas$area = inputs.areas[area]) !== null && _inputs$areas$area !== void 0 && _inputs$areas$area.liveResize ? inputs.resizeSession : captureSiblingWidths(area); var existing = inputs.areas[area]; if (inputs.dimensions && existing) { // Keyboard completion has no preview; calculate it using the accepted // request, not the parent's previous controlled width. Pointer completion // keeps below-minimum cascade results even though its payload is clamped. var completed = existing.liveResize ? areaStatesRef.current : (0, _calculateLayoutAreaStates.calculateLayoutAreaStates)(_objectSpread(_objectSpread({}, inputs.dimensions), {}, { occupiedWidth: inputs.occupiedWidth, mainMinWidth: inputs.mainMinWidth, areas: _objectSpread(_objectSpread({}, inputs.areas), {}, (0, _defineProperty2.default)({}, area, _objectSpread(_objectSpread({}, existing), {}, { liveResize: { mode: mode, width: width } }))), resizeSession: _session })); var completedWidths = {}; var _iterator2 = _createForOfIteratorHelper(['side-nav', 'panel', 'chat-panel']), _step2; try { for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) { var _completed$name; var name = _step2.value; if (((_completed$name = completed[name]) === null || _completed$name === void 0 ? void 0 : _completed$name.mode) === 'inline') { completedWidths[name] = completed[name].width; } } } catch (err) { _iterator2.e(err); } finally { _iterator2.f(); } setResizeSession(_objectSpread(_objectSpread({}, _session), {}, { completedWidth: width, completedWidths: completedWidths })); } } setAreas(function (current) { var existing = current[area]; if (!existing) { return current; } if (mode === 'overlay' && area !== 'panel') { return _objectSpread(_objectSpread({}, current), {}, (0, _defineProperty2.default)({}, area, _objectSpread(_objectSpread({}, existing), {}, { liveResize: undefined, preferredOverlayWidth: width }))); } return _objectSpread(_objectSpread({}, current), {}, (0, _defineProperty2.default)({}, area, _objectSpread(_objectSpread({}, existing), {}, { liveResize: undefined, // Local panels have one preference, regardless of the resized mode. preferredWidth: width }))); }); }, [captureSiblingWidths]); (0, _react.useEffect)(function () { // Loading the interaction engine is not a layout event. Adopt the CSS layout // without changing widths or modes; subsequent changes to configuration or // available space may redistribute it. Content stays mounted during the handoff. var root = layoutRef === null || layoutRef === void 0 ? void 0 : layoutRef.current; if (onUpdate && root && dimensions && isPreservingInitialLayout) { var measured = {}; var selectors = { 'side-nav': ':scope > nav[data-layout-slot]', panel: '[data-layout-with-panel-slot]', 'chat-panel': ':scope > [data-layout-chat-panel]' }; var _iterator3 = _createForOfIteratorHelper(['side-nav', 'panel', 'chat-panel']), _step3; try { for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) { var name = _step3.value; var element = root.querySelector(selectors[name]); var config = areas[name]; if (!element || !(config !== null && config !== void 0 && config.isOpen)) { continue; } var style = getComputedStyle(element); var mode = style.gridArea.split('/')[0].trim() === name ? 'inline' : 'overlay'; measured[name] = { mode: mode, width: element.getBoundingClientRect().width, minWidth: (0, _resolveLayoutWidth.resolveLayoutWidth)(config.minWidth, dimensions.viewportWidth) }; } } catch (err) { _iterator3.e(err); } finally { _iterator3.f(); } setInitialStates(measured); } }, [areas, dimensions, isPreservingInitialLayout, layoutRef, onUpdate]); var areaStates = (0, _react.useMemo)(function () { var _initialStates$sideN, _initialStates$panel, _initialStates$chatP; if (!dimensions) { return {}; } var allocated = (0, _calculateLayoutAreaStates.calculateLayoutAreaStates)(_objectSpread(_objectSpread({}, dimensions), {}, { occupiedWidth: occupiedWidth, mainMinWidth: mainMinWidth, areas: areas, resizeSession: resizeSession, initialModes: onUpdate && layoutRef !== null && layoutRef !== void 0 && layoutRef.current && isPreservingInitialLayout ? { 'side-nav': (_initialStates$sideN = initialStates['side-nav']) === null || _initialStates$sideN === void 0 ? void 0 : _initialStates$sideN.mode, panel: (_initialStates$panel = initialStates.panel) === null || _initialStates$panel === void 0 ? void 0 : _initialStates$panel.mode, 'chat-panel': (_initialStates$chatP = initialStates['chat-panel']) === null || _initialStates$chatP === void 0 ? void 0 : _initialStates$chatP.mode } : undefined })); if (onUpdate && layoutRef !== null && layoutRef !== void 0 && layoutRef.current && isPreservingInitialLayout) { var _iterator4 = _createForOfIteratorHelper(['side-nav', 'panel', 'chat-panel']), _step4; try { for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) { var name = _step4.value; if (initialStates[name] && allocated[name]) { allocated[name] = _objectSpread(_objectSpread({}, allocated[name]), initialStates[name]); } else { delete allocated[name]; } } } catch (err) { _iterator4.e(err); } finally { _iterator4.f(); } } return allocated; }, [areas, mainMinWidth, resizeSession, dimensions, occupiedWidth, isPreservingInitialLayout, initialStates, layoutRef, onUpdate]); (0, _react.useEffect)(function () { var _areas$sideNav; if (hasInteraction && (resizeSession === null || resizeSession === void 0 ? void 0 : resizeSession.area) !== 'side-nav' && (_areas$sideNav = areas['side-nav']) !== null && _areas$sideNav !== void 0 && _areas$sideNav.isOpen && !areaStates['side-nav'] && dimensions) { var _areas$sideNav$onReq, _areas$sideNav2; (_areas$sideNav$onReq = (_areas$sideNav2 = areas['side-nav']).onRequestClose) === null || _areas$sideNav$onReq === void 0 || _areas$sideNav$onReq.call(_areas$sideNav2); } }, [areaStates, areas, dimensions, hasInteraction, resizeSession]); (0, _react.useEffect)(function () { areaStatesRef.current = areaStates; }, [areaStates]); // Read bounds lazily: changing a sibling's capacity must not publish new render // state or recreate drag listeners for a region whose own width did not change. var getResizeBounds = (0, _react.useCallback)(function (area) { var _areaStatesRef$curren; var bounds = (_areaStatesRef$curren = areaStatesRef.current[area]) === null || _areaStatesRef$curren === void 0 ? void 0 : _areaStatesRef$curren.resizeBounds; return bounds ? { min: "".concat(bounds.min, "px"), max: "".concat(bounds.max, "px") } : undefined; }, []); var value = (0, _react.useMemo)(function () { return { getResizeBounds: getResizeBounds, registerArea: registerArea, unregisterArea: unregisterArea, startResize: startResize, resize: resize, completeResize: completeResize, setMainMinWidth: setMainMinWidth }; }, [completeResize, getResizeBounds, registerArea, resize, startResize, unregisterArea]); var sideNavState = useStableAreaState(areaStates['side-nav']); var panelState = useStableAreaState(areaStates.panel); var chatPanelState = useStableAreaState(areaStates['chat-panel']); var SideNavStateProvider = _layoutAreaStateContexts.layoutAreaStateContexts['side-nav'].Provider; var PanelStateProvider = _layoutAreaStateContexts.layoutAreaStateContexts.panel.Provider; var ChatPanelStateProvider = _layoutAreaStateContexts.layoutAreaStateContexts['chat-panel'].Provider; (0, _react.useEffect)(function () { onReady === null || onReady === void 0 || onReady(value); return function () { return onReady === null || onReady === void 0 ? void 0 : onReady(null); }; }, [onReady, value]); (0, _react.useEffect)(function () { if (!adoptedConstraints.current && (sideNavState || panelState || chatPanelState)) { adoptedConstraints.current = { occupiedWidth: occupiedWidth, mainMinWidth: mainMinWidth }; } onUpdate === null || onUpdate === void 0 || onUpdate({ 'side-nav': sideNavState, panel: panelState, 'chat-panel': chatPanelState }); }, [onUpdate, sideNavState, panelState, chatPanelState, occupiedWidth, mainMinWidth]); return /*#__PURE__*/_react.default.createElement(_layoutAreaSizingContext.LayoutAreaSizingContext.Provider, { value: value }, /*#__PURE__*/_react.default.createElement(SideNavStateProvider, { value: sideNavState }, /*#__PURE__*/_react.default.createElement(PanelStateProvider, { value: panelState }, /*#__PURE__*/_react.default.createElement(ChatPanelStateProvider, { value: chatPanelState }, children)))); }