UNPKG

@wordpress/components

Version:
8 lines (7 loc) 7 kB
{ "version": 3, "sources": ["../../src/utils/element-rect.ts"], "sourcesContent": ["/* eslint-disable jsdoc/require-param */\n/**\n * WordPress dependencies\n */\nimport { useLayoutEffect, useRef, useState } from '@wordpress/element';\nimport { useEvent, useResizeObserver } from '@wordpress/compose';\n\n/**\n * The position and dimensions of an element, relative to its offset parent.\n */\n\n/**\n * An `ElementOffsetRect` object with all values set to zero.\n */\nexport const NULL_ELEMENT_OFFSET_RECT = {\n element: undefined,\n top: 0,\n right: 0,\n bottom: 0,\n left: 0,\n width: 0,\n height: 0\n};\n\n/**\n * Returns the position and dimensions of an element, relative to its offset\n * parent, with subpixel precision. Values reflect the real measures before any\n * potential scaling distortions along the X and Y axes.\n *\n * Useful in contexts where plain `getBoundingClientRect` calls or `ResizeObserver`\n * entries are not suitable, such as when the element is transformed, and when\n * `element.offset<Top|Left|Width|Height>` methods are not precise enough.\n *\n * **Note:** in some contexts, like when the scale is 0, this method will fail\n * because it's impossible to calculate a scaling ratio. When that happens, it\n * will return `undefined`.\n */\nexport function getElementOffsetRect(element) {\n // Position and dimension values computed with `getBoundingClientRect` have\n // subpixel precision, but are affected by distortions since they represent\n // the \"real\" measures, or in other words, the actual final values as rendered\n // by the browser.\n const rect = element.getBoundingClientRect();\n if (rect.width === 0 || rect.height === 0) {\n return;\n }\n const offsetParent = element.offsetParent;\n const offsetParentRect = offsetParent?.getBoundingClientRect() ?? NULL_ELEMENT_OFFSET_RECT;\n const offsetParentScrollX = offsetParent?.scrollLeft ?? 0;\n const offsetParentScrollY = offsetParent?.scrollTop ?? 0;\n\n // Computed widths and heights have subpixel precision, and are not affected\n // by distortions.\n const computedWidth = parseFloat(getComputedStyle(element).width);\n const computedHeight = parseFloat(getComputedStyle(element).height);\n\n // We can obtain the current scale factor for the element by comparing \"computed\"\n // dimensions with the \"real\" ones.\n const scaleX = computedWidth / rect.width;\n const scaleY = computedHeight / rect.height;\n return {\n element,\n // To obtain the adjusted values for the position:\n // 1. Compute the element's position relative to the offset parent.\n // 2. Correct for the scale factor.\n // 3. Adjust for the scroll position of the offset parent.\n top: (rect.top - offsetParentRect?.top) * scaleY + offsetParentScrollY,\n right: (offsetParentRect?.right - rect.right) * scaleX - offsetParentScrollX,\n bottom: (offsetParentRect?.bottom - rect.bottom) * scaleY - offsetParentScrollY,\n left: (rect.left - offsetParentRect?.left) * scaleX + offsetParentScrollX,\n // Computed dimensions don't need any adjustments.\n width: computedWidth,\n height: computedHeight\n };\n}\nconst POLL_RATE = 100;\n\n/**\n * Tracks the position and dimensions of an element, relative to its offset\n * parent. The element can be changed dynamically.\n *\n * When no element is provided (`null` or `undefined`), the hook will return\n * a \"null\" rect, in which all values are `0` and `element` is `undefined`.\n *\n * **Note:** sometimes, the measurement will fail (see `getElementOffsetRect`'s\n * documentation for more details). When that happens, this hook will attempt\n * to measure again after a frame, and if that fails, it will poll every 100\n * milliseconds until it succeeds.\n */\nexport function useTrackElementOffsetRect(targetElement, deps = []) {\n const [indicatorPosition, setIndicatorPosition] = useState(NULL_ELEMENT_OFFSET_RECT);\n const intervalRef = useRef(undefined);\n const measure = useEvent(() => {\n // Check that the targetElement is still attached to the DOM, in case\n // it was removed since the last `measure` call.\n if (targetElement && targetElement.isConnected) {\n const elementOffsetRect = getElementOffsetRect(targetElement);\n if (elementOffsetRect) {\n setIndicatorPosition(elementOffsetRect);\n clearInterval(intervalRef.current);\n return true;\n }\n } else {\n clearInterval(intervalRef.current);\n }\n return false;\n });\n const setElement = useResizeObserver(() => {\n if (!measure()) {\n requestAnimationFrame(() => {\n if (!measure()) {\n intervalRef.current = setInterval(measure, POLL_RATE);\n }\n });\n }\n });\n useLayoutEffect(() => {\n setElement(targetElement);\n if (!targetElement) {\n setIndicatorPosition(NULL_ELEMENT_OFFSET_RECT);\n }\n }, [setElement, targetElement]);\n\n // Escape hatch to force a remeasurement when something else changes rather\n // than the target elements' ref or size (for example, the target element\n // can change its position within the tablist).\n useLayoutEffect(() => {\n measure();\n // `measure` is a stable function, so it's safe to omit it from the deps array.\n // deps can't be statically analyzed by ESLint\n }, deps);\n return indicatorPosition;\n}\n\n/* eslint-enable jsdoc/require-param */"], "mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAIA,qBAAkD;AAClD,qBAA4C;AASrC,IAAM,2BAA2B;AAAA,EACtC,SAAS;AAAA,EACT,KAAK;AAAA,EACL,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,OAAO;AAAA,EACP,QAAQ;AACV;AAeO,SAAS,qBAAqB,SAAS;AAK5C,QAAM,OAAO,QAAQ,sBAAsB;AAC3C,MAAI,KAAK,UAAU,KAAK,KAAK,WAAW,GAAG;AACzC;AAAA,EACF;AACA,QAAM,eAAe,QAAQ;AAC7B,QAAM,mBAAmB,cAAc,sBAAsB,KAAK;AAClE,QAAM,sBAAsB,cAAc,cAAc;AACxD,QAAM,sBAAsB,cAAc,aAAa;AAIvD,QAAM,gBAAgB,WAAW,iBAAiB,OAAO,EAAE,KAAK;AAChE,QAAM,iBAAiB,WAAW,iBAAiB,OAAO,EAAE,MAAM;AAIlE,QAAM,SAAS,gBAAgB,KAAK;AACpC,QAAM,SAAS,iBAAiB,KAAK;AACrC,SAAO;AAAA,IACL;AAAA;AAAA;AAAA;AAAA;AAAA,IAKA,MAAM,KAAK,MAAM,kBAAkB,OAAO,SAAS;AAAA,IACnD,QAAQ,kBAAkB,QAAQ,KAAK,SAAS,SAAS;AAAA,IACzD,SAAS,kBAAkB,SAAS,KAAK,UAAU,SAAS;AAAA,IAC5D,OAAO,KAAK,OAAO,kBAAkB,QAAQ,SAAS;AAAA;AAAA,IAEtD,OAAO;AAAA,IACP,QAAQ;AAAA,EACV;AACF;AACA,IAAM,YAAY;AAcX,SAAS,0BAA0B,eAAe,OAAO,CAAC,GAAG;AAClE,QAAM,CAAC,mBAAmB,oBAAoB,QAAI,yBAAS,wBAAwB;AACnF,QAAM,kBAAc,uBAAO,MAAS;AACpC,QAAM,cAAU,yBAAS,MAAM;AAG7B,QAAI,iBAAiB,cAAc,aAAa;AAC9C,YAAM,oBAAoB,qBAAqB,aAAa;AAC5D,UAAI,mBAAmB;AACrB,6BAAqB,iBAAiB;AACtC,sBAAc,YAAY,OAAO;AACjC,eAAO;AAAA,MACT;AAAA,IACF,OAAO;AACL,oBAAc,YAAY,OAAO;AAAA,IACnC;AACA,WAAO;AAAA,EACT,CAAC;AACD,QAAM,iBAAa,kCAAkB,MAAM;AACzC,QAAI,CAAC,QAAQ,GAAG;AACd,4BAAsB,MAAM;AAC1B,YAAI,CAAC,QAAQ,GAAG;AACd,sBAAY,UAAU,YAAY,SAAS,SAAS;AAAA,QACtD;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF,CAAC;AACD,sCAAgB,MAAM;AACpB,eAAW,aAAa;AACxB,QAAI,CAAC,eAAe;AAClB,2BAAqB,wBAAwB;AAAA,IAC/C;AAAA,EACF,GAAG,CAAC,YAAY,aAAa,CAAC;AAK9B,sCAAgB,MAAM;AACpB,YAAQ;AAAA,EAGV,GAAG,IAAI;AACP,SAAO;AACT;", "names": [] }