@tamagui/use-element-layout 1.129.6 → 1.129.8
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.
- package/dist/cjs/index.cjs +45 -64
- package/dist/cjs/index.js +56 -50
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/index.native.js +61 -63
- package/dist/cjs/index.native.js.map +2 -2
- package/dist/esm/index.js +56 -50
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/index.mjs +45 -64
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/index.native.js +60 -69
- package/dist/esm/index.native.js.map +1 -1
- package/package.json +4 -4
- package/src/index.ts +74 -65
- package/types/index.d.ts +1 -2
- package/types/index.d.ts.map +5 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["isClient","useIsomorphicLayoutEffect","isEqualShallow","LayoutHandlers","WeakMap","Nodes","Set","strategy","setOnLayoutStrategy","state","NodeRectCache","ParentRectCache","
|
|
1
|
+
{"version":3,"names":["isClient","useIsomorphicLayoutEffect","isEqualShallow","LayoutHandlers","WeakMap","Nodes","Set","strategy","setOnLayoutStrategy","state","NodeRectCache","ParentRectCache","LastChangeTime","rAF","window","requestAnimationFrame","avoidUpdates","queuedUpdates","Map","enable","forEach","cb","clear","layoutOnAnimationFrame","now","Date","timeSinceLastFrame","lastFrameAt","expectedFrameTime","hasRecentSyncWork","numDroppedFramesUntilPause","node","updateLayoutIfChanged","layoutOnAnimationFrame2","frameId","parentNode","parentElement","nodeRect","parentRect","nr","pr","Promise","all","getBoundingClientRectAsync","getBoundingClientRect","onLayout","get","cachedRect","cachedParentRect","set","event","getElementLayoutEvent","event1","process","env","NODE_ENV","console","warn","nativeEvent","layout","getRelativeDimensions","target","timeStamp","measureLayout","relativeTo","callback","relativeNode","HTMLElement","nodeDim","relativeNodeDim","x","y","width","height","left","top","getElementLayoutEventAsync","measureLayoutAsync","Error","a","b","useElementLayout","ref","_ref_current","ensureWebElement","current","host","_ref_current2","node2","add","delete","res","nodeType","io","IntersectionObserver","entries"],"sources":["../../src/index.ts"],"sourcesContent":[null],"mappings":"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","ignoreList":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tamagui/use-element-layout",
|
|
3
|
-
"version": "1.129.
|
|
3
|
+
"version": "1.129.8",
|
|
4
4
|
"types": "./types/index.d.ts",
|
|
5
5
|
"main": "dist/cjs",
|
|
6
6
|
"module": "dist/esm",
|
|
@@ -31,11 +31,11 @@
|
|
|
31
31
|
}
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@tamagui/constants": "1.129.
|
|
35
|
-
"@tamagui/is-equal-shallow": "1.129.
|
|
34
|
+
"@tamagui/constants": "1.129.8",
|
|
35
|
+
"@tamagui/is-equal-shallow": "1.129.8"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@tamagui/build": "1.129.
|
|
38
|
+
"@tamagui/build": "1.129.8",
|
|
39
39
|
"react": "*"
|
|
40
40
|
},
|
|
41
41
|
"publishConfig": {
|
package/src/index.ts
CHANGED
|
@@ -40,7 +40,6 @@ const DebounceTimers = new WeakMap<HTMLElement, NodeJS.Timeout>()
|
|
|
40
40
|
const LastChangeTime = new WeakMap<HTMLElement, number>()
|
|
41
41
|
|
|
42
42
|
const rAF = typeof window !== 'undefined' ? window.requestAnimationFrame : undefined
|
|
43
|
-
const DEBOUNCE_DELAY = 32 // 32ms debounce (2 frames at 60fps)
|
|
44
43
|
|
|
45
44
|
// prevent thrashing during first hydration (somewhat, streaming gets trickier)
|
|
46
45
|
let avoidUpdates = true
|
|
@@ -62,10 +61,33 @@ if (isClient) {
|
|
|
62
61
|
let lastFrameAt = Date.now()
|
|
63
62
|
const numDroppedFramesUntilPause = 2 // adjust sensitivity
|
|
64
63
|
|
|
65
|
-
async function updateLayoutIfChanged(node: HTMLElement) {
|
|
66
|
-
const nodeRect = node.getBoundingClientRect()
|
|
64
|
+
async function updateLayoutIfChanged(node: HTMLElement, frameId: number) {
|
|
67
65
|
const parentNode = node.parentElement
|
|
68
|
-
|
|
66
|
+
|
|
67
|
+
let nodeRect: DOMRectReadOnly
|
|
68
|
+
let parentRect: DOMRectReadOnly | undefined
|
|
69
|
+
|
|
70
|
+
if (strategy === 'async') {
|
|
71
|
+
const [nr, pr] = await Promise.all([
|
|
72
|
+
getBoundingClientRectAsync(node),
|
|
73
|
+
getBoundingClientRectAsync(parentNode),
|
|
74
|
+
])
|
|
75
|
+
|
|
76
|
+
// cancel if we skipped a frame
|
|
77
|
+
if (frameId !== lastFrameAt) {
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
nodeRect = nr
|
|
82
|
+
parentRect = pr
|
|
83
|
+
} else {
|
|
84
|
+
nodeRect = node.getBoundingClientRect()
|
|
85
|
+
parentRect = parentNode?.getBoundingClientRect()
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!parentRect) {
|
|
89
|
+
return
|
|
90
|
+
}
|
|
69
91
|
|
|
70
92
|
const onLayout = LayoutHandlers.get(node)
|
|
71
93
|
if (typeof onLayout !== 'function') return
|
|
@@ -86,45 +108,12 @@ if (isClient) {
|
|
|
86
108
|
|
|
87
109
|
if (avoidUpdates) {
|
|
88
110
|
// Use sync version for queued updates to avoid promise complications
|
|
89
|
-
const event = getElementLayoutEvent(
|
|
111
|
+
const event = getElementLayoutEvent(nodeRect, parentRect)
|
|
90
112
|
queuedUpdates.set(node, () => onLayout(event))
|
|
91
113
|
} else if (strategy === 'async') {
|
|
92
|
-
// For async strategy, debounce the layout update
|
|
93
|
-
const now = Date.now()
|
|
94
|
-
LastChangeTime.set(node, now)
|
|
95
|
-
|
|
96
|
-
// Clear existing debounce timer
|
|
97
|
-
const existingTimer = DebounceTimers.get(node)
|
|
98
|
-
if (existingTimer) {
|
|
99
|
-
clearTimeout(existingTimer)
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Set new debounce timer
|
|
103
|
-
const timer = setTimeout(async () => {
|
|
104
|
-
const lastChange = LastChangeTime.get(node) || 0
|
|
105
|
-
const timeSinceChange = Date.now() - lastChange
|
|
106
|
-
|
|
107
|
-
// Only fire if at least DEBOUNCE_DELAY has passed since last change
|
|
108
|
-
if (timeSinceChange >= DEBOUNCE_DELAY) {
|
|
109
|
-
const event = await getElementLayoutEventAsync(node)
|
|
110
|
-
onLayout(event)
|
|
111
|
-
DebounceTimers.delete(node)
|
|
112
|
-
} else {
|
|
113
|
-
// Reschedule if not enough time has passed
|
|
114
|
-
const remainingDelay = DEBOUNCE_DELAY - timeSinceChange
|
|
115
|
-
const newTimer = setTimeout(async () => {
|
|
116
|
-
const event = await getElementLayoutEventAsync(node)
|
|
117
|
-
onLayout(event)
|
|
118
|
-
DebounceTimers.delete(node)
|
|
119
|
-
}, remainingDelay)
|
|
120
|
-
DebounceTimers.set(node, newTimer)
|
|
121
|
-
}
|
|
122
|
-
}, DEBOUNCE_DELAY)
|
|
123
|
-
|
|
124
|
-
DebounceTimers.set(node, timer)
|
|
125
114
|
} else {
|
|
126
115
|
// Sync strategy - use sync version
|
|
127
|
-
const event = getElementLayoutEvent(
|
|
116
|
+
const event = getElementLayoutEvent(nodeRect, parentRect)
|
|
128
117
|
onLayout(event)
|
|
129
118
|
}
|
|
130
119
|
}
|
|
@@ -139,15 +128,19 @@ if (isClient) {
|
|
|
139
128
|
lastFrameAt = now
|
|
140
129
|
|
|
141
130
|
if (strategy !== 'off') {
|
|
131
|
+
// for both strategies:
|
|
142
132
|
// avoid updates if we've been dropping frames (indicates sync work happening)
|
|
143
133
|
const expectedFrameTime = 16.67 // ~60fps
|
|
144
134
|
const hasRecentSyncWork =
|
|
145
135
|
timeSinceLastFrame > expectedFrameTime * numDroppedFramesUntilPause
|
|
146
136
|
|
|
147
137
|
if (!hasRecentSyncWork) {
|
|
148
|
-
Nodes.forEach(
|
|
138
|
+
Nodes.forEach((node) => {
|
|
139
|
+
updateLayoutIfChanged(node, lastFrameAt)
|
|
140
|
+
})
|
|
149
141
|
}
|
|
150
142
|
}
|
|
143
|
+
|
|
151
144
|
rAF!(layoutOnAnimationFrame)
|
|
152
145
|
}
|
|
153
146
|
} else {
|
|
@@ -159,22 +152,17 @@ if (isClient) {
|
|
|
159
152
|
}
|
|
160
153
|
}
|
|
161
154
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
173
|
-
})
|
|
174
|
-
if (!res) {
|
|
175
|
-
throw new Error(`‼️`) // impossible
|
|
155
|
+
export const getElementLayoutEvent = (
|
|
156
|
+
nodeRect: DOMRectReadOnly,
|
|
157
|
+
parentRect: DOMRectReadOnly
|
|
158
|
+
): LayoutEvent => {
|
|
159
|
+
return {
|
|
160
|
+
nativeEvent: {
|
|
161
|
+
layout: getRelativeDimensions(nodeRect, parentRect),
|
|
162
|
+
target: nodeRect,
|
|
163
|
+
},
|
|
164
|
+
timeStamp: Date.now(),
|
|
176
165
|
}
|
|
177
|
-
return res
|
|
178
166
|
}
|
|
179
167
|
|
|
180
168
|
export const measureLayout = (
|
|
@@ -227,8 +215,8 @@ export const measureLayoutAsync = async (
|
|
|
227
215
|
const relativeNode = relativeTo || node?.parentElement
|
|
228
216
|
if (relativeNode instanceof HTMLElement) {
|
|
229
217
|
const [nodeDim, relativeNodeDim] = await Promise.all([
|
|
230
|
-
node
|
|
231
|
-
relativeNode
|
|
218
|
+
getBoundingClientRectAsync(node),
|
|
219
|
+
getBoundingClientRectAsync(relativeNode),
|
|
232
220
|
])
|
|
233
221
|
|
|
234
222
|
if (relativeNodeDim && nodeDim) {
|
|
@@ -266,19 +254,22 @@ export function useElementLayout(
|
|
|
266
254
|
|
|
267
255
|
LayoutHandlers.set(node, onLayout)
|
|
268
256
|
Nodes.add(node)
|
|
269
|
-
|
|
257
|
+
|
|
258
|
+
// always do one immediate sync layout event no matter the strategy for accuracy
|
|
259
|
+
const parentNode = node.parentNode
|
|
260
|
+
if (parentNode) {
|
|
261
|
+
onLayout(
|
|
262
|
+
getElementLayoutEvent(
|
|
263
|
+
node.getBoundingClientRect(),
|
|
264
|
+
parentNode.getBoundingClientRect()
|
|
265
|
+
)
|
|
266
|
+
)
|
|
267
|
+
}
|
|
270
268
|
|
|
271
269
|
return () => {
|
|
272
270
|
Nodes.delete(node)
|
|
273
271
|
LayoutHandlers.delete(node)
|
|
274
272
|
NodeRectCache.delete(node)
|
|
275
|
-
|
|
276
|
-
// Clean up debounce timer and tracking
|
|
277
|
-
const timer = DebounceTimers.get(node)
|
|
278
|
-
if (timer) {
|
|
279
|
-
clearTimeout(timer)
|
|
280
|
-
DebounceTimers.delete(node)
|
|
281
|
-
}
|
|
282
273
|
LastChangeTime.delete(node)
|
|
283
274
|
}
|
|
284
275
|
}, [ref, !!onLayout])
|
|
@@ -291,6 +282,24 @@ function ensureWebElement<X>(x: X): HTMLElement | undefined {
|
|
|
291
282
|
return x instanceof HTMLElement ? x : undefined
|
|
292
283
|
}
|
|
293
284
|
|
|
285
|
+
const getBoundingClientRectAsync = (
|
|
286
|
+
node: HTMLElement | null
|
|
287
|
+
): Promise<DOMRectReadOnly> => {
|
|
288
|
+
return new Promise<DOMRectReadOnly>((res) => {
|
|
289
|
+
if (!node || node.nodeType !== 1) return
|
|
290
|
+
const io = new IntersectionObserver(
|
|
291
|
+
(entries) => {
|
|
292
|
+
io.disconnect()
|
|
293
|
+
return res(entries[0].boundingClientRect)
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
threshold: 0,
|
|
297
|
+
}
|
|
298
|
+
)
|
|
299
|
+
io.observe(node)
|
|
300
|
+
})
|
|
301
|
+
}
|
|
302
|
+
|
|
294
303
|
const getBoundingClientRect = (node: HTMLElement | null): undefined | DOMRect => {
|
|
295
304
|
if (!node || node.nodeType !== 1) return
|
|
296
305
|
return node.getBoundingClientRect?.()
|
package/types/index.d.ts
CHANGED
|
@@ -20,8 +20,7 @@ export type LayoutEvent = {
|
|
|
20
20
|
timeStamp: number;
|
|
21
21
|
};
|
|
22
22
|
export declare function enable(): void;
|
|
23
|
-
|
|
24
|
-
export declare const getElementLayoutEvent: (target: HTMLElement) => LayoutEvent;
|
|
23
|
+
export declare const getElementLayoutEvent: (nodeRect: DOMRectReadOnly, parentRect: DOMRectReadOnly) => LayoutEvent;
|
|
25
24
|
export declare const measureLayout: (node: HTMLElement, relativeTo: HTMLElement | null, callback: (x: number, y: number, width: number, height: number, left: number, top: number) => void) => void;
|
|
26
25
|
export declare const getElementLayoutEventAsync: (target: HTMLElement) => Promise<LayoutEvent>;
|
|
27
26
|
export declare const measureLayoutAsync: (node: HTMLElement, relativeTo?: HTMLElement | null) => Promise<null | LayoutValue>;
|
package/types/index.d.ts.map
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"mappings": "AAEA,cAAc,iBAAiB,OAAO;KAKjC,+BAA+B;CAClC;AACD;KAEI,4BAA4B,QAAQ,SAAS;AAIlD,OAAO,iBAAS,oBAAoBA,OAAO;AAI3C,YAAY,cAAc;CACxB;CACA;CACA;CACA;CACA;CACA;AACD;AAED,YAAY,cAAc;CACxB,aAAa;EACX,QAAQ;EACR;CACD;CACD;AACD;
|
|
2
|
+
"mappings": "AAEA,cAAc,iBAAiB,OAAO;KAKjC,+BAA+B;CAClC;AACD;KAEI,4BAA4B,QAAQ,SAAS;AAIlD,OAAO,iBAAS,oBAAoBA,OAAO;AAI3C,YAAY,cAAc;CACxB;CACA;CACA;CACA;CACA;CACA;AACD;AAED,YAAY,cAAc;CACxB,aAAa;EACX,QAAQ;EACR;CACD;CACD;AACD;AAaD,OAAO,iBAAS;AA2GhB,OAAO,cAAM,wBACXC,UAAU,iBACVC,YAAY,oBACX;AAUH,OAAO,cAAM,gBACXC,MAAM,aACNC,YAAY,oBACZC,WACEC,WACAC,WACAC,eACAC,gBACAC,cACAC;AAkBJ,OAAO,cAAM,6BACXC,QAAQ,gBACP,QAAQ;AAcX,OAAO,cAAM,qBACXT,MAAM,aACNU,aAAa,uBACZ,eAAe;AA0BlB,OAAO,iBAAS,iBACdC,KAAK,UAAU,+BACfC,aAAaC,GAAG;AAkElB,OAAO,cAAM,UAAWb,MAAM,gBAAc",
|
|
3
3
|
"names": [
|
|
4
4
|
"state: LayoutMeasurementStrategy",
|
|
5
|
-
"
|
|
5
|
+
"nodeRect: DOMRectReadOnly",
|
|
6
|
+
"parentRect: DOMRectReadOnly",
|
|
6
7
|
"node: HTMLElement",
|
|
7
8
|
"relativeTo: HTMLElement | null",
|
|
8
9
|
"callback: (\n x: number,\n y: number,\n width: number,\n height: number,\n left: number,\n top: number\n ) => void",
|
|
@@ -12,6 +13,7 @@
|
|
|
12
13
|
"height: number",
|
|
13
14
|
"left: number",
|
|
14
15
|
"top: number",
|
|
16
|
+
"target: HTMLElement",
|
|
15
17
|
"relativeTo?: HTMLElement | null",
|
|
16
18
|
"ref: RefObject<TamaguiComponentStatePartial>",
|
|
17
19
|
"onLayout?: ((e: LayoutEvent) => void) | null",
|
|
@@ -21,7 +23,7 @@
|
|
|
21
23
|
"src/index.ts"
|
|
22
24
|
],
|
|
23
25
|
"sourcesContent": [
|
|
24
|
-
"import { isClient, useIsomorphicLayoutEffect } from '@tamagui/constants'\nimport { isEqualShallow } from '@tamagui/is-equal-shallow'\nimport type { RefObject } from 'react'\n\nconst LayoutHandlers = new WeakMap<HTMLElement, Function>()\nconst Nodes = new Set<HTMLElement>()\n\ntype TamaguiComponentStatePartial = {\n host?: any\n}\n\ntype LayoutMeasurementStrategy = 'off' | 'sync' | 'async'\n\nlet strategy: LayoutMeasurementStrategy = 'async'\n\nexport function setOnLayoutStrategy(state: LayoutMeasurementStrategy): void {\n strategy = state\n}\n\nexport type LayoutValue = {\n x: number\n y: number\n width: number\n height: number\n left: number\n top: number\n}\n\nexport type LayoutEvent = {\n nativeEvent: {\n layout: LayoutValue\n target: any\n }\n timeStamp: number\n}\n\nconst NodeRectCache = new WeakMap<HTMLElement, DOMRect>()\nconst ParentRectCache = new WeakMap<HTMLElement, DOMRect>()\nconst DebounceTimers = new WeakMap<HTMLElement, NodeJS.Timeout>()\nconst LastChangeTime = new WeakMap<HTMLElement, number>()\n\nconst rAF = typeof window !== 'undefined' ? window.requestAnimationFrame : undefined\
|
|
26
|
+
"import { isClient, useIsomorphicLayoutEffect } from '@tamagui/constants'\nimport { isEqualShallow } from '@tamagui/is-equal-shallow'\nimport type { RefObject } from 'react'\n\nconst LayoutHandlers = new WeakMap<HTMLElement, Function>()\nconst Nodes = new Set<HTMLElement>()\n\ntype TamaguiComponentStatePartial = {\n host?: any\n}\n\ntype LayoutMeasurementStrategy = 'off' | 'sync' | 'async'\n\nlet strategy: LayoutMeasurementStrategy = 'async'\n\nexport function setOnLayoutStrategy(state: LayoutMeasurementStrategy): void {\n strategy = state\n}\n\nexport type LayoutValue = {\n x: number\n y: number\n width: number\n height: number\n left: number\n top: number\n}\n\nexport type LayoutEvent = {\n nativeEvent: {\n layout: LayoutValue\n target: any\n }\n timeStamp: number\n}\n\nconst NodeRectCache = new WeakMap<HTMLElement, DOMRect>()\nconst ParentRectCache = new WeakMap<HTMLElement, DOMRect>()\nconst DebounceTimers = new WeakMap<HTMLElement, NodeJS.Timeout>()\nconst LastChangeTime = new WeakMap<HTMLElement, number>()\n\nconst rAF = typeof window !== 'undefined' ? window.requestAnimationFrame : undefined\n\n// prevent thrashing during first hydration (somewhat, streaming gets trickier)\nlet avoidUpdates = true\nconst queuedUpdates = new Map<HTMLElement, Function>()\n\nexport function enable(): void {\n if (avoidUpdates) {\n avoidUpdates = false\n if (queuedUpdates) {\n queuedUpdates.forEach((cb) => cb())\n queuedUpdates.clear()\n }\n }\n}\n\nif (isClient) {\n if (rAF) {\n // track frame timing to detect sync work and avoid updates during heavy periods\n let lastFrameAt = Date.now()\n const numDroppedFramesUntilPause = 2 // adjust sensitivity\n\n async function updateLayoutIfChanged(node: HTMLElement, frameId: number) {\n const parentNode = node.parentElement\n\n let nodeRect: DOMRectReadOnly\n let parentRect: DOMRectReadOnly | undefined\n\n if (strategy === 'async') {\n const [nr, pr] = await Promise.all([\n getBoundingClientRectAsync(node),\n getBoundingClientRectAsync(parentNode),\n ])\n\n // cancel if we skipped a frame\n if (frameId !== lastFrameAt) {\n return\n }\n\n nodeRect = nr\n parentRect = pr\n } else {\n nodeRect = node.getBoundingClientRect()\n parentRect = parentNode?.getBoundingClientRect()\n }\n\n if (!parentRect) {\n return\n }\n\n const onLayout = LayoutHandlers.get(node)\n if (typeof onLayout !== 'function') return\n\n const cachedRect = NodeRectCache.get(node)\n const cachedParentRect = parentNode ? NodeRectCache.get(parentNode) : null\n\n if (\n !cachedRect ||\n // has changed one rect\n (!isEqualShallow(cachedRect, nodeRect) &&\n (!cachedParentRect || !isEqualShallow(cachedParentRect, parentRect)))\n ) {\n NodeRectCache.set(node, nodeRect)\n if (parentRect && parentNode) {\n ParentRectCache.set(parentNode, parentRect)\n }\n\n if (avoidUpdates) {\n // Use sync version for queued updates to avoid promise complications\n const event = getElementLayoutEvent(nodeRect, parentRect)\n queuedUpdates.set(node, () => onLayout(event))\n } else if (strategy === 'async') {\n } else {\n // Sync strategy - use sync version\n const event = getElementLayoutEvent(nodeRect, parentRect)\n onLayout(event)\n }\n }\n }\n\n // note that getBoundingClientRect() does not thrash layout if its after an animation frame\n rAF!(layoutOnAnimationFrame)\n\n function layoutOnAnimationFrame() {\n const now = Date.now()\n const timeSinceLastFrame = now - lastFrameAt\n lastFrameAt = now\n\n if (strategy !== 'off') {\n // for both strategies:\n // avoid updates if we've been dropping frames (indicates sync work happening)\n const expectedFrameTime = 16.67 // ~60fps\n const hasRecentSyncWork =\n timeSinceLastFrame > expectedFrameTime * numDroppedFramesUntilPause\n\n if (!hasRecentSyncWork) {\n Nodes.forEach((node) => {\n updateLayoutIfChanged(node, lastFrameAt)\n })\n }\n }\n\n rAF!(layoutOnAnimationFrame)\n }\n } else {\n if (process.env.NODE_ENV === 'development') {\n console.warn(\n `No requestAnimationFrame - please polyfill for onLayout to work correctly`\n )\n }\n }\n}\n\nexport const getElementLayoutEvent = (\n nodeRect: DOMRectReadOnly,\n parentRect: DOMRectReadOnly\n): LayoutEvent => {\n return {\n nativeEvent: {\n layout: getRelativeDimensions(nodeRect, parentRect),\n target: nodeRect,\n },\n timeStamp: Date.now(),\n }\n}\n\nexport const measureLayout = (\n node: HTMLElement,\n relativeTo: HTMLElement | null,\n callback: (\n x: number,\n y: number,\n width: number,\n height: number,\n left: number,\n top: number\n ) => void\n): void => {\n const relativeNode = relativeTo || node?.parentElement\n if (relativeNode instanceof HTMLElement) {\n const nodeDim = node.getBoundingClientRect()\n const relativeNodeDim = relativeNode.getBoundingClientRect()\n\n if (relativeNodeDim && nodeDim) {\n const { x, y, width, height, left, top } = getRelativeDimensions(\n nodeDim,\n relativeNodeDim\n )\n callback(x, y, width, height, left, top)\n }\n }\n}\n\nexport const getElementLayoutEventAsync = async (\n target: HTMLElement\n): Promise<LayoutEvent> => {\n const layout = await measureLayoutAsync(target)\n if (!layout) {\n throw new Error(`‼️`) // impossible\n }\n return {\n nativeEvent: {\n layout,\n target,\n },\n timeStamp: Date.now(),\n }\n}\n\nexport const measureLayoutAsync = async (\n node: HTMLElement,\n relativeTo?: HTMLElement | null\n): Promise<null | LayoutValue> => {\n const relativeNode = relativeTo || node?.parentElement\n if (relativeNode instanceof HTMLElement) {\n const [nodeDim, relativeNodeDim] = await Promise.all([\n getBoundingClientRectAsync(node),\n getBoundingClientRectAsync(relativeNode),\n ])\n\n if (relativeNodeDim && nodeDim) {\n const { x, y, width, height, left, top } = getRelativeDimensions(\n nodeDim,\n relativeNodeDim\n )\n return { x, y, width, height, left, top }\n }\n }\n return null\n}\n\nconst getRelativeDimensions = (a: DOMRectReadOnly, b: DOMRectReadOnly) => {\n const { height, left, top, width } = a\n const x = left - b.left\n const y = top - b.top\n return { x, y, width, height, left, top }\n}\n\nexport function useElementLayout(\n ref: RefObject<TamaguiComponentStatePartial>,\n onLayout?: ((e: LayoutEvent) => void) | null\n): void {\n // ensure always up to date so we can avoid re-running effect\n const node = ensureWebElement(ref.current?.host)\n if (node && onLayout) {\n LayoutHandlers.set(node, onLayout)\n }\n\n useIsomorphicLayoutEffect(() => {\n if (!onLayout) return\n const node = ref.current?.host\n if (!node) return\n\n LayoutHandlers.set(node, onLayout)\n Nodes.add(node)\n\n // always do one immediate sync layout event no matter the strategy for accuracy\n const parentNode = node.parentNode\n if (parentNode) {\n onLayout(\n getElementLayoutEvent(\n node.getBoundingClientRect(),\n parentNode.getBoundingClientRect()\n )\n )\n }\n\n return () => {\n Nodes.delete(node)\n LayoutHandlers.delete(node)\n NodeRectCache.delete(node)\n LastChangeTime.delete(node)\n }\n }, [ref, !!onLayout])\n}\n\nfunction ensureWebElement<X>(x: X): HTMLElement | undefined {\n if (typeof HTMLElement === 'undefined') {\n return undefined\n }\n return x instanceof HTMLElement ? x : undefined\n}\n\nconst getBoundingClientRectAsync = (\n node: HTMLElement | null\n): Promise<DOMRectReadOnly> => {\n return new Promise<DOMRectReadOnly>((res) => {\n if (!node || node.nodeType !== 1) return\n const io = new IntersectionObserver(\n (entries) => {\n io.disconnect()\n return res(entries[0].boundingClientRect)\n },\n {\n threshold: 0,\n }\n )\n io.observe(node)\n })\n}\n\nconst getBoundingClientRect = (node: HTMLElement | null): undefined | DOMRect => {\n if (!node || node.nodeType !== 1) return\n return node.getBoundingClientRect?.()\n}\n\nexport const getRect = (node: HTMLElement): LayoutValue | undefined => {\n const rect = getBoundingClientRect(node)\n if (!rect) return\n const { x, y, top, left } = rect\n return { x, y, width: node.offsetWidth, height: node.offsetHeight, top, left }\n}\n"
|
|
25
27
|
],
|
|
26
28
|
"version": 3
|
|
27
29
|
}
|