@elastic/eui
Version:
Elastic UI Component Library
731 lines (701 loc) • 41.5 kB
JavaScript
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';