@ahrowe/ui 0.41.1 → 0.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/dist/cjs-types/common/floorPlan/floorPlan.graph.d.ts +10 -3
  2. package/dist/cjs-types/common/floorPlan/floorPlan.plan.d.ts +5 -0
  3. package/dist/cjs-types/common/floorPlan/floorPlan.rooms.d.ts +6 -2
  4. package/dist/cjs-types/common/floorPlan/floorPlan.snap.d.ts +1 -3
  5. package/dist/cjs-types/common/floorPlan/index.d.ts +1 -3
  6. package/dist/cjs-types/common/hooks/useAnchorTracking.d.ts +4 -3
  7. package/dist/cjs-types/common/planCanvas/planCanvas.types.d.ts +1 -1
  8. package/dist/cjs-types/common/popover/usePopoverPosition.d.ts +6 -1
  9. package/dist/cjs-types/common/roomDrawer/roomDrawer.types.d.ts +29 -8
  10. package/dist/cjs-types/common/roomViewer/roomViewer.types.d.ts +4 -2
  11. package/dist/esm/common/fab/fab.module.mjs.map +1 -1
  12. package/dist/esm/common/floorPlan/floorPlan.faces.mjs +1 -1
  13. package/dist/esm/common/floorPlan/floorPlan.faces.mjs.map +1 -1
  14. package/dist/esm/common/floorPlan/floorPlan.geometry.mjs +1 -1
  15. package/dist/esm/common/floorPlan/floorPlan.geometry.mjs.map +1 -1
  16. package/dist/esm/common/floorPlan/floorPlan.graph.mjs +1 -1
  17. package/dist/esm/common/floorPlan/floorPlan.graph.mjs.map +1 -1
  18. package/dist/esm/common/floorPlan/floorPlan.hit.mjs +1 -1
  19. package/dist/esm/common/floorPlan/floorPlan.hit.mjs.map +1 -1
  20. package/dist/esm/common/floorPlan/floorPlan.json.mjs +1 -1
  21. package/dist/esm/common/floorPlan/floorPlan.json.mjs.map +1 -1
  22. package/dist/esm/common/floorPlan/floorPlan.mesh.mjs +1 -1
  23. package/dist/esm/common/floorPlan/floorPlan.mesh.mjs.map +1 -1
  24. package/dist/esm/common/floorPlan/floorPlan.path.mjs +1 -1
  25. package/dist/esm/common/floorPlan/floorPlan.path.mjs.map +1 -1
  26. package/dist/esm/common/floorPlan/floorPlan.plan.mjs +1 -1
  27. package/dist/esm/common/floorPlan/floorPlan.plan.mjs.map +1 -1
  28. package/dist/esm/common/floorPlan/floorPlan.rooms.mjs +1 -1
  29. package/dist/esm/common/floorPlan/floorPlan.rooms.mjs.map +1 -1
  30. package/dist/esm/common/floorPlan/floorPlan.snap.mjs +1 -1
  31. package/dist/esm/common/floorPlan/floorPlan.snap.mjs.map +1 -1
  32. package/dist/esm/common/floorPlan/floorPlan.triangulate.mjs +1 -1
  33. package/dist/esm/common/floorPlan/floorPlan.triangulate.mjs.map +1 -1
  34. package/dist/esm/common/floorPlan/floorPlan.types.mjs +1 -1
  35. package/dist/esm/common/floorPlan/floorPlan.types.mjs.map +1 -1
  36. package/dist/esm/common/hooks/useAnchorTracking.mjs +1 -1
  37. package/dist/esm/common/hooks/useAnchorTracking.mjs.map +1 -1
  38. package/dist/esm/common/hooks/useFocusBoundary.mjs +1 -1
  39. package/dist/esm/common/hooks/useFocusBoundary.mjs.map +1 -1
  40. package/dist/esm/common/hooks/useOverlay.mjs +1 -1
  41. package/dist/esm/common/hooks/useOverlay.mjs.map +1 -1
  42. package/dist/esm/common/modal/modal.module.mjs.map +1 -1
  43. package/dist/esm/common/planCanvas/planCanvas.mjs +1 -1
  44. package/dist/esm/common/planCanvas/planCanvas.mjs.map +1 -1
  45. package/dist/esm/common/popover/popover.mjs +1 -1
  46. package/dist/esm/common/popover/popover.mjs.map +1 -1
  47. package/dist/esm/common/popover/popover.module.mjs.map +1 -1
  48. package/dist/esm/common/popover/usePopoverPosition.mjs +1 -1
  49. package/dist/esm/common/popover/usePopoverPosition.mjs.map +1 -1
  50. package/dist/esm/common/roomDrawer/roomDrawer.mjs +1 -1
  51. package/dist/esm/common/roomDrawer/roomDrawer.mjs.map +1 -1
  52. package/dist/esm/common/roomViewer/roomViewer.mjs +1 -1
  53. package/dist/esm/common/roomViewer/roomViewer.mjs.map +1 -1
  54. package/dist/esm/common/styles/sheet.module.mjs.map +1 -1
  55. package/dist/esm/common/tooltip/tooltip.module.mjs.map +1 -1
  56. package/dist/esm/floor-plan.mjs +1 -0
  57. package/dist/esm/index.mjs +1 -1
  58. package/dist/floor-plan.cjs +1 -0
  59. package/dist/floorPlan-DLe5XAx6.cjs +2 -0
  60. package/dist/floorPlan-DLe5XAx6.cjs.map +1 -0
  61. package/dist/index.cjs +3 -3
  62. package/dist/index.cjs.map +1 -1
  63. package/dist/style.css +1 -1
  64. package/dist/types/common/floorPlan/floorPlan.graph.d.ts +10 -3
  65. package/dist/types/common/floorPlan/floorPlan.plan.d.ts +5 -0
  66. package/dist/types/common/floorPlan/floorPlan.rooms.d.ts +6 -2
  67. package/dist/types/common/floorPlan/floorPlan.snap.d.ts +1 -3
  68. package/dist/types/common/floorPlan/index.d.ts +1 -3
  69. package/dist/types/common/hooks/useAnchorTracking.d.ts +4 -3
  70. package/dist/types/common/planCanvas/planCanvas.types.d.ts +1 -1
  71. package/dist/types/common/popover/usePopoverPosition.d.ts +6 -1
  72. package/dist/types/common/roomDrawer/roomDrawer.types.d.ts +29 -8
  73. package/dist/types/common/roomViewer/roomViewer.types.d.ts +4 -2
  74. package/docs/CLAUDE.md +3 -1
  75. package/docs/FloorPlan.md +18 -14
  76. package/docs/Modal.md +1 -1
  77. package/docs/RoomDrawer.md +42 -13
  78. package/docs/RoomViewer.md +1 -14
  79. package/package.json +18 -1
  80. package/dist/cjs-types/common/floorPlan/legacySvg.d.ts +0 -27
  81. package/dist/esm/common/floorPlan/legacySvg.mjs +0 -2
  82. package/dist/esm/common/floorPlan/legacySvg.mjs.map +0 -1
  83. package/dist/types/common/floorPlan/legacySvg.d.ts +0 -27
