UNPKG

@base-ui/react

Version:

Base UI is a library of headless ('unstyled') React components and low-level hooks. You gain complete control over your app's CSS and accessibility features.

198 lines (196 loc) 6.31 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createInlineMiddleware = createInlineMiddleware; exports.getInlineRectTriggerProps = getInlineRectTriggerProps; exports.updateInlineRectCoords = updateInlineRectCoords; var _dom = require("@floating-ui/utils/dom"); // Floating UI ships an `inline()` middleware. This local version mirrors its line-rect // selection while adding trigger identity checks, delayed-open hit-line reuse, and // improved left/right edge grouping for Preview Card's reusable trigger model. function createRect(left, top, right, bottom) { return { left, top, right, bottom, x: left, y: top, width: right - left, height: bottom - top }; } function copyRect(rect) { return { left: rect.left, top: rect.top, right: rect.right, bottom: rect.bottom, width: rect.width, height: rect.height }; } function getLineRects(rects) { const lines = []; let previousRect; let left = Number.POSITIVE_INFINITY; let top = Number.POSITIVE_INFINITY; let right = Number.NEGATIVE_INFINITY; let bottom = Number.NEGATIVE_INFINITY; for (const rect of Array.from(rects).sort((a, b) => a.top - b.top)) { left = Math.min(left, rect.left); top = Math.min(top, rect.top); right = Math.max(right, rect.right); bottom = Math.max(bottom, rect.bottom); if (!previousRect || rect.top - previousRect.top > previousRect.height / 2) { lines.push(copyRect(rect)); } else { const line = lines[lines.length - 1]; line.left = Math.min(line.left, rect.left); line.right = Math.max(line.right, rect.right); line.bottom = Math.max(line.bottom, rect.bottom); line.width = line.right - line.left; line.height = line.bottom - line.top; } previousRect = rect; } return { lines, fallback: createRect(left, top, right, bottom) }; } function findLineIndex(lines, x, y) { return lines.findIndex(lineRect => x > lineRect.left - 2 && x < lineRect.right + 2 && y > lineRect.top - 2 && y < lineRect.bottom + 2); } function createClientRect(rect) { return createRect(rect.left, rect.top, rect.right, rect.bottom); } function getInlineRectCoords(element, clientX, clientY) { const { lines } = getLineRects(element.getClientRects()); if (lines.length < 2) { return undefined; } const lineIndex = findLineIndex(lines, clientX, clientY); return { x: clientX, y: clientY, lineIndex: lineIndex === -1 ? undefined : lineIndex, element }; } function getInlineReferenceRect(reference, placement, coords) { const { lines, fallback } = getLineRects(reference.getClientRects()); if (lines.length < 2) { return null; } const x = coords?.x; const y = coords?.y; const side = placement[0]; if (coords?.lineIndex != null && lines[coords.lineIndex]) { return createClientRect(lines[coords.lineIndex]); } if (x != null && y != null) { const lineIndex = findLineIndex(lines, x, y); if (lineIndex !== -1) { return createClientRect(lines[lineIndex]); } } if (lines.length === 2 && lines[0].left > lines[1].right && x != null && y != null) { return fallback; } if (side === 't' || side === 'b') { const firstRect = lines[0]; const lastRect = lines[lines.length - 1]; const targetRect = side === 't' ? firstRect : lastRect; return createRect(targetRect.left, firstRect.top, targetRect.right, lastRect.bottom); } const isLeft = side === 'l'; let left = lines[0].left; let right = lines[0].right; let edge = isLeft ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY; let targetFirstRect = lines[0]; let targetLastRect = lines[0]; for (const rect of lines) { left = Math.min(left, rect.left); right = Math.max(right, rect.right); const nextEdge = isLeft ? rect.left : rect.right; if (isLeft && nextEdge < edge || !isLeft && nextEdge > edge) { edge = nextEdge; targetFirstRect = rect; targetLastRect = rect; } else if (nextEdge === edge) { targetLastRect = rect; } } return createRect(left, targetFirstRect.top, right, targetLastRect.bottom); } function getContextElement(reference) { if ('contextElement' in reference && reference.contextElement) { return reference.contextElement; } return (0, _dom.isElement)(reference) ? reference : undefined; } function getInlineRectTriggerProps(coordsRef, isOpen) { function updateCoords(event) { updateInlineRectCoords(coordsRef, event.currentTarget, event.clientX, event.clientY); } function updateCoordsOnMove(event) { if (!isOpen) { updateCoords(event); } } return { onFocus() { coordsRef.current = undefined; }, onMouseEnter: updateCoords, onMouseMove: updateCoordsOnMove }; } function updateInlineRectCoords(coordsRef, element, clientX, clientY) { const nextCoords = getInlineRectCoords(element, clientX, clientY); coordsRef.current = nextCoords; return nextCoords; } function createInlineMiddleware(coordsRef) { return { name: 'inline', async fn(state) { const reference = state.elements.reference; if (typeof reference?.getClientRects !== 'function') { return {}; } const contextElement = getContextElement(reference); const coords = coordsRef.current; const currentCoords = coords?.element === reference || coords?.element === contextElement ? coords : undefined; const rect = getInlineReferenceRect(reference, state.placement, currentCoords); if (!rect || typeof state.platform.getElementRects !== 'function') { return {}; } const resetRects = await state.platform.getElementRects({ reference: { contextElement, getBoundingClientRect() { return rect; } }, floating: state.elements.floating, strategy: state.strategy }); if (state.rects.reference.x === resetRects.reference.x && state.rects.reference.y === resetRects.reference.y && state.rects.reference.width === resetRects.reference.width && state.rects.reference.height === resetRects.reference.height) { return {}; } return { reset: { rects: resetRects } }; } }; }