UNPKG

@elastic/eui

Version:

Elastic UI Component Library

227 lines (210 loc) 12.3 kB
function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } 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 _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } } function _arrayWithHoles(r) { if (Array.isArray(r)) return r; } /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License * 2.0 and the Server Side Public License, v 1; you may not use this file except * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ import { useCallback, useEffect, useMemo, useState } from 'react'; import { useEuiTheme } from '../../../services'; import { usePropsWithComponentDefaults } from '../../provider/component_defaults'; import { useResizeObserver } from '../../observer/resize_observer'; import { setLayoutMode, setReferenceWidth } from './actions'; import { useFlyoutManager } from './hooks'; import { LAYOUT_MODE_SIDE_BY_SIDE, LAYOUT_MODE_STACKED } from './const'; /** * Hook to handle responsive layout mode for managed flyouts. * Decides whether to place flyouts side-by-side or stacked based on * the reference width (the reference container's width, defaulting to * document.body when not set) and flyout widths/sizes. */ export var useApplyFlyoutLayoutMode = function useApplyFlyoutLayoutMode() { var _ref, _sessions, _state$flyouts$find, _state$flyouts$find2, _context$state; var _useEuiTheme = useEuiTheme(), euiTheme = _useEuiTheme.euiTheme; var context = useFlyoutManager(); var state = context === null || context === void 0 ? void 0 : context.state; // Read the container from manager state (set by flyout components when they // receive a container prop), falling back to componentDefaults (used when // the container is configured globally, e.g. by Kibana). Resolve getter // and selector string so defaults can supply () => HTMLElement | null or // a CSS selector to avoid race when the element is not yet in the DOM. var stateContainerElement = state === null || state === void 0 ? void 0 : state.containerElement; var _usePropsWithComponen = usePropsWithComponentDefaults('EuiFlyout', {}), defaultContainerRaw = _usePropsWithComponen.container; var defaultContainer = defaultContainerRaw == null ? null : typeof defaultContainerRaw === 'function' ? defaultContainerRaw() : typeof defaultContainerRaw === 'string' ? function () { if (typeof document === 'undefined') return null; var el = document.querySelector(defaultContainerRaw); return el instanceof HTMLElement ? el : null; }() : defaultContainerRaw instanceof HTMLElement ? defaultContainerRaw : null; var container = (_ref = stateContainerElement !== null && stateContainerElement !== void 0 ? stateContainerElement : defaultContainer) !== null && _ref !== void 0 ? _ref : null; // Derive all session/flyout data from the single context read above var sessions = state === null || state === void 0 ? void 0 : state.sessions; var currentSession = sessions ? (_sessions = sessions[sessions.length - 1]) !== null && _sessions !== void 0 ? _sessions : null : null; var parentFlyoutId = currentSession === null || currentSession === void 0 ? void 0 : currentSession.mainFlyoutId; var childFlyoutId = currentSession === null || currentSession === void 0 ? void 0 : currentSession.childFlyoutId; var parentFlyout = parentFlyoutId ? (_state$flyouts$find = state === null || state === void 0 ? void 0 : state.flyouts.find(function (f) { return f.flyoutId === parentFlyoutId; })) !== null && _state$flyouts$find !== void 0 ? _state$flyouts$find : null : null; var childFlyout = childFlyoutId ? (_state$flyouts$find2 = state === null || state === void 0 ? void 0 : state.flyouts.find(function (f) { return f.flyoutId === childFlyoutId; })) !== null && _state$flyouts$find2 !== void 0 ? _state$flyouts$find2 : null : null; var parentWidth = parentFlyout === null || parentFlyout === void 0 ? void 0 : parentFlyout.width; var childWidth = childFlyout === null || childFlyout === void 0 ? void 0 : childFlyout.width; var hasFlyouts = Boolean(parentFlyoutId); // Observe the container element's width (returns { width: 0 } when null) var containerDimensions = useResizeObserver(container !== null && container !== void 0 ? container : null, 'width'); var _useState = useState(typeof window !== 'undefined' ? window.innerWidth : Infinity), _useState2 = _slicedToArray(_useState, 2), windowWidth = _useState2[0], setWindowWidth = _useState2[1]; // Use container width when available, otherwise fall back to window width var containerWidth = container ? containerDimensions.width || container.clientWidth : 0; var referenceWidth = containerWidth || windowWidth; // Extract specific context values var dispatch = context === null || context === void 0 ? void 0 : context.dispatch; var currentLayoutMode = context === null || context === void 0 || (_context$state = context.state) === null || _context$state === void 0 ? void 0 : _context$state.layoutMode; var setMode = useCallback(function (layoutMode) { if (dispatch) { dispatch(setLayoutMode(layoutMode)); } }, [dispatch]); // Only listen to window resize when not using a container useEffect(function () { if (typeof window === 'undefined' || container) { return; } var rafId = 0; var handleResize = function handleResize() { if (rafId) { cancelAnimationFrame(rafId); } rafId = requestAnimationFrame(function () { return setWindowWidth(window.innerWidth); }); }; window.addEventListener('resize', handleResize); return function () { if (rafId) { cancelAnimationFrame(rafId); } window.removeEventListener('resize', handleResize); }; }, [container]); // Calculate the desired layout mode var desiredLayoutMode = useMemo(function () { // Skip calculation if no flyouts open if (!hasFlyouts) { return null; } // Thresholds to prevent thrashing near the breakpoint. var THRESHOLD_TO_SIDE_BY_SIDE = 85; var THRESHOLD_TO_STACKED = 95; // If the reference width is too small, set the mode to stacked. // // The value is based on the maximum width of a flyout in // `composeFlyoutSizing` in `flyout.styles.ts` multiplied // by 2 (open flyouts side-by-side). if (referenceWidth < Math.round(euiTheme.breakpoint.s * 1.4)) { return LAYOUT_MODE_STACKED; } if (!childFlyoutId) { return LAYOUT_MODE_SIDE_BY_SIDE; } var isFillParent = (parentFlyout === null || parentFlyout === void 0 ? void 0 : parentFlyout.size) === 'fill'; var isFillChild = (childFlyout === null || childFlyout === void 0 ? void 0 : childFlyout.size) === 'fill'; var hasFill = isFillParent || isFillChild; var parentWidthValue = parentWidth; var childWidthValue = childWidth; // Resolve unmeasured widths. For fill-size flyouts, estimate as // (90% of referenceWidth − sibling width) rather than a flat 90%. // This avoids the combined estimate exceeding 90% and incorrectly // triggering stacked mode on initial mount. if (!parentWidthValue && parentFlyout !== null && parentFlyout !== void 0 && parentFlyout.size) { if (isFillParent && childWidthValue) { parentWidthValue = Math.max(0, Math.round(referenceWidth * 0.9 - childWidthValue)); } else { parentWidthValue = getWidthFromSize(parentFlyout.size, referenceWidth); } } if (!childWidthValue && childFlyout !== null && childFlyout !== void 0 && childFlyout.size) { if (isFillChild && parentWidthValue) { childWidthValue = Math.max(0, Math.round(referenceWidth * 0.9 - parentWidthValue)); } else { childWidthValue = getWidthFromSize(childFlyout.size, referenceWidth); } } if (!parentWidthValue || !childWidthValue) { return LAYOUT_MODE_SIDE_BY_SIDE; } var combinedWidth = parentWidthValue + childWidthValue; var combinedWidthPercentage = combinedWidth / referenceWidth * 100; // Fill flyouts defeat the hysteresis thresholds, so use the // reference-width breakpoint alone to decide the layout mode. if (hasFill) { return referenceWidth >= Math.round(euiTheme.breakpoint.s * 1.4) ? LAYOUT_MODE_SIDE_BY_SIDE : LAYOUT_MODE_STACKED; } if (currentLayoutMode === LAYOUT_MODE_STACKED) { return combinedWidthPercentage <= THRESHOLD_TO_SIDE_BY_SIDE ? LAYOUT_MODE_SIDE_BY_SIDE : LAYOUT_MODE_STACKED; } else { return combinedWidthPercentage >= THRESHOLD_TO_STACKED ? LAYOUT_MODE_STACKED : LAYOUT_MODE_SIDE_BY_SIDE; } }, [hasFlyouts, referenceWidth, euiTheme, childFlyoutId, parentWidth, childWidth, parentFlyout === null || parentFlyout === void 0 ? void 0 : parentFlyout.size, childFlyout === null || childFlyout === void 0 ? void 0 : childFlyout.size, currentLayoutMode]); // Apply the desired layout mode useEffect(function () { if (desiredLayoutMode && currentLayoutMode !== desiredLayoutMode) { setMode(desiredLayoutMode); } }, [desiredLayoutMode, currentLayoutMode, setMode]); // Store reference width in manager state when flyouts are open so the resize // clamp uses the same value as layout mode, avoiding resize past the container. useEffect(function () { if (dispatch && hasFlyouts && Number.isFinite(referenceWidth)) { dispatch(setReferenceWidth(referenceWidth)); } }, [dispatch, hasFlyouts, referenceWidth]); }; /** * Convert a flyout `size` value to a pixel width. * When `referenceWidth` is provided, named sizes are calculated as a percentage * of that width (container-relative). Otherwise falls back to `window.innerWidth`. */ export var getWidthFromSize = function getWidthFromSize(size, referenceWidth) { if (typeof size === 'number') { return size; } if (typeof size === 'string') { var parsed = parseInt(size, 10); if (!Number.isNaN(parsed)) { return parsed; } var refWidth = referenceWidth !== null && referenceWidth !== void 0 ? referenceWidth : typeof window !== 'undefined' ? window.innerWidth : 0; // Size is a function of a percentage of the reference width, // matching the proportions defined in `composeFlyoutSizing` switch (size) { case 's': return Math.round(refWidth * 0.25); case 'm': return Math.round(refWidth * 0.5); case 'l': return Math.round(refWidth * 0.75); case 'fill': return Math.round(refWidth * 0.9); default: break; } } return 0; }; /** Current layout mode for managed flyouts (`side-by-side` or `stacked`). */ export var useFlyoutLayoutMode = function useFlyoutLayoutMode() { var _context$state2; var context = useFlyoutManager(); return (context === null || context === void 0 || (_context$state2 = context.state) === null || _context$state2 === void 0 ? void 0 : _context$state2.layoutMode) || LAYOUT_MODE_SIDE_BY_SIDE; };