{"version":3,"file":"use-measure-element.cjs","sources":["../../../src/hooks/use-measure-element.tsx"],"sourcesContent":["import { useContext, useLayoutEffect, type RefObject } from 'react';\nimport { CellIdContext } from '../context';\nimport { useGraphStore } from './use-graph-store';\nimport type { TransformElementLayout } from '../store/create-elements-size-observer';\nimport { usePaper } from './use-paper';\nimport type { ElementSize } from '../types/cell.types';\nimport { useCell } from './use-cell';\nimport { selectElementSize } from '../selectors';\nimport { MEASURING_CLASS_NAME } from '../utils/class-names';\n\n/**\n * Options for {@link useMeasureElement}, controlling how the measured DOM size is\n * turned into the graph element's size.\n * @group Types\n * @expand\n */\nexport interface MeasureElementOptions {\n  /**\n   * Adjusts the measured size before it is written to the graph element, e.g. to\n   * add padding or reserve space for a header. Receives the measured dimensions\n   * plus the element's current layout ({@link TransformElementLayoutParams}) and\n   * returns the `width`/`height` (and optionally `x`/`y`) to apply.\n   * @default When omitted, the measured `width`/`height` are applied to the element unchanged.\n   * @example\n   * ```tsx\n   * const transform = ({ width, height }) => ({\n   *   width: width + 20, // 10px padding on each side\n   *   height: height + 20,\n   * });\n   * useMeasureElement(nodeRef, { transform });\n   * ```\n   */\n  readonly transform?: TransformElementLayout;\n}\n\nconst EMPTY_OBJECT: MeasureElementOptions = {};\n\n/**\n * Measures a rendered DOM node and keeps the graph element's size in sync with\n * it. Point `nodeRef` at the HTML or SVG node that defines the element's size;\n * whenever that node resizes, the matching graph element is resized to match\n * (optionally adjusted by a `transform`, see {@link MeasureElementOptions}), and\n * the element's current `width`/`height` are returned for your own layout math.\n *\n * Reach for it when an element's size is driven by its rendered content rather\n * than fixed up front, e.g. text that wraps or a list that grows.\n * @remarks\n * - Call this inside a `renderElement` callback (or a component rendered from\n *   one); it reads the current cell from context and throws otherwise.\n * - When several `useMeasureElement` calls target the same element, the most\n *   recently mounted one wins. When it unmounts, the previous one takes over\n *   again.\n * - Do not also read the size back with the {@link selectElementSize} selector\n *   (or `useCell((cell) => cell.size)`) in the same component. This hook already\n *   syncs the size and returns the live `width`/`height`; reading it again only\n *   adds a redundant subscription and an extra render.\n * @param nodeRef - Ref to the HTML or SVG node to measure. It must be mounted in\n *   the DOM while the hook runs.\n * @param options - Optional {@link MeasureElementOptions}; the main option is a\n *   `transform` that adjusts the measured size before it is applied.\n * @returns The graph element's current `width` and `height` (always defined).\n * @throws If used outside a `renderElement` context, or if the current cell is a\n *   link rather than an element.\n * @group Hooks\n * @example Basic usage\n * ```tsx\n * import { useMeasureElement } from '@joint/react';\n * import { useRef } from 'react';\n *\n * // The element grows to fit its text label.\n * function LabelElement() {\n *   const textRef = useRef<SVGTextElement>(null);\n *   const { width, height } = useMeasureElement(textRef);\n *\n *   return (\n *     <>\n *       <rect width={width} height={height} fill=\"#333\" />\n *       <text ref={textRef} x={4} y={16} fill=\"#fff\">Hello world</text>\n *     </>\n *   );\n * }\n * ```\n * @example Use the returned size\n * ```tsx\n * import { useMeasureElement } from '@joint/react';\n * import { useRef } from 'react';\n *\n * const iconURL = 'https://example.com/icon.svg';\n *\n * // Size follows the HTML content; use the returned size to place an icon inside.\n * function Card() {\n *   const contentRef = useRef<HTMLDivElement>(null);\n *   const { width, height } = useMeasureElement(contentRef);\n *   const iconSize = 16;\n *\n *   return (\n *     <>\n *       <rect width={width} height={height} fill=\"#333\" />\n *       <image href={iconURL} x={width - iconSize} y={0} width={iconSize} height={iconSize} />\n *       <foreignObject width={width} height={height}>\n *         <div ref={contentRef} style={{ padding: 8, color: '#fff' }}>Card content</div>\n *       </foreignObject>\n *     </>\n *   );\n * }\n * ```\n * @example Adjust size with a transform\n * ```tsx\n * import { useMeasureElement, type TransformElementLayout } from '@joint/react';\n * import { useRef, useCallback } from 'react';\n *\n * function ListElement() {\n *   const divRef = useRef<HTMLDivElement>(null);\n *   const padding = 10;\n *   const headerHeight = 50;\n *\n *   const transform: TransformElementLayout = useCallback(\n *     ({ width: measuredWidth, height: measuredHeight }) => {\n *       return {\n *         width: padding + measuredWidth + padding,\n *         height: headerHeight + measuredHeight + padding,\n *       };\n *     },\n *     []\n *   );\n *\n *   const { width, height } = useMeasureElement(divRef, { transform });\n *\n *   return (\n *     <>\n *       <rect width={width} height={height} fill=\"#121826\" />\n *       <foreignObject x={padding} y={headerHeight} width={width - 2 * padding} height={height - headerHeight - padding}>\n *         <div ref={divRef}>Content</div>\n *       </foreignObject>\n *     </>\n *   );\n * }\n * ```\n */\nexport function useMeasureElement(\n  nodeRef: RefObject<HTMLElement | SVGElement | null>,\n  options: MeasureElementOptions = EMPTY_OBJECT\n): Required<ElementSize> {\n  const { transform } = options;\n  const { graph, setMeasuredNode } = useGraphStore();\n  const { paper } = usePaper();\n  const id = useContext(CellIdContext);\n  if (id === undefined) {\n    throw new Error('useMeasureElement() must be used inside renderElement');\n  }\n  const size = useCell(selectElementSize);\n\n  useLayoutEffect(() => {\n    const element = nodeRef.current;\n    if (!element) return;\n\n    const cell = graph.getCell(id);\n    if (!cell?.isElement()) {\n      throw new Error(\n        '`useMeasureElement` can only be used with elements, not links. ' +\n          `The cell with id \"${id}\" is not an element.`\n      );\n    }\n    if (!nodeRef.current) {\n      return;\n    }\n\n    // Hide the element view's until the element view is updated by the paper's\n    // update cycle. This prevents flashes of incorrect position when the element\n    // is measured, but waits for the paper to apply the new position.\n    const view = paper?.findViewByModel(id);\n    if (view && paper) {\n      view.el.classList.add(MEASURING_CLASS_NAME);\n      // Request a view update to remove the measuring class as part of its\n      // next update cycle.\n      // Note: the class will be removed even if the size will not change\n      // after the measurement (e.g. if the transform returns the same size\n      // or if the measured size is the same as the current model size).\n      paper.requestViewUpdate(view, paper.FLAG_MEASURE, view.UPDATE_PRIORITY);\n    }\n\n    const clean = setMeasuredNode({ id, node: nodeRef.current, transform });\n    return () => {\n      // No class cleanup here: views aren't recycled today, so the view\n      // is gone with the cell.\n      clean();\n    };\n    // transform is not a dependency because it doesn't change\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [nodeRef, graph, id, paper, setMeasuredNode]);\n\n  return size;\n}\n"],"names":["useGraphStore","usePaper","useContext","CellIdContext","useCell","selectElementSize","useLayoutEffect","MEASURING_CLASS_NAME"],"mappings":"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"}