@navikt/ds-react
Version:
React components from the Norwegian Labour and Welfare Administration.
211 lines • 11.6 kB
JavaScript
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