UNPKG

@elastic/eui

Version:

Elastic UI Component Library

731 lines (701 loc) 41.5 kB
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } var _excluded = ["className", "children", "as", "hideCloseButton", "flyoutMenuProps", "flyoutMenuDisplayMode", "closeButtonProps", "closeButtonPosition", "onClose", "ownFocus", "side", "size", "paddingSize", "maxWidth", "style", "hasChildBackground", "maskProps", "type", "outsideClickCloses", "pushMinBreakpoint", "pushAnimation", "hasAnimation", "focusTrapProps", "includeFixedHeadersInFocusTrap", "includeSelectorInFocusTrap", "aria-describedby", "aria-labelledby", "id", "resizable", "minWidth", "onResize", "onAnimationEnd", "container"]; function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); } function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); } 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) { _defineProperty(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 _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } 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; } function _objectWithoutProperties(e, t) { if (null == e) return {}; var o, r, i = _objectWithoutPropertiesLoose(e, t); if (Object.getOwnPropertySymbols) { var n = Object.getOwnPropertySymbols(e); for (r = 0; r < n.length; r++) o = n[r], t.indexOf(o) >= 0 || {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]); } return i; } function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (e.indexOf(n) >= 0) continue; t[n] = r[n]; } return t; } /* * 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. */ /* eslint-disable local/i18n */ import React, { useEffect, useLayoutEffect, useRef, useMemo, useCallback, useState, forwardRef } from 'react'; import classnames from 'classnames'; import { keys, EuiWindowEvent, useCombinedRefs, useEuiMemoizedStyles, useGeneratedHtmlId, useEuiThemeCSSVariables, focusTrapPubSub } from '../../services'; import { useIsInManagedFlyout, useFlyoutId, useFlyoutManager } from './manager'; import { LAYOUT_MODE_SIDE_BY_SIDE, LAYOUT_MODE_STACKED, LEVEL_MAIN, PROPERTY_LEVEL } from './manager/const'; import { EuiFocusTrap } from '../focus_trap'; import { EuiI18n } from '../i18n'; import { useResizeObserver } from '../observer/resize_observer'; import { EuiScreenReaderOnly } from '../accessibility'; import { EuiFlyoutCloseButton } from './_flyout_close_button'; import { euiFlyoutStyles, composeFlyoutInlineStyles } from './flyout.styles'; import { usePropsWithComponentDefaults } from '../provider/component_defaults'; import { DEFAULT_MENU_DISPLAY_MODE, DEFAULT_PADDING_SIZE, DEFAULT_PUSH_MIN_BREAKPOINT, DEFAULT_SIDE, DEFAULT_SIZE, DEFAULT_TYPE, isEuiFlyoutSizeNamed } from './const'; import { useIsPushed } from './hooks'; import { EuiFlyoutMenu } from './flyout_menu'; import { EuiFlyoutOverlay } from './_flyout_overlay'; import { EuiFlyoutResizeButton } from './_flyout_resize_button'; import { useEuiFlyoutResizable } from './use_flyout_resizable'; import { useEuiFlyoutZIndex } from './use_flyout_z_index'; import { EuiFlyoutParentProvider } from './flyout_parent_context'; import { useEuiFlyoutMenu } from './use_flyout_menu'; import { jsx as ___EmotionJSX } from "@emotion/react"; /** * Resolves the container prop (element, getter, or selector string) to an * HTMLElement, or null if not found / invalid. */ var resolveContainer = function resolveContainer(raw) { if (raw == null) return null; if (typeof raw === 'string') { if (typeof document === 'undefined') return null; var el = document.querySelector(raw); return el instanceof HTMLElement ? el : null; } if (typeof raw === 'function') return raw(); if (typeof HTMLElement === 'undefined') return null; return raw instanceof HTMLElement ? raw : null; }; var defaultElement = 'div'; export var EuiFlyoutComponent = /*#__PURE__*/forwardRef(function (props, ref) { var _maskProps$headerZind, _managerSessions, _managerState$layoutM, _managerState$current, _managerState$flyouts, _managerState$pushPad; var _usePropsWithComponen = usePropsWithComponentDefaults('EuiFlyout', props), className = _usePropsWithComponen.className, children = _usePropsWithComponen.children, as = _usePropsWithComponen.as, _usePropsWithComponen2 = _usePropsWithComponen.hideCloseButton, hideCloseButton = _usePropsWithComponen2 === void 0 ? false : _usePropsWithComponen2, _flyoutMenuProps = _usePropsWithComponen.flyoutMenuProps, _usePropsWithComponen3 = _usePropsWithComponen.flyoutMenuDisplayMode, flyoutMenuDisplayMode = _usePropsWithComponen3 === void 0 ? DEFAULT_MENU_DISPLAY_MODE : _usePropsWithComponen3, closeButtonProps = _usePropsWithComponen.closeButtonProps, _usePropsWithComponen4 = _usePropsWithComponen.closeButtonPosition, closeButtonPosition = _usePropsWithComponen4 === void 0 ? 'inside' : _usePropsWithComponen4, onClose = _usePropsWithComponen.onClose, _usePropsWithComponen5 = _usePropsWithComponen.ownFocus, ownFocus = _usePropsWithComponen5 === void 0 ? true : _usePropsWithComponen5, _usePropsWithComponen6 = _usePropsWithComponen.side, side = _usePropsWithComponen6 === void 0 ? DEFAULT_SIDE : _usePropsWithComponen6, _usePropsWithComponen7 = _usePropsWithComponen.size, _size = _usePropsWithComponen7 === void 0 ? DEFAULT_SIZE : _usePropsWithComponen7, _usePropsWithComponen8 = _usePropsWithComponen.paddingSize, paddingSize = _usePropsWithComponen8 === void 0 ? DEFAULT_PADDING_SIZE : _usePropsWithComponen8, _usePropsWithComponen9 = _usePropsWithComponen.maxWidth, maxWidth = _usePropsWithComponen9 === void 0 ? false : _usePropsWithComponen9, style = _usePropsWithComponen.style, _usePropsWithComponen10 = _usePropsWithComponen.hasChildBackground, hasChildBackground = _usePropsWithComponen10 === void 0 ? false : _usePropsWithComponen10, maskProps = _usePropsWithComponen.maskProps, _usePropsWithComponen11 = _usePropsWithComponen.type, type = _usePropsWithComponen11 === void 0 ? DEFAULT_TYPE : _usePropsWithComponen11, outsideClickCloses = _usePropsWithComponen.outsideClickCloses, _usePropsWithComponen12 = _usePropsWithComponen.pushMinBreakpoint, pushMinBreakpoint = _usePropsWithComponen12 === void 0 ? DEFAULT_PUSH_MIN_BREAKPOINT : _usePropsWithComponen12, _pushAnimation = _usePropsWithComponen.pushAnimation, _hasAnimation = _usePropsWithComponen.hasAnimation, _focusTrapProps = _usePropsWithComponen.focusTrapProps, _usePropsWithComponen13 = _usePropsWithComponen.includeFixedHeadersInFocusTrap, includeFixedHeadersInFocusTrap = _usePropsWithComponen13 === void 0 ? true : _usePropsWithComponen13, includeSelectorInFocusTrap = _usePropsWithComponen.includeSelectorInFocusTrap, _ariaDescribedBy = _usePropsWithComponen['aria-describedby'], _ariaLabelledBy = _usePropsWithComponen['aria-labelledby'], id = _usePropsWithComponen.id, _usePropsWithComponen14 = _usePropsWithComponen.resizable, resizable = _usePropsWithComponen14 === void 0 ? false : _usePropsWithComponen14, minWidth = _usePropsWithComponen.minWidth, onResize = _usePropsWithComponen.onResize, onAnimationEnd = _usePropsWithComponen.onAnimationEnd, containerProp = _usePropsWithComponen.container, rest = _objectWithoutProperties(_usePropsWithComponen, _excluded); var container = resolveContainer(containerProp); var hasAnimationDefault = type === 'overlay'; var hasAnimation = _hasAnimation !== null && _hasAnimation !== void 0 ? _hasAnimation : hasAnimationDefault; var _useEuiThemeCSSVariab = useEuiThemeCSSVariables(), setGlobalCSSVariables = _useEuiThemeCSSVariab.setGlobalCSSVariables; var Element = as || defaultElement; var maskRef = useRef(null); // Ref for the main flyout element to pass to context var internalParentFlyoutRef = useRef(null); // Observe the container's dimensions so the resize hook and // positioning styles stay aligned with its bounding rect. // When no container is provided, these remain inert (null/undefined). var containerDimensions = useResizeObserver(container !== null && container !== void 0 ? container : null, 'width'); var containerReferenceWidth = container ? containerDimensions.width || container.clientWidth : undefined; var isPushed = useIsPushed({ type: type, pushMinBreakpoint: pushMinBreakpoint, containerWidth: containerReferenceWidth }); // When no explicit container is provided, push padding targets // document.body and global push-offset CSS vars are set. When a // container is provided, only that element receives padding. var shouldSetGlobalPushVars = container == null; if ('container' in props && ('maskProps' in props || 'includeFixedHeadersInFocusTrap' in props) && process.env.NODE_ENV === 'development') { if ('maskProps' in props) { console.warn('EuiFlyout: `maskProps` is deprecated. Prefer using the `container` prop to scope flyouts.'); } if ('includeFixedHeadersInFocusTrap' in props) { console.warn('EuiFlyout: `includeFixedHeadersInFocusTrap` is deprecated. Prefer `includeSelectorInFocusTrap` when using `container`.'); } } // Explicit viewport flyouts (container={null}) default to 'above' so // they render above fixed headers. Container-scoped and legacy flyouts // (no container prop) default to 'below'. var effectiveHeaderZindexLocation = (_maskProps$headerZind = maskProps === null || maskProps === void 0 ? void 0 : maskProps.headerZindexLocation) !== null && _maskProps$headerZind !== void 0 ? _maskProps$headerZind : containerProp === null ? 'above' : 'below'; // Report the container element to the flyout manager for layout calculations. useEffect(function () { var _flyoutManagerRef$cur; if (!container) return; (_flyoutManagerRef$cur = flyoutManagerRef.current) === null || _flyoutManagerRef$cur === void 0 || _flyoutManagerRef$cur.setContainerElement(container); return function () { var _flyoutManagerRef$cur2; if (((_flyoutManagerRef$cur2 = flyoutManagerRef.current) === null || _flyoutManagerRef$cur2 === void 0 || (_flyoutManagerRef$cur2 = _flyoutManagerRef$cur2.state) === null || _flyoutManagerRef$cur2 === void 0 ? void 0 : _flyoutManagerRef$cur2.containerElement) === container) { flyoutManagerRef.current.setContainerElement(null); } }; }, [container]); // Performance: read context once and derive all manager-dependent values inline. var isInManagedContext = useIsInManagedFlyout(); var flyoutId = useFlyoutId(id); var flyoutManager = useFlyoutManager(); var managerState = flyoutManager === null || flyoutManager === void 0 ? void 0 : flyoutManager.state; var managerSessions = managerState === null || managerState === void 0 ? void 0 : managerState.sessions; var currentSession = managerSessions ? (_managerSessions = managerSessions[managerSessions.length - 1]) !== null && _managerSessions !== void 0 ? _managerSessions : null : null; var layoutMode = (_managerState$layoutM = managerState === null || managerState === void 0 ? void 0 : managerState.layoutMode) !== null && _managerState$layoutM !== void 0 ? _managerState$layoutM : LAYOUT_MODE_SIDE_BY_SIDE; var isActiveManagedFlyout = (currentSession === null || currentSession === void 0 ? void 0 : currentSession.mainFlyoutId) === flyoutId || (currentSession === null || currentSession === void 0 ? void 0 : currentSession.childFlyoutId) === flyoutId; var currentZIndexRef = useRef((_managerState$current = managerState === null || managerState === void 0 ? void 0 : managerState.currentZIndex) !== null && _managerState$current !== void 0 ? _managerState$current : 0); var _useEuiFlyoutMenu = useEuiFlyoutMenu({ flyoutMenuProps: _flyoutMenuProps, flyoutMenuDisplayMode: flyoutMenuDisplayMode, ariaLabelledBy: _ariaLabelledBy }), flyoutMenuId = _useEuiFlyoutMenu.flyoutMenuId, flyoutMenuProps = _useEuiFlyoutMenu.flyoutMenuProps, shouldRenderMenu = _useEuiFlyoutMenu.shouldRenderMenu, ariaLabelledBy = _useEuiFlyoutMenu.ariaLabelledBy; useEffect(function () { if (process.env.NODE_ENV === 'development' && _flyoutMenuProps && 'hideTitle' in _flyoutMenuProps) { console.warn('EuiFlyout: `flyoutMenuProps.hideTitle` is deprecated. Use `EuiFlyoutHeader` for visible titles instead.'); } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // Use a ref to access the latest flyoutManager without triggering effect re-runs var flyoutManagerRef = useRef(flyoutManager); useEffect(function () { flyoutManagerRef.current = flyoutManager; }, [flyoutManager]); useEffect(function () { // Keep track of unmanaged flyouts to properly calculate z-index // values for all flyouts if (!isInManagedContext) { var _flyoutManagerRef$cur3; (_flyoutManagerRef$cur3 = flyoutManagerRef.current) === null || _flyoutManagerRef$cur3 === void 0 || _flyoutManagerRef$cur3.addUnmanagedFlyout(flyoutId); return function () { var _flyoutManagerRef$cur4; return (_flyoutManagerRef$cur4 = flyoutManagerRef.current) === null || _flyoutManagerRef$cur4 === void 0 ? void 0 : _flyoutManagerRef$cur4.closeUnmanagedFlyout(flyoutId); }; } }, [isInManagedContext, flyoutId]); // Derive flyout identity and sibling info from the single context read var flyoutIdentity = useMemo(function () { if (!flyoutId || !currentSession) { return { isMainFlyout: false, siblingFlyoutId: null, hasValidSession: false }; } var siblingFlyoutId = currentSession.mainFlyoutId === flyoutId ? currentSession.childFlyoutId : currentSession.mainFlyoutId; return { isMainFlyout: currentSession.mainFlyoutId === flyoutId, siblingFlyoutId: siblingFlyoutId, hasValidSession: true }; }, [flyoutId, currentSession]); // Destructure for easier use var siblingFlyoutId = flyoutIdentity.siblingFlyoutId, isMainFlyout = flyoutIdentity.isMainFlyout; // Derive sibling flyout data from manager state (single read, no extra hooks) var siblingFlyout = siblingFlyoutId ? (_managerState$flyouts = managerState === null || managerState === void 0 ? void 0 : managerState.flyouts.find(function (f) { return f.flyoutId === siblingFlyoutId; })) !== null && _managerState$flyouts !== void 0 ? _managerState$flyouts : null : null; var _siblingFlyoutWidth = siblingFlyout === null || siblingFlyout === void 0 ? void 0 : siblingFlyout.width; var isSiblingFill = (siblingFlyout === null || siblingFlyout === void 0 ? void 0 : siblingFlyout.size) === 'fill'; var siblingFlyoutMinWidth = siblingFlyout === null || siblingFlyout === void 0 ? void 0 : siblingFlyout.minWidth; var siblingFlyoutWidth = layoutMode === LAYOUT_MODE_STACKED ? 0 : _siblingFlyoutWidth; // Track the container's bounding rect for positioning the flyout. var _useState = useState(null), _useState2 = _slicedToArray(_useState, 2), containerRect = _useState2[0], setContainerRect = _useState2[1]; useLayoutEffect(function () { if (!container) { setContainerRect(null); return; } setContainerRect(container.getBoundingClientRect()); }, [container, containerDimensions.width]); useEffect(function () { if (!container) return; var updateRect = function updateRect() { var next = container.getBoundingClientRect(); setContainerRect(function (prev) { if (prev && prev.top === next.top && prev.left === next.left && prev.width === next.width && prev.height === next.height) { return prev; } return next; }); }; window.addEventListener('scroll', updateRect, { passive: true }); window.addEventListener('resize', updateRect, { passive: true }); return function () { window.removeEventListener('scroll', updateRect); window.removeEventListener('resize', updateRect); }; }, [container]); // Prefer the manager's reference width when available so resize clamp // uses the same value as layout mode. When we have a container, cap by // its measured width so we never allow resize past the container (e.g. if // manager had viewport fallback or timing mismatch). var managerRefWidth = isInManagedContext && typeof (managerState === null || managerState === void 0 ? void 0 : managerState.referenceWidth) === 'number' && managerState.referenceWidth > 0 ? managerState.referenceWidth : undefined; var effectiveReferenceWidth = containerReferenceWidth != null && containerReferenceWidth > 0 ? managerRefWidth != null ? Math.min(managerRefWidth, containerReferenceWidth) : containerReferenceWidth : managerRefWidth !== null && managerRefWidth !== void 0 ? managerRefWidth : containerReferenceWidth; var _useEuiFlyoutResizabl = useEuiFlyoutResizable({ enabled: resizable, minWidth: minWidth, maxWidth: typeof maxWidth === 'number' ? maxWidth : undefined, // For fill siblings, clamp based on their minWidth rather than their // current measured width. Fill siblings dynamically adjust via CSS // (`calc(90% - mainWidth)`), so subtracting their current width from // the max creates a circular dependency where the main flyout can // never grow (max = 90% - (90% - main) = main). Using minWidth // breaks this cycle while ensuring the fill sibling never collapses // below its configured minimum. siblingFlyoutWidth: isSiblingFill ? siblingFlyoutMinWidth : siblingFlyoutWidth !== null && siblingFlyoutWidth !== void 0 ? siblingFlyoutWidth : undefined, referenceWidth: effectiveReferenceWidth, onResize: onResize, side: side, size: _size }), onMouseDownResizableButton = _useEuiFlyoutResizabl.onMouseDown, onKeyDownResizableButton = _useEuiFlyoutResizabl.onKeyDown, size = _useEuiFlyoutResizabl.size, setFlyoutRef = _useEuiFlyoutResizabl.setFlyoutRef; // Publish the main flyout's resolved width as a CSS custom property on // the document element so that the child (fill) flyout can track it // synchronously during drag resize, avoiding the 1-frame lag that // results from the async ResizeObserver → manager-state pipeline. useLayoutEffect(function () { if (!isMainFlyout) return; // Only set when we have a computed percentage (during active resize) if (typeof size === 'string' && size.endsWith('%')) { document.documentElement.style.setProperty('--euiFlyoutMainWidth', size); } return function () { document.documentElement.style.removeProperty('--euiFlyoutMainWidth'); }; }, [isMainFlyout, size]); /** * Setting up the refs on the actual flyout element in order to * accommodate for the `isPushed` state by adding padding to the body equal to the width of the element */ var _useState3 = useState(null), _useState4 = _slicedToArray(_useState3, 2), resizeRef = _useState4[0], setResizeRef = _useState4[1]; var setRef = useCombinedRefs([setResizeRef, ref, internalParentFlyoutRef, setFlyoutRef]); var _useResizeObserver = useResizeObserver(isPushed ? resizeRef : null, 'width'), width = _useResizeObserver.width; /** * Effect for adding push padding so the flyout has room. Targets the * container element when one is provided, otherwise document.body. * Uses useLayoutEffect so padding is applied before child render. */ useLayoutEffect(function () { if (!isPushed) { return; } var paddingTarget = container !== null && container !== void 0 ? container : document.body; var shouldApplyPadding = !isInManagedContext || isActiveManagedFlyout; var paddingSide = side === 'left' ? 'paddingInlineStart' : 'paddingInlineEnd'; var cssVarName = "--euiPushFlyoutOffset".concat(side === 'left' ? 'InlineStart' : 'InlineEnd'); var managerSide = side === 'left' ? 'left' : 'right'; // Capture pre-existing inline padding so it can be restored on cleanup var previousPadding = paddingTarget.style[paddingSide]; var paddingWidth = layoutMode === LAYOUT_MODE_STACKED && isMainFlyout && _siblingFlyoutWidth ? _siblingFlyoutWidth : width; if (shouldApplyPadding) { paddingTarget.style[paddingSide] = "".concat(paddingWidth, "px"); if (shouldSetGlobalPushVars) { setGlobalCSSVariables(_defineProperty({}, cssVarName, "".concat(paddingWidth, "px"))); } if (isInManagedContext && flyoutManagerRef.current) { flyoutManagerRef.current.setPushPadding(managerSide, paddingWidth); } } else { paddingTarget.style[paddingSide] = previousPadding; if (shouldSetGlobalPushVars) { setGlobalCSSVariables(_defineProperty({}, cssVarName, null)); } if (isInManagedContext && flyoutManagerRef.current) { flyoutManagerRef.current.setPushPadding(managerSide, 0); } } return function () { paddingTarget.style[paddingSide] = previousPadding; if (shouldSetGlobalPushVars) { setGlobalCSSVariables(_defineProperty({}, cssVarName, null)); } if (isInManagedContext && flyoutManagerRef.current) { flyoutManagerRef.current.setPushPadding(managerSide, 0); } }; }, [isPushed, isInManagedContext, isActiveManagedFlyout, setGlobalCSSVariables, side, width, layoutMode, isMainFlyout, _siblingFlyoutWidth, shouldSetGlobalPushVars, container]); /** * This class doesn't actually do anything by EUI, but is nice to add for consumers (JIC) */ useEffect(function () { document.body.classList.add('euiBody--hasFlyout'); return function () { document.body.classList.remove('euiBody--hasFlyout'); }; }, []); var hasChildFlyout = (currentSession === null || currentSession === void 0 ? void 0 : currentSession.childFlyoutId) != null; var isChildFlyout = isInManagedContext && hasChildFlyout && (currentSession === null || currentSession === void 0 ? void 0 : currentSession.childFlyoutId) === id; var shouldCloseOnEscape = useMemo(function () { // Regular flyout - always close on ESC if (!isInManagedContext) { return true; } // Managed flyout with no child - close on ESC if (!hasChildFlyout) { return true; } // Child flyout - close on ESC if (isChildFlyout) { return true; } // Main flyout with child flyout - don't close on ESC return false; }, [isInManagedContext, hasChildFlyout, isChildFlyout]); var handleClose = useCallback(function (event, reason) { onClose(event, reason ? { reason: reason } : undefined); }, [onClose]); /** * ESC key closes flyout based on flyout hierarchy rules */ var onKeyDown = useCallback(function (event) { if (!isPushed && event.key === keys.ESCAPE && shouldCloseOnEscape) { event.preventDefault(); handleClose(event, 'escape'); } }, [handleClose, isPushed, shouldCloseOnEscape]); var managedFlyoutIndex = currentZIndexRef.current; if (isInManagedContext && currentSession) { managedFlyoutIndex = currentSession.zIndex; } var _useEuiFlyoutZIndex = useEuiFlyoutZIndex({ headerZindexLocation: effectiveHeaderZindexLocation, isPushed: isPushed, managedFlyoutIndex: managedFlyoutIndex, isChildFlyout: isChildFlyout }), flyoutZIndex = _useEuiFlyoutZIndex.flyoutZIndex, maskZIndex = _useEuiFlyoutZIndex.maskZIndex; /** * Inline styles position the flyout inside the reference container's * bounding rect (document.body or a specific element) while remaining in * document.body with position: fixed. */ var inlineStyles = useMemo(function () { var composedStyles = composeFlyoutInlineStyles(size, layoutMode, siblingFlyoutId, siblingFlyoutWidth || null, maxWidth, flyoutZIndex); // Constrain the flyout to the reference container's bounding rect. var containerPositionStyles = {}; if (containerRect) { var containerMaxWidth = containerRect.width * 0.9; // Use clientWidth (excludes scrollbar) to match the coordinate // system of getBoundingClientRect() and CSS position: fixed. var viewportWidth = document.documentElement.clientWidth; var containerRightOffset = viewportWidth - containerRect.right; // Compute the container-relative width for this flyout. // // For `position: fixed` elements CSS resolves `%` values against // the viewport, not the container. The resize hook outputs // percentage strings relative to `_referenceWidth` (the container), // so we must convert them to pixel values here. // // Named sizes use their standard proportions (matching // `composeFlyoutSizing`). Percentage strings from the resize hook // are parsed and resolved against the container width. Numeric // values are used directly. var sizePercentMap = { s: 0.25, m: 0.5, l: 0.75, fill: 0.9 }; var containerRelativeWidth; if (typeof size === 'string') { var namedPct = sizePercentMap[size]; if (namedPct !== undefined) { containerRelativeWidth = containerRect.width * namedPct; } else if (size.endsWith('%')) { // Percentage string from the resize hook — the value is // relative to `_referenceWidth` (container width). Parse it // and resolve against the container to get the correct pixels. var pct = parseFloat(size); if (!isNaN(pct)) { containerRelativeWidth = containerRect.width * (pct / 100); } } } else if (typeof size === 'number') { containerRelativeWidth = size; } // All container-scoped flyouts get top/height from the container rect. // Reset minInlineSize to 0 so that the CSS `min-inline-size` (which // resolves against the viewport for `position: fixed`) does not // prevent the container-relative width constraints from taking effect. containerPositionStyles = { top: containerRect.top, height: containerRect.height, minInlineSize: 0 }; if (isChildFlyout) { // Child flyouts position themselves relative to the main flyout. // In side-by-side mode, `siblingFlyoutWidth` is the main flyout's // pixel width; in stacked mode it's 0 (child sits on top). var siblingPx = siblingFlyoutWidth || 0; if (side === 'left') { containerPositionStyles.left = containerRect.left + siblingPx; } else { containerPositionStyles.right = containerRightOffset + siblingPx; } if (containerRelativeWidth !== undefined) { // Set `inlineSize` (logical property) so it properly overrides // `inlineSize` from composedStyles. We intentionally do NOT set // maxInlineSize — the CSS `max-inline-size` from the size class // provides the size-specific cap on initial render, and the // resize hook's clamp handles it after resize interactions. // // For fill-size children in side-by-side mode, subtract the // main flyout's width so the child takes only the remaining // available space. For non-fill children, do NOT subtract — // a fill-size main will shrink dynamically to accommodate. var fillDeduction = size === 'fill' && layoutMode === LAYOUT_MODE_SIDE_BY_SIDE && siblingFlyoutWidth ? siblingFlyoutWidth : 0; containerPositionStyles.inlineSize = Math.min(Math.max(0, containerRelativeWidth - fillDeduction), containerMaxWidth); } else { containerPositionStyles.maxInlineSize = containerMaxWidth; } } else { // Main/standalone flyouts align to the container's edge. if (side === 'left') { containerPositionStyles.left = containerRect.left; } else { containerPositionStyles.right = containerRightOffset; } if (containerRelativeWidth !== undefined) { // For fill-size flyouts in side-by-side mode, subtract the // sibling's width so the main shrinks to accommodate the child. var _siblingPx = size === 'fill' && layoutMode === LAYOUT_MODE_SIDE_BY_SIDE && siblingFlyoutWidth ? siblingFlyoutWidth : 0; containerPositionStyles.inlineSize = Math.min(Math.max(0, containerRelativeWidth - _siblingPx), containerMaxWidth); } else { containerPositionStyles.maxInlineSize = containerMaxWidth; } } } return _objectSpread(_objectSpread(_objectSpread({}, style), composedStyles), containerPositionStyles); }, [style, size, layoutMode, siblingFlyoutId, siblingFlyoutWidth, maxWidth, flyoutZIndex, containerRect, side, isChildFlyout]); var styles = useEuiMemoizedStyles(euiFlyoutStyles); var cssStyles = [styles.euiFlyout, styles.paddingSizes[paddingSize], isEuiFlyoutSizeNamed(size) && styles[size], maxWidth === false && styles.noMaxWidth, isPushed ? styles.push.push : styles.overlay.overlay, isPushed ? styles.push[side] : styles.overlay[side], !hasAnimation && styles.noAnimation, styles[side]]; var classes = classnames('euiFlyout', isChildFlyout && hasChildBackground && 'euiFlyout--hasChildBackground', className); var flyoutToggle = useRef(document.activeElement); var _useState5 = useState([]), _useState6 = _slicedToArray(_useState5, 2), focusTrapShards = _useState6[0], setFocusTrapShards = _useState6[1]; var isSideBySideChild = isChildFlyout && layoutMode === LAYOUT_MODE_SIDE_BY_SIDE; // Side-by-side: main + child form a single modal unit; only main has aria-modal // to avoid competing dialog semantics. Stacked children are independent modals. var announcesAsModal = useMemo(function () { return !isPushed && !isSideBySideChild; }, [isPushed, isSideBySideChild]); var focusTrapSelectors = useMemo(function () { var selectors = []; if (includeSelectorInFocusTrap) { selectors = Array.isArray(includeSelectorInFocusTrap) ? includeSelectorInFocusTrap : [includeSelectorInFocusTrap]; } if (includeFixedHeadersInFocusTrap) { selectors.push('.euiHeader[data-fixed-header]'); } // Include parent in focus trap shards for side-by-side mode (unified navigation). // In stacked mode, parent is hidden behind child so shouldn't be navigable. if (isSideBySideChild) { selectors.push("[".concat(PROPERTY_LEVEL, "=\"").concat(LEVEL_MAIN, "\"]")); } return selectors; }, [includeSelectorInFocusTrap, includeFixedHeadersInFocusTrap, isSideBySideChild]); /** * Finds the shards to include in the focus trap by querying by `focusTrapSelectors`. * * @param shouldAutoFocus Whether to auto-focus the flyout wrapper when the focus trap is activated. * This is necessary because when a flyout is toggled from within a shard, the focus trap's `autoFocus` * feature doesn't work. This logic manually focuses the flyout as a workaround. */ var findShards = useCallback(function () { var shouldAutoFocus = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; if (focusTrapSelectors.length > 0) { var shardsEls = focusTrapSelectors.flatMap(function (selector) { return Array.from(document.querySelectorAll(selector)); }); setFocusTrapShards(Array.from(shardsEls)); if (shouldAutoFocus) { shardsEls.forEach(function (shard) { if (shard.contains(flyoutToggle.current)) { resizeRef === null || resizeRef === void 0 || resizeRef.focus(); } }); } } else { // Clear existing shards if necessary, e.g. switching to `false` setFocusTrapShards(function (shards) { return shards.length ? [] : shards; }); } }, [focusTrapSelectors, resizeRef]); useEffect(function () { // Auto-focus should only happen on initial flyout mount (or when the dependencies change) // because it snaps focus to the flyout wrapper, which steals it from subsequently focused elements. findShards(true); var unsubscribe = focusTrapPubSub.subscribe(function () { return findShards(); }); return unsubscribe; }, [findShards]); var focusTrapProps = useMemo(function () { return _objectSpread(_objectSpread({}, _focusTrapProps), {}, { shards: [].concat(_toConsumableArray(focusTrapShards), _toConsumableArray((_focusTrapProps === null || _focusTrapProps === void 0 ? void 0 : _focusTrapProps.shards) || [])) }); }, [_focusTrapProps, focusTrapShards]); /* * Provide meaningful screen reader instructions/details */ var hasOverlayMask = ownFocus && !isPushed; var descriptionId = useGeneratedHtmlId(); var ariaDescribedBy = classnames(descriptionId, _ariaDescribedBy); var screenReaderDescription = useMemo(function () { return ___EmotionJSX(EuiScreenReaderOnly, null, ___EmotionJSX("p", { id: descriptionId }, hasOverlayMask ? ___EmotionJSX(EuiI18n, { token: "euiFlyout.screenReaderModalDialog", default: "You are in a modal dialog. Press Escape or tap/click outside the dialog on the shadowed overlay to close." }) : ___EmotionJSX(EuiI18n, { token: "euiFlyout.screenReaderNoOverlayMaskDialog", default: "You are in a modal dialog. To close the dialog, press Escape." }), ' ', focusTrapShards.length > 0 && ___EmotionJSX(EuiI18n, { token: "euiFlyout.screenReaderFocusTrapShards", default: "You can still continue tabbing through other global page landmarks." }))); }, [hasOverlayMask, descriptionId, focusTrapShards.length]); /* * Trap focus even when `ownFocus={false}`, otherwise closing * the flyout won't return focus to the originating button. * * Set `clickOutsideDisables={true}` when `ownFocus={false}` * to allow non-keyboard users the ability to interact with * elements outside the flyout. * * Set `onClickOutside={onClose}` when `ownFocus` and `type` are the defaults, * or if `outsideClickCloses={true}` to close on clicks that target * (both mousedown and mouseup) the overlay mask. */ var onClickOutside = useCallback(function (event) { // Do not close the flyout for any external click if (outsideClickCloses === false) return undefined; if (hasOverlayMask) { // The overlay mask is present, so only clicks on the mask should close the flyout, regardless of outsideClickCloses if (event.target === maskRef.current) return handleClose(event, 'outside-click'); } else { // No overlay mask is present, so any outside clicks should close the flyout if (outsideClickCloses === true) return handleClose(event, 'outside-click'); } // Otherwise if ownFocus is false and outsideClickCloses is undefined, outside clicks should not close the flyout return undefined; }, [handleClose, hasOverlayMask, outsideClickCloses]); var maskCombinedRefs = useCombinedRefs([maskProps === null || maskProps === void 0 ? void 0 : maskProps.maskRef, maskRef]); /** * For overlay flyouts in managed contexts, coordinate scroll locking with push flyout state. */ var pushPadding = (_managerState$pushPad = managerState === null || managerState === void 0 ? void 0 : managerState.pushPadding) !== null && _managerState$pushPad !== void 0 ? _managerState$pushPad : { left: 0, right: 0 }; var hasPushPaddingInManager = pushPadding.left > 0 || pushPadding.right > 0; var shouldDeferScrollLock = !isPushed && isInManagedContext && hasPushPaddingInManager; var shouldUseScrollLock = hasOverlayMask && !shouldDeferScrollLock; return ___EmotionJSX(EuiFlyoutOverlay, { hasOverlayMask: hasOverlayMask, isPushed: isPushed, maskZIndex: maskZIndex, headerZindexLocation: effectiveHeaderZindexLocation, containerRect: containerRect, maskProps: _objectSpread(_objectSpread({}, maskProps), {}, { maskRef: maskCombinedRefs, hasAnimation: hasAnimation }) }, ___EmotionJSX(EuiWindowEvent, { event: "keydown", handler: onKeyDown }), ___EmotionJSX(EuiFocusTrap, _extends({ disabled: isPushed, scrollLock: shouldUseScrollLock, clickOutsideDisables: !ownFocus, onClickOutside: onClickOutside }, focusTrapProps), ___EmotionJSX(Element, _extends({ className: classes, css: cssStyles, style: inlineStyles, ref: setRef, id: id }, rest, { role: announcesAsModal ? 'dialog' : rest.role, "aria-modal": announcesAsModal ? true : undefined, tabIndex: !isPushed ? 0 : rest.tabIndex, "aria-describedby": !isPushed ? ariaDescribedBy : _ariaDescribedBy, "aria-labelledby": ariaLabelledBy, "data-autofocus": !isPushed || undefined, onAnimationEnd: onAnimationEnd }), !isPushed && screenReaderDescription, shouldRenderMenu ? ___EmotionJSX(EuiFlyoutMenu, _extends({}, flyoutMenuProps, { titleId: flyoutMenuId })) : !hideCloseButton && ___EmotionJSX(EuiFlyoutCloseButton, _extends({}, closeButtonProps, { onClose: function onClose(event) { return handleClose(event, 'close-button'); }, closeButtonPosition: closeButtonPosition, side: side })), resizable && ___EmotionJSX(EuiFlyoutResizeButton, { type: type, side: side, ownFocus: ownFocus, isPushed: isPushed, onMouseDown: onMouseDownResizableButton, onTouchStart: onMouseDownResizableButton, onKeyDown: onKeyDownResizableButton }), ___EmotionJSX(EuiFlyoutParentProvider, null, children)))); } // React.forwardRef interferes with the inferred element type // Casting to ensure correct element prop type checking for `as` // e.g., `href` is not on a `div` ); // Recast to allow `displayName` EuiFlyoutComponent.displayName = 'EuiFlyoutComponent';