@@ -1 +1 @@
1
- {"version":3,"file":"planCanvas.mjs","names":[],"sources":["../../../../package/common/planCanvas/planCanvas.tsx"],"sourcesContent":["import { useMemo, type ReactElement } from 'react';\nimport cx from 'classnames';\n\nimport {\n OpeningKind,\n buildWallPaths,\n openingGeometry,\n type DerivedRoom,\n type PlanOpening,\n type PlanPoint,\n type PlanSelection,\n} from '../floorPlan';\nimport type { PlanCanvasProps } from './planCanvas.types';\nimport styles from './planCanvas.module.pcss';\n\n/**\n * The read-only renderer shared by RoomDrawer and RoomViewer.\n *\n * It owns the projection, the layer stack, and the accessible room shapes. It owns no\n * editing state: the drawer supplies its handles and previews through `overlay`. Having\n * one renderer is the point, so that a plan cannot look different in the editor than it\n * does in the viewer.\n *\n * The viewport is a TRANSFORM on a group, not an SVG `viewBox`. That is what lets the\n * screen-space layer draw labels and handles at a fixed pixel size while the drawing\n * itself scales.\n */\n\nconst DEFAULT_OUTLINE_PX = 1;\n\n/** Stable identity, so a default-valued `selection` prop does not re-run memos. */\nconst EMPTY_SELECTION: readonly PlanSelection[] = [];\n\nfunction formatAreaDefault(areaMm2: number): string {\n const m2 = areaMm2 / 1_000_000;\n return `${m2.toFixed(m2 < 10 ? 1 : 0)} m²`;\n}\n\nfunction ringToPath(ring: PlanPoint[]): string {\n if (ring.length === 0) return '';\n return `${ring.map((p, i) => `${i === 0 ? 'M' : 'L'} ${p.x},${p.y}`).join(' ')} Z`;\n}\n\n/** Outer ring plus holes as one path; `fill-rule: evenodd` punches the holes out. */\nfunction faceToPath(face: DerivedRoom): string {\n return [ringToPath(face.outer), ...face.holes.map(ringToPath)].filter(Boolean).join(' ');\n}\n\n/**\n * The ref lands on the `<svg>` itself, which is what a consumer wants for measuring or\n * screenshotting. Both the drawer and the viewer expose it as `getSvgElement()`.\n */\nfunction PlanCanvas(props: PlanCanvasProps) {\n const {\n ref,\n floor,\n underlay,\n rooms,\n view,\n selection = EMPTY_SELECTION,\n hovered = null,\n interactiveRooms = false,\n interactiveOpenings = false,\n showGrid = false,\n gridSize = 100,\n showRoomLabels = true,\n showOpeningSymbols = true,\n getRoomColor,\n renderRoomLabel,\n getRoomLabel,\n formatArea = formatAreaDefault,\n onRoomActivate,\n onOpeningActivate,\n onBackgroundActivate,\n onHoverChange,\n overlay,\n worldOverlay,\n size,\n className,\n style,\n classNames,\n styles: slotStyles,\n 'aria-label': ariaLabel,\n } = props;\n\n const pxToWorld = view.zoom > 0 ? 1 / view.zoom : 1;\n const outlineWidth = DEFAULT_OUTLINE_PX * pxToWorld;\n\n const wallPaths = useMemo(() => buildWallPaths(floor), [floor]);\n\n /**\n * Centrelines only, at one stroke width. The underlay is there to line walls up\n * against, so it wants to read as a tracing rather than compete with the floor being\n * drawn; the two-pass thick-wall treatment would give it equal weight.\n */\n const underlayLines = useMemo(() => {\n if (!underlay) return [];\n return Object.values(underlay.walls).flatMap((wall) => {\n const a = underlay.nodes[wall.a];\n const b = underlay.nodes[wall.b];\n return a && b ? [{ id: wall.id, x1: a.x, y1: a.y, x2: b.x, y2: b.y }] : [];\n });\n }, [underlay]);\n\n /** World point to screen point, for the untransformed layer. */\n const toScreen = (p: PlanPoint) => ({\n x: (p.x - view.x) * view.zoom,\n y: (p.y - view.y) * view.zoom,\n });\n\n // Grid lines are generated for the visible rect only. Deliberately not an SVG\n // <pattern>: a pattern needs an id, and two canvases on one page then collide.\n const gridLines = useMemo(() => {\n if (!showGrid || gridSize <= 0) return null;\n const worldW = size.width * pxToWorld;\n const worldH = size.height * pxToWorld;\n // Keep the line count sane when zoomed far out.\n const step = gridSize * Math.max(1, Math.pow(10, Math.ceil(Math.log10(worldW / gridSize / 60))));\n const startX = Math.floor(view.x / step) * step;\n const startY = Math.floor(view.y / step) * step;\n const verticals: number[] = [];\n const horizontals: number[] = [];\n for (let x = startX; x <= view.x + worldW; x += step) verticals.push(x);\n for (let y = startY; y <= view.y + worldH; y += step) horizontals.push(y);\n return { verticals, horizontals, step, worldW, worldH };\n }, [showGrid, gridSize, size.width, size.height, pxToWorld, view.x, view.y]);\n\n const isSelected = (type: string, id: string) => selection.some((entry) => entry.type === type && entry.id === id);\n const isHovered = (type: string, id: string) => hovered?.type === type && hovered.id === id;\n\n /**\n * The walls bounding the selected rooms. A SELECTED room is marked by colouring its\n * boundary rather than by flooding its floor, which would bury its doors and its label;\n * hover keeps the faint floor tint instead, since the two say different things.\n */\n const highlightedWalls = useMemo(() => {\n const selectedRooms = new Set(selection.filter((entry) => entry.type === 'room').map((entry) => entry.id));\n const ids = new Set<string>();\n for (const room of rooms) {\n if (room.roomId && selectedRooms.has(room.roomId)) {\n // A wall shared by two selected rooms belongs to both; the set draws it once.\n for (const id of room.wallIds) ids.add(id);\n }\n }\n return ids;\n }, [rooms, selection]);\n\n const roomEdge = useMemo(\n () => (highlightedWalls.size > 0 ? buildWallPaths(floor, { wallIds: highlightedWalls }) : { groups: [], caps: [] }),\n [floor, highlightedWalls],\n );\n\n function renderOpening(opening: PlanOpening) {\n const geo = openingGeometry(floor, opening);\n if (!geo) return null;\n const wall = floor.walls[opening.wallId];\n const half = wall ? wall.thickness / 2 : 50;\n const { p1, p2, normal, dir, centre } = geo;\n\n const selected = isSelected('opening', opening.id);\n const hoveredNow = isHovered('opening', opening.id);\n const width = Math.hypot(p2.x - p1.x, p2.y - p1.y);\n\n const parts: ReactElement[] = [];\n\n if (showOpeningSymbols && opening.kind === OpeningKind.Door) {\n // Leaf plus swing arc, drawn on the wall's left side.\n const hingeAtStart = opening.swing !== 'endLeft' && opening.swing !== 'endRight';\n const hinge = hingeAtStart ? p1 : p2;\n const sweepDir = hingeAtStart ? 1 : -1;\n const leafEnd = { x: hinge.x + normal.x * width, y: hinge.y + normal.y * width };\n const arcEnd = {\n x: hinge.x + dir.x * width * sweepDir,\n y: hinge.y + dir.y * width * sweepDir,\n };\n parts.push(\n <path\n key='leaf'\n className={styles.planCanvasDoorLeaf}\n d={`M ${hinge.x},${hinge.y} L ${leafEnd.x},${leafEnd.y}`}\n strokeWidth={outlineWidth}\n />,\n <path\n key='swing'\n className={styles.planCanvasDoorSwing}\n d={`M ${leafEnd.x},${leafEnd.y} A ${width},${width} 0 0,${hingeAtStart ? 0 : 1} ${arcEnd.x},${arcEnd.y}`}\n strokeWidth={outlineWidth}\n />,\n );\n }\n\n if (showOpeningSymbols && opening.kind === OpeningKind.Window) {\n // A pane line down the middle of the wall's thickness.\n parts.push(\n <path\n key='pane'\n className={styles.planCanvasWindowPane}\n d={`M ${p1.x},${p1.y} L ${p2.x},${p2.y}`}\n strokeWidth={outlineWidth * 2}\n />,\n );\n }\n\n const hitPath = `M ${p1.x + normal.x * half},${p1.y + normal.y * half} L ${\n p2.x + normal.x * half\n },${p2.y + normal.y * half} L ${p2.x - normal.x * half},${p2.y - normal.y * half} L ${\n p1.x - normal.x * half\n },${p1.y - normal.y * half} Z`;\n\n return (\n <g\n key={opening.id}\n className={cx(\n selected && styles.planCanvasOpeningSelected,\n hoveredNow && styles.planCanvasOpeningHovered,\n classNames?.opening,\n )}\n style={slotStyles?.opening}\n data-opening-kind={opening.kind}\n >\n {parts}\n <path\n className={cx(styles.planCanvasOpeningHit, interactiveOpenings && styles.planCanvasOpeningHitInteractive)}\n d={hitPath}\n onClick={\n interactiveOpenings && onOpeningActivate\n ? (e) => {\n e.stopPropagation();\n onOpeningActivate(opening.id);\n }\n : undefined\n }\n onPointerEnter={onHoverChange ? () => onHoverChange({ type: 'opening', id: opening.id }) : undefined}\n >\n {interactiveOpenings && <title>{opening.kind}</title>}\n </path>\n {(selected || hoveredNow) && (\n <path className={styles.planCanvasOpeningOutline} d={hitPath} vectorEffect='non-scaling-stroke' />\n )}\n {/* The jamb faces, so an opening reads as a hole with sides rather than a blank. */}\n <path\n className={cx(\n styles.planCanvasJamb,\n // The jamb face belongs to the wall it is cut into, so it follows that wall\n // into the highlight rather than staying white against it.\n highlightedWalls.has(opening.wallId) && styles.planCanvasJambHighlighted,\n )}\n d={`M ${p1.x - normal.x * half},${p1.y - normal.y * half} L ${p1.x + normal.x * half},${\n p1.y + normal.y * half\n } M ${p2.x - normal.x * half},${p2.y - normal.y * half} L ${p2.x + normal.x * half},${\n p2.y + normal.y * half\n }`}\n strokeWidth={outlineWidth}\n />\n {centre && null}\n </g>\n );\n }\n\n return (\n <svg\n ref={ref}\n className={cx(styles.planCanvas, className, classNames?.canvas)}\n style={{ ...style, ...slotStyles?.canvas }}\n viewBox={`0 0 ${Math.max(1, size.width)} ${Math.max(1, size.height)}`}\n width={size.width}\n height={size.height}\n role='img'\n aria-label={ariaLabel}\n onClick={onBackgroundActivate ? () => onBackgroundActivate() : undefined}\n onPointerLeave={onHoverChange ? () => onHoverChange(null) : undefined}\n >\n <g transform={`translate(${-view.x * view.zoom} ${-view.y * view.zoom}) scale(${view.zoom})`}>\n {/* 1. Grid */}\n {gridLines && (\n <g className={cx(styles.planCanvasGrid, classNames?.grid)} style={slotStyles?.grid} aria-hidden='true'>\n {gridLines.verticals.map((x) => (\n <line key={`v${x}`} x1={x} y1={view.y} x2={x} y2={view.y + gridLines.worldH} strokeWidth={pxToWorld} />\n ))}\n {gridLines.horizontals.map((y) => (\n <line key={`h${y}`} x1={view.x} y1={y} x2={view.x + gridLines.worldW} y2={y} strokeWidth={pxToWorld} />\n ))}\n </g>\n )}\n\n {/* 1b. The storey below, traced */}\n {underlayLines.length > 0 && (\n <g\n className={cx(styles.planCanvasUnderlay, classNames?.underlay)}\n style={slotStyles?.underlay}\n aria-hidden='true'\n >\n {/* Screen units: the rule sets vector-effect: non-scaling-stroke, so the width\n is read in screen pixels rather than world millimetres. */}\n {underlayLines.map((line) => (\n <line key={line.id} x1={line.x1} y1={line.y1} x2={line.x2} y2={line.y2} strokeWidth={1.5} />\n ))}\n </g>\n )}\n\n {/* 2. Room fills — the only hit target for room selection */}\n <g className={cx(styles.planCanvasRooms, classNames?.rooms)} style={slotStyles?.rooms}>\n {rooms.map((room) => {\n const selected = room.roomId ? isSelected('room', room.roomId) : false;\n const hoveredNow = room.roomId ? isHovered('room', room.roomId) : false;\n const colour = room.meta ? getRoomColor?.(room.meta, room.area) : undefined;\n const label =\n getRoomLabel?.(room.meta, { area: room.area }) ??\n `${room.meta?.name ?? 'Unnamed room'}, ${formatArea(room.area)}`;\n\n return (\n <path\n key={room.id}\n className={cx(\n styles.planCanvasRoomFill,\n hoveredNow && styles.planCanvasRoomFillHovered,\n interactiveRooms && styles.planCanvasRoomFillInteractive,\n classNames?.roomFill,\n )}\n data-state={selected ? 'selected' : hoveredNow ? 'hovered' : undefined}\n style={{ ...slotStyles?.roomFill, ...(colour ? { fill: colour } : null) }}\n d={faceToPath(room)}\n fillRule='evenodd'\n tabIndex={interactiveRooms ? 0 : undefined}\n role={interactiveRooms ? 'button' : undefined}\n aria-pressed={interactiveRooms ? selected : undefined}\n onClick={\n interactiveRooms && onRoomActivate && room.roomId\n ? (e) => {\n e.stopPropagation();\n onRoomActivate(room.roomId!);\n }\n : undefined\n }\n onKeyDown={\n interactiveRooms && onRoomActivate && room.roomId\n ? (e) => {\n if (e.key !== 'Enter' && e.key !== ' ') return;\n e.preventDefault();\n onRoomActivate(room.roomId!);\n }\n : undefined\n }\n onPointerEnter={\n onHoverChange && room.roomId ? () => onHoverChange({ type: 'room', id: room.roomId! }) : undefined\n }\n >\n {/* Both a <title> and the aria-label: aria-label on SVG shapes is\n unreliable in older Safari/VoiceOver, and <title> doubles as the\n native tooltip. */}\n {interactiveRooms && <title>{label}</title>}\n </path>\n );\n })}\n </g>\n\n {/* 3. Wall bodies — outline pass, then fill pass. Never interleaved: the visible\n outline is what is left of the first pass after the second covers its middle. */}\n <g className={cx(styles.planCanvasWalls, classNames?.walls)} style={slotStyles?.walls} aria-hidden='true'>\n <g className={styles.planCanvasWallOutline}>\n {wallPaths.groups.map((group) => (\n <path\n key={`o${group.thickness}`}\n className={classNames?.wallBody}\n style={slotStyles?.wallBody}\n d={group.d}\n strokeWidth={group.thickness}\n />\n ))}\n </g>\n <g className={styles.planCanvasWallFill}>\n {wallPaths.groups.map((group) => {\n const fillWidth = group.thickness - 2 * outlineWidth;\n // Zoomed far out the fill would invert; walls collapsing to solid lines is\n // the right look at that scale.\n if (fillWidth <= 0) return null;\n return <path key={`f${group.thickness}`} d={group.d} strokeWidth={fillWidth} />;\n })}\n </g>\n <g className={styles.planCanvasRoomEdgeSelected}>\n {/* The same two passes the walls themselves use, on the same path data: an\n outline at full thickness, then a fill inside it. Recolouring only the body\n left the wall edged in its normal colour, so it did not read as one object. */}\n {roomEdge.groups.map((group) => (\n <path\n key={`o${group.thickness}`}\n className={styles.planCanvasRoomEdgeOutline}\n d={group.d}\n strokeWidth={group.thickness}\n />\n ))}\n {roomEdge.groups.map((group) => {\n const fillWidth = group.thickness - 2 * outlineWidth;\n if (fillWidth <= 0) return null;\n return (\n <path\n key={`f${group.thickness}`}\n className={styles.planCanvasRoomEdgeFill}\n d={group.d}\n strokeWidth={fillWidth}\n />\n );\n })}\n </g>\n\n <g className={styles.planCanvasWallCaps}>\n {/* A highlighted wall's caps are drawn below in the highlight colour. Skipped\n here rather than painted over: an antialiased hairline is never fully\n covered, and the two would blend into a pale seam. */}\n {wallPaths.caps\n .filter((cap) => !highlightedWalls.has(cap.wallId))\n .map((cap, i) => (\n <line\n key={`${cap.wallId}-${cap.reason}-${i}`}\n x1={cap.a.x}\n y1={cap.a.y}\n x2={cap.b.x}\n y2={cap.b.y}\n strokeWidth={outlineWidth}\n />\n ))}\n </g>\n <g className={styles.planCanvasRoomEdgeCaps}>\n {roomEdge.caps.map((cap, i) => (\n <line\n key={`${cap.wallId}-${cap.reason}-${i}`}\n x1={cap.a.x}\n y1={cap.a.y}\n x2={cap.b.x}\n y2={cap.b.y}\n strokeWidth={outlineWidth}\n />\n ))}\n </g>\n </g>\n\n {/* 4. Openings */}\n <g className={cx(styles.planCanvasOpenings, classNames?.openings)} style={slotStyles?.openings}>\n {Object.values(floor.openings).map(renderOpening)}\n </g>\n\n {/* 5. Selection / hover outlines for walls and nodes */}\n <g className={styles.planCanvasSelection} aria-hidden='true'>\n {selection\n .filter((entry) => entry.type === 'wall')\n .map((entry) => {\n const wall = floor.walls[entry.id];\n if (!wall) return null;\n const a = floor.nodes[wall.a];\n const b = floor.nodes[wall.b];\n if (!a || !b) return null;\n return (\n <line\n key={entry.id}\n x1={a.x}\n y1={a.y}\n x2={b.x}\n y2={b.y}\n strokeWidth={wall.thickness}\n className={styles.planCanvasWallSelected}\n />\n );\n })}\n {hovered?.type === 'wall' &&\n !isSelected('wall', hovered.id) &&\n (() => {\n const wall = floor.walls[hovered.id];\n if (!wall) return null;\n const a = floor.nodes[wall.a];\n const b = floor.nodes[wall.b];\n if (!a || !b) return null;\n return (\n <line\n x1={a.x}\n y1={a.y}\n x2={b.x}\n y2={b.y}\n strokeWidth={wall.thickness}\n className={styles.planCanvasWallHovered}\n />\n );\n })()}\n </g>\n\n {worldOverlay}\n </g>\n\n {/* Screen space: sized in pixels, so labels and handles stay constant under zoom */}\n <g className={cx(styles.planCanvasScreen, classNames?.overlay)} style={slotStyles?.overlay}>\n {showRoomLabels &&\n rooms.map((room) => {\n const at = toScreen(room.anchor);\n if (at.x < -200 || at.y < -200 || at.x > size.width + 200 || at.y > size.height + 200) {\n return null;\n }\n const custom = renderRoomLabel?.(room.meta, { area: room.area, faceId: room.id });\n if (custom === null) return null;\n\n return (\n <g\n key={room.id}\n className={cx(styles.planCanvasRoomLabel, classNames?.roomLabel)}\n style={slotStyles?.roomLabel}\n transform={`translate(${at.x} ${at.y})`}\n aria-hidden='true'\n >\n {custom !== undefined ? (\n custom\n ) : (\n <>\n <text className={styles.planCanvasRoomName} textAnchor='middle'>\n {room.meta?.name ?? ''}\n </text>\n <text className={styles.planCanvasRoomArea} textAnchor='middle' dy='1.2em'>\n {formatArea(room.area)}\n </text>\n </>\n )}\n </g>\n );\n })}\n {overlay}\n </g>\n </svg>\n );\n}\n\nexport default PlanCanvas;\n"],"mappings":"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"}
1
+ {"version":3,"file":"planCanvas.mjs","names":[],"sources":["../../../../package/common/planCanvas/planCanvas.tsx"],"sourcesContent":["import { useMemo, type ReactElement } from 'react';\nimport cx from 'classnames';\n\nimport {\n OpeningKind,\n buildWallPaths,\n openingGeometry,\n type DerivedRoom,\n type PlanOpening,\n type PlanPoint,\n type PlanSelection,\n} from '../floorPlan';\nimport type { PlanCanvasProps } from './planCanvas.types';\nimport styles from './planCanvas.module.pcss';\n\n/**\n * The read-only renderer shared by RoomDrawer and RoomViewer.\n *\n * It owns the projection, the layer stack, and the accessible room shapes. It owns no\n * editing state: the drawer supplies its handles and previews through `overlay`. Having\n * one renderer is the point, so that a plan cannot look different in the editor than it\n * does in the viewer.\n *\n * The viewport is a TRANSFORM on a group, not an SVG `viewBox`. That is what lets the\n * screen-space layer draw labels and handles at a fixed pixel size while the drawing\n * itself scales.\n */\n\nconst DEFAULT_OUTLINE_PX = 1;\n\n/** Stable identity, so a default-valued `selection` prop does not re-run memos. */\nconst EMPTY_SELECTION: readonly PlanSelection[] = [];\n\nfunction formatAreaDefault(areaMm2: number): string {\n const m2 = areaMm2 / 1_000_000;\n return `${m2.toFixed(m2 < 10 ? 1 : 0)} m²`;\n}\n\nfunction ringToPath(ring: PlanPoint[]): string {\n if (ring.length === 0) return '';\n return `${ring.map((p, i) => `${i === 0 ? 'M' : 'L'} ${p.x},${p.y}`).join(' ')} Z`;\n}\n\n/** Outer ring plus holes as one path; `fill-rule: evenodd` punches the holes out. */\nfunction faceToPath(face: DerivedRoom): string {\n return [ringToPath(face.outer), ...face.holes.map(ringToPath)].filter(Boolean).join(' ');\n}\n\n/**\n * The ref lands on the `<svg>` itself, which is what a consumer wants for measuring or\n * screenshotting. Both the drawer and the viewer expose it as `getSvgElement()`.\n */\nfunction PlanCanvas(props: PlanCanvasProps) {\n const {\n ref,\n floor,\n underlay,\n rooms,\n view,\n selection = EMPTY_SELECTION,\n hovered = null,\n interactiveRooms = false,\n interactiveOpenings = false,\n showGrid = false,\n gridSize = 100,\n showRoomLabels = true,\n showOpeningSymbols = true,\n getRoomColor,\n renderRoomLabel,\n getRoomLabel,\n formatArea = formatAreaDefault,\n onRoomActivate,\n onOpeningActivate,\n onBackgroundActivate,\n onHoverChange,\n overlay,\n worldOverlay,\n size,\n className,\n style,\n classNames,\n styles: slotStyles,\n 'aria-label': ariaLabel,\n } = props;\n\n const pxToWorld = view.zoom > 0 ? 1 / view.zoom : 1;\n const outlineWidth = DEFAULT_OUTLINE_PX * pxToWorld;\n\n const wallPaths = useMemo(() => buildWallPaths(floor), [floor]);\n\n /**\n * Centrelines only, at one stroke width. The underlay is there to line walls up\n * against, so it wants to read as a tracing rather than compete with the floor being\n * drawn; the two-pass thick-wall treatment would give it equal weight.\n */\n const underlayLines = useMemo(() => {\n if (!underlay) return [];\n return Object.values(underlay.walls).flatMap((wall) => {\n const a = underlay.nodes[wall.a];\n const b = underlay.nodes[wall.b];\n return a && b ? [{ id: wall.id, x1: a.x, y1: a.y, x2: b.x, y2: b.y }] : [];\n });\n }, [underlay]);\n\n /** World point to screen point, for the untransformed layer. */\n const toScreen = (p: PlanPoint) => ({\n x: (p.x - view.x) * view.zoom,\n y: (p.y - view.y) * view.zoom,\n });\n\n // Grid lines are generated for the visible rect only. Deliberately not an SVG\n // <pattern>: a pattern needs an id, and two canvases on one page then collide.\n const gridLines = useMemo(() => {\n if (!showGrid || gridSize <= 0) return null;\n const worldW = size.width * pxToWorld;\n const worldH = size.height * pxToWorld;\n // Keep the line count sane when zoomed far out.\n const step = gridSize * Math.max(1, Math.pow(10, Math.ceil(Math.log10(worldW / gridSize / 60))));\n const startX = Math.floor(view.x / step) * step;\n const startY = Math.floor(view.y / step) * step;\n const verticals: number[] = [];\n const horizontals: number[] = [];\n for (let x = startX; x <= view.x + worldW; x += step) verticals.push(x);\n for (let y = startY; y <= view.y + worldH; y += step) horizontals.push(y);\n return { verticals, horizontals, step, worldW, worldH };\n }, [showGrid, gridSize, size.width, size.height, pxToWorld, view.x, view.y]);\n\n const isSelected = (type: string, id: string) => selection.some((entry) => entry.type === type && entry.id === id);\n const isHovered = (type: string, id: string) => hovered?.type === type && hovered.id === id;\n\n /**\n * The walls bounding the selected rooms. A SELECTED room is marked by colouring its\n * boundary rather than by flooding its floor, which would bury its doors and its label;\n * hover keeps the faint floor tint instead, since the two say different things.\n */\n const highlightedWalls = useMemo(() => {\n const selectedRooms = new Set(selection.filter((entry) => entry.type === 'room').map((entry) => entry.id));\n const ids = new Set<string>();\n for (const room of rooms) {\n if (room.roomId && selectedRooms.has(room.roomId)) {\n // A wall shared by two selected rooms belongs to both; the set draws it once.\n for (const id of room.wallIds) ids.add(id);\n }\n }\n return ids;\n }, [rooms, selection]);\n\n const roomEdge = useMemo(\n () => (highlightedWalls.size > 0 ? buildWallPaths(floor, { wallIds: highlightedWalls }) : { groups: [], caps: [] }),\n [floor, highlightedWalls],\n );\n\n function renderOpening(opening: PlanOpening) {\n const geo = openingGeometry(floor, opening);\n if (!geo) return null;\n const wall = floor.walls[opening.wallId];\n const half = wall ? wall.thickness / 2 : 50;\n const { p1, p2, normal, dir, centre } = geo;\n\n const selected = isSelected('opening', opening.id);\n const hoveredNow = isHovered('opening', opening.id);\n const width = Math.hypot(p2.x - p1.x, p2.y - p1.y);\n\n const parts: ReactElement[] = [];\n\n if (showOpeningSymbols && opening.kind === OpeningKind.Door) {\n // Leaf plus swing arc, drawn on the wall's left side.\n const hingeAtStart = opening.swing !== 'endLeft' && opening.swing !== 'endRight';\n const hinge = hingeAtStart ? p1 : p2;\n const sweepDir = hingeAtStart ? 1 : -1;\n const leafEnd = { x: hinge.x + normal.x * width, y: hinge.y + normal.y * width };\n const arcEnd = {\n x: hinge.x + dir.x * width * sweepDir,\n y: hinge.y + dir.y * width * sweepDir,\n };\n parts.push(\n <path\n key='leaf'\n className={styles.planCanvasDoorLeaf}\n d={`M ${hinge.x},${hinge.y} L ${leafEnd.x},${leafEnd.y}`}\n strokeWidth={outlineWidth}\n />,\n <path\n key='swing'\n className={styles.planCanvasDoorSwing}\n d={`M ${leafEnd.x},${leafEnd.y} A ${width},${width} 0 0,${hingeAtStart ? 0 : 1} ${arcEnd.x},${arcEnd.y}`}\n strokeWidth={outlineWidth}\n />,\n );\n }\n\n if (showOpeningSymbols && opening.kind === OpeningKind.Window) {\n // A pane line down the middle of the wall's thickness.\n parts.push(\n <path\n key='pane'\n className={styles.planCanvasWindowPane}\n d={`M ${p1.x},${p1.y} L ${p2.x},${p2.y}`}\n strokeWidth={outlineWidth * 2}\n />,\n );\n }\n\n const hitPath = `M ${p1.x + normal.x * half},${p1.y + normal.y * half} L ${\n p2.x + normal.x * half\n },${p2.y + normal.y * half} L ${p2.x - normal.x * half},${p2.y - normal.y * half} L ${\n p1.x - normal.x * half\n },${p1.y - normal.y * half} Z`;\n\n return (\n <g\n key={opening.id}\n className={cx(\n selected && styles.planCanvasOpeningSelected,\n hoveredNow && styles.planCanvasOpeningHovered,\n classNames?.opening,\n )}\n style={slotStyles?.opening}\n data-opening-kind={opening.kind}\n >\n {parts}\n <path\n className={cx(styles.planCanvasOpeningHit, interactiveOpenings && styles.planCanvasOpeningHitInteractive)}\n d={hitPath}\n onClick={\n interactiveOpenings && onOpeningActivate\n ? (e) => {\n e.stopPropagation();\n onOpeningActivate(opening.id);\n }\n : undefined\n }\n onPointerEnter={onHoverChange ? () => onHoverChange({ type: 'opening', id: opening.id }) : undefined}\n >\n {interactiveOpenings && <title>{opening.kind}</title>}\n </path>\n {(selected || hoveredNow) && (\n <path className={styles.planCanvasOpeningOutline} d={hitPath} vectorEffect='non-scaling-stroke' />\n )}\n {/* The jamb faces, so an opening reads as a hole with sides rather than a blank. */}\n <path\n className={cx(\n styles.planCanvasJamb,\n // The jamb face belongs to the wall it is cut into, so it follows that wall\n // into the highlight rather than staying white against it.\n highlightedWalls.has(opening.wallId) && styles.planCanvasJambHighlighted,\n )}\n d={`M ${p1.x - normal.x * half},${p1.y - normal.y * half} L ${p1.x + normal.x * half},${\n p1.y + normal.y * half\n } M ${p2.x - normal.x * half},${p2.y - normal.y * half} L ${p2.x + normal.x * half},${\n p2.y + normal.y * half\n }`}\n strokeWidth={outlineWidth}\n />\n {centre && null}\n </g>\n );\n }\n\n return (\n <svg\n ref={ref}\n className={cx(styles.planCanvas, className, classNames?.canvas)}\n style={{ ...style, ...slotStyles?.canvas }}\n viewBox={`0 0 ${Math.max(1, size.width)} ${Math.max(1, size.height)}`}\n width={size.width}\n height={size.height}\n role='img'\n aria-label={ariaLabel}\n onClick={onBackgroundActivate ? () => onBackgroundActivate() : undefined}\n onPointerLeave={onHoverChange ? () => onHoverChange(null) : undefined}\n >\n <g transform={`translate(${-view.x * view.zoom} ${-view.y * view.zoom}) scale(${view.zoom})`}>\n {/* 1. Grid */}\n {gridLines && (\n <g className={cx(styles.planCanvasGrid, classNames?.grid)} style={slotStyles?.grid} aria-hidden='true'>\n {gridLines.verticals.map((x) => (\n <line key={`v${x}`} x1={x} y1={view.y} x2={x} y2={view.y + gridLines.worldH} strokeWidth={pxToWorld} />\n ))}\n {gridLines.horizontals.map((y) => (\n <line key={`h${y}`} x1={view.x} y1={y} x2={view.x + gridLines.worldW} y2={y} strokeWidth={pxToWorld} />\n ))}\n </g>\n )}\n\n {/* 1b. The storey below, traced */}\n {underlayLines.length > 0 && (\n <g\n className={cx(styles.planCanvasUnderlay, classNames?.underlay)}\n style={slotStyles?.underlay}\n aria-hidden='true'\n >\n {/* Screen units: the rule sets vector-effect: non-scaling-stroke, so the width\n is read in screen pixels rather than world millimetres. */}\n {underlayLines.map((line) => (\n <line key={line.id} x1={line.x1} y1={line.y1} x2={line.x2} y2={line.y2} strokeWidth={1.5} />\n ))}\n </g>\n )}\n\n {/* 2. Room fills — the only hit target for room selection */}\n <g className={cx(styles.planCanvasRooms, classNames?.rooms)} style={slotStyles?.rooms}>\n {rooms.map((room) => {\n const selected = room.roomId ? isSelected('room', room.roomId) : false;\n const hoveredNow = room.roomId ? isHovered('room', room.roomId) : false;\n const colour = room.meta ? getRoomColor?.(room.meta, room.area) : undefined;\n const label = getRoomLabel?.(room.meta, { area: room.area });\n\n return (\n <path\n key={room.id}\n className={cx(\n styles.planCanvasRoomFill,\n hoveredNow && styles.planCanvasRoomFillHovered,\n interactiveRooms && styles.planCanvasRoomFillInteractive,\n classNames?.roomFill,\n )}\n data-state={selected ? 'selected' : hoveredNow ? 'hovered' : undefined}\n style={{ ...slotStyles?.roomFill, ...(colour ? { fill: colour } : null) }}\n d={faceToPath(room)}\n fillRule='evenodd'\n tabIndex={interactiveRooms ? 0 : undefined}\n role={interactiveRooms ? 'button' : undefined}\n aria-pressed={interactiveRooms ? selected : undefined}\n onClick={\n interactiveRooms && onRoomActivate && room.roomId\n ? (e) => {\n e.stopPropagation();\n onRoomActivate(room.roomId!);\n }\n : undefined\n }\n onKeyDown={\n interactiveRooms && onRoomActivate && room.roomId\n ? (e) => {\n if (e.key !== 'Enter' && e.key !== ' ') return;\n e.preventDefault();\n onRoomActivate(room.roomId!);\n }\n : undefined\n }\n onPointerEnter={\n onHoverChange && room.roomId ? () => onHoverChange({ type: 'room', id: room.roomId! }) : undefined\n }\n >\n {/* Both a <title> and the aria-label: aria-label on SVG shapes is\n unreliable in older Safari/VoiceOver, and <title> doubles as the\n native tooltip. */}\n {interactiveRooms && label && <title>{label}</title>}\n </path>\n );\n })}\n </g>\n\n {/* 3. Wall bodies — outline pass, then fill pass. Never interleaved: the visible\n outline is what is left of the first pass after the second covers its middle. */}\n <g className={cx(styles.planCanvasWalls, classNames?.walls)} style={slotStyles?.walls} aria-hidden='true'>\n <g className={styles.planCanvasWallOutline}>\n {wallPaths.groups.map((group) => (\n <path\n key={`o${group.thickness}`}\n className={classNames?.wallBody}\n style={slotStyles?.wallBody}\n d={group.d}\n strokeWidth={group.thickness}\n />\n ))}\n </g>\n <g className={styles.planCanvasWallFill}>\n {wallPaths.groups.map((group) => {\n const fillWidth = group.thickness - 2 * outlineWidth;\n // Zoomed far out the fill would invert; walls collapsing to solid lines is\n // the right look at that scale.\n if (fillWidth <= 0) return null;\n return <path key={`f${group.thickness}`} d={group.d} strokeWidth={fillWidth} />;\n })}\n </g>\n <g className={styles.planCanvasRoomEdgeSelected}>\n {/* The same two passes the walls themselves use, on the same path data: an\n outline at full thickness, then a fill inside it. Recolouring only the body\n left the wall edged in its normal colour, so it did not read as one object. */}\n {roomEdge.groups.map((group) => (\n <path\n key={`o${group.thickness}`}\n className={styles.planCanvasRoomEdgeOutline}\n d={group.d}\n strokeWidth={group.thickness}\n />\n ))}\n {roomEdge.groups.map((group) => {\n const fillWidth = group.thickness - 2 * outlineWidth;\n if (fillWidth <= 0) return null;\n return (\n <path\n key={`f${group.thickness}`}\n className={styles.planCanvasRoomEdgeFill}\n d={group.d}\n strokeWidth={fillWidth}\n />\n );\n })}\n </g>\n\n <g className={styles.planCanvasWallCaps}>\n {/* A highlighted wall's caps are drawn below in the highlight colour. Skipped\n here rather than painted over: an antialiased hairline is never fully\n covered, and the two would blend into a pale seam. */}\n {wallPaths.caps\n .filter((cap) => !highlightedWalls.has(cap.wallId))\n .map((cap, i) => (\n <line\n key={`${cap.wallId}-${cap.reason}-${i}`}\n x1={cap.a.x}\n y1={cap.a.y}\n x2={cap.b.x}\n y2={cap.b.y}\n strokeWidth={outlineWidth}\n />\n ))}\n </g>\n <g className={styles.planCanvasRoomEdgeCaps}>\n {roomEdge.caps.map((cap, i) => (\n <line\n key={`${cap.wallId}-${cap.reason}-${i}`}\n x1={cap.a.x}\n y1={cap.a.y}\n x2={cap.b.x}\n y2={cap.b.y}\n strokeWidth={outlineWidth}\n />\n ))}\n </g>\n </g>\n\n {/* 4. Openings */}\n <g className={cx(styles.planCanvasOpenings, classNames?.openings)} style={slotStyles?.openings}>\n {Object.values(floor.openings).map(renderOpening)}\n </g>\n\n {/* 5. Selection / hover outlines for walls and nodes */}\n <g className={styles.planCanvasSelection} aria-hidden='true'>\n {selection\n .filter((entry) => entry.type === 'wall')\n .map((entry) => {\n const wall = floor.walls[entry.id];\n if (!wall) return null;\n const a = floor.nodes[wall.a];\n const b = floor.nodes[wall.b];\n if (!a || !b) return null;\n return (\n <line\n key={entry.id}\n x1={a.x}\n y1={a.y}\n x2={b.x}\n y2={b.y}\n strokeWidth={wall.thickness}\n className={styles.planCanvasWallSelected}\n />\n );\n })}\n {hovered?.type === 'wall' &&\n !isSelected('wall', hovered.id) &&\n (() => {\n const wall = floor.walls[hovered.id];\n if (!wall) return null;\n const a = floor.nodes[wall.a];\n const b = floor.nodes[wall.b];\n if (!a || !b) return null;\n return (\n <line\n x1={a.x}\n y1={a.y}\n x2={b.x}\n y2={b.y}\n strokeWidth={wall.thickness}\n className={styles.planCanvasWallHovered}\n />\n );\n })()}\n </g>\n\n {worldOverlay}\n </g>\n\n {/* Screen space: sized in pixels, so labels and handles stay constant under zoom */}\n <g className={cx(styles.planCanvasScreen, classNames?.overlay)} style={slotStyles?.overlay}>\n {showRoomLabels &&\n rooms.map((room) => {\n const at = toScreen(room.anchor);\n if (at.x < -200 || at.y < -200 || at.x > size.width + 200 || at.y > size.height + 200) {\n return null;\n }\n const custom = renderRoomLabel?.(room.meta, { area: room.area, faceId: room.id });\n if (custom === null) return null;\n\n return (\n <g\n key={room.id}\n className={cx(styles.planCanvasRoomLabel, classNames?.roomLabel)}\n style={slotStyles?.roomLabel}\n transform={`translate(${at.x} ${at.y})`}\n aria-hidden='true'\n >\n {custom !== undefined ? (\n custom\n ) : (\n <>\n <text className={styles.planCanvasRoomName} textAnchor='middle'>\n {room.meta?.name ?? ''}\n </text>\n <text className={styles.planCanvasRoomArea} textAnchor='middle' dy='1.2em'>\n {formatArea(room.area)}\n </text>\n </>\n )}\n </g>\n );\n })}\n {overlay}\n </g>\n </svg>\n );\n}\n\nexport default PlanCanvas;\n"],"mappings":"mUA4BA,IAAM,EAAqB,EAGrB,EAA4C,CAAC,EAEnD,SAAS,EAAkB,EAAyB,CAClD,IAAM,EAAK,EAAU,IACrB,MAAO,GAAG,EAAG,QAAQ,IAAK,GAAU,EAAE,IACxC,CAEA,SAAS,EAAW,EAA2B,CAE7C,OADI,EAAK,SAAW,EAAU,GACvB,GAAG,EAAK,KAAK,EAAG,IAAM,GAAG,IAAM,EAAI,IAAM,IAAI,GAAG,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE,GACjF,CAGA,SAAS,EAAW,EAA2B,CAC7C,MAAO,CAAC,EAAW,EAAK,KAAK,EAAG,GAAG,EAAK,MAAM,IAAI,CAAU,CAAC,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG,CACzF,CAMA,SAAS,EAAW,EAAwB,CAC1C,GAAM,CACJ,MACA,QACA,WACA,QACA,OACA,YAAY,EACZ,UAAU,KACV,mBAAmB,GACnB,sBAAsB,GACtB,WAAW,GACX,WAAW,IACX,iBAAiB,GACjB,qBAAqB,GACrB,eACA,kBACA,eACA,aAAa,EACb,iBACA,oBACA,uBACA,gBACA,UACA,eACA,OACA,YACA,QACA,aACA,OAAQ,EACR,aAAc,GACZ,EAEE,EAAY,EAAK,KAAO,EAAI,EAAI,EAAK,KAAO,EAC5C,EAAe,EAAqB,EAEpC,EAAY,MAAc,EAAe,CAAK,EAAG,CAAC,CAAK,CAAC,EAOxD,EAAgB,MACf,EACE,OAAO,OAAO,EAAS,KAAK,CAAC,CAAC,QAAS,GAAS,CACrD,IAAM,EAAI,EAAS,MAAM,EAAK,GACxB,EAAI,EAAS,MAAM,EAAK,GAC9B,OAAO,GAAK,EAAI,CAAC,CAAE,GAAI,EAAK,GAAI,GAAI,EAAE,EAAG,GAAI,EAAE,EAAG,GAAI,EAAE,EAAG,GAAI,EAAE,CAAE,CAAC,EAAI,CAAC,CAC3E,CAAC,EALqB,CAAC,EAMtB,CAAC,CAAQ,CAAC,EAGP,EAAY,IAAkB,CAClC,GAAI,EAAE,EAAI,EAAK,GAAK,EAAK,KACzB,GAAI,EAAE,EAAI,EAAK,GAAK,EAAK,IAC3B,GAIM,EAAY,MAAc,CAC9B,GAAI,CAAC,GAAY,GAAY,EAAG,OAAO,KACvC,IAAM,EAAS,EAAK,MAAQ,EACtB,EAAS,EAAK,OAAS,EAEvB,EAAO,EAAW,KAAK,IAAI,EAAY,IAAI,KAAK,KAAK,KAAK,MAAM,EAAS,EAAW,EAAE,CAAC,CAAE,EACzF,EAAS,KAAK,MAAM,EAAK,EAAI,CAAI,EAAI,EACrC,EAAS,KAAK,MAAM,EAAK,EAAI,CAAI,EAAI,EACrC,EAAsB,CAAC,EACvB,EAAwB,CAAC,EAC/B,IAAK,IAAI,EAAI,EAAQ,GAAK,EAAK,EAAI,EAAQ,GAAK,EAAM,EAAU,KAAK,CAAC,EACtE,IAAK,IAAI,EAAI,EAAQ,GAAK,EAAK,EAAI,EAAQ,GAAK,EAAM,EAAY,KAAK,CAAC,EACxE,MAAO,CAAE,YAAW,cAAa,OAAM,SAAQ,QAAO,CACxD,EAAG,CAAC,EAAU,EAAU,EAAK,MAAO,EAAK,OAAQ,EAAW,EAAK,EAAG,EAAK,CAAC,CAAC,EAErE,GAAc,EAAc,IAAe,EAAU,KAAM,GAAU,EAAM,OAAS,GAAQ,EAAM,KAAO,CAAE,EAC3G,GAAa,EAAc,IAAe,GAAS,OAAS,GAAQ,EAAQ,KAAO,EAOnF,EAAmB,MAAc,CACrC,IAAM,EAAgB,IAAI,IAAI,EAAU,OAAQ,GAAU,EAAM,OAAS,MAAM,CAAC,CAAC,IAAK,GAAU,EAAM,EAAE,CAAC,EACnG,EAAM,IAAI,IAChB,IAAK,IAAM,KAAQ,EACjB,GAAI,EAAK,QAAU,EAAc,IAAI,EAAK,MAAM,EAE9C,IAAK,IAAM,KAAM,EAAK,QAAS,EAAI,IAAI,CAAE,EAG7C,OAAO,CACT,EAAG,CAAC,EAAO,CAAS,CAAC,EAEf,EAAW,MACR,EAAiB,KAAO,EAAI,EAAe,EAAO,CAAE,QAAS,CAAiB,CAAC,EAAI,CAAE,OAAQ,CAAC,EAAG,KAAM,CAAC,CAAE,EACjH,CAAC,EAAO,CAAgB,CAC1B,EAEA,SAAS,EAAc,EAAsB,CAC3C,IAAM,EAAM,EAAgB,EAAO,CAAO,EAC1C,GAAI,CAAC,EAAK,OAAO,KACjB,IAAM,EAAO,EAAM,MAAM,EAAQ,QAC3B,EAAO,EAAO,EAAK,UAAY,EAAI,GACnC,CAAE,KAAI,KAAI,SAAQ,MAAK,UAAW,EAElC,EAAW,EAAW,UAAW,EAAQ,EAAE,EAC3C,EAAa,EAAU,UAAW,EAAQ,EAAE,EAC5C,EAAQ,KAAK,MAAM,EAAG,EAAI,EAAG,EAAG,EAAG,EAAI,EAAG,CAAC,EAE3C,EAAwB,CAAC,EAE/B,GAAI,GAAsB,EAAQ,OAAS,EAAY,KAAM,CAE3D,IAAM,EAAe,EAAQ,QAAU,WAAa,EAAQ,QAAU,WAChE,EAAQ,EAAe,EAAK,EAC5B,EAAW,EAAe,EAAI,GAC9B,EAAU,CAAE,EAAG,EAAM,EAAI,EAAO,EAAI,EAAO,EAAG,EAAM,EAAI,EAAO,EAAI,CAAM,EACzE,EAAS,CACb,EAAG,EAAM,EAAI,EAAI,EAAI,EAAQ,EAC7B,EAAG,EAAM,EAAI,EAAI,EAAI,EAAQ,CAC/B,EACA,EAAM,KACJ,EAAC,OAAD,CAEE,UAAW,EAAO,mBAClB,EAAG,KAAK,EAAM,EAAE,GAAG,EAAM,EAAE,KAAK,EAAQ,EAAE,GAAG,EAAQ,IACrD,YAAa,CACd,EAJK,MAIL,EACD,EAAC,OAAD,CAEE,UAAW,EAAO,oBAClB,EAAG,KAAK,EAAQ,EAAE,GAAG,EAAQ,EAAE,KAAK,EAAM,GAAG,EAAM,OAAO,IAAqB,GAAG,EAAO,EAAE,GAAG,EAAO,IACrG,YAAa,CACd,EAJK,OAIL,CACH,CACF,CAEI,GAAsB,EAAQ,OAAS,EAAY,QAErD,EAAM,KACJ,EAAC,OAAD,CAEE,UAAW,EAAO,qBAClB,EAAG,KAAK,EAAG,EAAE,GAAG,EAAG,EAAE,KAAK,EAAG,EAAE,GAAG,EAAG,IACrC,YAAa,EAAe,CAC7B,EAJK,MAIL,CACH,EAGF,IAAM,EAAU,KAAK,EAAG,EAAI,EAAO,EAAI,EAAK,GAAG,EAAG,EAAI,EAAO,EAAI,EAAK,KACpE,EAAG,EAAI,EAAO,EAAI,EACnB,GAAG,EAAG,EAAI,EAAO,EAAI,EAAK,KAAK,EAAG,EAAI,EAAO,EAAI,EAAK,GAAG,EAAG,EAAI,EAAO,EAAI,EAAK,KAC/E,EAAG,EAAI,EAAO,EAAI,EACnB,GAAG,EAAG,EAAI,EAAO,EAAI,EAAK,IAE3B,OACE,EAAC,IAAD,CAEE,UAAW,EACT,GAAY,EAAO,0BACnB,GAAc,EAAO,yBACrB,GAAY,OACd,EACA,MAAO,GAAY,QACnB,oBAAmB,EAAQ,KAR7B,SAAA,CAUG,EACD,EAAC,OAAD,CACE,UAAW,EAAG,EAAO,qBAAsB,GAAuB,EAAO,+BAA+B,EACxG,EAAG,EACH,QACE,GAAuB,EAClB,GAAM,CACL,EAAE,gBAAgB,EAClB,EAAkB,EAAQ,EAAE,CAC9B,EACA,IAAA,GAEN,eAAgB,MAAsB,EAAc,CAAE,KAAM,UAAW,GAAI,EAAQ,EAAG,CAAC,EAAI,IAAA,GAE1F,SAAA,GAAuB,EAAC,QAAD,CAAA,SAAQ,EAAQ,IAAY,CAAA,CAChD,CAAA,GACJ,GAAY,IACZ,EAAC,OAAD,CAAM,UAAW,EAAO,yBAA0B,EAAG,EAAS,aAAa,oBAAsB,CAAA,EAGnG,EAAC,OAAD,CACE,UAAW,EACT,EAAO,eAGP,EAAiB,IAAI,EAAQ,MAAM,GAAK,EAAO,yBACjD,EACA,EAAG,KAAK,EAAG,EAAI,EAAO,EAAI,EAAK,GAAG,EAAG,EAAI,EAAO,EAAI,EAAK,KAAK,EAAG,EAAI,EAAO,EAAI,EAAK,GACnF,EAAG,EAAI,EAAO,EAAI,EACnB,KAAK,EAAG,EAAI,EAAO,EAAI,EAAK,GAAG,EAAG,EAAI,EAAO,EAAI,EAAK,KAAK,EAAG,EAAI,EAAO,EAAI,EAAK,GACjF,EAAG,EAAI,EAAO,EAAI,IAEpB,YAAa,CACd,CAAA,EACA,GAAU,IACV,CA5CI,EAAA,EAAQ,EA4CZ,CAEP,CAEA,OACE,EAAC,MAAD,CACO,MACL,UAAW,EAAG,EAAO,WAAY,EAAW,GAAY,MAAM,EAC9D,MAAO,CAAE,GAAG,EAAO,GAAG,GAAY,MAAO,EACzC,QAAS,OAAO,KAAK,IAAI,EAAG,EAAK,KAAK,EAAE,GAAG,KAAK,IAAI,EAAG,EAAK,MAAM,IAClE,MAAO,EAAK,MACZ,OAAQ,EAAK,OACb,KAAK,MACL,aAAY,EACZ,QAAS,MAA6B,EAAqB,EAAI,IAAA,GAC/D,eAAgB,MAAsB,EAAc,IAAI,EAAI,IAAA,GAV9D,SAAA,CAYE,EAAC,IAAD,CAAG,UAAW,aAAa,CAAC,EAAK,EAAI,EAAK,KAAK,GAAG,CAAC,EAAK,EAAI,EAAK,KAAK,UAAU,EAAK,KAAK,GAA1F,SAAA,CAEG,GACC,EAAC,IAAD,CAAG,UAAW,EAAG,EAAO,eAAgB,GAAY,IAAI,EAAG,MAAO,GAAY,KAAM,cAAY,OAAhG,SAAA,CACG,EAAU,UAAU,IAAK,GACxB,EAAC,OAAD,CAAoB,GAAI,EAAG,GAAI,EAAK,EAAG,GAAI,EAAG,GAAI,EAAK,EAAI,EAAU,OAAQ,YAAa,CAAY,EAA3F,IAAI,GAAuF,CACvG,EACA,EAAU,YAAY,IAAK,GAC1B,EAAC,OAAD,CAAoB,GAAI,EAAK,EAAG,GAAI,EAAG,GAAI,EAAK,EAAI,EAAU,OAAQ,GAAI,EAAG,YAAa,CAAY,EAA3F,IAAI,GAAuF,CACvG,CACA,IAIJ,EAAc,OAAS,GACtB,EAAC,IAAD,CACE,UAAW,EAAG,EAAO,mBAAoB,GAAY,QAAQ,EAC7D,MAAO,GAAY,SACnB,cAAY,OAIX,SAAA,EAAc,IAAK,GAClB,EAAC,OAAD,CAAoB,GAAI,EAAK,GAAI,GAAI,EAAK,GAAI,GAAI,EAAK,GAAI,GAAI,EAAK,GAAI,YAAa,GAAM,EAAhF,EAAK,EAA2E,CAC5F,CACA,CAAA,EAIL,EAAC,IAAD,CAAG,UAAW,EAAG,EAAO,gBAAiB,GAAY,KAAK,EAAG,MAAO,GAAY,MAC7E,SAAA,EAAM,IAAK,GAAS,CACnB,IAAM,EAAW,EAAK,OAAS,EAAW,OAAQ,EAAK,MAAM,EAAI,GAC3D,EAAa,EAAK,OAAS,EAAU,OAAQ,EAAK,MAAM,EAAI,GAC5D,EAAS,EAAK,KAAO,IAAe,EAAK,KAAM,EAAK,IAAI,EAAI,IAAA,GAC5D,EAAQ,IAAe,EAAK,KAAM,CAAE,KAAM,EAAK,IAAK,CAAC,EAE3D,OACE,EAAC,OAAD,CAEE,UAAW,EACT,EAAO,mBACP,GAAc,EAAO,0BACrB,GAAoB,EAAO,8BAC3B,GAAY,QACd,EACA,aAAY,EAAW,WAAa,EAAa,UAAY,IAAA,GAC7D,MAAO,CAAE,GAAG,GAAY,SAAU,GAAI,EAAS,CAAE,KAAM,CAAO,EAAI,IAAM,EACxE,EAAG,EAAW,CAAI,EAClB,SAAS,UACT,SAAU,EAAmB,EAAI,IAAA,GACjC,KAAM,EAAmB,SAAW,IAAA,GACpC,eAAc,EAAmB,EAAW,IAAA,GAC5C,QACE,GAAoB,GAAkB,EAAK,OACtC,GAAM,CACL,EAAE,gBAAgB,EAClB,EAAe,EAAK,MAAO,CAC7B,EACA,IAAA,GAEN,UACE,GAAoB,GAAkB,EAAK,OACtC,GAAM,EACD,EAAE,MAAQ,SAAW,EAAE,MAAQ,OACnC,EAAE,eAAe,EACjB,EAAe,EAAK,MAAO,EAC7B,EACA,IAAA,GAEN,eACE,GAAiB,EAAK,WAAe,EAAc,CAAE,KAAM,OAAQ,GAAI,EAAK,MAAQ,CAAC,EAAI,IAAA,GAM1F,SAAA,GAAoB,GAAS,EAAC,QAAD,CAAA,SAAQ,CAAa,CAAA,CAC/C,EAvCC,EAAK,EAuCN,CAEV,CAAC,CACA,CAAA,EAIH,EAAC,IAAD,CAAG,UAAW,EAAG,EAAO,gBAAiB,GAAY,KAAK,EAAG,MAAO,GAAY,MAAO,cAAY,OAAnG,SAAA,CACE,EAAC,IAAD,CAAG,UAAW,EAAO,sBAClB,SAAA,EAAU,OAAO,IAAK,GACrB,EAAC,OAAD,CAEE,UAAW,GAAY,SACvB,MAAO,GAAY,SACnB,EAAG,EAAM,EACT,YAAa,EAAM,SACpB,EALM,IAAI,EAAM,WAKhB,CACF,CACA,CAAA,EACH,EAAC,IAAD,CAAG,UAAW,EAAO,mBAClB,SAAA,EAAU,OAAO,IAAK,GAAU,CAC/B,IAAM,EAAY,EAAM,UAAY,EAAI,EAIxC,OADI,GAAa,EAAU,KACpB,EAAC,OAAD,CAAkC,EAAG,EAAM,EAAG,YAAa,CAAY,EAA5D,IAAI,EAAM,WAAkD,CAChF,CAAC,CACA,CAAA,EACH,EAAC,IAAD,CAAG,UAAW,EAAO,2BAArB,SAAA,CAIG,EAAS,OAAO,IAAK,GACpB,EAAC,OAAD,CAEE,UAAW,EAAO,0BAClB,EAAG,EAAM,EACT,YAAa,EAAM,SACpB,EAJM,IAAI,EAAM,WAIhB,CACF,EACA,EAAS,OAAO,IAAK,GAAU,CAC9B,IAAM,EAAY,EAAM,UAAY,EAAI,EAExC,OADI,GAAa,EAAU,KAEzB,EAAC,OAAD,CAEE,UAAW,EAAO,uBAClB,EAAG,EAAM,EACT,YAAa,CACd,EAJM,IAAI,EAAM,WAIhB,CAEL,CAAC,CACA,IAEH,EAAC,IAAD,CAAG,UAAW,EAAO,mBAIlB,SAAA,EAAU,KACR,OAAQ,GAAQ,CAAC,EAAiB,IAAI,EAAI,MAAM,CAAC,CAAC,CAClD,KAAK,EAAK,IACT,EAAC,OAAD,CAEE,GAAI,EAAI,EAAE,EACV,GAAI,EAAI,EAAE,EACV,GAAI,EAAI,EAAE,EACV,GAAI,EAAI,EAAE,EACV,YAAa,CACd,EANM,GAAG,EAAI,OAAO,GAAG,EAAI,OAAO,GAAG,GAMrC,CACF,CACF,CAAA,EACH,EAAC,IAAD,CAAG,UAAW,EAAO,uBAClB,SAAA,EAAS,KAAK,KAAK,EAAK,IACvB,EAAC,OAAD,CAEE,GAAI,EAAI,EAAE,EACV,GAAI,EAAI,EAAE,EACV,GAAI,EAAI,EAAE,EACV,GAAI,EAAI,EAAE,EACV,YAAa,CACd,EANM,GAAG,EAAI,OAAO,GAAG,EAAI,OAAO,GAAG,GAMrC,CACF,CACA,CAAA,CACF,IAGH,EAAC,IAAD,CAAG,UAAW,EAAG,EAAO,mBAAoB,GAAY,QAAQ,EAAG,MAAO,GAAY,SACnF,SAAA,OAAO,OAAO,EAAM,QAAQ,CAAC,CAAC,IAAI,CAAa,CAC/C,CAAA,EAGH,EAAC,IAAD,CAAG,UAAW,EAAO,oBAAqB,cAAY,OAAtD,SAAA,CACG,EACE,OAAQ,GAAU,EAAM,OAAS,MAAM,CAAC,CACxC,IAAK,GAAU,CACd,IAAM,EAAO,EAAM,MAAM,EAAM,IAC/B,GAAI,CAAC,EAAM,OAAO,KAClB,IAAM,EAAI,EAAM,MAAM,EAAK,GACrB,EAAI,EAAM,MAAM,EAAK,GAE3B,MADI,CAAC,GAAK,CAAC,EAAU,KAEnB,EAAC,OAAD,CAEE,GAAI,EAAE,EACN,GAAI,EAAE,EACN,GAAI,EAAE,EACN,GAAI,EAAE,EACN,YAAa,EAAK,UAClB,UAAW,EAAO,sBACnB,EAPM,EAAM,EAOZ,CAEL,CAAC,EACF,GAAS,OAAS,QACjB,CAAC,EAAW,OAAQ,EAAQ,EAAE,QACvB,CACL,IAAM,EAAO,EAAM,MAAM,EAAQ,IACjC,GAAI,CAAC,EAAM,OAAO,KAClB,IAAM,EAAI,EAAM,MAAM,EAAK,GACrB,EAAI,EAAM,MAAM,EAAK,GAE3B,MADI,CAAC,GAAK,CAAC,EAAU,KAEnB,EAAC,OAAD,CACE,GAAI,EAAE,EACN,GAAI,EAAE,EACN,GAAI,EAAE,EACN,GAAI,EAAE,EACN,YAAa,EAAK,UAClB,UAAW,EAAO,qBACnB,CAAA,CAEL,EAAA,CAAG,CACJ,IAEF,CACA,CAGH,CAAA,EAAA,EAAC,IAAD,CAAG,UAAW,EAAG,EAAO,iBAAkB,GAAY,OAAO,EAAG,MAAO,GAAY,QAAnF,SAAA,CACG,GACC,EAAM,IAAK,GAAS,CAClB,IAAM,EAAK,EAAS,EAAK,MAAM,EAC/B,GAAI,EAAG,EAAI,MAAQ,EAAG,EAAI,MAAQ,EAAG,EAAI,EAAK,MAAQ,KAAO,EAAG,EAAI,EAAK,OAAS,IAChF,OAAO,KAET,IAAM,EAAS,IAAkB,EAAK,KAAM,CAAE,KAAM,EAAK,KAAM,OAAQ,EAAK,EAAG,CAAC,EAGhF,OAFI,IAAW,KAAa,KAG1B,EAAC,IAAD,CAEE,UAAW,EAAG,EAAO,oBAAqB,GAAY,SAAS,EAC/D,MAAO,GAAY,UACnB,UAAW,aAAa,EAAG,EAAE,GAAG,EAAG,EAAE,GACrC,cAAY,OAEX,SAAA,IAAW,IAAA,GAGV,EAAA,EAAA,CAAA,SAAA,CACE,EAAC,OAAD,CAAM,UAAW,EAAO,mBAAoB,WAAW,SACpD,SAAA,EAAK,MAAM,MAAQ,EAChB,CAAA,EACN,EAAC,OAAD,CAAM,UAAW,EAAO,mBAAoB,WAAW,SAAS,GAAG,QAChE,SAAA,EAAW,EAAK,IAAI,CACjB,CAAA,CACN,CAAA,CAAA,EATF,CAWD,EAlBI,EAAK,EAkBT,CAEP,CAAC,EACF,CACA,CACA,CAAA,CAAA,GAET"}
@@ -1,2 +1,2 @@
1
- "use client";import{useComponentDefaults as e}from"../configProvider/useComponentDefaults.mjs";import{usePresentationDefault as t}from"../configProvider/usePresentationDefault.mjs";import{Presentation as n}from"../configProvider/presentation.types.mjs";import r from"../bodyEnd/bodyEnd.mjs";import i from"./popover.module.mjs";import a from"../styles/sheet.module.mjs";import ee from"../sheetBackdrop/sheetBackdrop.mjs";import{PopoverAlign as te,PopoverPlacement as ne,PopoverTrigger as o}from"./popover.types.mjs";import{usePopoverPosition as re}from"./usePopoverPosition.mjs";import{tabbablesIn as s,useFocusBoundary as ie}from"../hooks/useFocusBoundary.mjs";import ae from"../hooks/useSheet.mjs";import{useOverlay as c}from"../hooks/useOverlay.mjs";import{useCallback as l,useEffect as u,useRef as d,useState as f}from"react";import p from"classnames";import{jsx as m,jsxs as h}from"react/jsx-runtime";var oe=1e3;function g(e,t){return e?{current:e}:t}function _(_){let{children:se=null,anchor:v,content:ce=null,placement:y=ne.Bottom,align:le=te.Center,trigger:b=o.Click,isOpen:x,defaultOpen:ue=!1,onOpenChange:S=()=>{},offset:C=10,withArrow:w=!0,closeOnOutsideClick:T=!0,closeOnEscape:de=!0,openDelay:E=100,closeDelay:fe=120,disabled:D=!1,focusOnOpen:O=`keyboard`,closeOnTriggerClick:pe=!0,width:me=`auto`,constrainWidth:he=!0,presentation:ge,className:_e,style:ve,classNames:k,styles:A,...ye}=e(`Popover`,_),j=d(null),M=d(null),N=d(null),P=d(null),F=d(void 0),I=d(!1),L=d(!1),R=d(!1),z=d(null);function B(){I.current=!0}function be(e){B(),z.current=e.target}let[V,xe]=f(()=>({anchor:v,ref:g(v,j)}));V.anchor!==v&&xe({anchor:v,ref:g(v,j)});let H=V.ref,U=d(null),W=v!==void 0;u(()=>{if(!W)return;function e(e){U.current={target:e.target,keyboard:e.detail===0,time:performance.now()}}return document.addEventListener(`click`,e,!0),()=>document.removeEventListener(`click`,e,!0)},[W]);let G=x!==void 0,[Se,Ce]=f(ue),K=G?x:Se,q=d(S);u(()=>{q.current=S});let J=l((e,t=!1)=>{D&&e||(e&&(R.current=t),G||Ce(e),q.current(e,{fromKeyboard:t}))},[D,G]),we=t(),Te=ge??we??n.Default,{isSheet:Ee,sheetStyle:De}=ae(Te),Y=Ee&&b===o.Click;re({isOpen:!!K&&!Y,placement:y,align:le,offset:C,triggerRef:H,floatingRef:M,panelRef:N,arrowRef:w?P:{current:null},matchTriggerWidth:me===`trigger`});let{restoreFocusToTrigger:X,focusAfterTrigger:Oe}=ie({triggerRef:H,contentRef:M}),Z=d(X);u(()=>{Z.current=X});let Q=d(!1),ke=l(e=>{if(M.current=e,Q.current=!1,e)return()=>{M.current=null;let t=document.activeElement;Q.current=!!t&&(e.contains(t)||t===z.current)}},[]);u(()=>{if(K||!Q.current)return;Q.current=!1;let e=document.activeElement;(!e||e===document.body)&&Z.current()},[K]),c({isActive:!!K,containerRef:N,onEscape:de?()=>{M.current?.contains(document.activeElement)&&X(),J(!1,!0)}:void 0,trapFocus:!1,lockScroll:Y}),u(()=>{if(!K){R.current=!1;return}let e=U.current;U.current=null;let t=!!v&&!!e?.keyboard&&v.contains(e.target)&&performance.now()-e.time<oe;if(!(O===!0||O===`keyboard`&&(R.current||t))||b!==o.Click)return;let n=s(N.current);n.length!==0&&(M.current?.setAttribute(`role`,`dialog`),n[0].focus())},[K,b,O,v]);function Ae(e){if(e.key!==`Tab`||b!==o.Click)return;let t=s(N.current);if(t.length===0)return;let n=document.activeElement;!e.shiftKey&&n===t[t.length-1]?(e.preventDefault(),Oe(),J(!1,!0)):e.shiftKey&&n===t[0]&&(e.preventDefault(),X(),J(!1,!0))}u(()=>{if(!K)return;I.current=!1,L.current=!1;function e(e){return!!(M.current?.contains(e)||j.current?.contains(e)||H.current?.contains(e))}function t(t){let n=I.current;return I.current=!1,n||e(t.target)}function n(e){L.current=!t(e)}function r(e){let n=L.current;L.current=!1,!t(e)&&n&&J(!1)}function i(n){if(t(n))return;let r=n.relatedTarget;r&&(r===z.current||e(r))&&J(!1,!L.current)}return T&&b!==o.Hover&&(window.addEventListener(`mousedown`,n),window.addEventListener(`mouseup`,r),document.addEventListener(`focusin`,i)),()=>{window.removeEventListener(`mousedown`,n),window.removeEventListener(`mouseup`,r),document.removeEventListener(`focusin`,i)}},[K,T,b,J,H]),u(()=>()=>clearTimeout(F.current),[]);function $(e){clearTimeout(F.current),F.current=setTimeout(()=>J(e),e?E:fe)}function je(e){K||e.key!==`ArrowDown`&&e.key!==`ArrowUp`||(e.preventDefault(),J(!0,!0))}let Me=b===o.Click?{onClick:e=>{(!K||pe)&&J(!K,e.detail===0)},onKeyDown:je}:b===o.Hover?{onMouseEnter:()=>$(!0),onMouseLeave:()=>$(!1)}:{onFocus:()=>J(!0),onBlur:()=>J(!1)},Ne=b===o.Hover?{onMouseEnter:()=>$(!0),onMouseLeave:()=>$(!1)}:{};return h(`div`,{...ye,className:p(i.popover,_e,k?.root),style:{...ve,...A?.root},children:[m(`div`,{ref:j,className:p(i.popoverTrigger,k?.trigger),style:A?.trigger,...Me,children:se}),K&&h(r,{children:[Y&&m(ee,{onClose:()=>{T&&J(!1)},className:k?.backdrop,style:A?.backdrop}),h(`div`,{ref:ke,className:p(Y?a.sheet:i.popoverFloating,k?.floating),style:{...Y?De:void 0,...A?.floating},"data-placement":y,onKeyDown:Ae,onMouseDownCapture:B,onMouseUpCapture:B,onFocusCapture:be,...Ne,children:[m(`div`,{ref:N,className:p(i.popoverPanel,he&&i.popoverPanelConstrained,Y&&a.sheetPanel,k?.panel),style:A?.panel,children:ce}),w&&!Y&&m(`div`,{ref:P,className:p(i.popoverArrow,k?.arrow),style:A?.arrow})]})]})]})}export{_ as default};
1
+ "use client";import{useComponentDefaults as e}from"../configProvider/useComponentDefaults.mjs";import{usePresentationDefault as t}from"../configProvider/usePresentationDefault.mjs";import{Presentation as n}from"../configProvider/presentation.types.mjs";import r from"../bodyEnd/bodyEnd.mjs";import i from"./popover.module.mjs";import a from"../styles/sheet.module.mjs";import ee from"../sheetBackdrop/sheetBackdrop.mjs";import{PopoverAlign as te,PopoverPlacement as ne,PopoverTrigger as o}from"./popover.types.mjs";import{usePopoverPosition as re}from"./usePopoverPosition.mjs";import{tabbablesIn as s,useFocusBoundary as ie}from"../hooks/useFocusBoundary.mjs";import ae from"../hooks/useSheet.mjs";import{useOverlay as oe}from"../hooks/useOverlay.mjs";import{useCallback as c,useEffect as l,useRef as u,useState as d}from"react";import f from"classnames";import{jsx as p,jsxs as m}from"react/jsx-runtime";var se=1e3;function h(e,t){return e?{current:e}:t}function g(g){let{children:ce=null,anchor:_,content:le=null,placement:v=ne.Bottom,align:ue=te.Center,trigger:y=o.Click,isOpen:b,defaultOpen:de=!1,onOpenChange:x=()=>{},offset:fe=10,withArrow:S=!0,closeOnOutsideClick:C=!0,closeOnEscape:pe=!0,openDelay:me=100,closeDelay:he=120,disabled:w=!1,focusOnOpen:T=`keyboard`,closeOnTriggerClick:ge=!0,width:_e=`auto`,constrainWidth:ve=!0,presentation:ye,className:be,style:xe,classNames:E,styles:D,...Se}=e(`Popover`,g),O=u(null),k=u(null),A=u(null),j=u(null),M=u(void 0),N=u(!1),P=u(!1),F=u(!1),I=u(null);function L(){N.current=!0}function Ce(e){L(),I.current=e.target}let[R,we]=d(()=>({anchor:_,ref:h(_,O)}));R.anchor!==_&&we({anchor:_,ref:h(_,O)});let z=R.ref,B=u(null),V=_!==void 0;l(()=>{if(!V)return;function e(e){B.current={target:e.target,keyboard:e.detail===0,time:performance.now()}}return document.addEventListener(`click`,e,!0),()=>document.removeEventListener(`click`,e,!0)},[V]);let H=b!==void 0,[Te,Ee]=d(de),U=H?b:Te,W=u(x);l(()=>{W.current=x});let G=c((e,t=!1)=>{w&&e||(e&&(F.current=t),H||Ee(e),W.current(e,{fromKeyboard:t}))},[w,H]),De=t(),Oe=ye??De??n.Default,{isSheet:ke,sheetStyle:Ae}=ae(Oe),K=ke&&y===o.Click,q=u(!1),J=u(!1);function Y(){J.current=!1;let e=s(A.current);e.length!==0&&(k.current?.setAttribute(`role`,`dialog`),e[0].focus())}re({isOpen:!!U&&!K,placement:v,align:ue,offset:fe,triggerRef:z,floatingRef:k,panelRef:A,arrowRef:S?j:{current:null},matchTriggerWidth:_e===`trigger`,onShown:()=>{q.current=!0,J.current&&Y()}});let{restoreFocusToTrigger:X,focusAfterTrigger:je}=ie({triggerRef:z,contentRef:k}),Z=u(X);l(()=>{Z.current=X});let Q=u(!1),Me=c(e=>{if(k.current=e,Q.current=!1,e)return()=>{k.current=null;let t=document.activeElement;Q.current=!!t&&(e.contains(t)||t===I.current)}},[]);l(()=>{if(U||!Q.current)return;Q.current=!1;let e=document.activeElement;(!e||e===document.body)&&Z.current()},[U]),oe({isActive:!!U,containerRef:A,onEscape:pe?()=>{k.current?.contains(document.activeElement)&&X(),G(!1,!0)}:void 0,trapFocus:!1,lockScroll:K}),l(()=>{if(!U){F.current=!1,q.current=!1,J.current=!1;return}let e=B.current;B.current=null;let t=!!_&&!!e?.keyboard&&_.contains(e.target)&&performance.now()-e.time<se;(T===!0||T===`keyboard`&&(F.current||t))&&y===o.Click&&(q.current?Y():J.current=!0)},[U,y,T,_]);function Ne(e){if(e.key!==`Tab`||y!==o.Click)return;let t=s(A.current);if(t.length===0)return;let n=document.activeElement;!e.shiftKey&&n===t[t.length-1]?(e.preventDefault(),je(),G(!1,!0)):e.shiftKey&&n===t[0]&&(e.preventDefault(),X(),G(!1,!0))}l(()=>{if(!U)return;N.current=!1,P.current=!1;function e(e){return!!(k.current?.contains(e)||O.current?.contains(e)||z.current?.contains(e))}function t(t){let n=N.current;return N.current=!1,n||e(t.target)}function n(e){P.current=!t(e)}function r(e){let n=P.current;P.current=!1,!t(e)&&n&&G(!1)}function i(n){if(t(n))return;let r=n.relatedTarget;r&&(r===I.current||e(r))&&G(!1,!P.current)}return C&&y!==o.Hover&&(window.addEventListener(`mousedown`,n),window.addEventListener(`mouseup`,r),document.addEventListener(`focusin`,i)),()=>{window.removeEventListener(`mousedown`,n),window.removeEventListener(`mouseup`,r),document.removeEventListener(`focusin`,i)}},[U,C,y,G,z]),l(()=>()=>clearTimeout(M.current),[]);function $(e){clearTimeout(M.current),M.current=setTimeout(()=>G(e),e?me:he)}function Pe(e){U||e.key!==`ArrowDown`&&e.key!==`ArrowUp`||(e.preventDefault(),G(!0,!0))}let Fe=y===o.Click?{onClick:e=>{(!U||ge)&&G(!U,e.detail===0)},onKeyDown:Pe}:y===o.Hover?{onMouseEnter:()=>$(!0),onMouseLeave:()=>$(!1)}:{onFocus:()=>G(!0),onBlur:()=>G(!1)},Ie=y===o.Hover?{onMouseEnter:()=>$(!0),onMouseLeave:()=>$(!1)}:{};return m(`div`,{...Se,className:f(i.popover,be,E?.root),style:{...xe,...D?.root},children:[p(`div`,{ref:O,className:f(i.popoverTrigger,E?.trigger),style:D?.trigger,...Fe,children:ce}),U&&m(r,{children:[K&&p(ee,{onClose:()=>{C&&G(!1)},className:E?.backdrop,style:D?.backdrop}),m(`div`,{ref:Me,className:f(K?a.sheet:i.popoverFloating,E?.floating),style:{...K?Ae:void 0,...D?.floating},"data-placement":v,onKeyDown:Ne,onMouseDownCapture:L,onMouseUpCapture:L,onFocusCapture:Ce,...Ie,children:[p(`div`,{ref:A,className:f(i.popoverPanel,ve&&i.popoverPanelConstrained,K&&a.sheetPanel,E?.panel),style:D?.panel,children:le}),S&&!K&&p(`div`,{ref:j,className:f(i.popoverArrow,E?.arrow),style:D?.arrow})]})]})]})}export{g as default};
2
2
  //# sourceMappingURL=popover.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"popover.mjs","names":[],"sources":["../../../../package/common/popover/popover.tsx"],"sourcesContent":["import { useRef, useEffect, useState, useCallback, type RefObject } from 'react';\nimport cx from 'classnames';\nimport BodyEnd from 'components/common/bodyEnd';\n\nimport styles from './popover.module.pcss';\nimport sheetStyles from '../styles/sheet.module.pcss';\nimport SheetBackdrop from '../sheetBackdrop';\nimport { usePopoverPosition } from './usePopoverPosition';\nimport { useFocusBoundary, tabbablesIn } from '../hooks/useFocusBoundary';\nimport { useSheet } from '../hooks/useSheet';\nimport { useOverlay } from '../hooks/useOverlay';\nimport { useComponentDefaults, usePresentationDefault } from '../configProvider';\nimport { Presentation } from '../configProvider/presentation.types';\nimport { PopoverPlacement, PopoverAlign, PopoverTrigger, type PopoverProps } from './popover.types';\n\n/**\n * How long after its click an anchored popover still counts as opened by it: long enough for the\n * render the click causes, short enough that an old keypress does not decide a later open.\n */\nconst ACTIVATION_WINDOW_MS = 1000;\n\nfunction toReference(\n anchor: HTMLElement | null | undefined,\n triggerRef: RefObject<HTMLElement | null>,\n): RefObject<HTMLElement | null> {\n return anchor ? { current: anchor } : triggerRef;\n}\n\nfunction Popover(props: PopoverProps) {\n const {\n children = null,\n anchor,\n content = null,\n placement = PopoverPlacement.Bottom,\n align = PopoverAlign.Center,\n trigger = PopoverTrigger.Click,\n isOpen,\n defaultOpen = false,\n onOpenChange = () => {},\n offset = 10,\n withArrow = true,\n closeOnOutsideClick = true,\n closeOnEscape = true,\n openDelay = 100,\n closeDelay = 120,\n disabled = false,\n focusOnOpen = 'keyboard',\n closeOnTriggerClick = true,\n width = 'auto',\n constrainWidth = true,\n presentation: presentationProp,\n className,\n style,\n classNames,\n styles: slotStyles,\n ...rest\n } = useComponentDefaults('Popover', props);\n const triggerRef = useRef<HTMLDivElement>(null);\n const floatingRef = useRef<HTMLDivElement>(null);\n const panelRef = useRef<HTMLDivElement>(null);\n const arrowRef = useRef<HTMLDivElement>(null);\n const hoverTimer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n // Set from a capture handler on the panel, which React fires for its whole\n // React subtree — including children that portal elsewhere in the DOM, such as\n // a Dropdown's list inside a DatePicker's calendar. A DOM `contains` check\n // cannot see those, and without this the panel closes when one is used.\n const panelInteractedRef = useRef(false);\n // An outside click is a press that starts and ends outside while the panel is open. One that\n // starts inside is a drag, such as selecting a field's text, and Chromium and WebKit send its\n // `mouseup` to wherever it ends. One that started before the panel opened is the gesture that\n // opened it, such as Calendar's drag to create, which opens an editor on `pointerup`.\n const pressStartedOutsideRef = useRef(false);\n // Whether the open that is running came from the keyboard, which is what the\n // default `focusOnOpen` follows and what `onOpenChange` reports. A click raised\n // by Enter or Space reports `detail === 0`, where a pointer press counts the\n // clicks; an open with no trigger event at all, such as a panel that appears\n // while the user types, leaves this false.\n const openedByKeyboardRef = useRef(false);\n // A nested portal sits outside the panel's DOM; this is how focus leaving one counts as leaving.\n const lastPanelFocusRef = useRef<EventTarget | null>(null);\n function markPanelInteraction() {\n panelInteractedRef.current = true;\n }\n function markPanelFocus(event: React.FocusEvent) {\n markPanelInteraction();\n lastPanelFocusRef.current = event.target;\n }\n\n // What the panel is placed against, and where the focus goes back to. A new object exactly when\n // `anchor` changes, which is what restarts the position tracking for the new element: state, not\n // a memo, because a memo React chose to drop would restart it for no reason.\n const [reference, setReference] = useState(() => ({ anchor, ref: toReference(anchor, triggerRef) }));\n if (reference.anchor !== anchor) setReference({ anchor, ref: toReference(anchor, triggerRef) });\n const referenceRef = reference.ref;\n\n // An anchored popover is opened by its consumer through `isOpen`, so none of the key handling\n // below sees how. The click that caused it does: Enter and Space raise one with `detail === 0`,\n // the same test the trigger uses. Not `:focus-visible`, which a text field matches after a\n // mouse click too, and which stays on a keyboard-focused button that is then clicked.\n const activationRef = useRef<{ target: Node; keyboard: boolean; time: number } | null>(null);\n const anchored = anchor !== undefined;\n useEffect(() => {\n if (!anchored) return;\n function onClick(event: MouseEvent) {\n activationRef.current = { target: event.target as Node, keyboard: event.detail === 0, time: performance.now() };\n }\n // Capturing, so it is recorded before the consumer's own handler opens the panel.\n document.addEventListener('click', onClick, true);\n return () => document.removeEventListener('click', onClick, true);\n }, [anchored]);\n\n const isControlled = isOpen !== undefined;\n const [internalOpen, setInternalOpen] = useState(defaultOpen);\n const open = isControlled ? isOpen : internalOpen;\n\n // Keep the outside-click/Escape listeners pointed at the latest setter without re-subscribing.\n const onOpenChangeRef = useRef(onOpenChange);\n useEffect(() => {\n onOpenChangeRef.current = onOpenChange;\n });\n\n const setOpen = useCallback(\n (next: boolean, fromKeyboard = false) => {\n if (disabled && next) return;\n if (next) openedByKeyboardRef.current = fromKeyboard;\n if (!isControlled) setInternalOpen(next);\n onOpenChangeRef.current(next, { fromKeyboard });\n },\n [disabled, isControlled],\n );\n\n // Explicit prop → this component's global default → the app-wide one → anchored.\n const appPresentation = usePresentationDefault();\n const presentation = presentationProp ?? appPresentation ?? Presentation.Default;\n const { isSheet: sheetPresentation, sheetStyle } = useSheet(presentation);\n // Hover and focus are pointer affordances that open something small next to what caused them;\n // a sheet is a deliberate, screen-owning open. Only a click asks for one.\n const isSheet = sheetPresentation && trigger === PopoverTrigger.Click;\n\n // A sheet isn't anchored to anything, so there's nothing to track, flip or clamp.\n usePopoverPosition({\n isOpen: !!open && !isSheet,\n placement,\n align,\n offset,\n triggerRef: referenceRef,\n floatingRef,\n panelRef,\n arrowRef: withArrow ? arrowRef : { current: null },\n matchTriggerWidth: width === 'trigger',\n });\n\n // Registered for as long as it's open, not just as a sheet: this is what puts the popover on\n // the overlay stack, so an enclosing Modal knows something sits above it and neither drags\n // focus out of this (portaled, so outside the modal's panel) panel nor closes itself on the\n // Escape meant for this. Focus isn't trapped: a Popover is deliberately non-modal, and it\n // already moves focus into the panel on open by itself.\n const { restoreFocusToTrigger, focusAfterTrigger } = useFocusBoundary({\n triggerRef: referenceRef,\n contentRef: floatingRef,\n });\n const restoreFocusRef = useRef(restoreFocusToTrigger);\n useEffect(() => {\n restoreFocusRef.current = restoreFocusToTrigger;\n });\n\n // Focus inside the panel returns to the trigger however it closes, as with `hidePopover()`. Read\n // in a ref cleanup, while the panel is still in the DOM; moved in an effect, because StrictMode\n // detaches a new panel's refs once more right after it took the focus.\n const focusLeftWithPanelRef = useRef(false);\n const floatingCallbackRef = useCallback((node: HTMLDivElement | null) => {\n floatingRef.current = node;\n focusLeftWithPanelRef.current = false;\n if (!node) return;\n return () => {\n floatingRef.current = null;\n const active = document.activeElement;\n focusLeftWithPanelRef.current = !!active && (node.contains(active) || active === lastPanelFocusRef.current);\n };\n }, []);\n useEffect(() => {\n if (open || !focusLeftWithPanelRef.current) return;\n focusLeftWithPanelRef.current = false;\n const active = document.activeElement;\n if (!active || active === document.body) restoreFocusRef.current();\n }, [open]);\n\n useOverlay({\n isActive: !!open,\n containerRef: panelRef,\n onEscape: closeOnEscape\n ? () => {\n // Hand focus back to the trigger when it currently sits inside the panel.\n if (floatingRef.current?.contains(document.activeElement)) restoreFocusToTrigger();\n setOpen(false, true);\n }\n : undefined,\n trapFocus: false,\n lockScroll: isSheet,\n });\n\n // The panel is portaled to the end of the DOM, so Tab never flows into it naturally. When we\n // manage focus (click trigger + focusable content), move focus in on open and, on Tab out of\n // either end, close and continue focus as if the panel sat right after the trigger.\n useEffect(() => {\n if (!open) {\n openedByKeyboardRef.current = false;\n return;\n }\n const activation = activationRef.current;\n activationRef.current = null;\n const anchorKeyed =\n !!anchor &&\n !!activation?.keyboard &&\n anchor.contains(activation.target) &&\n performance.now() - activation.time < ACTIVATION_WINDOW_MS;\n const takeFocus =\n focusOnOpen === true || (focusOnOpen === 'keyboard' && (openedByKeyboardRef.current || anchorKeyed));\n if (!takeFocus || trigger !== PopoverTrigger.Click) return;\n const focusables = tabbablesIn(panelRef.current);\n if (focusables.length === 0) return;\n // Only claim the dialog role when we're actually managing focus for real content.\n floatingRef.current?.setAttribute('role', 'dialog');\n focusables[0].focus();\n }, [open, trigger, focusOnOpen, anchor]);\n\n function handleFloatingKeyDown(event: React.KeyboardEvent) {\n if (event.key !== 'Tab' || trigger !== PopoverTrigger.Click) return;\n const focusables = tabbablesIn(panelRef.current);\n if (focusables.length === 0) return;\n const active = document.activeElement;\n if (!event.shiftKey && active === focusables[focusables.length - 1]) {\n event.preventDefault();\n focusAfterTrigger();\n setOpen(false, true);\n } else if (event.shiftKey && active === focusables[0]) {\n event.preventDefault();\n restoreFocusToTrigger();\n setOpen(false, true);\n }\n }\n\n // Outside click. Registered only while open and only for the relevant trigger mode; Escape is\n // handled by `useOverlay` above, which scopes it to the innermost open layer.\n useEffect(() => {\n if (!open) return;\n // Focus that landed in the panel before these listeners existed (ours on open, or content\n // that focuses itself on mount) set the flag with nothing to clear it, and the first\n // click outside was swallowed.\n panelInteractedRef.current = false;\n pressStartedOutsideRef.current = false;\n\n function containsNode(node: Node) {\n // Both trigger refs: with an anchor, `children` still toggle the panel, and a press on them is theirs.\n return Boolean(\n floatingRef.current?.contains(node) ||\n triggerRef.current?.contains(node) ||\n referenceRef.current?.contains(node),\n );\n }\n function isInside(event: Event) {\n // Read and clear: the flag describes this one event, and the panel's React\n // handler has already run by the time this does.\n const inPanelSubtree = panelInteractedRef.current;\n panelInteractedRef.current = false;\n return inPanelSubtree || containsNode(event.target as Node);\n }\n function onMouseDown(event: MouseEvent) {\n pressStartedOutsideRef.current = !isInside(event);\n }\n function onMouseUp(event: MouseEvent) {\n const pressStartedOutside = pressStartedOutsideRef.current;\n pressStartedOutsideRef.current = false;\n if (!isInside(event) && pressStartedOutside) setOpen(false);\n }\n // Keyboard counterpart to the outside click. Only focus that comes from inside closes it: code\n // moving focus elsewhere (a Modal restoring it) must not, nor `null`, focus back from another tab.\n function onFocusIn(event: FocusEvent) {\n if (isInside(event)) return;\n const from = event.relatedTarget as Node | null;\n if (!from || (from !== lastPanelFocusRef.current && !containsNode(from))) return;\n // A press on an outside field focuses it before its `mouseup`, and that is not the keyboard.\n setOpen(false, !pressStartedOutsideRef.current);\n }\n\n if (closeOnOutsideClick && trigger !== PopoverTrigger.Hover) {\n window.addEventListener('mousedown', onMouseDown);\n window.addEventListener('mouseup', onMouseUp);\n document.addEventListener('focusin', onFocusIn);\n }\n return () => {\n window.removeEventListener('mousedown', onMouseDown);\n window.removeEventListener('mouseup', onMouseUp);\n document.removeEventListener('focusin', onFocusIn);\n };\n }, [open, closeOnOutsideClick, trigger, setOpen, referenceRef]);\n\n useEffect(() => () => clearTimeout(hoverTimer.current), []);\n\n function scheduleHover(next: boolean) {\n clearTimeout(hoverTimer.current);\n hoverTimer.current = setTimeout(() => setOpen(next), next ? openDelay : closeDelay);\n }\n\n // Opening from the keyboard without the trigger having to be a button that\n // happens to toggle on Enter: the arrow keys are what the menu-button pattern\n // asks for, and what a consumer reaches for first.\n function handleTriggerKeyDown(event: React.KeyboardEvent) {\n if (open || (event.key !== 'ArrowDown' && event.key !== 'ArrowUp')) return;\n event.preventDefault();\n setOpen(true, true);\n }\n\n const triggerHandlers =\n trigger === PopoverTrigger.Click\n ? {\n onClick: (event: React.MouseEvent) => {\n // A field that opens a picker keeps it open when clicked again, so\n // that placing the caret in the field does not close the panel.\n if (open && !closeOnTriggerClick) return;\n setOpen(!open, event.detail === 0);\n },\n onKeyDown: handleTriggerKeyDown,\n }\n : trigger === PopoverTrigger.Hover\n ? { onMouseEnter: () => scheduleHover(true), onMouseLeave: () => scheduleHover(false) }\n : { onFocus: () => setOpen(true), onBlur: () => setOpen(false) };\n\n const floatingHoverHandlers =\n trigger === PopoverTrigger.Hover\n ? { onMouseEnter: () => scheduleHover(true), onMouseLeave: () => scheduleHover(false) }\n : {};\n\n return (\n <div\n {...rest}\n className={cx(styles.popover, className, classNames?.root)}\n style={{ ...style, ...slotStyles?.root }}\n >\n <div\n ref={triggerRef}\n className={cx(styles.popoverTrigger, classNames?.trigger)}\n style={slotStyles?.trigger}\n {...triggerHandlers}\n >\n {children}\n </div>\n {open && (\n <BodyEnd>\n {isSheet && (\n <SheetBackdrop\n onClose={() => {\n if (closeOnOutsideClick) setOpen(false);\n }}\n className={classNames?.backdrop}\n style={slotStyles?.backdrop}\n />\n )}\n <div\n ref={floatingCallbackRef}\n className={cx(isSheet ? sheetStyles.sheet : styles.popoverFloating, classNames?.floating)}\n style={{ ...(isSheet ? sheetStyle : undefined), ...slotStyles?.floating }}\n data-placement={placement}\n onKeyDown={handleFloatingKeyDown}\n onMouseDownCapture={markPanelInteraction}\n onMouseUpCapture={markPanelInteraction}\n onFocusCapture={markPanelFocus}\n {...floatingHoverHandlers}\n >\n <div\n ref={panelRef}\n className={cx(\n styles.popoverPanel,\n constrainWidth && styles.popoverPanelConstrained,\n isSheet && sheetStyles.sheetPanel,\n classNames?.panel,\n )}\n style={slotStyles?.panel}\n >\n {content}\n </div>\n {withArrow && !isSheet && (\n <div ref={arrowRef} className={cx(styles.popoverArrow, classNames?.arrow)} style={slotStyles?.arrow} />\n )}\n </div>\n </BodyEnd>\n )}\n </div>\n );\n}\n\nexport default Popover;\n"],"mappings":"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"}
