@elastic/eui
Version:
Elastic UI Component Library
727 lines (698 loc) • 40.1 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _typeof = require("@babel/runtime/helpers/typeof");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.EuiFlyoutComponent = void 0;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties"));
var _react = _interopRequireWildcard(require("react"));
var _classnames = _interopRequireDefault(require("classnames"));
var _services = require("../../services");
var _manager = require("./manager");
var _const = require("./manager/const");
var _focus_trap = require("../focus_trap");
var _i18n = require("../i18n");
var _resize_observer = require("../observer/resize_observer");
var _accessibility = require("../accessibility");
var _flyout_close_button = require("./_flyout_close_button");
var _flyout = require("./flyout.styles");
var _component_defaults = require("../provider/component_defaults");
var _const2 = require("./const");
var _hooks = require("./hooks");
var _flyout_menu = require("./flyout_menu");
var _flyout_overlay = require("./_flyout_overlay");
var _flyout_resize_button = require("./_flyout_resize_button");
var _use_flyout_resizable = require("./use_flyout_resizable");
var _use_flyout_z_index = require("./use_flyout_z_index");
var _flyout_parent_context = require("./flyout_parent_context");
var _use_flyout_menu = require("./use_flyout_menu");
var _react2 = require("@emotion/react");
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"];
/*
* 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 */
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != _typeof(e) && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), 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; }
/**
* 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';
var EuiFlyoutComponent = exports.EuiFlyoutComponent = /*#__PURE__*/(0, _react.forwardRef)(function (props, ref) {
var _maskProps$headerZind, _managerSessions, _managerState$layoutM, _managerState$current, _managerState$flyouts, _managerState$pushPad;
var _usePropsWithComponen = (0, _component_defaults.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 ? _const2.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 ? _const2.DEFAULT_SIDE : _usePropsWithComponen6,
_usePropsWithComponen7 = _usePropsWithComponen.size,
_size = _usePropsWithComponen7 === void 0 ? _const2.DEFAULT_SIZE : _usePropsWithComponen7,
_usePropsWithComponen8 = _usePropsWithComponen.paddingSize,
paddingSize = _usePropsWithComponen8 === void 0 ? _const2.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 ? _const2.DEFAULT_TYPE : _usePropsWithComponen11,
outsideClickCloses = _usePropsWithComponen.outsideClickCloses,
_usePropsWithComponen12 = _usePropsWithComponen.pushMinBreakpoint,
pushMinBreakpoint = _usePropsWithComponen12 === void 0 ? _const2.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 = (0, _objectWithoutProperties2.default)(_usePropsWithComponen, _excluded);
var container = resolveContainer(containerProp);
var hasAnimationDefault = type === 'overlay';
var hasAnimation = _hasAnimation !== null && _hasAnimation !== void 0 ? _hasAnimation : hasAnimationDefault;
var _useEuiThemeCSSVariab = (0, _services.useEuiThemeCSSVariables)(),
setGlobalCSSVariables = _useEuiThemeCSSVariab.setGlobalCSSVariables;
var Element = as || defaultElement;
var maskRef = (0, _react.useRef)(null);
// Ref for the main flyout element to pass to context
var internalParentFlyoutRef = (0, _react.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 = (0, _resize_observer.useResizeObserver)(container !== null && container !== void 0 ? container : null, 'width');
var containerReferenceWidth = container ? containerDimensions.width || container.clientWidth : undefined;
var isPushed = (0, _hooks.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.
(0, _react.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 = (0, _manager.useIsInManagedFlyout)();
var flyoutId = (0, _manager.useFlyoutId)(id);
var flyoutManager = (0, _manager.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 : _const.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 = (0, _react.useRef)((_managerState$current = managerState === null || managerState === void 0 ? void 0 : managerState.currentZIndex) !== null && _managerState$current !== void 0 ? _managerState$current : 0);
var _useEuiFlyoutMenu = (0, _use_flyout_menu.useEuiFlyoutMenu)({
flyoutMenuProps: _flyoutMenuProps,
flyoutMenuDisplayMode: flyoutMenuDisplayMode,
ariaLabelledBy: _ariaLabelledBy
}),
flyoutMenuId = _useEuiFlyoutMenu.flyoutMenuId,
flyoutMenuProps = _useEuiFlyoutMenu.flyoutMenuProps,
shouldRenderMenu = _useEuiFlyoutMenu.shouldRenderMenu,
ariaLabelledBy = _useEuiFlyoutMenu.ariaLabelledBy;
(0, _react.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 = (0, _react.useRef)(flyoutManager);
(0, _react.useEffect)(function () {
flyoutManagerRef.current = flyoutManager;
}, [flyoutManager]);
(0, _react.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 = (0, _react.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 === _const.LAYOUT_MODE_STACKED ? 0 : _siblingFlyoutWidth;
// Track the container's bounding rect for positioning the flyout.
var _useState = (0, _react.useState)(null),
_useState2 = (0, _slicedToArray2.default)(_useState, 2),
containerRect = _useState2[0],
setContainerRect = _useState2[1];
(0, _react.useLayoutEffect)(function () {
if (!container) {
setContainerRect(null);
return;
}
setContainerRect(container.getBoundingClientRect());
}, [container, containerDimensions.width]);
(0, _react.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 = (0, _use_flyout_resizable.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.
(0, _react.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 = (0, _react.useState)(null),
_useState4 = (0, _slicedToArray2.default)(_useState3, 2),
resizeRef = _useState4[0],
setResizeRef = _useState4[1];
var setRef = (0, _services.useCombinedRefs)([setResizeRef, ref, internalParentFlyoutRef, setFlyoutRef]);
var _useResizeObserver = (0, _resize_observer.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.
*/
(0, _react.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 === _const.LAYOUT_MODE_STACKED && isMainFlyout && _siblingFlyoutWidth ? _siblingFlyoutWidth : width;
if (shouldApplyPadding) {
paddingTarget.style[paddingSide] = "".concat(paddingWidth, "px");
if (shouldSetGlobalPushVars) {
setGlobalCSSVariables((0, _defineProperty2.default)({}, cssVarName, "".concat(paddingWidth, "px")));
}
if (isInManagedContext && flyoutManagerRef.current) {
flyoutManagerRef.current.setPushPadding(managerSide, paddingWidth);
}
} else {
paddingTarget.style[paddingSide] = previousPadding;
if (shouldSetGlobalPushVars) {
setGlobalCSSVariables((0, _defineProperty2.default)({}, cssVarName, null));
}
if (isInManagedContext && flyoutManagerRef.current) {
flyoutManagerRef.current.setPushPadding(managerSide, 0);
}
}
return function () {
paddingTarget.style[paddingSide] = previousPadding;
if (shouldSetGlobalPushVars) {
setGlobalCSSVariables((0, _defineProperty2.default)({}, 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)
*/
(0, _react.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 = (0, _react.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 = (0, _react.useCallback)(function (event, reason) {
onClose(event, reason ? {
reason: reason
} : undefined);
}, [onClose]);
/**
* ESC key closes flyout based on flyout hierarchy rules
*/
var onKeyDown = (0, _react.useCallback)(function (event) {
if (!isPushed && event.key === _services.keys.ESCAPE && shouldCloseOnEscape) {
event.preventDefault();
handleClose(event, 'escape');
}
}, [handleClose, isPushed, shouldCloseOnEscape]);
var managedFlyoutIndex = currentZIndexRef.current;
if (isInManagedContext && currentSession) {
managedFlyoutIndex = currentSession.zIndex;
}
var _useEuiFlyoutZIndex = (0, _use_flyout_z_index.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 = (0, _react.useMemo)(function () {
var composedStyles = (0, _flyout.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 === _const.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 === _const.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 = (0, _services.useEuiMemoizedStyles)(_flyout.euiFlyoutStyles);
var cssStyles = [styles.euiFlyout, styles.paddingSizes[paddingSize], (0, _const2.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 = (0, _classnames.default)('euiFlyout', isChildFlyout && hasChildBackground && 'euiFlyout--hasChildBackground', className);
var flyoutToggle = (0, _react.useRef)(document.activeElement);
var _useState5 = (0, _react.useState)([]),
_useState6 = (0, _slicedToArray2.default)(_useState5, 2),
focusTrapShards = _useState6[0],
setFocusTrapShards = _useState6[1];
var isSideBySideChild = isChildFlyout && layoutMode === _const.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 = (0, _react.useMemo)(function () {
return !isPushed && !isSideBySideChild;
}, [isPushed, isSideBySideChild]);
var focusTrapSelectors = (0, _react.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(_const.PROPERTY_LEVEL, "=\"").concat(_const.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 = (0, _react.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]);
(0, _react.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 = _services.focusTrapPubSub.subscribe(function () {
return findShards();
});
return unsubscribe;
}, [findShards]);
var focusTrapProps = (0, _react.useMemo)(function () {
return _objectSpread(_objectSpread({}, _focusTrapProps), {}, {
shards: [].concat((0, _toConsumableArray2.default)(focusTrapShards), (0, _toConsumableArray2.default)((_focusTrapProps === null || _focusTrapProps === void 0 ? void 0 : _focusTrapProps.shards) || []))
});
}, [_focusTrapProps, focusTrapShards]);
/*
* Provide meaningful screen reader instructions/details
*/
var hasOverlayMask = ownFocus && !isPushed;
var descriptionId = (0, _services.useGeneratedHtmlId)();
var ariaDescribedBy = (0, _classnames.default)(descriptionId, _ariaDescribedBy);
var screenReaderDescription = (0, _react.useMemo)(function () {
return (0, _react2.jsx)(_accessibility.EuiScreenReaderOnly, null, (0, _react2.jsx)("p", {
id: descriptionId
}, hasOverlayMask ? (0, _react2.jsx)(_i18n.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."
}) : (0, _react2.jsx)(_i18n.EuiI18n, {
token: "euiFlyout.screenReaderNoOverlayMaskDialog",
default: "You are in a modal dialog. To close the dialog, press Escape."
}), ' ', focusTrapShards.length > 0 && (0, _react2.jsx)(_i18n.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 = (0, _react.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 = (0, _services.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 (0, _react2.jsx)(_flyout_overlay.EuiFlyoutOverlay, {
hasOverlayMask: hasOverlayMask,
isPushed: isPushed,
maskZIndex: maskZIndex,
headerZindexLocation: effectiveHeaderZindexLocation,
containerRect: containerRect,
maskProps: _objectSpread(_objectSpread({}, maskProps), {}, {
maskRef: maskCombinedRefs,
hasAnimation: hasAnimation
})
}, (0, _react2.jsx)(_services.EuiWindowEvent, {
event: "keydown",
handler: onKeyDown
}), (0, _react2.jsx)(_focus_trap.EuiFocusTrap, (0, _extends2.default)({
disabled: isPushed,
scrollLock: shouldUseScrollLock,
clickOutsideDisables: !ownFocus,
onClickOutside: onClickOutside
}, focusTrapProps), (0, _react2.jsx)(Element, (0, _extends2.default)({
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 ? (0, _react2.jsx)(_flyout_menu.EuiFlyoutMenu, (0, _extends2.default)({}, flyoutMenuProps, {
titleId: flyoutMenuId
})) : !hideCloseButton && (0, _react2.jsx)(_flyout_close_button.EuiFlyoutCloseButton, (0, _extends2.default)({}, closeButtonProps, {
onClose: function onClose(event) {
return handleClose(event, 'close-button');
},
closeButtonPosition: closeButtonPosition,
side: side
})), resizable && (0, _react2.jsx)(_flyout_resize_button.EuiFlyoutResizeButton, {
type: type,
side: side,
ownFocus: ownFocus,
isPushed: isPushed,
onMouseDown: onMouseDownResizableButton,
onTouchStart: onMouseDownResizableButton,
onKeyDown: onKeyDownResizableButton
}), (0, _react2.jsx)(_flyout_parent_context.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';