UNPKG

@carbon/react

Version:

React components for the Carbon Design System

342 lines (340 loc) 13.1 kB
/** * Copyright IBM Corp. 2016, 2026 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. */ const require_runtime = require("../../_virtual/_rolldown/runtime.js"); const require_usePrefix = require("../../internal/usePrefix.js"); const require_useIsomorphicEffect = require("../../internal/useIsomorphicEffect.js"); const require_deprecateValuesWithin = require("../../prop-types/deprecateValuesWithin.js"); const require_utils = require("../../internal/utils.js"); const require_useMergedRefs = require("../../internal/useMergedRefs.js"); const require_useEvent = require("../../internal/useEvent.js"); const require_mapPopoverAlign = require("../../tools/mapPopoverAlign.js"); const require_index = require("../FeatureFlags/index.js"); let classnames = require("classnames"); classnames = require_runtime.__toESM(classnames); let react = require("react"); react = require_runtime.__toESM(react); let prop_types = require("prop-types"); prop_types = require_runtime.__toESM(prop_types); let react_jsx_runtime = require("react/jsx-runtime"); let _floating_ui_react = require("@floating-ui/react"); //#region src/components/Popover/index.tsx /** * Copyright IBM Corp. 2016, 2026 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. */ const PopoverContext = react.default.createContext({ setFloating: { current: null }, caretRef: { current: null }, autoAlign: null }); const Popover = react.default.forwardRef(function PopoverRenderFunction({ isTabTip, align: initialAlign = isTabTip ? "bottom-start" : "bottom", as: BaseComponent = "span", autoAlign = false, autoAlignBoundary, backgroundToken = "layer", caret = !isTabTip, className: customClassName, children, border = false, dropShadow = true, highContrast = false, onRequestClose, open, alignmentAxisOffset, ...rest }, forwardRef) { const prefix = require_usePrefix.usePrefix(); const floating = (0, react.useRef)(null); const caretRef = (0, react.useRef)(null); const popover = (0, react.useRef)(null); const enableFloatingStyles = require_index.useFeatureFlag("enable-v12-dynamic-floating-styles") || autoAlign; const lastClickWasInsidePopoverContent = (0, react.useRef)(false); let align = require_mapPopoverAlign.mapPopoverAlign(initialAlign); require_useEvent.useEvent(popover, "mousedown", (event) => { const target = event.target; lastClickWasInsidePopoverContent.current = refs.floating.current?.contains(target) || false; if (lastClickWasInsidePopoverContent.current) setTimeout(() => { lastClickWasInsidePopoverContent.current = false; }, 0); }); require_useEvent.useEvent(popover, "focusout", (event) => { const relatedTarget = event.relatedTarget; if (!relatedTarget) { if (lastClickWasInsidePopoverContent.current) { lastClickWasInsidePopoverContent.current = false; return; } onRequestClose?.(); } else if (relatedTarget && !popover.current?.contains(relatedTarget)) { const isOutsideFloating = enableFloatingStyles && refs.floating.current ? !refs.floating.current.contains(relatedTarget) : true; const isFocusableWrapper = relatedTarget && popover.current && relatedTarget.contains(popover.current); if (isOutsideFloating && !isFocusableWrapper) onRequestClose?.(); } }); require_useEvent.useWindowEvent("click", ({ target }) => { if (open && target instanceof Node && !popover.current?.contains(target)) onRequestClose?.(); }); const popoverDimensions = (0, react.useRef)({ offset: 10, caretHeight: react.default.Children.toArray(children).some((x) => { return x?.props?.className?.includes("slug") || x?.props?.className?.includes("ai-label"); }) ? 7 : 6 }); require_useIsomorphicEffect.default(() => { if (caret && popover.current) { const getStyle = window.getComputedStyle(popover.current, null); const offsetProperty = getStyle.getPropertyValue(`--${prefix}-popover-offset`); const caretProperty = getStyle.getPropertyValue(`--${prefix}-popover-caret-height`); if (offsetProperty) popoverDimensions.current.offset = offsetProperty.includes("px") ? Number(offsetProperty.split("px", 1)[0]) * 1 : Number(offsetProperty.split("rem", 1)[0]) * 16; if (caretProperty) popoverDimensions.current.caretHeight = caretProperty.includes("px") ? Number(caretProperty.split("px", 1)[0]) * 1 : Number(caretProperty.split("rem", 1)[0]) * 16; } }); const { refs, floatingStyles, placement, middlewareData, elements, update } = (0, _floating_ui_react.useFloating)(enableFloatingStyles ? { placement: align, strategy: "fixed", middleware: [ (0, _floating_ui_react.offset)(!isTabTip ? { alignmentAxis: alignmentAxisOffset, mainAxis: caret ? popoverDimensions?.current?.offset : 4 } : 0), autoAlign && (0, _floating_ui_react.flip)({ fallbackPlacements: isTabTip ? align.includes("bottom") ? [ "bottom-start", "bottom-end", "top-start", "top-end" ] : [ "top-start", "top-end", "bottom-start", "bottom-end" ] : align.includes("bottom") ? [ "bottom", "bottom-start", "bottom-end", "right", "right-start", "right-end", "left", "left-start", "left-end", "top", "top-start", "top-end" ] : [ "top", "top-start", "top-end", "left", "left-start", "left-end", "right", "right-start", "right-end", "bottom", "bottom-start", "bottom-end" ], fallbackStrategy: "initialPlacement", fallbackAxisSideDirection: "start", boundary: autoAlignBoundary }), (0, _floating_ui_react.arrow)({ element: caretRef, padding: 16 }), autoAlign && (0, _floating_ui_react.hide)() ] } : {}); (0, react.useEffect)(() => { if (!enableFloatingStyles) return; if (open && elements.reference && elements.floating) return (0, _floating_ui_react.autoUpdate)(elements.reference, elements.floating, update); }, [ enableFloatingStyles, open, elements, update ]); const value = (0, react.useMemo)(() => { return { floating, setFloating: refs.setFloating, caretRef, autoAlign }; }, [refs.setFloating, autoAlign]); if (isTabTip) { if (!["bottom-start", "bottom-end"].includes(align)) align = "bottom-start"; } (0, react.useEffect)(() => { if (enableFloatingStyles) { const updatedFloatingStyles = { ...floatingStyles, visibility: middlewareData.hide?.referenceHidden ? "hidden" : "visible" }; Object.keys(updatedFloatingStyles).forEach((style) => { if (refs.floating.current) refs.floating.current.style[style] = updatedFloatingStyles[style]; }); if (caret && middlewareData && middlewareData.arrow && caretRef?.current) { const { x, y } = middlewareData.arrow; const staticSide = { top: "bottom", right: "left", bottom: "top", left: "right" }[placement.split("-")[0]]; caretRef.current.style.left = x != null ? `${x}px` : ""; caretRef.current.style.top = y != null ? `${y}px` : ""; caretRef.current.style.right = ""; caretRef.current.style.bottom = ""; if (staticSide) caretRef.current.style[staticSide] = `${-popoverDimensions?.current?.caretHeight}px`; } } }, [ floatingStyles, refs.floating, enableFloatingStyles, middlewareData, placement, caret ]); const ref = require_useMergedRefs.useMergedRefs([forwardRef, popover]); const currentAlignment = autoAlign && placement !== align ? placement : align; const className = (0, classnames.default)({ [`${prefix}--popover-container`]: true, [`${prefix}--popover--caret`]: caret, [`${prefix}--popover--drop-shadow`]: dropShadow, [`${prefix}--popover--border`]: border, [`${prefix}--popover--high-contrast`]: highContrast, [`${prefix}--popover--open`]: open, [`${prefix}--popover--auto-align ${prefix}--autoalign`]: enableFloatingStyles, [`${prefix}--popover--${currentAlignment}`]: true, [`${prefix}--popover--tab-tip`]: isTabTip, [`${prefix}--popover--background-token__background`]: backgroundToken === "background" && !highContrast }, customClassName); const mappedChildren = react.default.Children.map(children, (child) => { const item = child; const isToggletipButton = item?.type?.displayName === "ToggletipButton"; const isToggletipContent = item?.type?.displayName === "ToggletipContent"; const isPopoverContent = require_utils.isComponentElement(item, PopoverContent); /** * Only trigger elements (button) or trigger components (ToggletipButton) should be * cloned because these will be decorated with a trigger-specific className and ref. * * There are also some specific components that should not be cloned when autoAlign * is on, even if they are a trigger element. */ const isTriggerElement = item?.type === "button"; const isTriggerComponent = enableFloatingStyles && isToggletipButton; const isAllowedTriggerComponent = enableFloatingStyles && !isToggletipContent && !isPopoverContent; if (react.default.isValidElement(item) && (isTriggerElement || isTriggerComponent || isAllowedTriggerComponent)) { const className = (item?.props)?.className; const ref = (item?.props).ref; const tabTipClasses = (0, classnames.default)(`${prefix}--popover--tab-tip__button`, className); return react.default.cloneElement(item, { className: isTabTip && item?.type === "button" ? tabTipClasses : className || "", ref: (node) => { if (enableFloatingStyles && !isPopoverContent) refs.setReference(node); if (typeof ref === "function") ref(node); else if (ref !== null && ref !== void 0) ref.current = node; } }); } else return item; }); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PopoverContext.Provider, { value, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BaseComponent, { ...rest, className, ref, children: enableFloatingStyles || isTabTip ? mappedChildren : children }) }); }); if (process.env.NODE_ENV !== "production") Popover.displayName = "Popover"; Popover.propTypes = { align: require_deprecateValuesWithin.deprecateValuesWithin(prop_types.default.oneOf([ "top", "top-left", "top-right", "bottom", "bottom-left", "bottom-right", "left", "left-bottom", "left-top", "right", "right-bottom", "right-top", "top-start", "top-end", "bottom-start", "bottom-end", "left-end", "left-start", "right-end", "right-start" ]), [ "top", "top-start", "top-end", "bottom", "bottom-start", "bottom-end", "left", "left-start", "left-end", "right", "right-start", "right-end" ], require_mapPopoverAlign.mapPopoverAlign), alignmentAxisOffset: prop_types.default.number, as: prop_types.default.oneOfType([prop_types.default.string, prop_types.default.elementType]), autoAlign: prop_types.default.bool, backgroundToken: prop_types.default.oneOf(["layer", "background"]), autoAlignBoundary: prop_types.default.oneOfType([ prop_types.default.oneOf(["clippingAncestors"]), prop_types.default.elementType, prop_types.default.arrayOf(prop_types.default.elementType), prop_types.default.exact({ x: prop_types.default.number.isRequired, y: prop_types.default.number.isRequired, width: prop_types.default.number.isRequired, height: prop_types.default.number.isRequired }) ]), caret: prop_types.default.bool, border: prop_types.default.bool, children: prop_types.default.node, className: prop_types.default.string, dropShadow: prop_types.default.bool, highContrast: prop_types.default.bool, isTabTip: prop_types.default.bool, onRequestClose: prop_types.default.func, open: prop_types.default.bool.isRequired }; const frFn = react.forwardRef; const PopoverContent = frFn((props, forwardRef) => { const { className, children, ...rest } = props; const prefix = require_usePrefix.usePrefix(); const { setFloating, caretRef, autoAlign } = react.default.useContext(PopoverContext); const ref = require_useMergedRefs.useMergedRefs([setFloating, forwardRef]); const enableFloatingStyles = require_index.useFeatureFlag("enable-v12-dynamic-floating-styles") || autoAlign; return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { ...rest, className: `${prefix}--popover`, children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { className: (0, classnames.default)(`${prefix}--popover-content`, className), ref, children: [children, enableFloatingStyles && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: (0, classnames.default)({ [`${prefix}--popover-caret`]: true, [`${prefix}--popover--auto-align`]: true }), ref: caretRef })] }), !enableFloatingStyles && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: (0, classnames.default)({ [`${prefix}--popover-caret`]: true }), ref: caretRef })] }); }); PopoverContent.displayName = "PopoverContent"; PopoverContent.propTypes = { children: prop_types.default.node, className: prop_types.default.string }; //#endregion exports.Popover = Popover; exports.PopoverContent = PopoverContent;