UNPKG

@navikt/ds-react

Version:

React components from the Norwegian Labour and Welfare Administration.

211 lines 11.6 kB
var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; import { autoUpdate, flip, arrow as floatingArrow, hide, limitShift, offset, shift, size, useFloating, } from "@floating-ui/react-dom"; import React, { forwardRef, useEffect, useRef, useState, } from "react"; import { useModalContext } from "../../modal/Modal.context.js"; import { Slot } from "../../slot/Slot.js"; import { createContext } from "../../util/create-context.js"; import { useCallbackRef, useClientLayoutEffect, useMergeRefs, } from "../../util/hooks/index.js"; import { useOpenChangeAnimationComplete } from "../overlay/hooks/useOpenChangeAnimationComplete.js"; import { getSideAndAlignFromPlacement, transformOrigin, } from "./Floating.utils.js"; export const [FloatingProvider, useFloatingContext] = createContext({ name: "FloatingContext", hookName: "useFloating", providerName: "FloatingProvider", }); const Floating = ({ children }) => { const [anchor, setAnchor] = useState(null); return (React.createElement(FloatingProvider, { anchor: anchor, onAnchorChange: setAnchor }, children)); }; /** * `FloatingAnchor` provides an anchor for a Floating instance. * Allows anchoring to non-DOM nodes like a cursor position when used with `virtualRef`. */ const FloatingAnchor = forwardRef((_a, forwardedRef) => { var { virtualRef, asChild } = _a, rest = __rest(_a, ["virtualRef", "asChild"]); const context = useFloatingContext(); const ref = useRef(null); const mergedRef = useMergeRefs(forwardedRef, ref); useEffect(() => { // Allows anchoring the floating to non-DOM nodes like a cursor position. // We replace `anchorRef` with a virtual ref in such cases. context.onAnchorChange((virtualRef === null || virtualRef === void 0 ? void 0 : virtualRef.current) || ref.current); }); const Comp = asChild ? Slot : "div"; return virtualRef ? null : React.createElement(Comp, Object.assign({ ref: mergedRef }, rest)); }); /** * Floating Arrow */ const OPPOSITE_SIDE = { top: "bottom", right: "left", bottom: "top", left: "right", }; const FloatingArrow = ({ width, height, className }) => { const context = useFloatingContentContext(); const side = OPPOSITE_SIDE[context.placedSide]; return (React.createElement("span", { ref: context.onArrowChange, style: { position: "absolute", left: context.arrowX, top: context.arrowY, [side]: 0, transformOrigin: { top: "", right: "0 0", bottom: "center 0", left: "100% 0", }[context.placedSide], transform: { top: "translateY(100%)", right: "translateY(50%) rotate(90deg) translateX(-50%)", bottom: `rotate(180deg)`, left: "translateY(50%) rotate(-90deg) translateX(50%)", }[context.placedSide], visibility: context.hideArrow ? "hidden" : undefined, }, "aria-hidden": true }, React.createElement("svg", { className: className, width: width, height: height, viewBox: "0 0 30 10", preserveAspectRatio: "none", style: { display: "block" } }, React.createElement("polygon", { points: "0,0 30,0 15,10" })))); }; const [FloatingContentProvider, useFloatingContentContext] = createContext({ name: "FloatingContentContext", hookName: "useFloatingContentContext", providerName: "FloatingContentProvider", }); const FloatingContent = forwardRef((_a, forwardedRef) => { var _b, _c, _d, _e, _f, _g; var { children, side = "bottom", sideOffset = 0, align = "center", alignOffset = 0, avoidCollisions = true, collisionBoundary = [], collisionPadding: collisionPaddingProp = 0, hideWhenDetached = false, updatePositionStrategy = "optimized", onPlaced, arrow: _arrow, fallbackPlacements, enabled = true, autoUpdateWhileMounted = true } = _a, contentProps = __rest(_a, ["children", "side", "sideOffset", "align", "alignOffset", "avoidCollisions", "collisionBoundary", "collisionPadding", "hideWhenDetached", "updatePositionStrategy", "onPlaced", "arrow", "fallbackPlacements", "enabled", "autoUpdateWhileMounted"]); const context = useFloatingContext(); const modalContext = useModalContext(false); const arrowDefaults = Object.assign({ padding: 5, width: 0, height: 0 }, _arrow); const [arrow, setArrow] = useState(null); const arrowWidth = arrowDefaults.width; const arrowHeight = arrowDefaults.height; const desiredPlacement = (side + (align !== "center" ? "-" + align : "")); const collisionPadding = typeof collisionPaddingProp === "number" ? collisionPaddingProp : Object.assign({ top: 0, right: 0, bottom: 0, left: 0 }, collisionPaddingProp); const boundary = Array.isArray(collisionBoundary) ? collisionBoundary : [collisionBoundary]; const hasExplicitBoundaries = boundary.length > 0; /** * .filter(x => x !== null) does not narrow the type of the array enough. */ function isNotNull(value) { return value !== null; } const detectOverflowOptions = { padding: collisionPadding, boundary: boundary.filter(isNotNull), // with `strategy: 'fixed'`, this is the only way to get it to respect boundaries altBoundary: hasExplicitBoundaries, /* https://floating-ui.com/docs/flip#fallbackaxissidedirection */ fallbackAxisSideDirection: "end", fallbackPlacements, }; const { refs, floatingStyles, placement, isPositioned, middlewareData, elements: floatingElements, update, } = useFloating({ open: enabled, // default to `fixed` strategy so users don't have to pick and we also avoid focus scroll issues strategy: "fixed", placement: desiredPlacement, whileElementsMounted: autoUpdateWhileMounted ? (...args) => { const cleanup = autoUpdate(...args, { animationFrame: updatePositionStrategy === "always", }); return cleanup; } : undefined, elements: { reference: context.anchor, }, middleware: [ offset({ mainAxis: sideOffset + arrowHeight, alignmentAxis: alignOffset, }), avoidCollisions && shift({ mainAxis: true, crossAxis: false, limiter: limitShift(), }), avoidCollisions && flip(Object.assign({}, detectOverflowOptions)), size(Object.assign(Object.assign({}, detectOverflowOptions), { apply: ({ elements, rects, availableWidth, availableHeight }) => { const { width: anchorWidth, height: anchorHeight } = rects.reference; const contentStyle = elements.floating.style; /** * Allows styling and animations based on the available space. */ contentStyle.setProperty("--ac-floating-available-width", `${availableWidth}px`); contentStyle.setProperty("--ac-floating-available-height", `${availableHeight}px`); contentStyle.setProperty("--ac-floating-anchor-width", `${anchorWidth}px`); contentStyle.setProperty("--ac-floating-anchor-height", `${anchorHeight}px`); } })), arrow && floatingArrow({ element: arrow, padding: arrowDefaults.padding }), transformOrigin({ arrowWidth, arrowHeight }), hideWhenDetached && hide(Object.assign({ strategy: "referenceHidden" }, detectOverflowOptions)), ], }); useEffect(() => { if (autoUpdateWhileMounted || !enabled) { return; } if (floatingElements.reference && floatingElements.floating) { const cleanup = autoUpdate(floatingElements.reference, floatingElements.floating, update); return () => { cleanup(); }; } }, [autoUpdateWhileMounted, enabled, floatingElements, update]); useOpenChangeAnimationComplete({ enabled: !!(modalContext === null || modalContext === void 0 ? void 0 : modalContext.ref), open: enabled, ref: modalContext === null || modalContext === void 0 ? void 0 : modalContext.ref, onComplete: update, }); const [placedSide, placedAlign] = getSideAndAlignFromPlacement(placement); const handlePlaced = useCallbackRef(onPlaced); useClientLayoutEffect(() => { isPositioned && (handlePlaced === null || handlePlaced === void 0 ? void 0 : handlePlaced()); }, [isPositioned, handlePlaced]); const arrowX = (_b = middlewareData.arrow) === null || _b === void 0 ? void 0 : _b.x; const arrowY = (_c = middlewareData.arrow) === null || _c === void 0 ? void 0 : _c.y; const cannotCenterArrow = ((_d = middlewareData.arrow) === null || _d === void 0 ? void 0 : _d.centerOffset) !== 0; return (React.createElement("div", { ref: refs.setFloating, "data-aksel-floating-content-wrapper": "", style: Object.assign(Object.assign({}, floatingStyles), { transform: isPositioned ? floatingStyles.transform : "translate(0, -200%)", minWidth: "max-content", zIndex: "9999999", ["--ac-floating-transform-origin"]: [ (_e = middlewareData.transformOrigin) === null || _e === void 0 ? void 0 : _e.x, (_f = middlewareData.transformOrigin) === null || _f === void 0 ? void 0 : _f.y, ].join(" ") }), // Floating UI uses the `dir` attribute on the reference/floating node for logical alignment. // This attribute is necessary for both portalled and inline calculations. dir: "ltr" }, React.createElement(FloatingContentProvider, { placedSide: placedSide, onArrowChange: setArrow, arrowX: arrowX, arrowY: arrowY, hideArrow: cannotCenterArrow }, React.createElement("div", Object.assign({ ref: forwardedRef, "data-side": placedSide, "data-align": placedAlign }, contentProps, { style: Object.assign(Object.assign({}, contentProps.style), { // if the FloatingContent hasn't been placed yet (not all measurements done) // we prevent animations so that users's animation don't kick in too early referring wrong sides animation: !isPositioned ? "none" : undefined, // hide the content if using the hide middleware and should be hidden opacity: ((_g = middlewareData.hide) === null || _g === void 0 ? void 0 : _g.referenceHidden) ? 0 : undefined }) }), children, (_arrow === null || _arrow === void 0 ? void 0 : _arrow.height) && (_arrow === null || _arrow === void 0 ? void 0 : _arrow.width) && (React.createElement(FloatingArrow, { width: _arrow.width, height: _arrow.height, className: _arrow.className })))))); }); Floating.Anchor = FloatingAnchor; Floating.Content = FloatingContent; export { Floating }; //# sourceMappingURL=Floating.js.map