// Copyright (c) Meta Platforms, Inc. and affiliates. 'use client'; /** * @file useLayer.tsx * @input Uses React hooks, Popover API, CSS anchor positioning, typography tokens * @output Exports the public useLayer hook plus internal trigger helpers. * @position Core layer utility; used by useHoverCard, useTooltip, etc. * * SYNC: When modified, update: * - /packages/core/src/Layer/useLayer.doc.mjs * - /packages/core/src/Layer/useLayer.test.tsx * - /packages/core/src/Layer/index.ts */ import React, { useCallback, useEffect, useId, useMemo, useRef, useState, type ReactNode, type RefCallback } from 'react'; import * as stylex from "../style-runtime.js"; import type { StyleXStyles } from "../style-runtime.js"; import '../popover-dom.js'; import { createPortal } from 'react-dom'; import { addAnchorName, removeAnchorName } from "./anchorName.js"; import { currentGesture, currentGestureHasClicked } from "./gestureCounter.js"; import { resolveLayerPortalTarget } from "./layerHost.js"; import "../theme/tokens.stylex.js"; import { typeScaleVars, typographyVars } from "../theme/tokens.stylex.js"; import { overlayPaddingReset } from "../Layout/padding.stylex.js"; const _temp = { keoZOQ: "x1vhfslr", k1K539: "xlm3tn6", "$$css": true }; const styles = { base: { keoZOQ: "xdj266r", k1K539: "xat24cr", keTefX: "x1lziwak", k71WvV: "x14z9mp", kLKAdn: "xexx8yu", kGO01o: "x18d9i69", kZCmMZ: "x1c1uobl", kwRFfy: "xyri2b", kMzoRj: "xc342km", ksu8eU: "xng3xce", kVQacm: "x1rea2x4", kMv6JI: "x9ynric", kGuDYH: "xjm74w1", kLWn49: "xw6l6zx", kWkggS: "xjbqb8w", $$css: true as const }, fixed: { kVAEAm: "xixxii4", $$css: true as const }, offsetBlock: (offset: string) => [_temp, { "--x-marginBlockStart": (val => typeof val === "number" ? val + "px" : val != null ? val : undefined)(offset), "--x-marginBlockEnd": (val => typeof val === "number" ? val + "px" : val != null ? val : undefined)(offset) }], offsetInline: (offset: string) => [{ keTefX: offset != null ? "x4lel18" : offset, k71WvV: offset != null ? "x1c9tiao" : offset, $$css: true as const }, { "--x-marginInlineStart": (val => typeof val === "number" ? val + "px" : val != null ? val : undefined)(offset), "--x-marginInlineEnd": (val => typeof val === "number" ? val + "px" : val != null ? val : undefined)(offset) }] }; /** * Props for a control that sits on the trigger but must not dismiss the layer. */ interface KeepLayerOpenProps { onPointerDown: React.PointerEventHandler; /** * Capture phase, so spreading these props never collides with the control's * own `onClick`. */ onClickCapture: React.MouseEventHandler; } /** * Position placement relative to anchor. * Logical: start/end resolve against the popover's own inherited direction * via CSS (RTL contexts mirror automatically, no JS involved). */ export type LayerPlacement = 'above' | 'below' | 'start' | 'end'; /** * Alignment along the placement axis */ export type LayerAlignment = 'start' | 'center' | 'end'; /** * Render props for context mode (anchor positioning) */ export interface ContextRenderProps { /** * Who authors the layer's position styles. * * `'anchor'` (default): the hook derives CSS anchor-positioning styles — * `position-area` and `position-try-fallbacks` — from the logical * `placement`/`alignment`. * * `'custom'`: the consumer authors its own position styles via `style` * (e.g. explicit `anchor()` insets or an `anchor-size()` cover). The hook * keeps the popover behavior and the `position-anchor` wiring but emits no * placement-derived styles, so direction handling becomes the consumer's * responsibility. `placement`/`alignment` are ignored. * * @default 'anchor' */ positioning?: 'anchor' | 'custom'; /** * Logical placement relative to the anchor. Ignored when `positioning` * is `'custom'`. */ placement?: LayerPlacement; /** * Alignment along the placement axis. Ignored when `positioning` * is `'custom'`. */ alignment?: LayerAlignment; /** * Clearance between the layer and its anchor, as a CSS length (a number is * treated as `px`). Applied along the placement axis and flip-safe, so the * gap survives a `position-try-fallbacks` flip to the opposite side. * * Layers sit flush by default: the hook zeroes the UA margins so anchor * positioning has a clean box, and clearance is a deliberate choice per * surface. `var(--spacing-1)` is the system's standard clearance. * * Ignored when `positioning` is `'custom'` — that mode owns its own insets. * * @default 0 */ offset?: number | string; /** * ARIA role applied to the popover container (e.g. `'tooltip'`). Lets * consumers complete the ARIA pattern and gives test tooling a stable, * non-hashed selector for the layer. */ role?: string; /** * Accessible name applied to the popover container via `aria-label`. * Pair with `role` so layers with a named role (e.g. `'dialog'`) expose a * proper name to assistive technology. */ 'aria-label'?: string; /** * StyleX styles for the popover container. */ xstyle?: StyleXStyles; /** * Additional CSS class name(s) for the popover container. * Use with themeProps() for theme targeting when reflecting visual props. */ className?: string; /** * Inline styles for the popover container. * Merged after StyleX and anchor positioning styles. */ style?: React.CSSProperties; /** * HTML tag to render the popover container as. * * Defaults to `'div'`. Context layers render an inert `