Repository: react-spring/react-use-measure
Branch: master
Commit: 2df0ee8dfad4
Files: 10
Total size: 20.9 KB
Directory structure:
gitextract_0msf38bd/
├── .github/
│ └── workflows/
│ └── CI.yml
├── .gitignore
├── .prettierrc
├── LICENSE
├── package.json
├── readme.md
├── src/
│ └── index.ts
├── tests/
│ └── index.test.tsx
├── tsconfig.json
└── vite.config.ts
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/CI.yml
================================================
name: CI
on:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
build-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- name: Install dependencies
run: yarn install
- name: Check build health
run: yarn build
- name: Run tests
run: yarn test
================================================
FILE: .gitignore
================================================
.DS_Store
node_modules
dist
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
================================================
FILE: .prettierrc
================================================
{
"semi": false,
"trailingComma": "all",
"singleQuote": true,
"tabWidth": 2,
"printWidth": 120
}
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2019-2025 Poimandres
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: package.json
================================================
{
"name": "react-use-measure",
"version": "2.1.7",
"description": "Utility to measure view bounds",
"keywords": [
"react",
"use",
"measure",
"bounds",
"hooks"
],
"author": "Paul Henschel",
"homepage": "https://github.com/pmndrs/react-use-measure",
"repository": "https://github.com/pmndrs/react-use-measure",
"license": "MIT",
"files": [
"dist/*",
"src/*"
],
"type": "module",
"types": "./dist/index.d.ts",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"exports": {
"require": {
"types": "./dist/index.d.ts",
"default": "./dist/index.cjs"
},
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"sideEffects": false,
"devDependencies": {
"@testing-library/dom": "^10.4.0",
"@testing-library/react": "^16.2.0",
"@types/node": "^22.12.0",
"@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3",
"@vitest/browser": "^3.0.4",
"playwright": "^1.50.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"resize-observer-polyfill": "^1.5.1",
"rimraf": "^6.0.1",
"typescript": "^5.7.3",
"vite": "^6.0.11",
"vitest": "^3.0.4"
},
"peerDependencies": {
"react": ">=16.13",
"react-dom": ">=16.13"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
}
},
"scripts": {
"dev": "vite",
"build": "rimraf dist && vite build && tsc",
"test": "npx playwright install && vitest run"
}
}
================================================
FILE: readme.md
================================================
<p align="center">
<img height="400" src="https://i.imgur.com/eMYYMla.jpg" />
</p>
yarn add react-use-measure
This small tool will measure the boundaries (for instance width, height, top, left) of a view you reference. It is reactive and responds to changes in size, window-scroll and nested-area-scroll.
### Why do we need this hook?
Because there is [no simple way](https://stackoverflow.com/questions/442404/retrieve-the-position-x-y-of-an-html-element) to just get relative view coordinates. Yes, there is getBoundingClientRect, but it does not work when your content sits inside scroll areas whose offsets are simply neglected (as well as page scroll). Worse, mouse coordinates are relative to the viewport (the visible rect that contains the page). There is no easy way, for instance, to know that the mouse hovers over the upper/left corner of an element. This hook solves it for you.
You can try a live demo here: https://codesandbox.io/s/musing-kare-4fblz
# Usage
```jsx
import useMeasure from 'react-use-measure'
function App() {
const [ref, bounds] = useMeasure()
// consider that knowing bounds is only possible *after* the view renders
// so you'll get zero values on the first run and be informed later
return <div ref={ref} />
}
```
# Api
```jsx
interface RectReadOnly {
readonly x: number
readonly y: number
readonly width: number
readonly height: number
readonly top: number
readonly right: number
readonly bottom: number
readonly left: number
}
type Options = {
// Debounce events in milliseconds
debounce?: number | { scroll: number; resize: number }
// React to nested scroll changes, don't use this if you know your view is static
scroll?: boolean
// You can optionally inject a resize-observer polyfill
polyfill?: { new (cb: ResizeObserverCallback): ResizeObserver }
// Measure size using offsetHeight and offsetWidth to ignore parent scale transforms
offsetSize?: boolean
}
useMeasure(
options: Options = { debounce: 0, scroll: false }
): [React.MutableRefObject<HTMLElement | SVGElement>, RectReadOnly]
```
# ⚠️ Notes
### Resize-observer polyfills
This lib relies on resize-observers. If you need a polyfill you can either polute the `window` object or inject it cleanly using the config options. We recommend [@juggle/resize-observer](https://github.com/juggle/resize-observer).
```jsx
import { ResizeObserver } from '@juggle/resize-observer'
function App() {
const [ref, bounds] = useMeasure({ polyfill: ResizeObserver })
```
### Multiple refs
useMeasure currently returns its own ref. We do this because we are using functional refs for unmount tracking. If you need to have a ref of your own on the same element, use [react-merge-refs](https://github.com/smooth-code/react-merge-refs).
================================================
FILE: src/index.ts
================================================
import { useEffect, useState, useRef, useMemo } from 'react'
function createDebounce<T extends (...args: any[]) => void>(callback: T, ms: number) {
let timeoutId: number
return (...args: Parameters<T>): void => {
window.clearTimeout(timeoutId)
timeoutId = window.setTimeout(() => callback(...args), ms)
}
}
declare type ResizeObserverCallback = (entries: any[], observer: ResizeObserver) => void
declare class ResizeObserver {
constructor(callback: ResizeObserverCallback)
observe(target: Element, options?: any): void
unobserve(target: Element): void
disconnect(): void
static toString(): string
}
export interface RectReadOnly {
readonly x: number
readonly y: number
readonly width: number
readonly height: number
readonly top: number
readonly right: number
readonly bottom: number
readonly left: number
[key: string]: number
}
type HTMLOrSVGElement = HTMLElement | SVGElement
type Result = [(element: HTMLOrSVGElement | null) => void, RectReadOnly, () => void]
type State = {
element: HTMLOrSVGElement | null
scrollContainers: HTMLOrSVGElement[] | null
resizeObserver: ResizeObserver | null
lastBounds: RectReadOnly
orientationHandler: null | (() => void)
}
export type Options = {
debounce?: number | { scroll: number; resize: number }
scroll?: boolean
polyfill?: { new (cb: ResizeObserverCallback): ResizeObserver }
offsetSize?: boolean
}
function useMeasure(
{ debounce, scroll, polyfill, offsetSize }: Options = { debounce: 0, scroll: false, offsetSize: false },
): Result {
const ResizeObserver =
polyfill || (typeof window === 'undefined' ? class ResizeObserver {} : (window as any).ResizeObserver)
if (!ResizeObserver) {
throw new Error(
'This browser does not support ResizeObserver out of the box. See: https://github.com/react-spring/react-use-measure/#resize-observer-polyfills',
)
}
const [bounds, set] = useState<RectReadOnly>({
left: 0,
top: 0,
width: 0,
height: 0,
bottom: 0,
right: 0,
x: 0,
y: 0,
})
// keep all state in a ref
const state = useRef<State>({
element: null,
scrollContainers: null,
resizeObserver: null,
lastBounds: bounds,
orientationHandler: null,
})
// set actual debounce values early, so effects know if they should react accordingly
const scrollDebounce = debounce ? (typeof debounce === 'number' ? debounce : debounce.scroll) : null
const resizeDebounce = debounce ? (typeof debounce === 'number' ? debounce : debounce.resize) : null
// make sure to update state only as long as the component is truly mounted
const mounted = useRef(false)
useEffect(() => {
mounted.current = true
return () => void (mounted.current = false)
})
// memoize handlers, so event-listeners know when they should update
const [forceRefresh, resizeChange, scrollChange] = useMemo(() => {
const callback = () => {
if (!state.current.element) return
const { left, top, width, height, bottom, right, x, y } =
state.current.element.getBoundingClientRect() as unknown as RectReadOnly
const size = {
left,
top,
width,
height,
bottom,
right,
x,
y,
}
if (state.current.element instanceof HTMLElement && offsetSize) {
size.height = state.current.element.offsetHeight
size.width = state.current.element.offsetWidth
}
Object.freeze(size)
if (mounted.current && !areBoundsEqual(state.current.lastBounds, size)) set((state.current.lastBounds = size))
}
return [
callback,
resizeDebounce ? createDebounce(callback, resizeDebounce) : callback,
scrollDebounce ? createDebounce(callback, scrollDebounce) : callback,
]
}, [set, offsetSize, scrollDebounce, resizeDebounce])
// cleanup current scroll-listeners / observers
function removeListeners() {
if (state.current.scrollContainers) {
state.current.scrollContainers.forEach((element) => element.removeEventListener('scroll', scrollChange, true))
state.current.scrollContainers = null
}
if (state.current.resizeObserver) {
state.current.resizeObserver.disconnect()
state.current.resizeObserver = null
}
if (state.current.orientationHandler) {
if ('orientation' in screen && 'removeEventListener' in screen.orientation) {
screen.orientation.removeEventListener('change', state.current.orientationHandler)
} else if ('onorientationchange' in window) {
window.removeEventListener('orientationchange', state.current.orientationHandler)
}
}
}
// add scroll-listeners / observers
function addListeners() {
if (!state.current.element) return
state.current.resizeObserver = new ResizeObserver(scrollChange)
state.current.resizeObserver!.observe(state.current.element)
if (scroll && state.current.scrollContainers) {
state.current.scrollContainers.forEach((scrollContainer) =>
scrollContainer.addEventListener('scroll', scrollChange, { capture: true, passive: true }),
)
}
// Handle orientation changes
state.current.orientationHandler = () => {
scrollChange()
}
// Use screen.orientation if available
if ('orientation' in screen && 'addEventListener' in screen.orientation) {
screen.orientation.addEventListener('change', state.current.orientationHandler)
} else if ('onorientationchange' in window) {
// Fallback to orientationchange event
window.addEventListener('orientationchange', state.current.orientationHandler)
}
}
// the ref we expose to the user
const ref = (node: HTMLOrSVGElement | null) => {
if (!node || node === state.current.element) return
removeListeners()
state.current.element = node
state.current.scrollContainers = findScrollContainers(node)
addListeners()
}
// add general event listeners
useOnWindowScroll(scrollChange, Boolean(scroll))
useOnWindowResize(resizeChange)
// respond to changes that are relevant for the listeners
useEffect(() => {
removeListeners()
addListeners()
}, [scroll, scrollChange, resizeChange])
// remove all listeners when the components unmounts
useEffect(() => removeListeners, [])
return [ref, bounds, forceRefresh]
}
// Adds native resize listener to window
function useOnWindowResize(onWindowResize: (event: Event) => void) {
useEffect(() => {
const cb = onWindowResize
window.addEventListener('resize', cb)
return () => void window.removeEventListener('resize', cb)
}, [onWindowResize])
}
function useOnWindowScroll(onScroll: () => void, enabled: boolean) {
useEffect(() => {
if (enabled) {
const cb = onScroll
window.addEventListener('scroll', cb, { capture: true, passive: true })
return () => void window.removeEventListener('scroll', cb, true)
}
}, [onScroll, enabled])
}
// Returns a list of scroll offsets
function findScrollContainers(element: HTMLOrSVGElement | null): HTMLOrSVGElement[] {
const result: HTMLOrSVGElement[] = []
if (!element || element === document.body) return result
const { overflow, overflowX, overflowY } = window.getComputedStyle(element)
if ([overflow, overflowX, overflowY].some((prop) => prop === 'auto' || prop === 'scroll')) result.push(element)
return [...result, ...findScrollContainers(element.parentElement)]
}
// Checks if element boundaries are equal
const keys: (keyof RectReadOnly)[] = ['x', 'y', 'top', 'bottom', 'left', 'right', 'width', 'height']
const areBoundsEqual = (a: RectReadOnly, b: RectReadOnly): boolean => keys.every((key) => a[key] === b[key])
export default useMeasure
================================================
FILE: tests/index.test.tsx
================================================
import * as React from 'react'
import { render, cleanup, RenderResult, fireEvent } from '@testing-library/react'
import Polyfill from 'resize-observer-polyfill'
import { afterEach, describe, it, expect } from 'vitest'
import useMeasure, { Options } from '../src/index'
/**
* Helpers
*/
const getBounds = (tools: RenderResult): DOMRect => JSON.parse(tools.getByTestId('box').innerHTML)
const nextFrame = () => new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)))
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
function ignoreWindowErrors(test: () => void) {
const onErrorBackup = window.onerror
window.onerror = () => null
const consoleError = console.error
console.error = () => null
test()
window.onerror = onErrorBackup
console.error = consoleError
}
/**
* Tests
*/
afterEach(() => {
cleanup()
window.scrollTo({ top: 0, left: 0 })
})
describe('useMeasure', () => {
type Props = {
switchRef?: boolean
scale?: number
onRender?: () => void
options?: Options
polyfill?: boolean
offsetSize?: boolean
}
function Test({ switchRef, options, onRender, polyfill, scale = 1, offsetSize = false }: Props) {
const [ref, bounds] = useMeasure({ ...options, polyfill: polyfill ? Polyfill : undefined, offsetSize })
const [big, setBig] = React.useState(false)
if (onRender) {
onRender()
}
return (
<>
<style>{'body, html { margin: 0; } body { height: 200vh; }'}</style>
<div
data-testid="wrapper"
style={{
transform: `scale(${scale})`,
width: '500px',
height: '500px',
overflow: 'auto',
}}
>
<div
ref={!switchRef ? ref : undefined}
data-testid="box"
onClick={() => setBig(!big)}
style={{
width: `${big ? 400 : 200}px`,
height: `${big ? 400 : 200}px`,
overflow: 'hidden',
fontSize: '8px',
}}
>
{JSON.stringify(bounds)}
</div>
<div style={{ width: 2000, height: 2000 }} />
</div>
<div ref={switchRef ? ref : null}>Dummy</div>
</>
)
}
it('gives empty initial bounds on first render', async () => {
const tools = render(<Test />)
expect(getBounds(tools).width).toBe(0)
expect(getBounds(tools).height).toBe(0)
expect(getBounds(tools).top).toBe(0)
expect(getBounds(tools).left).toBe(0)
})
it('renders 1 additional time after first render', async () => {
let count = 0
render(<Test onRender={() => count++} />)
await nextFrame()
expect(count).toBe(2)
})
it('gives correct dimensions and positions after initial render', async () => {
const tools = render(<Test />)
await nextFrame()
expect(getBounds(tools).width).toBe(200)
expect(getBounds(tools).height).toBe(200)
expect(getBounds(tools).top).toBe(0)
expect(getBounds(tools).left).toBe(0)
})
it('gives correct dimensions and positions when the tracked elements changes in size', async () => {
const tools = render(<Test />)
fireEvent.click(tools.getByTestId('box'))
await nextFrame()
expect(getBounds(tools).width).toBe(400)
expect(getBounds(tools).height).toBe(400)
expect(getBounds(tools).top).toBe(0)
expect(getBounds(tools).left).toBe(0)
})
it('gives correct dimensions and positions when the page is scrolled', async () => {
const tools = render(<Test options={{ scroll: true }} />)
window.scrollTo({ top: 200 })
await nextFrame()
expect(getBounds(tools).top).toBe(-200)
expect(getBounds(tools).left).toBe(0)
})
it('gives correct dimensions and positions when the wrapper is scrolled', async () => {
const tools = render(<Test options={{ scroll: true }} />)
tools.getByTestId('wrapper').scrollTo({ top: 200 })
await nextFrame()
expect(getBounds(tools).top).toBe(-200)
expect(getBounds(tools).left).toBe(0)
})
it('gives correct size when offsetSize: true and parent is scaled', async () => {
const tools = render(<Test offsetSize scale={0.8} />)
await nextFrame()
expect(getBounds(tools).width).toBe(200)
expect(getBounds(tools).height).toBe(200)
})
it('gives correct size when offsetSize: false and parent is scaled', async () => {
const tools = render(<Test scale={0.8} />)
await nextFrame()
expect(getBounds(tools).width).toBe(200 * 0.8)
expect(getBounds(tools).height).toBe(200 * 0.8)
})
it('debounces the scroll events', async () => {
const tools = render(<Test options={{ scroll: true, debounce: { scroll: 50, resize: 0 } }} />)
const wrapper = tools.getByTestId('wrapper')
wrapper.scrollTo({ top: 200 })
await nextFrame()
wrapper.scrollTo({ top: 201 })
await nextFrame()
wrapper.scrollTo({ top: 202 })
await nextFrame()
expect(getBounds(tools).top).toBe(0)
await wait(100)
expect(getBounds(tools).top).toBe(-202)
})
// this one fails and needs to be fixed
it('detects changes in ref', async () => {
const tools = render(<Test />)
await wait(100)
tools.rerender(<Test switchRef />)
await nextFrame()
expect(getBounds(tools).top).toBe(500)
})
it('throws an descriptive error when the browser does not support ResizeObserver', () => {
const RO = (window as any).ResizeObserver
;(window as any).ResizeObserver = null
ignoreWindowErrors(() => {
expect(() => render(<Test />)).toThrow(
'This browser does not support ResizeObserver out of the box. See: https://github.com/react-spring/react-use-measure/#resize-observer-polyfills',
)
})
;(window as any).ResizeObserver = RO
})
it('does not throw when a ResizeObserver polyfill was provided', () => {
const RO = (window as any).ResizeObserver
;(window as any).ResizeObserver = null
ignoreWindowErrors(() => {
expect(() => render(<Test polyfill />)).not.toThrow(
'This browser does not support ResizeObserver out of the box. See: https://github.com/react-spring/react-use-measure/#resize-observer-polyfills',
)
})
;(window as any).ResizeObserver = RO
})
})
================================================
FILE: tsconfig.json
================================================
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"moduleResolution": "node",
"esModuleInterop": true,
"jsx": "react",
"pretty": true,
"strict": true,
"skipLibCheck": true,
"declaration": true,
"removeComments": true,
"emitDeclarationOnly": true,
"outDir": "dist",
"resolveJsonModule": true
},
"include": ["./src"],
"exclude": ["./node_modules/**/*"]
}
================================================
FILE: vite.config.ts
================================================
import * as path from 'node:path'
import * as vite from 'vite'
export default vite.defineConfig({
root: process.argv[2] ? undefined : 'demo',
resolve: {
alias: {
'use-measure': path.resolve(__dirname, './src'),
},
},
test: {
browser: {
provider: 'playwright',
enabled: true,
headless: true,
screenshotFailures: false,
instances: [{ browser: 'chromium' }],
},
},
build: {
target: 'es2018',
sourcemap: true,
lib: {
formats: ['es', 'cjs'],
entry: 'src/index.ts',
fileName: '[name]',
},
rollupOptions: {
external: (id: string) => !id.startsWith('.') && !path.isAbsolute(id),
output: {
sourcemapExcludeSources: true,
},
},
},
plugins: [
{
name: 'vite-minify',
renderChunk: {
order: 'post',
handler(code, { fileName }) {
return vite.transformWithEsbuild(code, fileName, { minify: true, target: 'es2018' })
},
},
},
],
})
gitextract_0msf38bd/ ├── .github/ │ └── workflows/ │ └── CI.yml ├── .gitignore ├── .prettierrc ├── LICENSE ├── package.json ├── readme.md ├── src/ │ └── index.ts ├── tests/ │ └── index.test.tsx ├── tsconfig.json └── vite.config.ts
SYMBOL INDEX (16 symbols across 3 files)
FILE: src/index.ts
function createDebounce (line 3) | function createDebounce<T extends (...args: any[]) => void>(callback: T,...
type ResizeObserverCallback (line 12) | type ResizeObserverCallback = (entries: any[], observer: ResizeObserver)...
class ResizeObserver (line 13) | class ResizeObserver {
type RectReadOnly (line 21) | interface RectReadOnly {
type HTMLOrSVGElement (line 33) | type HTMLOrSVGElement = HTMLElement | SVGElement
type Result (line 35) | type Result = [(element: HTMLOrSVGElement | null) => void, RectReadOnly,...
type State (line 37) | type State = {
type Options (line 45) | type Options = {
function useMeasure (line 52) | function useMeasure(
function useOnWindowResize (line 199) | function useOnWindowResize(onWindowResize: (event: Event) => void) {
function useOnWindowScroll (line 206) | function useOnWindowScroll(onScroll: () => void, enabled: boolean) {
function findScrollContainers (line 217) | function findScrollContainers(element: HTMLOrSVGElement | null): HTMLOrS...
FILE: tests/index.test.tsx
function ignoreWindowErrors (line 16) | function ignoreWindowErrors(test: () => void) {
type Props (line 38) | type Props = {
function Test (line 47) | function Test({ switchRef, options, onRender, polyfill, scale = 1, offse...
FILE: vite.config.ts
method handler (line 40) | handler(code, { fileName }) {
Condensed preview — 10 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (23K chars).
[
{
"path": ".github/workflows/CI.yml",
"chars": 380,
"preview": "name: CI\non:\n push:\n branches: [master]\n pull_request:\n branches: [master]\n\njobs:\n build-test:\n runs-on: ubu"
},
{
"path": ".gitignore",
"chars": 110,
"preview": ".DS_Store\nnode_modules\ndist\n\n# Editor directories and files\n.idea\n.vscode\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
},
{
"path": ".prettierrc",
"chars": 107,
"preview": "{\n \"semi\": false,\n \"trailingComma\": \"all\",\n \"singleQuote\": true,\n \"tabWidth\": 2,\n \"printWidth\": 120\n}\n"
},
{
"path": "LICENSE",
"chars": 1072,
"preview": "MIT License\n\nCopyright (c) 2019-2025 Poimandres\n\nPermission is hereby granted, free of charge, to any person obtaining a"
},
{
"path": "package.json",
"chars": 1515,
"preview": "{\n \"name\": \"react-use-measure\",\n \"version\": \"2.1.7\",\n \"description\": \"Utility to measure view bounds\",\n \"keywords\": "
},
{
"path": "readme.md",
"chars": 2785,
"preview": "<p align=\"center\">\n <img height=\"400\" src=\"https://i.imgur.com/eMYYMla.jpg\" />\n</p>\n\n yarn add react-use-measure\n\nTh"
},
{
"path": "src/index.ts",
"chars": 7698,
"preview": "import { useEffect, useState, useRef, useMemo } from 'react'\n\nfunction createDebounce<T extends (...args: any[]) => void"
},
{
"path": "tests/index.test.tsx",
"chars": 6293,
"preview": "import * as React from 'react'\nimport { render, cleanup, RenderResult, fireEvent } from '@testing-library/react'\nimport "
},
{
"path": "tsconfig.json",
"chars": 425,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"es6\",\n \"module\": \"commonjs\",\n \"moduleResolution\": \"node\",\n \"esModuleInt"
},
{
"path": "vite.config.ts",
"chars": 1014,
"preview": "import * as path from 'node:path'\nimport * as vite from 'vite'\n\nexport default vite.defineConfig({\n root: process.argv["
}
]
About this extraction
This page contains the full source code of the react-spring/react-use-measure GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 10 files (20.9 KB), approximately 5.7k tokens, and a symbol index with 16 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.