1
+ {"version":3,"file":"popover.mjs","names":[],"sources":["../../../../package/common/popover/popover.tsx"],"sourcesContent":["import { useRef, useEffect, useState, useCallback, type RefObject } from 'react';\nimport cx from 'classnames';\nimport BodyEnd from 'components/common/bodyEnd';\n\nimport styles from './popover.module.pcss';\nimport sheetStyles from '../styles/sheet.module.pcss';\nimport SheetBackdrop from '../sheetBackdrop';\nimport { usePopoverPosition } from './usePopoverPosition';\nimport { useFocusBoundary, tabbablesIn } from '../hooks/useFocusBoundary';\nimport { useSheet } from '../hooks/useSheet';\nimport { useOverlay } from '../hooks/useOverlay';\nimport { useComponentDefaults, usePresentationDefault } from '../configProvider';\nimport { Presentation } from '../configProvider/presentation.types';\nimport { PopoverPlacement, PopoverAlign, PopoverTrigger, type PopoverProps } from './popover.types';\n\n/**\n * How long after its click an anchored popover still counts as opened by it: long enough for the\n * render the click causes, short enough that an old keypress does not decide a later open.\n */\nconst ACTIVATION_WINDOW_MS = 1000;\n\nfunction toReference(\n anchor: HTMLElement | null | undefined,\n triggerRef: RefObject<HTMLElement | null>,\n): RefObject<HTMLElement | null> {\n return anchor ? { current: anchor } : triggerRef;\n}\n\nfunction Popover(props: PopoverProps) {\n const {\n children = null,\n anchor,\n content = null,\n placement = PopoverPlacement.Bottom,\n align = PopoverAlign.Center,\n trigger = PopoverTrigger.Click,\n isOpen,\n defaultOpen = false,\n onOpenChange = () => {},\n offset = 10,\n withArrow = true,\n closeOnOutsideClick = true,\n closeOnEscape = true,\n openDelay = 100,\n closeDelay = 120,\n disabled = false,\n focusOnOpen = 'keyboard',\n closeOnTriggerClick = true,\n width = 'auto',\n constrainWidth = true,\n presentation: presentationProp,\n className,\n style,\n classNames,\n styles: slotStyles,\n ...rest\n } = useComponentDefaults('Popover', props);\n const triggerRef = useRef<HTMLDivElement>(null);\n const floatingRef = useRef<HTMLDivElement>(null);\n const panelRef = useRef<HTMLDivElement>(null);\n const arrowRef = useRef<HTMLDivElement>(null);\n const hoverTimer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n // Set from a capture handler on the panel, which React fires for its whole\n // React subtree — including children that portal elsewhere in the DOM, such as\n // a Dropdown's list inside a DatePicker's calendar. A DOM `contains` check\n // cannot see those, and without this the panel closes when one is used.\n const panelInteractedRef = useRef(false);\n // An outside click is a press that starts and ends outside while the panel is open. One that\n // starts inside is a drag, such as selecting a field's text, and Chromium and WebKit send its\n // `mouseup` to wherever it ends. One that started before the panel opened is the gesture that\n // opened it, such as Calendar's drag to create, which opens an editor on `pointerup`.\n const pressStartedOutsideRef = useRef(false);\n // Whether the open that is running came from the keyboard, which is what the\n // default `focusOnOpen` follows and what `onOpenChange` reports. A click raised\n // by Enter or Space reports `detail === 0`, where a pointer press counts the\n // clicks; an open with no trigger event at all, such as a panel that appears\n // while the user types, leaves this false.\n const openedByKeyboardRef = useRef(false);\n // A nested portal sits outside the panel's DOM; this is how focus leaving one counts as leaving.\n const lastPanelFocusRef = useRef<EventTarget | null>(null);\n function markPanelInteraction() {\n panelInteractedRef.current = true;\n }\n function markPanelFocus(event: React.FocusEvent) {\n markPanelInteraction();\n lastPanelFocusRef.current = event.target;\n }\n\n // What the panel is placed against, and where the focus goes back to. A new object exactly when\n // `anchor` changes, which is what restarts the position tracking for the new element: state, not\n // a memo, because a memo React chose to drop would restart it for no reason.\n const [reference, setReference] = useState(() => ({ anchor, ref: toReference(anchor, triggerRef) }));\n if (reference.anchor !== anchor) setReference({ anchor, ref: toReference(anchor, triggerRef) });\n const referenceRef = reference.ref;\n\n // An anchored popover is opened by its consumer through `isOpen`, so none of the key handling\n // below sees how. The click that caused it does: Enter and Space raise one with `detail === 0`,\n // the same test the trigger uses. Not `:focus-visible`, which a text field matches after a\n // mouse click too, and which stays on a keyboard-focused button that is then clicked.\n const activationRef = useRef<{ target: Node; keyboard: boolean; time: number } | null>(null);\n const anchored = anchor !== undefined;\n useEffect(() => {\n if (!anchored) return;\n function onClick(event: MouseEvent) {\n activationRef.current = { target: event.target as Node, keyboard: event.detail === 0, time: performance.now() };\n }\n // Capturing, so it is recorded before the consumer's own handler opens the panel.\n document.addEventListener('click', onClick, true);\n return () => document.removeEventListener('click', onClick, true);\n }, [anchored]);\n\n const isControlled = isOpen !== undefined;\n const [internalOpen, setInternalOpen] = useState(defaultOpen);\n const open = isControlled ? isOpen : internalOpen;\n\n // Keep the outside-click/Escape listeners pointed at the latest setter without re-subscribing.\n const onOpenChangeRef = useRef(onOpenChange);\n useEffect(() => {\n onOpenChangeRef.current = onOpenChange;\n });\n\n const setOpen = useCallback(\n (next: boolean, fromKeyboard = false) => {\n if (disabled && next) return;\n if (next) openedByKeyboardRef.current = fromKeyboard;\n if (!isControlled) setInternalOpen(next);\n onOpenChangeRef.current(next, { fromKeyboard });\n },\n [disabled, isControlled],\n );\n\n // Explicit prop → this component's global default → the app-wide one → anchored.\n const appPresentation = usePresentationDefault();\n const presentation = presentationProp ?? appPresentation ?? Presentation.Default;\n const { isSheet: sheetPresentation, sheetStyle } = useSheet(presentation);\n // Hover and focus are pointer affordances that open something small next to what caused them;\n // a sheet is a deliberate, screen-owning open. Only a click asks for one.\n const isSheet = sheetPresentation && trigger === PopoverTrigger.Click;\n\n // Focus into the panel waits until it shows, since one that waits out a moving ancestor hidden\n // takes none.\n const shownRef = useRef(false);\n const focusPendingRef = useRef(false);\n function focusPanel() {\n focusPendingRef.current = false;\n const focusables = tabbablesIn(panelRef.current);\n if (focusables.length === 0) return;\n // Only claim the dialog role when we're actually managing focus for real content.\n floatingRef.current?.setAttribute('role', 'dialog');\n focusables[0].focus();\n }\n\n // A sheet isn't anchored to anything, so there's nothing to track, flip or clamp.\n usePopoverPosition({\n isOpen: !!open && !isSheet,\n placement,\n align,\n offset,\n triggerRef: referenceRef,\n floatingRef,\n panelRef,\n arrowRef: withArrow ? arrowRef : { current: null },\n matchTriggerWidth: width === 'trigger',\n onShown: () => {\n shownRef.current = true;\n if (focusPendingRef.current) focusPanel();\n },\n });\n\n // Registered for as long as it's open, not just as a sheet: this is what puts the popover on\n // the overlay stack, so an enclosing Modal knows something sits above it and neither drags\n // focus out of this (portaled, so outside the modal's panel) panel nor closes itself on the\n // Escape meant for this. Focus isn't trapped: a Popover is deliberately non-modal, and it\n // already moves focus into the panel on open by itself.\n const { restoreFocusToTrigger, focusAfterTrigger } = useFocusBoundary({\n triggerRef: referenceRef,\n contentRef: floatingRef,\n });\n const restoreFocusRef = useRef(restoreFocusToTrigger);\n useEffect(() => {\n restoreFocusRef.current = restoreFocusToTrigger;\n });\n\n // Focus inside the panel returns to the trigger however it closes, as with `hidePopover()`. Read\n // in a ref cleanup, while the panel is still in the DOM; moved in an effect, because StrictMode\n // detaches a new panel's refs once more right after it took the focus.\n const focusLeftWithPanelRef = useRef(false);\n const floatingCallbackRef = useCallback((node: HTMLDivElement | null) => {\n floatingRef.current = node;\n focusLeftWithPanelRef.current = false;\n if (!node) return;\n return () => {\n floatingRef.current = null;\n const active = document.activeElement;\n focusLeftWithPanelRef.current = !!active && (node.contains(active) || active === lastPanelFocusRef.current);\n };\n }, []);\n useEffect(() => {\n if (open || !focusLeftWithPanelRef.current) return;\n focusLeftWithPanelRef.current = false;\n const active = document.activeElement;\n if (!active || active === document.body) restoreFocusRef.current();\n }, [open]);\n\n useOverlay({\n isActive: !!open,\n containerRef: panelRef,\n onEscape: closeOnEscape\n ? () => {\n // Hand focus back to the trigger when it currently sits inside the panel.\n if (floatingRef.current?.contains(document.activeElement)) restoreFocusToTrigger();\n setOpen(false, true);\n }\n : undefined,\n trapFocus: false,\n lockScroll: isSheet,\n });\n\n // The panel is portaled to the end of the DOM, so Tab never flows into it naturally. When we\n // manage focus (click trigger + focusable content), move focus in on open and, on Tab out of\n // either end, close and continue focus as if the panel sat right after the trigger.\n useEffect(() => {\n if (!open) {\n openedByKeyboardRef.current = false;\n shownRef.current = false;\n focusPendingRef.current = false;\n return;\n }\n const activation = activationRef.current;\n activationRef.current = null;\n const anchorKeyed =\n !!anchor &&\n !!activation?.keyboard &&\n anchor.contains(activation.target) &&\n performance.now() - activation.time < ACTIVATION_WINDOW_MS;\n const takeFocus =\n focusOnOpen === true || (focusOnOpen === 'keyboard' && (openedByKeyboardRef.current || anchorKeyed));\n if (!takeFocus || trigger !== PopoverTrigger.Click) return;\n if (shownRef.current) focusPanel();\n else focusPendingRef.current = true;\n }, [open, trigger, focusOnOpen, anchor]);\n\n function handleFloatingKeyDown(event: React.KeyboardEvent) {\n if (event.key !== 'Tab' || trigger !== PopoverTrigger.Click) return;\n const focusables = tabbablesIn(panelRef.current);\n if (focusables.length === 0) return;\n const active = document.activeElement;\n if (!event.shiftKey && active === focusables[focusables.length - 1]) {\n event.preventDefault();\n focusAfterTrigger();\n setOpen(false, true);\n } else if (event.shiftKey && active === focusables[0]) {\n event.preventDefault();\n restoreFocusToTrigger();\n setOpen(false, true);\n }\n }\n\n // Outside click. Registered only while open and only for the relevant trigger mode; Escape is\n // handled by `useOverlay` above, which scopes it to the innermost open layer.\n useEffect(() => {\n if (!open) return;\n // Focus that landed in the panel before these listeners existed (ours on open, or content\n // that focuses itself on mount) set the flag with nothing to clear it, and the first\n // click outside was swallowed.\n panelInteractedRef.current = false;\n pressStartedOutsideRef.current = false;\n\n function containsNode(node: Node) {\n // Both trigger refs: with an anchor, `children` still toggle the panel, and a press on them is theirs.\n return Boolean(\n floatingRef.current?.contains(node) ||\n triggerRef.current?.contains(node) ||\n referenceRef.current?.contains(node),\n );\n }\n function isInside(event: Event) {\n // Read and clear: the flag describes this one event, and the panel's React\n // handler has already run by the time this does.\n const inPanelSubtree = panelInteractedRef.current;\n panelInteractedRef.current = false;\n return inPanelSubtree || containsNode(event.target as Node);\n }\n function onMouseDown(event: MouseEvent) {\n pressStartedOutsideRef.current = !isInside(event);\n }\n function onMouseUp(event: MouseEvent) {\n const pressStartedOutside = pressStartedOutsideRef.current;\n pressStartedOutsideRef.current = false;\n if (!isInside(event) && pressStartedOutside) setOpen(false);\n }\n // Keyboard counterpart to the outside click. Only focus that comes from inside closes it: code\n // moving focus elsewhere (a Modal restoring it) must not, nor `null`, focus back from another tab.\n function onFocusIn(event: FocusEvent) {\n if (isInside(event)) return;\n const from = event.relatedTarget as Node | null;\n if (!from || (from !== lastPanelFocusRef.current && !containsNode(from))) return;\n // A press on an outside field focuses it before its `mouseup`, and that is not the keyboard.\n setOpen(false, !pressStartedOutsideRef.current);\n }\n\n if (closeOnOutsideClick && trigger !== PopoverTrigger.Hover) {\n window.addEventListener('mousedown', onMouseDown);\n window.addEventListener('mouseup', onMouseUp);\n document.addEventListener('focusin', onFocusIn);\n }\n return () => {\n window.removeEventListener('mousedown', onMouseDown);\n window.removeEventListener('mouseup', onMouseUp);\n document.removeEventListener('focusin', onFocusIn);\n };\n }, [open, closeOnOutsideClick, trigger, setOpen, referenceRef]);\n\n useEffect(() => () => clearTimeout(hoverTimer.current), []);\n\n function scheduleHover(next: boolean) {\n clearTimeout(hoverTimer.current);\n hoverTimer.current = setTimeout(() => setOpen(next), next ? openDelay : closeDelay);\n }\n\n // Opening from the keyboard without the trigger having to be a button that\n // happens to toggle on Enter: the arrow keys are what the menu-button pattern\n // asks for, and what a consumer reaches for first.\n function handleTriggerKeyDown(event: React.KeyboardEvent) {\n if (open || (event.key !== 'ArrowDown' && event.key !== 'ArrowUp')) return;\n event.preventDefault();\n setOpen(true, true);\n }\n\n const triggerHandlers =\n trigger === PopoverTrigger.Click\n ? {\n onClick: (event: React.MouseEvent) => {\n // A field that opens a picker keeps it open when clicked again, so\n // that placing the caret in the field does not close the panel.\n if (open && !closeOnTriggerClick) return;\n setOpen(!open, event.detail === 0);\n },\n onKeyDown: handleTriggerKeyDown,\n }\n : trigger === PopoverTrigger.Hover\n ? { onMouseEnter: () => scheduleHover(true), onMouseLeave: () => scheduleHover(false) }\n : { onFocus: () => setOpen(true), onBlur: () => setOpen(false) };\n\n const floatingHoverHandlers =\n trigger === PopoverTrigger.Hover\n ? { onMouseEnter: () => scheduleHover(true), onMouseLeave: () => scheduleHover(false) }\n : {};\n\n return (\n <div\n {...rest}\n className={cx(styles.popover, className, classNames?.root)}\n style={{ ...style, ...slotStyles?.root }}\n >\n <div\n ref={triggerRef}\n className={cx(styles.popoverTrigger, classNames?.trigger)}\n style={slotStyles?.trigger}\n {...triggerHandlers}\n >\n {children}\n </div>\n {open && (\n <BodyEnd>\n {isSheet && (\n <SheetBackdrop\n onClose={() => {\n if (closeOnOutsideClick) setOpen(false);\n }}\n className={classNames?.backdrop}\n style={slotStyles?.backdrop}\n />\n )}\n <div\n ref={floatingCallbackRef}\n className={cx(isSheet ? sheetStyles.sheet : styles.popoverFloating, classNames?.floating)}\n style={{ ...(isSheet ? sheetStyle : undefined), ...slotStyles?.floating }}\n data-placement={placement}\n onKeyDown={handleFloatingKeyDown}\n onMouseDownCapture={markPanelInteraction}\n onMouseUpCapture={markPanelInteraction}\n onFocusCapture={markPanelFocus}\n {...floatingHoverHandlers}\n >\n <div\n ref={panelRef}\n className={cx(\n styles.popoverPanel,\n constrainWidth && styles.popoverPanelConstrained,\n isSheet && sheetStyles.sheetPanel,\n classNames?.panel,\n )}\n style={slotStyles?.panel}\n >\n {content}\n </div>\n {withArrow && !isSheet && (\n <div ref={arrowRef} className={cx(styles.popoverArrow, classNames?.arrow)} style={slotStyles?.arrow} />\n )}\n </div>\n </BodyEnd>\n )}\n </div>\n );\n}\n\nexport default Popover;\n"],"mappings":"04BAmBA,IAAM,GAAuB,IAE7B,SAAS,EACP,EACA,EAC+B,CAC/B,OAAO,EAAS,CAAE,QAAS,CAAO,EAAI,CACxC,CAEA,SAAS,EAAQ,EAAqB,CACpC,GAAM,CACJ,YAAW,KACX,SACA,WAAU,KACV,YAAY,GAAiB,OAC7B,SAAQ,GAAa,OACrB,UAAU,EAAe,MACzB,SACA,eAAc,GACd,mBAAqB,CAAC,EACtB,UAAS,GACT,YAAY,GACZ,sBAAsB,GACtB,iBAAgB,GAChB,aAAY,IACZ,cAAa,IACb,WAAW,GACX,cAAc,WACd,uBAAsB,GACtB,SAAQ,OACR,kBAAiB,GACjB,aAAc,GACd,aACA,SACA,aACA,OAAQ,EACR,GAAG,IACD,EAAqB,UAAW,CAAK,EACnC,EAAa,EAAuB,IAAI,EACxC,EAAc,EAAuB,IAAI,EACzC,EAAW,EAAuB,IAAI,EACtC,EAAW,EAAuB,IAAI,EACtC,EAAa,EAAkD,IAAA,EAAS,EAKxE,EAAqB,EAAO,EAAK,EAKjC,EAAyB,EAAO,EAAK,EAMrC,EAAsB,EAAO,EAAK,EAElC,EAAoB,EAA2B,IAAI,EACzD,SAAS,GAAuB,CAC9B,EAAmB,QAAU,EAC/B,CACA,SAAS,GAAe,EAAyB,CAC/C,EAAqB,EACrB,EAAkB,QAAU,EAAM,MACpC,CAKA,GAAM,CAAC,EAAW,IAAgB,OAAgB,CAAE,SAAQ,IAAK,EAAY,EAAQ,CAAU,CAAE,EAAE,EAC/F,EAAU,SAAW,GAAQ,GAAa,CAAE,SAAQ,IAAK,EAAY,EAAQ,CAAU,CAAE,CAAC,EAC9F,IAAM,EAAe,EAAU,IAMzB,EAAgB,EAAiE,IAAI,EACrF,EAAW,IAAW,IAAA,GAC5B,MAAgB,CACd,GAAI,CAAC,EAAU,OACf,SAAS,EAAQ,EAAmB,CAClC,EAAc,QAAU,CAAE,OAAQ,EAAM,OAAgB,SAAU,EAAM,SAAW,EAAG,KAAM,YAAY,IAAI,CAAE,CAChH,CAGA,OADA,SAAS,iBAAiB,QAAS,EAAS,EAAI,MACnC,SAAS,oBAAoB,QAAS,EAAS,EAAI,CAClE,EAAG,CAAC,CAAQ,CAAC,EAEb,IAAM,EAAe,IAAW,IAAA,GAC1B,CAAC,GAAc,IAAmB,EAAS,EAAW,EACtD,EAAO,EAAe,EAAS,GAG/B,EAAkB,EAAO,CAAY,EAC3C,MAAgB,CACd,EAAgB,QAAU,CAC5B,CAAC,EAED,IAAM,EAAU,GACb,EAAe,EAAe,KAAU,CACnC,GAAY,IACZ,IAAM,EAAoB,QAAU,GACnC,GAAc,GAAgB,CAAI,EACvC,EAAgB,QAAQ,EAAM,CAAE,cAAa,CAAC,EAChD,EACA,CAAC,EAAU,CAAY,CACzB,EAGM,GAAkB,EAAuB,EACzC,GAAe,IAAoB,IAAmB,EAAa,QACnE,CAAE,QAAS,GAAmB,eAAe,GAAS,EAAY,EAGlE,EAAU,IAAqB,IAAY,EAAe,MAI1D,EAAW,EAAO,EAAK,EACvB,EAAkB,EAAO,EAAK,EACpC,SAAS,GAAa,CACpB,EAAgB,QAAU,GAC1B,IAAM,EAAa,EAAY,EAAS,OAAO,EAC3C,EAAW,SAAW,IAE1B,EAAY,SAAS,aAAa,OAAQ,QAAQ,EAClD,EAAW,EAAE,CAAC,MAAM,EACtB,CAGA,GAAmB,CACjB,OAAQ,CAAC,CAAC,GAAQ,CAAC,EACnB,YACA,SACA,UACA,WAAY,EACZ,cACA,WACA,SAAU,EAAY,EAAW,CAAE,QAAS,IAAK,EACjD,kBAAmB,KAAU,UAC7B,YAAe,CACb,EAAS,QAAU,GACf,EAAgB,SAAS,EAAW,CAC1C,CACF,CAAC,EAOD,GAAM,CAAE,wBAAuB,sBAAsB,GAAiB,CACpE,WAAY,EACZ,WAAY,CACd,CAAC,EACK,EAAkB,EAAO,CAAqB,EACpD,MAAgB,CACd,EAAgB,QAAU,CAC5B,CAAC,EAKD,IAAM,EAAwB,EAAO,EAAK,EACpC,GAAsB,EAAa,GAAgC,CACvE,KAAY,QAAU,EACtB,EAAsB,QAAU,GAC3B,EACL,UAAa,CACX,EAAY,QAAU,KACtB,IAAM,EAAS,SAAS,cACxB,EAAsB,QAAU,CAAC,CAAC,IAAW,EAAK,SAAS,CAAM,GAAK,IAAW,EAAkB,QACrG,CACF,EAAG,CAAC,CAAC,EACL,MAAgB,CACd,GAAI,GAAQ,CAAC,EAAsB,QAAS,OAC5C,EAAsB,QAAU,GAChC,IAAM,EAAS,SAAS,eACpB,CAAC,GAAU,IAAW,SAAS,OAAM,EAAgB,QAAQ,CACnE,EAAG,CAAC,CAAI,CAAC,EAET,GAAW,CACT,SAAU,CAAC,CAAC,EACZ,aAAc,EACd,SAAU,OACA,CAEA,EAAY,SAAS,SAAS,SAAS,aAAa,GAAG,EAAsB,EACjF,EAAQ,GAAO,EAAI,CACrB,EACA,IAAA,GACJ,UAAW,GACX,WAAY,CACd,CAAC,EAKD,MAAgB,CACd,GAAI,CAAC,EAAM,CACT,EAAoB,QAAU,GAC9B,EAAS,QAAU,GACnB,EAAgB,QAAU,GAC1B,MACF,CACA,IAAM,EAAa,EAAc,QACjC,EAAc,QAAU,KACxB,IAAM,EACJ,CAAC,CAAC,GACF,CAAC,CAAC,GAAY,UACd,EAAO,SAAS,EAAW,MAAM,GACjC,YAAY,IAAI,EAAI,EAAW,KAAO,IAEtC,IAAgB,IAAS,IAAgB,aAAe,EAAoB,SAAW,KACvE,IAAY,EAAe,QACzC,EAAS,QAAS,EAAW,EAC5B,EAAgB,QAAU,GACjC,EAAG,CAAC,EAAM,EAAS,EAAa,CAAM,CAAC,EAEvC,SAAS,GAAsB,EAA4B,CACzD,GAAI,EAAM,MAAQ,OAAS,IAAY,EAAe,MAAO,OAC7D,IAAM,EAAa,EAAY,EAAS,OAAO,EAC/C,GAAI,EAAW,SAAW,EAAG,OAC7B,IAAM,EAAS,SAAS,cACpB,CAAC,EAAM,UAAY,IAAW,EAAW,EAAW,OAAS,IAC/D,EAAM,eAAe,EACrB,GAAkB,EAClB,EAAQ,GAAO,EAAI,GACV,EAAM,UAAY,IAAW,EAAW,KACjD,EAAM,eAAe,EACrB,EAAsB,EACtB,EAAQ,GAAO,EAAI,EAEvB,CAIA,MAAgB,CACd,GAAI,CAAC,EAAM,OAIX,EAAmB,QAAU,GAC7B,EAAuB,QAAU,GAEjC,SAAS,EAAa,EAAY,CAEhC,MAAO,GACL,EAAY,SAAS,SAAS,CAAI,GAClC,EAAW,SAAS,SAAS,CAAI,GACjC,EAAa,SAAS,SAAS,CAAI,EAEvC,CACA,SAAS,EAAS,EAAc,CAG9B,IAAM,EAAiB,EAAmB,QAE1C,MADA,GAAmB,QAAU,GACtB,GAAkB,EAAa,EAAM,MAAc,CAC5D,CACA,SAAS,EAAY,EAAmB,CACtC,EAAuB,QAAU,CAAC,EAAS,CAAK,CAClD,CACA,SAAS,EAAU,EAAmB,CACpC,IAAM,EAAsB,EAAuB,QACnD,EAAuB,QAAU,GAC7B,CAAC,EAAS,CAAK,GAAK,GAAqB,EAAQ,EAAK,CAC5D,CAGA,SAAS,EAAU,EAAmB,CACpC,GAAI,EAAS,CAAK,EAAG,OACrB,IAAM,EAAO,EAAM,cACd,IAAS,IAAS,EAAkB,SAAY,EAAa,CAAI,IAEtE,EAAQ,GAAO,CAAC,EAAuB,OAAO,CAChD,CAOA,OALI,GAAuB,IAAY,EAAe,QACpD,OAAO,iBAAiB,YAAa,CAAW,EAChD,OAAO,iBAAiB,UAAW,CAAS,EAC5C,SAAS,iBAAiB,UAAW,CAAS,OAEnC,CACX,OAAO,oBAAoB,YAAa,CAAW,EACnD,OAAO,oBAAoB,UAAW,CAAS,EAC/C,SAAS,oBAAoB,UAAW,CAAS,CACnD,CACF,EAAG,CAAC,EAAM,EAAqB,EAAS,EAAS,CAAY,CAAC,EAE9D,UAAsB,aAAa,EAAW,OAAO,EAAG,CAAC,CAAC,EAE1D,SAAS,EAAc,EAAe,CACpC,aAAa,EAAW,OAAO,EAC/B,EAAW,QAAU,eAAiB,EAAQ,CAAI,EAAG,EAAO,GAAY,EAAU,CACpF,CAKA,SAAS,GAAqB,EAA4B,CACpD,GAAS,EAAM,MAAQ,aAAe,EAAM,MAAQ,YACxD,EAAM,eAAe,EACrB,EAAQ,GAAM,EAAI,EACpB,CAEA,IAAM,GACJ,IAAY,EAAe,MACvB,CACE,QAAU,GAA4B,EAGhC,IAAS,KACb,EAAQ,CAAC,EAAM,EAAM,SAAW,CAAC,CACnC,EACA,UAAW,EACb,EACA,IAAY,EAAe,MACzB,CAAE,iBAAoB,EAAc,EAAI,EAAG,iBAAoB,EAAc,EAAK,CAAE,EACpF,CAAE,YAAe,EAAQ,EAAI,EAAG,WAAc,EAAQ,EAAK,CAAE,EAE/D,GACJ,IAAY,EAAe,MACvB,CAAE,iBAAoB,EAAc,EAAI,EAAG,iBAAoB,EAAc,EAAK,CAAE,EACpF,CAAC,EAEP,OACE,EAAC,MAAD,CACE,GAAI,GACJ,UAAW,EAAG,EAAO,QAAS,GAAW,GAAY,IAAI,EACzD,MAAO,CAAE,GAAG,GAAO,GAAG,GAAY,IAAK,EAHzC,SAAA,CAKE,EAAC,MAAD,CACE,IAAK,EACL,UAAW,EAAG,EAAO,eAAgB,GAAY,OAAO,EACxD,MAAO,GAAY,QACnB,GAAI,GAEH,WACE,CAAA,EACJ,GACC,EAAC,EAAD,CAAA,SAAA,CACG,GACC,EAAC,GAAD,CACE,YAAe,CACT,GAAqB,EAAQ,EAAK,CACxC,EACA,UAAW,GAAY,SACvB,MAAO,GAAY,QACpB,CAAA,EAEH,EAAC,MAAD,CACE,IAAK,GACL,UAAW,EAAG,EAAU,EAAY,MAAQ,EAAO,gBAAiB,GAAY,QAAQ,EACxF,MAAO,CAAE,GAAI,EAAU,GAAa,IAAA,GAAY,GAAG,GAAY,QAAS,EACxE,iBAAgB,EAChB,UAAW,GACX,mBAAoB,EACpB,iBAAkB,EAClB,eAAgB,GAChB,GAAI,GATN,SAAA,CAWE,EAAC,MAAD,CACE,IAAK,EACL,UAAW,EACT,EAAO,aACP,IAAkB,EAAO,wBACzB,GAAW,EAAY,WACvB,GAAY,KACd,EACA,MAAO,GAAY,MAElB,SAAA,EACE,CAAA,EACJ,GAAa,CAAC,GACb,EAAC,MAAD,CAAK,IAAK,EAAU,UAAW,EAAG,EAAO,aAAc,GAAY,KAAK,EAAG,MAAO,GAAY,KAAQ,CAAA,CAErG,CACE,CAAA,CAAA,CAAA,CAAA,CAER,GAET"}
@@ -1 +1 @@
1
- {"version":3,"file":"popover.module.mjs","names":[],"sources":["../../../../package/common/popover/popover.module.pcss"],"sourcesContent":["/* Neither of these two generates a box, so the trigger lands in the consumer's\n layout exactly as if Popover were not around it: it is the flex item, the grid\n item, the block-level child. A field that fills its column still fills it, a\n short button is still only as wide as itself, and a row that stretches its\n children reaches the trigger instead of stopping at a wrapper around it. They\n stay in the event path, so the handlers on them still fire, and\n `usePopoverPosition` already looks past boxless wrappers for its anchor.\n\n Every box we tried here was wrong in one direction or the other. A wrapper that\n spans its container opens the panel from the empty space beside a short button.\n `width: fit-content` collapses a field whose own width is a percentage of its\n parent. A flex root stretches the trigger's height but sizes its width by\n content, which collapsed KlipyPicker's search field.\n\n The cost is that `className`, `style` and `classNames.root` cannot paint\n anything on these two, because there is nothing to paint on. */\n.popover {\n display: contents;\n\n &-trigger {\n display: contents;\n }\n\n &-floating {\n position: fixed;\n top: 0;\n left: 0;\n z-index: 1;\n\n /* Hidden until usePopoverPosition writes the first coordinates, to avoid a top-left flash. */\n visibility: hidden;\n }\n\n &-panel {\n position: relative;\n\n /* Above the arrow so the panel background hides the arrow's inner half — only the\n tip pokes out past the panel edge. */\n z-index: 1;\n color: var(--text-color);\n font-family: var(--default-font);\n font-size: var(--default-font-size);\n background: var(--background-modal);\n box-shadow: var(--card-shadow);\n border-radius: calc(var(--default-border-radius) + 4px);\n padding: 12px 14px;\n\n /* A readable measure for a popover of text. A panel that brings its own\n width — a calendar, a media grid — turns it off with `constrainWidth`.\n `.sheet-panel` overrides this by name for the sheet path; check there\n before touching the cap. */\n &-constrained {\n max-width: min(320px, calc(100vw - 24px));\n }\n\n animation: popoverIn 0.12s ease-out forwards;\n }\n\n &-arrow {\n position: absolute;\n width: 10px;\n height: 10px;\n background: var(--background-modal);\n box-shadow: var(--card-shadow);\n transform: rotate(45deg);\n }\n\n /* Arrow sits on the panel edge facing the trigger; --arrow-x / --arrow-y centre it on the trigger. */\n &-floating[data-placement='bottom'] &-arrow {\n top: -5px;\n left: var(--arrow-x, 50%);\n margin-left: -5px;\n }\n\n &-floating[data-placement='top'] &-arrow {\n bottom: -5px;\n left: var(--arrow-x, 50%);\n margin-left: -5px;\n }\n\n &-floating[data-placement='right'] &-arrow {\n left: -5px;\n top: var(--arrow-y, 50%);\n margin-top: -5px;\n }\n\n &-floating[data-placement='left'] &-arrow {\n right: -5px;\n top: var(--arrow-y, 50%);\n margin-top: -5px;\n }\n\n /* Grow from the edge nearest the trigger. */\n &-floating[data-placement='bottom'] &-panel {\n transform-origin: top center;\n }\n\n &-floating[data-placement='top'] &-panel {\n transform-origin: bottom center;\n }\n\n &-floating[data-placement='right'] &-panel {\n transform-origin: center left;\n }\n\n &-floating[data-placement='left'] &-panel {\n transform-origin: center right;\n }\n\n @keyframes popoverIn {\n from {\n opacity: 0.4;\n transform: scale(0.7);\n }\n\n to {\n opacity: 1;\n transform: scale(1);\n }\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .popover-panel {\n animation: none;\n }\n}\n"],"mappings":""}
1
+ {"version":3,"file":"popover.module.mjs","names":[],"sources":["../../../../package/common/popover/popover.module.pcss"],"sourcesContent":["/* Neither of these two generates a box, so the trigger lands in the consumer's\n layout exactly as if Popover were not around it: it is the flex item, the grid\n item, the block-level child. A field that fills its column still fills it, a\n short button is still only as wide as itself, and a row that stretches its\n children reaches the trigger instead of stopping at a wrapper around it. They\n stay in the event path, so the handlers on them still fire, and\n `usePopoverPosition` already looks past boxless wrappers for its anchor.\n\n Every box we tried here was wrong in one direction or the other. A wrapper that\n spans its container opens the panel from the empty space beside a short button.\n `width: fit-content` collapses a field whose own width is a percentage of its\n parent. A flex root stretches the trigger's height but sizes its width by\n content, which collapsed KlipyPicker's search field.\n\n The cost is that `className`, `style` and `classNames.root` cannot paint\n anything on these two, because there is nothing to paint on. */\n.popover {\n display: contents;\n\n &-trigger {\n display: contents;\n }\n\n &-floating {\n position: fixed;\n top: 0;\n left: 0;\n\n /* Shared by every overlay, so the order they are portaled in, which is the order they opened in, decides. */\n z-index: 1;\n\n /* Hidden until usePopoverPosition writes the first coordinates, to avoid a top-left flash. */\n visibility: hidden;\n }\n\n &-panel {\n position: relative;\n\n /* Above the arrow so the panel background hides the arrow's inner half — only the\n tip pokes out past the panel edge. */\n z-index: 1;\n color: var(--text-color);\n font-family: var(--default-font);\n font-size: var(--default-font-size);\n background: var(--background-modal);\n box-shadow: var(--card-shadow);\n border-radius: calc(var(--default-border-radius) + 4px);\n padding: 12px 14px;\n\n /* A readable measure for a popover of text. A panel that brings its own\n width — a calendar, a media grid — turns it off with `constrainWidth`.\n `.sheet-panel` overrides this by name for the sheet path; check there\n before touching the cap. */\n &-constrained {\n max-width: min(320px, calc(100vw - 24px));\n }\n\n animation: popoverIn 0.12s ease-out forwards;\n }\n\n &-arrow {\n position: absolute;\n width: 10px;\n height: 10px;\n background: var(--background-modal);\n box-shadow: var(--card-shadow);\n transform: rotate(45deg);\n }\n\n /* Arrow sits on the panel edge facing the trigger; --arrow-x / --arrow-y centre it on the trigger. */\n &-floating[data-placement='bottom'] &-arrow {\n top: -5px;\n left: var(--arrow-x, 50%);\n margin-left: -5px;\n }\n\n &-floating[data-placement='top'] &-arrow {\n bottom: -5px;\n left: var(--arrow-x, 50%);\n margin-left: -5px;\n }\n\n &-floating[data-placement='right'] &-arrow {\n left: -5px;\n top: var(--arrow-y, 50%);\n margin-top: -5px;\n }\n\n &-floating[data-placement='left'] &-arrow {\n right: -5px;\n top: var(--arrow-y, 50%);\n margin-top: -5px;\n }\n\n /* Grow from the edge nearest the trigger. */\n &-floating[data-placement='bottom'] &-panel {\n transform-origin: top center;\n }\n\n &-floating[data-placement='top'] &-panel {\n transform-origin: bottom center;\n }\n\n &-floating[data-placement='right'] &-panel {\n transform-origin: center left;\n }\n\n &-floating[data-placement='left'] &-panel {\n transform-origin: center right;\n }\n\n @keyframes popoverIn {\n from {\n opacity: 0.4;\n transform: scale(0.7);\n }\n\n to {\n opacity: 1;\n transform: scale(1);\n }\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .popover-panel {\n animation: none;\n }\n}\n"],"mappings":""}
@@ -1,2 +1,2 @@
1
- "use client";import{isHiddenByAnyAncestor as e}from"../utils/scrollAncestors.mjs";import{getVisualViewportRect as t}from"../utils/visualViewport.mjs";import{useAnchorTracking as n}from"../hooks/useAnchorTracking.mjs";import{PopoverAlign as r,PopoverPlacement as i}from"./popover.types.mjs";import{useId as a,useRef as o}from"react";var s={[i.Top]:`bottom`,[i.Bottom]:`top`,[i.Left]:`right`,[i.Right]:`left`},c=8,l=160,u=3;function d(e){let t=e;for(let e=0;e<=u;e++){let e=t.getBoundingClientRect();if(e.width>0||e.height>0)return{element:t,rect:e};let n=t.firstElementChild;if(!n)break;t=n}return{element:null,rect:e.getBoundingClientRect()}}var f;function p(){if(f!==void 0)return f;if(typeof CSS>`u`||!CSS.supports?.(`top: anchor(top, 0px)`))return f=!1;let e=document.createElement(`div`);e.style.cssText=`position: fixed; top: 0; left: 0; transform: translateY(100px); visibility: hidden`;let t=document.createElement(`div`);t.style.cssText=`anchor-name: --popover-probe; width: 1px; height: 1px`;let n=document.createElement(`div`);return n.style.cssText=`position: fixed; position-anchor: --popover-probe; top: anchor(top, 0px); left: 0; visibility: hidden`,e.append(t),document.body.append(e,n),f=Math.abs(n.getBoundingClientRect().top-t.getBoundingClientRect().top)<1,e.remove(),n.remove(),f}function m(e){return e.style.getPropertyValue(`anchor-name`).split(`,`).map(e=>e.trim()).filter(e=>e&&e!==`none`)}function h(e,t){let n=m(e);n.includes(t)||e.style.setProperty(`anchor-name`,[...n,t].join(`, `))}function g(e,t){let n=m(e).filter(e=>e!==t);n.length>0?e.style.setProperty(`anchor-name`,n.join(`, `)):e.style.removeProperty(`anchor-name`)}function _(e,t,n,r){return r?`calc(anchor(${e}, ${n}px) + ${t-n}px)`:`${t}px`}function v({isOpen:u,placement:f,align:m,offset:v,triggerRef:y,floatingRef:b,panelRef:x,arrowRef:S,matchTriggerWidth:C=!1,shrinkToFit:w=!1}){let T=`--popover-${a().replace(/[^\w-]/g,``)}`,E=o(null);function D(e){E.current!==e&&(E.current&&g(E.current,T),e&&h(e,T),E.current=e)}n({isOpen:u,triggerRef:y,elementRefs:[b,x],resizeRefs:[x],deps:[f,m,v,b,x,S,C,w],update:(n,a)=>{let o=b.current,u=x.current;if(!o||!u)return;let{element:h,rect:g}=d(n),y=h!==null&&p();D(y?h:null),y?o.style.setProperty(`position-anchor`,T):o.style.removeProperty(`position-anchor`),C?u.style.minWidth=`${g.width}px`:u.style.removeProperty(`min-width`),w&&u.style.removeProperty(`max-width`);let{width:E,height:O}=o.getBoundingClientRect(),k=t(),A=f===i.Top||f===i.Bottom;if(w&&A){let e=m===r.End?g.right-(k.left+c):m===r.Start?k.left+k.width-c-g.left:k.width-16,t=Math.min(e,k.width-16);E>t&&t>=l&&(u.style.maxWidth=`${t}px`,{width:E,height:O}=o.getBoundingClientRect())}let j={top:g.top-k.top,bottom:k.top+k.height-g.bottom,left:g.left-k.left,right:k.left+k.width-g.right},M=f===i.Top||f===i.Bottom?O:E,N=f,P=s[f];j[N]<M+v&&j[P]>j[N]&&(N=P);let F=N===`top`||N===`bottom`,I,L;N===`bottom`?I=g.bottom+v:N===`top`?I=g.top-v-O:L=N===`right`?g.right+v:g.left-v-E;let R=!1,z=!1;if(F){L=m===r.Start?g.left:m===r.End?g.right-E:g.left+g.width/2-E/2;let e=Math.max(k.left+c,Math.min(L,k.left+k.width-E-c));R=e!==L,L=e}else{I=m===r.Start?g.top:m===r.End?g.bottom-O:g.top+g.height/2-O/2;let e=Math.max(k.top+c,Math.min(I,k.top+k.height-O-c));z=e!==I,I=e}o.style.top=_(`top`,I,g.top,y&&!z),o.style.left=_(`left`,L,g.left,y&&!R),o.style.visibility=e(g,a)?`hidden`:`visible`,o.dataset.placement=N;let B=S.current;if(B){let e=B.offsetWidth/2||5;if(F){let t=g.left+g.width/2-L;B.style.setProperty(`--arrow-x`,`${Math.max(e+2,Math.min(t,E-e-2))}px`),B.style.removeProperty(`--arrow-y`)}else{let t=g.top+g.height/2-I;B.style.setProperty(`--arrow-y`,`${Math.max(e+2,Math.min(t,O-e-2))}px`),B.style.removeProperty(`--arrow-x`)}}},teardown:()=>{D(null);let e=b.current;if(!e)return;e.style.top=``,e.style.left=``,e.style.visibility=``,e.style.removeProperty(`position-anchor`);let t=x.current;t?.style.removeProperty(`min-width`),t?.style.removeProperty(`max-width`)}})}export{p as supportsAnchoring,v as usePopoverPosition};
1
+ "use client";import{isHiddenByAnyAncestor as e}from"../utils/scrollAncestors.mjs";import{getVisualViewportRect as t}from"../utils/visualViewport.mjs";import{useAnchorTracking as n}from"../hooks/useAnchorTracking.mjs";import{PopoverAlign as r,PopoverPlacement as i}from"./popover.types.mjs";import{useId as a,useRef as o}from"react";var s={[i.Top]:`bottom`,[i.Bottom]:`top`,[i.Left]:`right`,[i.Right]:`left`},c=8,l=160,u=3;function d(e){let t=e;for(let e=0;e<=u;e++){let e=t.getBoundingClientRect();if(e.width>0||e.height>0)return{element:t,rect:e};let n=t.firstElementChild;if(!n)break;t=n}return{element:null,rect:e.getBoundingClientRect()}}var f;function p(){if(f!==void 0)return f;if(typeof CSS>`u`||!CSS.supports?.(`top: anchor(top, 0px)`))return f=!1;let e=document.createElement(`div`);e.style.cssText=`position: fixed; top: 0; left: 0; transform: translateY(100px); visibility: hidden`;let t=document.createElement(`div`);t.style.cssText=`anchor-name: --popover-probe; width: 1px; height: 1px`;let n=document.createElement(`div`);return n.style.cssText=`position: fixed; position-anchor: --popover-probe; top: anchor(top, 0px); left: 0; visibility: hidden`,e.append(t),document.body.append(e,n),f=Math.abs(n.getBoundingClientRect().top-t.getBoundingClientRect().top)<1,e.remove(),n.remove(),f}function m(e){return e.style.getPropertyValue(`anchor-name`).split(`,`).map(e=>e.trim()).filter(e=>e&&e!==`none`)}function h(e,t){let n=m(e);n.includes(t)||e.style.setProperty(`anchor-name`,[...n,t].join(`, `))}function g(e,t){let n=m(e).filter(e=>e!==t);n.length>0?e.style.setProperty(`anchor-name`,n.join(`, `)):e.style.removeProperty(`anchor-name`)}var _=[`transform`,`translate`,`scale`,`rotate`],v=1e3;function y(e){return(document.getAnimations?.()??[]).filter(t=>{if(t.playState!==`running`||!(t.effect instanceof KeyframeEffect))return!1;let{target:n,pseudoElement:r}=t.effect;if(!n||r||n===e||!n.contains(e))return!1;let{endTime:i,localTime:a}=t.effect.getComputedTiming();return Number(i)-Number(a)>v?!1:t.effect.getKeyframes().some(e=>_.some(t=>t in e))})}function b(e,t,n,r){return r?`calc(anchor(${e}, ${n}px) + ${t-n}px)`:`${t}px`}function x({isOpen:u,placement:f,align:m,offset:_,triggerRef:v,floatingRef:x,panelRef:S,arrowRef:C,matchTriggerWidth:w=!1,shrinkToFit:T=!1,onShown:E}){let D=`--popover-${a().replace(/[^\w-]/g,``)}`,O=o(null),k=o(null),A=o(new WeakMap);function j(e){O.current!==e&&(O.current&&g(O.current,D),e&&h(e,D),O.current=e)}n({isOpen:u,triggerRef:v,elementRefs:[x,S],resizeRefs:[S],deps:[f,m,_,x,S,C,w,T],update:(n,a,o)=>{let u=x.current,h=S.current;if(!u||!h)return;let{element:g,rect:v}=d(n),O=g!==null&&p();j(O?g:null),O?u.style.setProperty(`position-anchor`,D):u.style.removeProperty(`position-anchor`),w?h.style.minWidth=`${v.width}px`:h.style.removeProperty(`min-width`),T&&h.style.removeProperty(`max-width`);let{width:M,height:N}=u.getBoundingClientRect(),P=t(),F=f===i.Top||f===i.Bottom;if(T&&F){let e=m===r.End?v.right-(P.left+c):m===r.Start?P.left+P.width-c-v.left:P.width-16,t=Math.min(e,P.width-16);M>t&&t>=l&&(h.style.maxWidth=`${t}px`,{width:M,height:N}=u.getBoundingClientRect())}let I={top:v.top-P.top,bottom:P.top+P.height-v.bottom,left:v.left-P.left,right:P.left+P.width-v.right},L=f===i.Top||f===i.Bottom?N:M,R=f,z=s[f];I[R]<L+_&&I[z]>I[R]&&(R=z);let B=R===`top`||R===`bottom`,V,H;R===`bottom`?V=v.bottom+_:R===`top`?V=v.top-_-N:H=R===`right`?v.right+_:v.left-_-M;let U=!1,W=!1;if(B){H=m===r.Start?v.left:m===r.End?v.right-M:v.left+v.width/2-M/2;let e=Math.max(P.left+c,Math.min(H,P.left+P.width-M-c));U=e!==H,H=e}else{V=m===r.Start?v.top:m===r.End?v.bottom-N:v.top+v.height/2-N/2;let e=Math.max(P.top+c,Math.min(V,P.top+P.height-N-c));W=e!==V,V=e}u.style.top=b(`top`,V,v.top,O&&!W),u.style.left=b(`left`,H,v.left,O&&!U);let G=k.current!==h,K=G?y(n):[];K.forEach(e=>{A.current.get(e)!==o&&(A.current.set(e,o),e.finished.then(o,o))});let q=e(v,a)||K.length>0;u.style.visibility=q?`hidden`:`visible`,!q&&G&&(k.current=h,h.getAnimations?.().forEach(e=>{e.currentTime=0}),E?.()),u.dataset.placement=R;let J=C.current;if(J){let e=J.offsetWidth/2||5;if(B){let t=v.left+v.width/2-H;J.style.setProperty(`--arrow-x`,`${Math.max(e+2,Math.min(t,M-e-2))}px`),J.style.removeProperty(`--arrow-y`)}else{let t=v.top+v.height/2-V;J.style.setProperty(`--arrow-y`,`${Math.max(e+2,Math.min(t,N-e-2))}px`),J.style.removeProperty(`--arrow-x`)}}},teardown:()=>{j(null);let e=x.current;if(!e)return;e.style.top=``,e.style.left=``,e.style.visibility=``,e.style.removeProperty(`position-anchor`);let t=S.current;t?.style.removeProperty(`min-width`),t?.style.removeProperty(`max-width`)}})}export{p as supportsAnchoring,x as usePopoverPosition};
2
2
  //# sourceMappingURL=usePopoverPosition.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"usePopoverPosition.mjs","names":[],"sources":["../../../../package/common/popover/usePopoverPosition.ts"],"sourcesContent":["import { useId, useRef, type RefObject } from 'react';\nimport { isHiddenByAnyAncestor } from 'components/common/utils/scrollAncestors';\nimport { getVisualViewportRect } from 'components/common/utils/visualViewport';\nimport { useAnchorTracking } from '../hooks/useAnchorTracking';\nimport { PopoverPlacement, PopoverAlign } from './popover.types';\n\ntype Axis = 'top' | 'bottom' | 'left' | 'right';\nconst OPPOSITE: Record<PopoverPlacement, Axis> = {\n [PopoverPlacement.Top]: 'bottom',\n [PopoverPlacement.Bottom]: 'top',\n [PopoverPlacement.Left]: 'right',\n [PopoverPlacement.Right]: 'left',\n};\n\nconst MARGIN = 8; // viewport gutter kept clear when shifting a panel back on-screen\n\n/**\n * Narrower than this and `shrinkToFit` gives up: a panel this size reads worse than\n * one that overhangs. Around four words a line for an error bubble, which is the\n * narrowest caller. A constant rather than an option, because the one caller that\n * shrinks has no reason to want a different floor; give it one the day a second\n * caller disagrees, as `shrinkToFit: boolean | number`.\n */\nconst MIN_SHRINK_WIDTH = 160;\n\n/** How far down to look for a box when the wrappers in between have none. */\nconst MAX_ANCHOR_DEPTH = 3;\n\n/**\n * The element to anchor to. A trigger with a box of its own is that element, which\n * is the case for a caller that hands over a real element (`Tooltip`).\n *\n * Popover's own trigger wrapper generates no box, deliberately, so that the\n * trigger keeps the place it would have had without Popover around it. Then the\n * real trigger is inside it, and this descends to find it, past any further\n * boxless element a consumer wrapped its trigger in: such an element has a zero\n * rect at the origin, which would otherwise send the panel to the top-left corner\n * of the screen.\n */\nfunction resolveAnchor(trigger: HTMLElement): { element: HTMLElement | null; rect: DOMRect } {\n let el: HTMLElement = trigger;\n for (let depth = 0; depth <= MAX_ANCHOR_DEPTH; depth++) {\n const rect = el.getBoundingClientRect();\n if (rect.width > 0 || rect.height > 0) return { element: el, rect };\n const child = el.firstElementChild as HTMLElement | null;\n if (!child) break;\n el = child;\n }\n return { element: null, rect: trigger.getBoundingClientRect() };\n}\n\nlet anchorSupport: boolean | undefined;\n\n// Probed, not just parsed: Firefox 155 resolves `anchor()` without the anchor's transforms, which\n// put a Dropdown in a VirtualList row, placed by `translateY`, above the list.\nexport function supportsAnchoring(): boolean {\n if (anchorSupport !== undefined) return anchorSupport;\n if (typeof CSS === 'undefined' || !CSS.supports?.('top: anchor(top, 0px)')) return (anchorSupport = false);\n const host = document.createElement('div');\n host.style.cssText = 'position: fixed; top: 0; left: 0; transform: translateY(100px); visibility: hidden';\n const anchor = document.createElement('div');\n anchor.style.cssText = 'anchor-name: --popover-probe; width: 1px; height: 1px';\n const probe = document.createElement('div');\n probe.style.cssText =\n 'position: fixed; position-anchor: --popover-probe; top: anchor(top, 0px); left: 0; visibility: hidden';\n host.append(anchor);\n document.body.append(host, probe);\n anchorSupport = Math.abs(probe.getBoundingClientRect().top - anchor.getBoundingClientRect().top) < 1;\n host.remove();\n probe.remove();\n return anchorSupport;\n}\n\nfunction anchorNames(el: HTMLElement): string[] {\n return el.style\n .getPropertyValue('anchor-name')\n .split(',')\n .map((name) => name.trim())\n .filter((name) => name && name !== 'none');\n}\n\n// Merged, not replaced: one trigger can carry a Tooltip and a Popover at once.\nfunction addAnchorName(el: HTMLElement, name: string) {\n const names = anchorNames(el);\n if (!names.includes(name)) el.style.setProperty('anchor-name', [...names, name].join(', '));\n}\n\nfunction removeAnchorName(el: HTMLElement, name: string) {\n const names = anchorNames(el).filter((n) => n !== name);\n if (names.length > 0) el.style.setProperty('anchor-name', names.join(', '));\n else el.style.removeProperty('anchor-name');\n}\n\n// Anchored, the browser moves the panel with the scroll in the same frame; the scroll listener\n// lands a frame or more later. The pixel fallback covers an anchor the browser refuses.\nfunction inset(side: 'top' | 'left', value: number, anchorEdge: number, anchored: boolean): string {\n if (!anchored) return `${value}px`;\n return `calc(anchor(${side}, ${anchorEdge}px) + ${value - anchorEdge}px)`;\n}\n\nexport interface UsePopoverPositionArgs {\n isOpen: boolean;\n placement: PopoverPlacement;\n align: PopoverAlign;\n offset: number;\n triggerRef: RefObject<HTMLElement | null>;\n /** `position: fixed` wrapper that receives top/left/visibility and the resolved-placement data attribute. */\n floatingRef: RefObject<HTMLElement | null>;\n /** Visible panel, measured to decide flip and shift. */\n panelRef: RefObject<HTMLElement | null>;\n /** Arrow element; gets `--arrow-x` / `--arrow-y` written to it. Pass a null ref when `withArrow` is false. */\n arrowRef: RefObject<HTMLElement | null>;\n /**\n * Give the panel at least the trigger's width, for a list that lines up with\n * the field it belongs to. A minimum rather than a fixed width: a menu on a\n * narrow icon button still sizes to its own content.\n */\n matchTriggerWidth?: boolean;\n /**\n * When the panel does not fit on the side its alignment puts it, narrow it\n * rather than sliding it away from the trigger. Sliding keeps the panel\n * on-screen but detaches it: an end-aligned bubble stops ending where its\n * trigger ends, and its arrow stops pointing at anything. Off by default, since\n * a panel whose width is its content, a menu or a list, would rather overhang\n * its trigger than wrap.\n *\n * Only down to `MIN_SHRINK_WIDTH`. Past that the panel is narrower than it is\n * readable, and a trigger that close to the screen edge is better served by a\n * panel that overhangs it.\n */\n shrinkToFit?: boolean;\n}\n\n/**\n * Positions a portaled, `position: fixed` popover on any of the four sides of its trigger with\n * start/center/end cross-axis alignment. Flips to the opposite side when the preferred one has no\n * room, shifts back on-screen along the cross axis, points the arrow at the trigger centre, and\n * hides (not closes) the panel once the trigger scrolls behind a clipping ancestor.\n *\n * `useAnchorTracking` supplies the lifecycle around this: mount wait, ancestor resolution, and\n * re-running on scroll, viewport resize and panel size changes.\n */\nexport function usePopoverPosition({\n isOpen,\n placement,\n align,\n offset,\n triggerRef,\n floatingRef,\n panelRef,\n arrowRef,\n matchTriggerWidth = false,\n shrinkToFit = false,\n}: UsePopoverPositionArgs) {\n const anchorName = `--popover-${useId().replace(/[^\\w-]/g, '')}`;\n const anchorRef = useRef<HTMLElement | null>(null);\n\n function setAnchor(next: HTMLElement | null) {\n if (anchorRef.current === next) return;\n if (anchorRef.current) removeAnchorName(anchorRef.current, anchorName);\n if (next) addAnchorName(next, anchorName);\n anchorRef.current = next;\n }\n\n useAnchorTracking({\n isOpen,\n triggerRef,\n elementRefs: [floatingRef, panelRef],\n // The panel's own size feeds the flip decision and, on the `top`/`left` sides, the coordinate\n // itself — so content that resizes while open has to be re-measured.\n resizeRefs: [panelRef],\n deps: [placement, align, offset, floatingRef, panelRef, arrowRef, matchTriggerWidth, shrinkToFit],\n\n update: (trigger, ancestors) => {\n const floating = floatingRef.current;\n const panel = panelRef.current;\n if (!floating || !panel) return;\n\n const { element: anchorElement, rect: t } = resolveAnchor(trigger);\n const anchored = anchorElement !== null && supportsAnchoring();\n setAnchor(anchored ? anchorElement : null);\n if (anchored) floating.style.setProperty('position-anchor', anchorName);\n else floating.style.removeProperty('position-anchor');\n // Written before the panel is measured, so `pw` below already accounts for it,\n // and taken back off when it is no longer wanted: the panel outlives the\n // switch, and an inline width would outrank whatever it switched to.\n if (matchTriggerWidth) panel.style.minWidth = `${t.width}px`;\n else panel.style.removeProperty('min-width');\n // The wrapper's box, not the panel's: `offsetWidth` rounds to whole pixels, so\n // aligning a panel 165.4px wide against a trigger edge at 406.47 left it 0.4px\n // past that edge. The panel's own rect is fractional but carries the entrance\n // animation's scale; the wrapper is the same layout box without the transform.\n // Cleared before measuring, or a cap left over from a narrower screen would be\n // read back as the panel's own width and never widen again.\n if (shrinkToFit) panel.style.removeProperty('max-width');\n let { width: pw, height: ph } = floating.getBoundingClientRect();\n // The visible area, not the full layout viewport — shrinks on mobile when a keyboard is\n // open, so the panel flips/clamps against what's actually on screen instead of ending up\n // hidden behind the keyboard.\n const vv = getVisualViewportRect();\n\n // Flipping only swaps a side for its opposite, so which axis the alignment runs\n // along is decided by the preferred placement alone, before any of that.\n const alignsHorizontally = placement === PopoverPlacement.Top || placement === PopoverPlacement.Bottom;\n\n if (shrinkToFit && alignsHorizontally) {\n const alignedSide =\n align === PopoverAlign.End\n ? t.right - (vv.left + MARGIN)\n : align === PopoverAlign.Start\n ? vv.left + vv.width - MARGIN - t.left\n : vv.width - 2 * MARGIN;\n const room = Math.min(alignedSide, vv.width - 2 * MARGIN);\n if (pw > room && room >= MIN_SHRINK_WIDTH) {\n panel.style.maxWidth = `${room}px`;\n // Wrapping makes it taller, and the height is what the flip below is decided on.\n ({ width: pw, height: ph } = floating.getBoundingClientRect());\n }\n }\n\n // Resolve the main-axis side, flipping when the preferred side lacks room and the\n // opposite side has more.\n const room: Record<Axis, number> = {\n top: t.top - vv.top,\n bottom: vv.top + vv.height - t.bottom,\n left: t.left - vv.left,\n right: vv.left + vv.width - t.right,\n };\n const need = placement === PopoverPlacement.Top || placement === PopoverPlacement.Bottom ? ph : pw;\n let side: Axis = placement as unknown as Axis;\n const opp = OPPOSITE[placement];\n if (room[side] < need + offset && room[opp] > room[side]) side = opp;\n\n const vertical = side === 'top' || side === 'bottom';\n\n // Main axis: place the panel just outside the trigger on the resolved side.\n let top: number;\n let left: number;\n if (side === 'bottom') {\n top = t.bottom + offset;\n } else if (side === 'top') {\n top = t.top - offset - ph;\n } else if (side === 'right') {\n left = t.right + offset;\n } else {\n left = t.left - offset - pw;\n }\n\n // Cross axis: align start/center/end against the trigger, then shift on-screen.\n let shiftedX = false;\n let shiftedY = false;\n if (vertical) {\n if (align === PopoverAlign.Start) left = t.left;\n else if (align === PopoverAlign.End) left = t.right - pw;\n else left = t.left + t.width / 2 - pw / 2;\n const clamped = Math.max(vv.left + MARGIN, Math.min(left!, vv.left + vv.width - pw - MARGIN));\n shiftedX = clamped !== left!;\n left = clamped;\n } else {\n if (align === PopoverAlign.Start) top = t.top;\n else if (align === PopoverAlign.End) top = t.bottom - ph;\n else top = t.top + t.height / 2 - ph / 2;\n const clamped = Math.max(vv.top + MARGIN, Math.min(top!, vv.top + vv.height - ph - MARGIN));\n shiftedY = clamped !== top!;\n top = clamped;\n }\n\n // A shifted axis is pinned to the viewport edge, so it stays in pixels: anchored, the scroll\n // would carry it off the edge until the next update pulled it back, which reads as shaking.\n floating.style.top = inset('top', top!, t.top, anchored && !shiftedY);\n floating.style.left = inset('left', left!, t.left, anchored && !shiftedX);\n floating.style.visibility = isHiddenByAnyAncestor(t, ancestors) ? 'hidden' : 'visible';\n floating.dataset.placement = side;\n\n // Arrow: centre it on the trigger along the cross axis, clamped inside the panel edges.\n const arrow = arrowRef.current;\n if (arrow) {\n const half = arrow.offsetWidth / 2 || 5;\n if (vertical) {\n const x = t.left + t.width / 2 - left!;\n arrow.style.setProperty('--arrow-x', `${Math.max(half + 2, Math.min(x, pw - half - 2))}px`);\n arrow.style.removeProperty('--arrow-y');\n } else {\n const y = t.top + t.height / 2 - top!;\n arrow.style.setProperty('--arrow-y', `${Math.max(half + 2, Math.min(y, ph - half - 2))}px`);\n arrow.style.removeProperty('--arrow-x');\n }\n }\n },\n\n teardown: () => {\n // Positioning is written as inline styles, which outrank any class the panel might\n // switch to while it stays mounted — a popover turning into a bottom sheet (`useSheet`)\n // would keep the coordinates computed for its anchored self.\n setAnchor(null);\n const floating = floatingRef.current;\n if (!floating) return;\n floating.style.top = '';\n floating.style.left = '';\n floating.style.visibility = '';\n floating.style.removeProperty('position-anchor');\n const panel = panelRef.current;\n panel?.style.removeProperty('min-width');\n panel?.style.removeProperty('max-width');\n },\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"usePopoverPosition.mjs","names":[],"sources":["../../../../package/common/popover/usePopoverPosition.ts"],"sourcesContent":["import { useId, useRef, type RefObject } from 'react';\nimport { isHiddenByAnyAncestor } from 'components/common/utils/scrollAncestors';\nimport { getVisualViewportRect } from 'components/common/utils/visualViewport';\nimport { useAnchorTracking } from '../hooks/useAnchorTracking';\nimport { PopoverPlacement, PopoverAlign } from './popover.types';\n\ntype Axis = 'top' | 'bottom' | 'left' | 'right';\nconst OPPOSITE: Record<PopoverPlacement, Axis> = {\n [PopoverPlacement.Top]: 'bottom',\n [PopoverPlacement.Bottom]: 'top',\n [PopoverPlacement.Left]: 'right',\n [PopoverPlacement.Right]: 'left',\n};\n\nconst MARGIN = 8; // viewport gutter kept clear when shifting a panel back on-screen\n\n/**\n * Narrower than this and `shrinkToFit` gives up: a panel this size reads worse than\n * one that overhangs. Around four words a line for an error bubble, which is the\n * narrowest caller. A constant rather than an option, because the one caller that\n * shrinks has no reason to want a different floor; give it one the day a second\n * caller disagrees, as `shrinkToFit: boolean | number`.\n */\nconst MIN_SHRINK_WIDTH = 160;\n\n/** How far down to look for a box when the wrappers in between have none. */\nconst MAX_ANCHOR_DEPTH = 3;\n\n/**\n * The element to anchor to. A trigger with a box of its own is that element, which\n * is the case for a caller that hands over a real element (`Tooltip`).\n *\n * Popover's own trigger wrapper generates no box, deliberately, so that the\n * trigger keeps the place it would have had without Popover around it. Then the\n * real trigger is inside it, and this descends to find it, past any further\n * boxless element a consumer wrapped its trigger in: such an element has a zero\n * rect at the origin, which would otherwise send the panel to the top-left corner\n * of the screen.\n */\nfunction resolveAnchor(trigger: HTMLElement): { element: HTMLElement | null; rect: DOMRect } {\n let el: HTMLElement = trigger;\n for (let depth = 0; depth <= MAX_ANCHOR_DEPTH; depth++) {\n const rect = el.getBoundingClientRect();\n if (rect.width > 0 || rect.height > 0) return { element: el, rect };\n const child = el.firstElementChild as HTMLElement | null;\n if (!child) break;\n el = child;\n }\n return { element: null, rect: trigger.getBoundingClientRect() };\n}\n\nlet anchorSupport: boolean | undefined;\n\n// Probed, not just parsed: Firefox 155 resolves `anchor()` without the anchor's transforms, which\n// put a Dropdown in a VirtualList row, placed by `translateY`, above the list.\nexport function supportsAnchoring(): boolean {\n if (anchorSupport !== undefined) return anchorSupport;\n if (typeof CSS === 'undefined' || !CSS.supports?.('top: anchor(top, 0px)')) return (anchorSupport = false);\n const host = document.createElement('div');\n host.style.cssText = 'position: fixed; top: 0; left: 0; transform: translateY(100px); visibility: hidden';\n const anchor = document.createElement('div');\n anchor.style.cssText = 'anchor-name: --popover-probe; width: 1px; height: 1px';\n const probe = document.createElement('div');\n probe.style.cssText =\n 'position: fixed; position-anchor: --popover-probe; top: anchor(top, 0px); left: 0; visibility: hidden';\n host.append(anchor);\n document.body.append(host, probe);\n anchorSupport = Math.abs(probe.getBoundingClientRect().top - anchor.getBoundingClientRect().top) < 1;\n host.remove();\n probe.remove();\n return anchorSupport;\n}\n\nfunction anchorNames(el: HTMLElement): string[] {\n return el.style\n .getPropertyValue('anchor-name')\n .split(',')\n .map((name) => name.trim())\n .filter((name) => name && name !== 'none');\n}\n\n// Merged, not replaced: one trigger can carry a Tooltip and a Popover at once.\nfunction addAnchorName(el: HTMLElement, name: string) {\n const names = anchorNames(el);\n if (!names.includes(name)) el.style.setProperty('anchor-name', [...names, name].join(', '));\n}\n\nfunction removeAnchorName(el: HTMLElement, name: string) {\n const names = anchorNames(el).filter((n) => n !== name);\n if (names.length > 0) el.style.setProperty('anchor-name', names.join(', '));\n else el.style.removeProperty('anchor-name');\n}\n\nconst MOVING_PROPERTIES = ['transform', 'translate', 'scale', 'rotate'];\n\n/**\n * Longer than this left to run and the panel shows anyway, mid-way, and is corrected when the\n * animation ends. A Modal or Drawer entrance is well under it; waiting out a long one read as\n * the panel not opening at all.\n */\nconst MAX_WAIT_MS = 1000;\n\n/** The running animations and transitions on ancestors of `trigger` that move it. */\nfunction animationsMoving(trigger: HTMLElement): Animation[] {\n return (document.getAnimations?.() ?? []).filter((animation) => {\n if (animation.playState !== 'running' || !(animation.effect instanceof KeyframeEffect)) return false;\n const { target, pseudoElement } = animation.effect;\n // A `::before` or `::after` is a box of its own: its transform moves nothing else.\n if (!target || pseudoElement || target === trigger || !target.contains(trigger)) return false;\n // Covers one that never ends, which would keep the panel hidden for good.\n const { endTime, localTime } = animation.effect.getComputedTiming();\n if (Number(endTime) - Number(localTime) > MAX_WAIT_MS) return false;\n return animation.effect.getKeyframes().some((frame) => MOVING_PROPERTIES.some((property) => property in frame));\n });\n}\n\n// Anchored, the browser moves the panel with the scroll in the same frame; the scroll listener\n// lands a frame or more later. The pixel fallback covers an anchor the browser refuses.\nfunction inset(side: 'top' | 'left', value: number, anchorEdge: number, anchored: boolean): string {\n if (!anchored) return `${value}px`;\n return `calc(anchor(${side}, ${anchorEdge}px) + ${value - anchorEdge}px)`;\n}\n\nexport interface UsePopoverPositionArgs {\n isOpen: boolean;\n placement: PopoverPlacement;\n align: PopoverAlign;\n offset: number;\n triggerRef: RefObject<HTMLElement | null>;\n /** `position: fixed` wrapper that receives top/left/visibility and the resolved-placement data attribute. */\n floatingRef: RefObject<HTMLElement | null>;\n /** Visible panel, measured to decide flip and shift. */\n panelRef: RefObject<HTMLElement | null>;\n /** Arrow element; gets `--arrow-x` / `--arrow-y` written to it. Pass a null ref when `withArrow` is false. */\n arrowRef: RefObject<HTMLElement | null>;\n /**\n * Give the panel at least the trigger's width, for a list that lines up with\n * the field it belongs to. A minimum rather than a fixed width: a menu on a\n * narrow icon button still sizes to its own content.\n */\n matchTriggerWidth?: boolean;\n /**\n * When the panel does not fit on the side its alignment puts it, narrow it\n * rather than sliding it away from the trigger. Sliding keeps the panel\n * on-screen but detaches it: an end-aligned bubble stops ending where its\n * trigger ends, and its arrow stops pointing at anything. Off by default, since\n * a panel whose width is its content, a menu or a list, would rather overhang\n * its trigger than wrap.\n *\n * Only down to `MIN_SHRINK_WIDTH`. Past that the panel is narrower than it is\n * readable, and a trigger that close to the screen edge is better served by a\n * panel that overhangs it.\n */\n shrinkToFit?: boolean;\n /**\n * Called when a newly opened panel first becomes visible. Focus has to wait for it: a panel\n * still hidden, waiting out an ancestor's animation, takes none.\n */\n onShown?: () => void;\n}\n\n/**\n * Positions a portaled, `position: fixed` popover on any of the four sides of its trigger with\n * start/center/end cross-axis alignment. Flips to the opposite side when the preferred one has no\n * room, shifts back on-screen along the cross axis, points the arrow at the trigger centre, and\n * hides (not closes) the panel once the trigger scrolls behind a clipping ancestor.\n *\n * `useAnchorTracking` supplies the lifecycle around this: mount wait, ancestor resolution, and\n * re-running on scroll, viewport resize and panel size changes.\n */\nexport function usePopoverPosition({\n isOpen,\n placement,\n align,\n offset,\n triggerRef,\n floatingRef,\n panelRef,\n arrowRef,\n matchTriggerWidth = false,\n shrinkToFit = false,\n onShown,\n}: UsePopoverPositionArgs) {\n const anchorName = `--popover-${useId().replace(/[^\\w-]/g, '')}`;\n const anchorRef = useRef<HTMLElement | null>(null);\n const shownPanelRef = useRef<HTMLElement | null>(null);\n // Every update before the first show finds the same animations again; one rerun each is enough.\n // Keyed on `rerun`, since a restarted tracking brings a new one that has to be called too.\n const awaitedRef = useRef(new WeakMap<Animation, () => void>());\n\n function setAnchor(next: HTMLElement | null) {\n if (anchorRef.current === next) return;\n if (anchorRef.current) removeAnchorName(anchorRef.current, anchorName);\n if (next) addAnchorName(next, anchorName);\n anchorRef.current = next;\n }\n\n useAnchorTracking({\n isOpen,\n triggerRef,\n elementRefs: [floatingRef, panelRef],\n // The panel's own size feeds the flip decision and, on the `top`/`left` sides, the coordinate\n // itself — so content that resizes while open has to be re-measured.\n resizeRefs: [panelRef],\n deps: [placement, align, offset, floatingRef, panelRef, arrowRef, matchTriggerWidth, shrinkToFit],\n\n update: (trigger, ancestors, rerun) => {\n const floating = floatingRef.current;\n const panel = panelRef.current;\n if (!floating || !panel) return;\n\n const { element: anchorElement, rect: t } = resolveAnchor(trigger);\n const anchored = anchorElement !== null && supportsAnchoring();\n setAnchor(anchored ? anchorElement : null);\n if (anchored) floating.style.setProperty('position-anchor', anchorName);\n else floating.style.removeProperty('position-anchor');\n // Written before the panel is measured, so `pw` below already accounts for it,\n // and taken back off when it is no longer wanted: the panel outlives the\n // switch, and an inline width would outrank whatever it switched to.\n if (matchTriggerWidth) panel.style.minWidth = `${t.width}px`;\n else panel.style.removeProperty('min-width');\n // The wrapper's box, not the panel's: `offsetWidth` rounds to whole pixels, so\n // aligning a panel 165.4px wide against a trigger edge at 406.47 left it 0.4px\n // past that edge. The panel's own rect is fractional but carries the entrance\n // animation's scale; the wrapper is the same layout box without the transform.\n // Cleared before measuring, or a cap left over from a narrower screen would be\n // read back as the panel's own width and never widen again.\n if (shrinkToFit) panel.style.removeProperty('max-width');\n let { width: pw, height: ph } = floating.getBoundingClientRect();\n // The visible area, not the full layout viewport — shrinks on mobile when a keyboard is\n // open, so the panel flips/clamps against what's actually on screen instead of ending up\n // hidden behind the keyboard.\n const vv = getVisualViewportRect();\n\n // Flipping only swaps a side for its opposite, so which axis the alignment runs\n // along is decided by the preferred placement alone, before any of that.\n const alignsHorizontally = placement === PopoverPlacement.Top || placement === PopoverPlacement.Bottom;\n\n if (shrinkToFit && alignsHorizontally) {\n const alignedSide =\n align === PopoverAlign.End\n ? t.right - (vv.left + MARGIN)\n : align === PopoverAlign.Start\n ? vv.left + vv.width - MARGIN - t.left\n : vv.width - 2 * MARGIN;\n const room = Math.min(alignedSide, vv.width - 2 * MARGIN);\n if (pw > room && room >= MIN_SHRINK_WIDTH) {\n panel.style.maxWidth = `${room}px`;\n // Wrapping makes it taller, and the height is what the flip below is decided on.\n ({ width: pw, height: ph } = floating.getBoundingClientRect());\n }\n }\n\n // Resolve the main-axis side, flipping when the preferred side lacks room and the\n // opposite side has more.\n const room: Record<Axis, number> = {\n top: t.top - vv.top,\n bottom: vv.top + vv.height - t.bottom,\n left: t.left - vv.left,\n right: vv.left + vv.width - t.right,\n };\n const need = placement === PopoverPlacement.Top || placement === PopoverPlacement.Bottom ? ph : pw;\n let side: Axis = placement as unknown as Axis;\n const opp = OPPOSITE[placement];\n if (room[side] < need + offset && room[opp] > room[side]) side = opp;\n\n const vertical = side === 'top' || side === 'bottom';\n\n // Main axis: place the panel just outside the trigger on the resolved side.\n let top: number;\n let left: number;\n if (side === 'bottom') {\n top = t.bottom + offset;\n } else if (side === 'top') {\n top = t.top - offset - ph;\n } else if (side === 'right') {\n left = t.right + offset;\n } else {\n left = t.left - offset - pw;\n }\n\n // Cross axis: align start/center/end against the trigger, then shift on-screen.\n let shiftedX = false;\n let shiftedY = false;\n if (vertical) {\n if (align === PopoverAlign.Start) left = t.left;\n else if (align === PopoverAlign.End) left = t.right - pw;\n else left = t.left + t.width / 2 - pw / 2;\n const clamped = Math.max(vv.left + MARGIN, Math.min(left!, vv.left + vv.width - pw - MARGIN));\n shiftedX = clamped !== left!;\n left = clamped;\n } else {\n if (align === PopoverAlign.Start) top = t.top;\n else if (align === PopoverAlign.End) top = t.bottom - ph;\n else top = t.top + t.height / 2 - ph / 2;\n const clamped = Math.max(vv.top + MARGIN, Math.min(top!, vv.top + vv.height - ph - MARGIN));\n shiftedY = clamped !== top!;\n top = clamped;\n }\n\n // A shifted axis is pinned to the viewport edge, so it stays in pixels: anchored, the scroll\n // would carry it off the edge until the next update pulled it back, which reads as shaking.\n floating.style.top = inset('top', top!, t.top, anchored && !shiftedY);\n floating.style.left = inset('left', left!, t.left, anchored && !shiftedX);\n // Until first shown, it waits out an ancestor that is still moving the trigger, such as a\n // Modal scaling in: placed mid-way, it showed away from the trigger and jumped at the end.\n // Awaited through `finished`, since a Web Animation fires no `animationend`; a cancelled\n // one stops moving the trigger too.\n const firstShow = shownPanelRef.current !== panel;\n const moving = firstShow ? animationsMoving(trigger) : [];\n moving.forEach((animation) => {\n if (awaitedRef.current.get(animation) === rerun) return;\n awaitedRef.current.set(animation, rerun);\n animation.finished.then(rerun, rerun);\n });\n const hidden = isHiddenByAnyAncestor(t, ancestors) || moving.length > 0;\n floating.style.visibility = hidden ? 'hidden' : 'visible';\n // The entrance animation starts on mount, so a panel first placed hidden played it unseen.\n // Per panel, since a new one mounts on every open.\n if (!hidden && firstShow) {\n shownPanelRef.current = panel;\n panel.getAnimations?.().forEach((animation) => {\n animation.currentTime = 0;\n });\n onShown?.();\n }\n floating.dataset.placement = side;\n\n // Arrow: centre it on the trigger along the cross axis, clamped inside the panel edges.\n const arrow = arrowRef.current;\n if (arrow) {\n const half = arrow.offsetWidth / 2 || 5;\n if (vertical) {\n const x = t.left + t.width / 2 - left!;\n arrow.style.setProperty('--arrow-x', `${Math.max(half + 2, Math.min(x, pw - half - 2))}px`);\n arrow.style.removeProperty('--arrow-y');\n } else {\n const y = t.top + t.height / 2 - top!;\n arrow.style.setProperty('--arrow-y', `${Math.max(half + 2, Math.min(y, ph - half - 2))}px`);\n arrow.style.removeProperty('--arrow-x');\n }\n }\n },\n\n teardown: () => {\n // Positioning is written as inline styles, which outrank any class the panel might\n // switch to while it stays mounted — a popover turning into a bottom sheet (`useSheet`)\n // would keep the coordinates computed for its anchored self.\n setAnchor(null);\n const floating = floatingRef.current;\n if (!floating) return;\n floating.style.top = '';\n floating.style.left = '';\n floating.style.visibility = '';\n floating.style.removeProperty('position-anchor');\n const panel = panelRef.current;\n panel?.style.removeProperty('min-width');\n panel?.style.removeProperty('max-width');\n },\n });\n}\n"],"mappings":"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"}