UNPKG

@kobalte/core

Version:

Unstyled components and primitives for building accessible web apps and design systems with SolidJS.

343 lines (336 loc) 11.3 kB
import { useLocale } from './XHJPQEZP.js'; import { Polymorphic } from './6Y7B2NEO.js'; import { createComponent, mergeProps, effect, setAttribute, template } from 'solid-js/web'; import { mergeDefaultProps, mergeRefs, getWindow } from '@kobalte/utils'; import { createContext, useContext, splitProps, createSignal, createEffect, onCleanup } from 'solid-js'; import { combineStyle } from '@solid-primitives/props'; import { autoUpdate, offset, flip, shift, size, hide, arrow, computePosition, platform } from '@floating-ui/dom'; var PopperContext = createContext(); function usePopperContext() { const context = useContext(PopperContext); if (context === void 0) { throw new Error("[kobalte]: `usePopperContext` must be used within a `Popper` component"); } return context; } // src/popper/popper-arrow.tsx var _tmpl$ = /* @__PURE__ */ template(`<svg display="block" viewBox="0 0 30 30" style="transform:scale(1.02)"><g><path fill="none" d="M23,27.8c1.1,1.2,3.4,2.2,5,2.2h2H0h2c1.7,0,3.9-1,5-2.2l6.6-7.2c0.7-0.8,2-0.8,2.7,0L23,27.8L23,27.8z"></path><path stroke="none" d="M23,27.8c1.1,1.2,3.4,2.2,5,2.2h2H0h2c1.7,0,3.9-1,5-2.2l6.6-7.2c0.7-0.8,2-0.8,2.7,0L23,27.8L23,27.8z">`); var DEFAULT_SIZE = 30; var HALF_DEFAULT_SIZE = DEFAULT_SIZE / 2; var ROTATION_DEG = { top: 180, right: -90, bottom: 0, left: 90 }; function PopperArrow(props) { const context = usePopperContext(); const mergedProps = mergeDefaultProps({ size: DEFAULT_SIZE }, props); const [local, others] = splitProps(mergedProps, ["ref", "style", "size"]); const dir = () => context.currentPlacement().split("-")[0]; const contentStyle = createComputedStyle(context.contentRef); const fill = () => contentStyle()?.getPropertyValue("background-color") || "none"; const stroke = () => contentStyle()?.getPropertyValue(`border-${dir()}-color`) || "none"; const borderWidth = () => contentStyle()?.getPropertyValue(`border-${dir()}-width`) || "0px"; const strokeWidth = () => { return Number.parseInt(borderWidth()) * 2 * (DEFAULT_SIZE / local.size); }; const rotate = () => { return `rotate(${ROTATION_DEG[dir()]} ${HALF_DEFAULT_SIZE} ${HALF_DEFAULT_SIZE}) translate(0 2)`; }; return createComponent(Polymorphic, mergeProps({ as: "div", ref(r$) { const _ref$ = mergeRefs(context.setArrowRef, local.ref); typeof _ref$ === "function" && _ref$(r$); }, "aria-hidden": "true", get style() { return combineStyle({ // server side rendering position: "absolute", "font-size": `${local.size}px`, width: "1em", height: "1em", "pointer-events": "none", fill: fill(), stroke: stroke(), "stroke-width": strokeWidth() }, local.style); } }, others, { get children() { const _el$ = _tmpl$(), _el$2 = _el$.firstChild; effect(() => setAttribute(_el$2, "transform", rotate())); return _el$; } })); } function createComputedStyle(element) { const [style, setStyle] = createSignal(); createEffect(() => { const el = element(); el && setStyle(getWindow(el).getComputedStyle(el)); }); return style; } function PopperPositioner(props) { const context = usePopperContext(); const [local, others] = splitProps(props, ["ref", "style"]); return createComponent(Polymorphic, mergeProps({ as: "div", ref(r$) { const _ref$ = mergeRefs(context.setPositionerRef, local.ref); typeof _ref$ === "function" && _ref$(r$); }, "data-popper-positioner": "", get style() { return combineStyle({ position: "absolute", top: 0, left: 0, "min-width": "max-content" }, local.style); } }, others)); } // src/popper/utils.ts function createDOMRect(anchorRect) { const { x = 0, y = 0, width = 0, height = 0 } = anchorRect ?? {}; if (typeof DOMRect === "function") { return new DOMRect(x, y, width, height); } const rect = { x, y, width, height, top: y, right: x + width, bottom: y + height, left: x }; return { ...rect, toJSON: () => rect }; } function getAnchorElement(anchor, getAnchorRect) { const contextElement = anchor; return { contextElement, getBoundingClientRect: () => { const anchorRect = getAnchorRect(anchor); if (anchorRect) { return createDOMRect(anchorRect); } if (anchor) { return anchor.getBoundingClientRect(); } return createDOMRect(); } }; } function isValidPlacement(flip2) { return /^(?:top|bottom|left|right)(?:-(?:start|end))?$/.test(flip2); } var REVERSE_BASE_PLACEMENT = { top: "bottom", right: "left", bottom: "top", left: "right" }; function getTransformOrigin(placement, readingDirection) { const [basePlacement, alignment] = placement.split("-"); const reversePlacement = REVERSE_BASE_PLACEMENT[basePlacement]; if (!alignment) { return `${reversePlacement} center`; } if (basePlacement === "left" || basePlacement === "right") { return `${reversePlacement} ${alignment === "start" ? "top" : "bottom"}`; } if (alignment === "start") { return `${reversePlacement} ${readingDirection === "rtl" ? "right" : "left"}`; } return `${reversePlacement} ${readingDirection === "rtl" ? "left" : "right"}`; } // src/popper/popper-root.tsx function PopperRoot(props) { const mergedProps = mergeDefaultProps({ getAnchorRect: (anchor) => anchor?.getBoundingClientRect(), placement: "bottom", gutter: 0, shift: 0, flip: true, slide: true, overlap: false, sameWidth: false, fitViewport: false, hideWhenDetached: false, detachedPadding: 0, arrowPadding: 4, overflowPadding: 8 }, props); const [positionerRef, setPositionerRef] = createSignal(); const [arrowRef, setArrowRef] = createSignal(); const [currentPlacement, setCurrentPlacement] = createSignal(mergedProps.placement); const anchorRef = () => getAnchorElement(mergedProps.anchorRef?.(), mergedProps.getAnchorRect); const { direction } = useLocale(); async function updatePosition() { const referenceEl = anchorRef(); const floatingEl = positionerRef(); const arrowEl = arrowRef(); if (!referenceEl || !floatingEl) { return; } const arrowOffset = (arrowEl?.clientHeight || 0) / 2; const finalGutter = typeof mergedProps.gutter === "number" ? mergedProps.gutter + arrowOffset : mergedProps.gutter ?? arrowOffset; floatingEl.style.setProperty("--kb-popper-content-overflow-padding", `${mergedProps.overflowPadding}px`); referenceEl.getBoundingClientRect(); const middleware = [ // https://floating-ui.com/docs/offset offset(({ placement }) => { const hasAlignment = !!placement.split("-")[1]; return { mainAxis: finalGutter, crossAxis: !hasAlignment ? mergedProps.shift : void 0, alignmentAxis: mergedProps.shift }; }) ]; if (mergedProps.flip !== false) { const fallbackPlacements = typeof mergedProps.flip === "string" ? mergedProps.flip.split(" ") : void 0; if (fallbackPlacements !== void 0 && !fallbackPlacements.every(isValidPlacement)) { throw new Error("`flip` expects a spaced-delimited list of placements"); } middleware.push(flip({ padding: mergedProps.overflowPadding, fallbackPlacements })); } if (mergedProps.slide || mergedProps.overlap) { middleware.push(shift({ mainAxis: mergedProps.slide, crossAxis: mergedProps.overlap, padding: mergedProps.overflowPadding })); } middleware.push(size({ padding: mergedProps.overflowPadding, apply({ availableWidth, availableHeight, rects }) { const referenceWidth = Math.round(rects.reference.width); availableWidth = Math.floor(availableWidth); availableHeight = Math.floor(availableHeight); floatingEl.style.setProperty("--kb-popper-anchor-width", `${referenceWidth}px`); floatingEl.style.setProperty("--kb-popper-content-available-width", `${availableWidth}px`); floatingEl.style.setProperty("--kb-popper-content-available-height", `${availableHeight}px`); if (mergedProps.sameWidth) { floatingEl.style.width = `${referenceWidth}px`; } if (mergedProps.fitViewport) { floatingEl.style.maxWidth = `${availableWidth}px`; floatingEl.style.maxHeight = `${availableHeight}px`; } } })); if (mergedProps.hideWhenDetached) { middleware.push(hide({ padding: mergedProps.detachedPadding })); } if (arrowEl) { middleware.push(arrow({ element: arrowEl, padding: mergedProps.arrowPadding })); } const pos = await computePosition(referenceEl, floatingEl, { placement: mergedProps.placement, strategy: "absolute", middleware, platform: { ...platform, isRTL: () => direction() === "rtl" } }); setCurrentPlacement(pos.placement); mergedProps.onCurrentPlacementChange?.(pos.placement); if (!floatingEl) { return; } floatingEl.style.setProperty("--kb-popper-content-transform-origin", getTransformOrigin(pos.placement, direction())); const x = Math.round(pos.x); const y = Math.round(pos.y); let visibility; if (mergedProps.hideWhenDetached) { visibility = pos.middlewareData.hide?.referenceHidden ? "hidden" : "visible"; } Object.assign(floatingEl.style, { top: "0", left: "0", transform: `translate3d(${x}px, ${y}px, 0)`, visibility }); if (arrowEl && pos.middlewareData.arrow) { const { x: arrowX, y: arrowY } = pos.middlewareData.arrow; const dir = pos.placement.split("-")[0]; Object.assign(arrowEl.style, { left: arrowX != null ? `${arrowX}px` : "", top: arrowY != null ? `${arrowY}px` : "", [dir]: "100%" }); } } createEffect(() => { const referenceEl = anchorRef(); const floatingEl = positionerRef(); if (!referenceEl || !floatingEl) { return; } const cleanupAutoUpdate = autoUpdate(referenceEl, floatingEl, updatePosition, { // JSDOM doesn't support ResizeObserver elementResize: typeof ResizeObserver === "function" }); onCleanup(cleanupAutoUpdate); }); createEffect(() => { const positioner = positionerRef(); const content = mergedProps.contentRef?.(); if (!positioner || !content) { return; } queueMicrotask(() => { positioner.style.zIndex = getComputedStyle(content).zIndex; }); }); const context = { currentPlacement, contentRef: () => mergedProps.contentRef?.(), setPositionerRef, setArrowRef }; return createComponent(PopperContext.Provider, { value: context, get children() { return mergedProps.children; } }); } // src/popper/index.tsx var Popper = Object.assign(PopperRoot, { Arrow: PopperArrow, Context: PopperContext, usePopperContext, Positioner: PopperPositioner }); export { Popper, PopperArrow, PopperContext, PopperPositioner, PopperRoot, usePopperContext };