@tamagui/use-element-layout 3.0.0-beta.831.1 → 3.0.0-beta.889.1
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 +54 -89
- package/dist/cjs/index.native.cjs +44 -64
- package/dist/cjs/index.native.js +44 -64
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/esm/index.js +53 -88
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/index.mjs +53 -88
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/index.native.js +44 -64
- package/dist/esm/index.native.js.map +1 -1
- package/package.json +4 -4
- package/src/index.tsx +78 -244
- package/types/index.d.ts +6 -6
- package/types/index.d.ts.map +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tamagui/use-element-layout",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
3
|
+
"version": "3.0.0-beta.889.1",
|
|
4
4
|
"gitHead": "a49cc7ea6b93ba384e77a4880ae48ac4a5635c14",
|
|
5
5
|
"files": [
|
|
6
6
|
"src",
|
|
@@ -34,11 +34,11 @@
|
|
|
34
34
|
"clean:build": "tamagui-build clean:build"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@tamagui/constants": "3.0.0-beta.
|
|
38
|
-
"@tamagui/is-equal-shallow": "3.0.0-beta.
|
|
37
|
+
"@tamagui/constants": "3.0.0-beta.889.1",
|
|
38
|
+
"@tamagui/is-equal-shallow": "3.0.0-beta.889.1"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@tamagui/build": "3.0.0-beta.
|
|
41
|
+
"@tamagui/build": "3.0.0-beta.889.1",
|
|
42
42
|
"react": "19.2.3"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
package/src/index.tsx
CHANGED
|
@@ -4,35 +4,22 @@ import { createContext, useContext, useId, type ReactNode, type RefObject } from
|
|
|
4
4
|
const LayoutHandlers = new WeakMap<HTMLElement, Function>()
|
|
5
5
|
const LayoutDisableKey = new WeakMap<HTMLElement, string>()
|
|
6
6
|
const Nodes = new Set<HTMLElement>()
|
|
7
|
+
const NodeRectCache = new WeakMap<HTMLElement, DOMRectReadOnly>()
|
|
8
|
+
const PrevHostNode = new WeakMap<object, HTMLElement | undefined>()
|
|
7
9
|
|
|
8
|
-
// feature flag to enable pre-transform dimension reporting (matches RN behavior)
|
|
9
|
-
// can be set via env var at build time or runtime global for testing
|
|
10
|
-
// see: https://github.com/tamagui/tamagui/pull/2329
|
|
11
10
|
const usePretransformDimensions = () =>
|
|
12
11
|
(globalThis as any).__TAMAGUI_ONLAYOUT_PRETRANSFORM === true ||
|
|
13
12
|
process.env.TAMAGUI_ONLAYOUT_PRETRANSFORM === '1'
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
function isDebugLayout() {
|
|
18
|
-
if (_debugLayout === undefined) {
|
|
19
|
-
_debugLayout =
|
|
20
|
-
typeof window !== 'undefined' &&
|
|
21
|
-
new URLSearchParams(window.location.search).has('__tamaDebugLayout')
|
|
22
|
-
}
|
|
23
|
-
return _debugLayout
|
|
24
|
-
}
|
|
14
|
+
const isDebugLayout = () =>
|
|
15
|
+
typeof window !== 'undefined' && window.location?.search?.includes('__tamaDebugLayout')
|
|
25
16
|
|
|
26
|
-
// separating to avoid all re-rendering
|
|
27
17
|
const DisableLayoutContextValues: Record<string, boolean> = {}
|
|
28
18
|
const DisableLayoutContextKey = createContext<string>('')
|
|
29
19
|
|
|
30
20
|
const ENABLE =
|
|
31
21
|
process.env.TAMAGUI_TARGET === 'web' && typeof IntersectionObserver !== 'undefined'
|
|
32
22
|
|
|
33
|
-
// internal testing - advanced helper to turn off layout measurement for extra performance
|
|
34
|
-
// TODO document!
|
|
35
|
-
// TODO could add frame skip control here
|
|
36
23
|
export const LayoutMeasurementController = ({
|
|
37
24
|
disable,
|
|
38
25
|
children,
|
|
@@ -41,16 +28,10 @@ export const LayoutMeasurementController = ({
|
|
|
41
28
|
children: ReactNode
|
|
42
29
|
}): ReactNode => {
|
|
43
30
|
const id = useId()
|
|
44
|
-
|
|
45
31
|
useIsomorphicLayoutEffect(() => {
|
|
46
32
|
const wasDisabled = DisableLayoutContextValues[id] === true
|
|
47
33
|
DisableLayoutContextValues[id] = disable
|
|
48
|
-
|
|
49
|
-
// waiting on a measurement to position itself, so don't make it sit out
|
|
50
|
-
// the frame-skip window
|
|
51
|
-
if (wasDisabled && !disable) {
|
|
52
|
-
measureOnNextFrame?.()
|
|
53
|
-
}
|
|
34
|
+
if (wasDisabled && !disable) measureOnNextFrame?.()
|
|
54
35
|
}, [disable, id])
|
|
55
36
|
|
|
56
37
|
return (
|
|
@@ -60,22 +41,11 @@ export const LayoutMeasurementController = ({
|
|
|
60
41
|
)
|
|
61
42
|
}
|
|
62
43
|
|
|
63
|
-
type TamaguiComponentStatePartial = {
|
|
64
|
-
host?: any
|
|
65
|
-
}
|
|
66
|
-
|
|
44
|
+
type TamaguiComponentStatePartial = { host?: any }
|
|
67
45
|
type LayoutMeasurementStrategy = 'off' | 'sync' | 'async'
|
|
68
46
|
|
|
69
47
|
let strategy: LayoutMeasurementStrategy = 'async'
|
|
70
|
-
|
|
71
|
-
// the measurement loop parks itself whenever it provably has nothing to do: no
|
|
72
|
-
// registered nodes, a hidden document, or measurement turned off. these are the
|
|
73
|
-
// only three ways work can reappear, so each one restarts it.
|
|
74
48
|
let resumeLayoutLoop: (() => void) | undefined
|
|
75
|
-
|
|
76
|
-
// restarts the loop AND makes its next frame a measuring frame, skipping the
|
|
77
|
-
// frame-skip backoff once. for consumers whose position depends on a pending
|
|
78
|
-
// measurement (an opening sheet parked off-screen).
|
|
79
49
|
let measureOnNextFrame: (() => void) | undefined
|
|
80
50
|
|
|
81
51
|
export type LayoutValue = {
|
|
@@ -88,16 +58,10 @@ export type LayoutValue = {
|
|
|
88
58
|
}
|
|
89
59
|
|
|
90
60
|
export type LayoutEvent = {
|
|
91
|
-
nativeEvent: {
|
|
92
|
-
layout: LayoutValue
|
|
93
|
-
target: any
|
|
94
|
-
}
|
|
61
|
+
nativeEvent: { layout: LayoutValue; target: any }
|
|
95
62
|
timeStamp: number
|
|
96
63
|
}
|
|
97
64
|
|
|
98
|
-
const NodeRectCache = new WeakMap<HTMLElement, DOMRectReadOnly>()
|
|
99
|
-
|
|
100
|
-
// prevent thrashing during first hydration (somewhat, streaming gets trickier)
|
|
101
65
|
let avoidUpdates = true
|
|
102
66
|
const queuedUpdates = new Map<HTMLElement, Function>()
|
|
103
67
|
|
|
@@ -111,15 +75,12 @@ export function enable(): void {
|
|
|
111
75
|
}
|
|
112
76
|
}
|
|
113
77
|
|
|
114
|
-
// optimization: inline rect comparison to avoid function call overhead on hot path
|
|
115
78
|
function rectsEqual(a: DOMRectReadOnly, b: DOMRectReadOnly): boolean {
|
|
116
79
|
return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height
|
|
117
80
|
}
|
|
118
81
|
|
|
119
82
|
if (ENABLE) {
|
|
120
83
|
const BoundingRects = new WeakMap<Element, DOMRectReadOnly>()
|
|
121
|
-
|
|
122
|
-
// optimization: persistent IO for rect fetching, reused across cycles
|
|
123
84
|
let rectFetchObserver: IntersectionObserver | null = null
|
|
124
85
|
let rectFetchResolve: ((value: boolean) => void) | null = null
|
|
125
86
|
let rectFetchStartTime = 0
|
|
@@ -127,16 +88,12 @@ if (ENABLE) {
|
|
|
127
88
|
|
|
128
89
|
function ensureRectFetchObserver() {
|
|
129
90
|
if (rectFetchObserver) return rectFetchObserver
|
|
130
|
-
|
|
131
91
|
rectFetchObserver = new IntersectionObserver(
|
|
132
92
|
(entries) => {
|
|
133
93
|
lastCallbackDelay = Math.round(performance.now() - rectFetchStartTime)
|
|
134
|
-
|
|
135
|
-
// store all rects
|
|
136
94
|
for (let i = 0; i < entries.length; i++) {
|
|
137
95
|
BoundingRects.set(entries[i].target, entries[i].boundingClientRect)
|
|
138
96
|
}
|
|
139
|
-
|
|
140
97
|
if (
|
|
141
98
|
process.env.NODE_ENV === 'development' &&
|
|
142
99
|
isDebugLayout() &&
|
|
@@ -149,90 +106,64 @@ if (ENABLE) {
|
|
|
149
106
|
'entries'
|
|
150
107
|
)
|
|
151
108
|
}
|
|
152
|
-
|
|
153
109
|
if (rectFetchResolve) {
|
|
154
110
|
rectFetchResolve(true)
|
|
155
111
|
rectFetchResolve = null
|
|
156
112
|
}
|
|
157
113
|
},
|
|
158
|
-
{
|
|
159
|
-
threshold: 0,
|
|
160
|
-
}
|
|
114
|
+
{ threshold: 0 }
|
|
161
115
|
)
|
|
162
|
-
|
|
163
116
|
return rectFetchObserver
|
|
164
117
|
}
|
|
165
118
|
|
|
166
|
-
|
|
119
|
+
function updateLayoutIfChanged(node: HTMLElement) {
|
|
167
120
|
const onLayout = LayoutHandlers.get(node)
|
|
168
121
|
if (typeof onLayout !== 'function') return
|
|
169
|
-
|
|
170
122
|
const parentNode = node.parentElement
|
|
171
123
|
if (!parentNode) return
|
|
172
124
|
|
|
173
125
|
let nodeRect: DOMRectReadOnly | undefined
|
|
174
126
|
let parentRect: DOMRectReadOnly | undefined
|
|
175
127
|
|
|
176
|
-
// respect the strategy contract
|
|
177
128
|
if (strategy === 'async') {
|
|
178
129
|
nodeRect = BoundingRects.get(node)
|
|
179
130
|
parentRect = BoundingRects.get(parentNode)
|
|
180
|
-
|
|
181
|
-
if (!nodeRect || !parentRect) {
|
|
182
|
-
return
|
|
183
|
-
}
|
|
131
|
+
if (!nodeRect || !parentRect) return
|
|
184
132
|
} else {
|
|
185
133
|
nodeRect = node.getBoundingClientRect()
|
|
186
134
|
parentRect = parentNode.getBoundingClientRect()
|
|
187
135
|
}
|
|
188
|
-
|
|
189
136
|
emitLayoutIfChanged(node, parentNode, nodeRect, parentRect)
|
|
190
137
|
}
|
|
191
138
|
|
|
192
139
|
const rAF =
|
|
193
140
|
typeof requestAnimationFrame !== 'undefined' ? requestAnimationFrame : undefined
|
|
194
|
-
|
|
195
|
-
// adaptive frame skipping with backoff
|
|
196
141
|
const userSkipVal = process.env.TAMAGUI_LAYOUT_FRAME_SKIP
|
|
197
142
|
const BASE_SKIP_FRAMES = userSkipVal ? +userSkipVal : 10
|
|
198
143
|
const MAX_SKIP_FRAMES = 20
|
|
199
144
|
let skipFrames = BASE_SKIP_FRAMES
|
|
200
145
|
let frameCount = 0
|
|
201
|
-
|
|
202
|
-
// stays true across the await below so a node registering mid-cycle cannot
|
|
203
|
-
// start a second concurrent loop
|
|
204
146
|
let frameScheduled = false
|
|
205
147
|
|
|
206
148
|
function scheduleLayoutFrame() {
|
|
207
|
-
if (frameScheduled || strategy === 'off' || Nodes.size === 0 || document.hidden)
|
|
149
|
+
if (frameScheduled || strategy === 'off' || Nodes.size === 0 || document.hidden)
|
|
208
150
|
return
|
|
209
|
-
}
|
|
210
151
|
frameScheduled = true
|
|
211
152
|
rAF ? rAF(layoutOnAnimationFrame) : setTimeout(layoutOnAnimationFrame, 16)
|
|
212
153
|
}
|
|
213
154
|
|
|
214
155
|
async function layoutOnAnimationFrame() {
|
|
215
|
-
// skip frames based on adaptive rate
|
|
216
156
|
if (frameCount++ % skipFrames !== 0) {
|
|
217
157
|
frameScheduled = false
|
|
218
158
|
scheduleLayoutFrame()
|
|
219
159
|
return
|
|
220
160
|
}
|
|
221
|
-
|
|
222
|
-
// reset frame count to avoid overflow
|
|
223
|
-
if (frameCount >= Number.MAX_SAFE_INTEGER) {
|
|
224
|
-
frameCount = 0
|
|
225
|
-
}
|
|
161
|
+
if (frameCount >= Number.MAX_SAFE_INTEGER) frameCount = 0
|
|
226
162
|
|
|
227
163
|
if (strategy !== 'off') {
|
|
228
164
|
const activeNodes: HTMLElement[] = []
|
|
229
|
-
// optimization: deduplicate parent observations
|
|
230
165
|
const parentsToObserve = new Set<HTMLElement>()
|
|
231
166
|
|
|
232
|
-
// collect non-disabled nodes and their unique parents. off-viewport
|
|
233
|
-
// nodes stay in: a sheet or popper parks off-screen until its own
|
|
234
|
-
// measurement arrives, so gating measurement on visibility deadlocks it
|
|
235
|
-
// there, and RN fires onLayout for off-screen views anyway
|
|
236
167
|
for (const node of Nodes) {
|
|
237
168
|
const parentElement = node.parentElement
|
|
238
169
|
if (!(parentElement instanceof HTMLElement)) {
|
|
@@ -248,45 +179,26 @@ if (ENABLE) {
|
|
|
248
179
|
if (activeNodes.length > 0) {
|
|
249
180
|
const io = ensureRectFetchObserver()
|
|
250
181
|
rectFetchStartTime = performance.now()
|
|
182
|
+
for (let i = 0; i < activeNodes.length; i++) io.observe(activeNodes[i])
|
|
183
|
+
for (const parent of parentsToObserve) io.observe(parent)
|
|
251
184
|
|
|
252
|
-
// observe all nodes
|
|
253
|
-
for (let i = 0; i < activeNodes.length; i++) {
|
|
254
|
-
io.observe(activeNodes[i])
|
|
255
|
-
}
|
|
256
|
-
// optimization: observe unique parents only (not N times for N children)
|
|
257
|
-
for (const parent of parentsToObserve) {
|
|
258
|
-
io.observe(parent)
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// wait for callback
|
|
262
185
|
await new Promise<boolean>((res) => {
|
|
263
186
|
rectFetchResolve = res
|
|
264
187
|
})
|
|
265
188
|
|
|
266
|
-
|
|
267
|
-
for (
|
|
268
|
-
io.unobserve(activeNodes[i])
|
|
269
|
-
}
|
|
270
|
-
for (const parent of parentsToObserve) {
|
|
271
|
-
io.unobserve(parent)
|
|
272
|
-
}
|
|
189
|
+
for (let i = 0; i < activeNodes.length; i++) io.unobserve(activeNodes[i])
|
|
190
|
+
for (const parent of parentsToObserve) io.unobserve(parent)
|
|
273
191
|
|
|
274
|
-
// adaptive backoff: if IO was slow, skip more frames next cycle
|
|
275
192
|
if (lastCallbackDelay > 50) {
|
|
276
193
|
skipFrames = Math.min(skipFrames + 2, MAX_SKIP_FRAMES)
|
|
277
194
|
} else if (lastCallbackDelay < 20) {
|
|
278
|
-
// recover back to base rate when things are fast
|
|
279
195
|
skipFrames = Math.max(skipFrames - 1, BASE_SKIP_FRAMES)
|
|
280
196
|
}
|
|
281
197
|
|
|
282
|
-
|
|
283
|
-
for (let i = 0; i < activeNodes.length; i++) {
|
|
284
|
-
updateLayoutIfChanged(activeNodes[i])
|
|
285
|
-
}
|
|
198
|
+
for (let i = 0; i < activeNodes.length; i++) updateLayoutIfChanged(activeNodes[i])
|
|
286
199
|
}
|
|
287
200
|
}
|
|
288
201
|
|
|
289
|
-
// schedule next frame
|
|
290
202
|
frameScheduled = false
|
|
291
203
|
scheduleLayoutFrame()
|
|
292
204
|
}
|
|
@@ -304,48 +216,33 @@ export const getElementLayoutEvent = (
|
|
|
304
216
|
nodeRect: DOMRectReadOnly,
|
|
305
217
|
parentRect: DOMRectReadOnly,
|
|
306
218
|
node?: HTMLElement
|
|
307
|
-
): LayoutEvent => {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
}
|
|
315
|
-
}
|
|
219
|
+
): LayoutEvent => ({
|
|
220
|
+
nativeEvent: {
|
|
221
|
+
layout: getRelativeDimensions(nodeRect, parentRect, node),
|
|
222
|
+
target: nodeRect,
|
|
223
|
+
},
|
|
224
|
+
timeStamp: Date.now(),
|
|
225
|
+
})
|
|
316
226
|
|
|
317
|
-
/**
|
|
318
|
-
* get pre-transform dimensions for a node.
|
|
319
|
-
* uses offsetWidth/offsetHeight which report CSS layout dimensions
|
|
320
|
-
* unaffected by transforms - this matches React Native's onLayout behavior.
|
|
321
|
-
*
|
|
322
|
-
* see: https://github.com/tamagui/tamagui/pull/2329
|
|
323
|
-
*/
|
|
324
227
|
const getPreTransformDimensions = (
|
|
325
228
|
node: HTMLElement
|
|
326
|
-
): { width: number; height: number } => {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
}
|
|
331
|
-
}
|
|
229
|
+
): { width: number; height: number } => ({
|
|
230
|
+
width: node.offsetWidth,
|
|
231
|
+
height: node.offsetHeight,
|
|
232
|
+
})
|
|
332
233
|
|
|
333
234
|
const getRelativeDimensions = (
|
|
334
235
|
a: DOMRectReadOnly,
|
|
335
236
|
b: DOMRectReadOnly,
|
|
336
237
|
aNode?: HTMLElement
|
|
337
238
|
) => {
|
|
338
|
-
const
|
|
339
|
-
const
|
|
340
|
-
const y = top - b.top
|
|
341
|
-
|
|
342
|
-
// get pre-transform dimensions when flag is enabled and node is available
|
|
239
|
+
const left = a.left - b.left
|
|
240
|
+
const top = a.top - b.top
|
|
343
241
|
const { width, height } =
|
|
344
242
|
usePretransformDimensions() && aNode
|
|
345
243
|
? getPreTransformDimensions(aNode)
|
|
346
244
|
: { width: a.width, height: a.height }
|
|
347
|
-
|
|
348
|
-
return { x, y, width, height, pageX: a.left, pageY: a.top }
|
|
245
|
+
return { x: left, y: top, width, height, pageX: a.left, pageY: a.top }
|
|
349
246
|
}
|
|
350
247
|
|
|
351
248
|
function emitLayoutIfChanged(
|
|
@@ -359,7 +256,6 @@ function emitLayoutIfChanged(
|
|
|
359
256
|
|
|
360
257
|
const cachedRect = NodeRectCache.get(node)
|
|
361
258
|
const cachedParentRect = NodeRectCache.get(parentNode)
|
|
362
|
-
|
|
363
259
|
const nodeChanged = !cachedRect || !rectsEqual(cachedRect, nodeRect)
|
|
364
260
|
const parentChanged = !cachedParentRect || !rectsEqual(cachedParentRect, parentRect)
|
|
365
261
|
|
|
@@ -369,17 +265,6 @@ function emitLayoutIfChanged(
|
|
|
369
265
|
NodeRectCache.set(parentNode, parentRect)
|
|
370
266
|
|
|
371
267
|
const event = getElementLayoutEvent(nodeRect, parentRect, node)
|
|
372
|
-
|
|
373
|
-
if (process.env.NODE_ENV === 'development' && isDebugLayout()) {
|
|
374
|
-
console.log('[useElementLayout] change', {
|
|
375
|
-
tag: node.tagName,
|
|
376
|
-
id: node.id || undefined,
|
|
377
|
-
className: (node.className || '').slice(0, 60) || undefined,
|
|
378
|
-
layout: event.nativeEvent.layout,
|
|
379
|
-
first: !cachedRect,
|
|
380
|
-
})
|
|
381
|
-
}
|
|
382
|
-
|
|
383
268
|
if (avoidUpdates) {
|
|
384
269
|
queuedUpdates.set(node, () => onLayout(event))
|
|
385
270
|
} else {
|
|
@@ -389,15 +274,11 @@ function emitLayoutIfChanged(
|
|
|
389
274
|
|
|
390
275
|
function observeLayoutNode(node: HTMLElement, disableKey?: string) {
|
|
391
276
|
Nodes.add(node)
|
|
392
|
-
if (disableKey)
|
|
393
|
-
|
|
394
|
-
} else {
|
|
395
|
-
LayoutDisableKey.delete(node)
|
|
396
|
-
}
|
|
277
|
+
if (disableKey) LayoutDisableKey.set(node, disableKey)
|
|
278
|
+
else LayoutDisableKey.delete(node)
|
|
397
279
|
resumeLayoutLoop?.()
|
|
398
280
|
}
|
|
399
281
|
|
|
400
|
-
// register an arbitrary DOM element into the measurement loop without React lifecycle
|
|
401
282
|
export function registerLayoutNode(
|
|
402
283
|
node: HTMLElement,
|
|
403
284
|
onChange: () => void,
|
|
@@ -415,12 +296,6 @@ function cleanupNode(node: HTMLElement) {
|
|
|
415
296
|
NodeRectCache.delete(node)
|
|
416
297
|
}
|
|
417
298
|
|
|
418
|
-
const PrevHostNode = new WeakMap<object, HTMLElement | undefined>()
|
|
419
|
-
|
|
420
|
-
// spec: onLayout fires one synchronous initial event on mount and on host swap
|
|
421
|
-
// (RN parity, and before-paint so consumers can position without flicker).
|
|
422
|
-
// bypasses the avoidUpdates queue on purpose; seeds the rect cache so the
|
|
423
|
-
// measurement loop doesn't re-emit an identical event a frame later.
|
|
424
299
|
function emitLayoutSync(node: HTMLElement) {
|
|
425
300
|
const onLayout = LayoutHandlers.get(node)
|
|
426
301
|
if (typeof onLayout !== 'function') return
|
|
@@ -439,85 +314,51 @@ export function useElementLayout(
|
|
|
439
314
|
onLayout?: ((e: LayoutEvent) => void) | null
|
|
440
315
|
): void {
|
|
441
316
|
const disableKey = useContext(DisableLayoutContextKey)
|
|
442
|
-
|
|
443
|
-
// keep handlers up to date so polling always calls the latest callback
|
|
444
317
|
const node = ensureWebElement(ref.current?.host)
|
|
445
318
|
if (node && onLayout) {
|
|
446
319
|
LayoutHandlers.set(node, onLayout)
|
|
447
320
|
LayoutDisableKey.set(node, disableKey)
|
|
448
321
|
}
|
|
449
322
|
|
|
450
|
-
// detect mounts + host swaps after commit and fire the immediate sync layout event
|
|
451
323
|
useIsomorphicLayoutEffect(() => {
|
|
452
324
|
if (!onLayout) return
|
|
453
325
|
const nextNode = ensureWebElement(ref.current?.host)
|
|
454
326
|
const prevNode = PrevHostNode.get(ref)
|
|
455
|
-
if (nextNode
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
emitLayoutSync(nextNode)
|
|
464
|
-
})
|
|
465
|
-
|
|
466
|
-
useIsomorphicLayoutEffect(() => {
|
|
467
|
-
if (!onLayout) return
|
|
468
|
-
const node = ref.current?.host
|
|
469
|
-
if (!node) return
|
|
470
|
-
|
|
471
|
-
LayoutHandlers.set(node, onLayout)
|
|
472
|
-
observeLayoutNode(node, disableKey)
|
|
473
|
-
|
|
474
|
-
if (process.env.NODE_ENV === 'development' && isDebugLayout()) {
|
|
475
|
-
console.log('[useElementLayout] register', {
|
|
476
|
-
tag: node.tagName,
|
|
477
|
-
id: node.id || undefined,
|
|
478
|
-
className: (node.className || '').slice(0, 60) || undefined,
|
|
479
|
-
totalNodes: Nodes.size,
|
|
480
|
-
})
|
|
327
|
+
if (nextNode !== prevNode) {
|
|
328
|
+
if (prevNode) cleanupNode(prevNode)
|
|
329
|
+
PrevHostNode.set(ref, nextNode)
|
|
330
|
+
if (nextNode) {
|
|
331
|
+
LayoutHandlers.set(nextNode, onLayout)
|
|
332
|
+
observeLayoutNode(nextNode, disableKey)
|
|
333
|
+
emitLayoutSync(nextNode)
|
|
334
|
+
}
|
|
481
335
|
}
|
|
482
|
-
|
|
483
336
|
return () => {
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
if (swappedNode && swappedNode !== node) {
|
|
489
|
-
cleanupNode(swappedNode)
|
|
490
|
-
}
|
|
337
|
+
const activeNode = ensureWebElement(ref.current?.host)
|
|
338
|
+
if (activeNode) cleanupNode(activeNode)
|
|
339
|
+
const swapped = PrevHostNode.get(ref)
|
|
340
|
+
if (swapped && swapped !== activeNode) cleanupNode(swapped)
|
|
491
341
|
PrevHostNode.delete(ref)
|
|
492
342
|
}
|
|
493
343
|
}, [ref, !!onLayout])
|
|
494
344
|
}
|
|
495
345
|
|
|
496
346
|
function ensureWebElement<X>(x: X): HTMLElement | undefined {
|
|
497
|
-
if (typeof HTMLElement === 'undefined')
|
|
498
|
-
return undefined
|
|
499
|
-
}
|
|
347
|
+
if (typeof HTMLElement === 'undefined') return undefined
|
|
500
348
|
return x instanceof HTMLElement ? x : undefined
|
|
501
349
|
}
|
|
502
350
|
|
|
503
351
|
export const getBoundingClientRectAsync = (
|
|
504
352
|
node: HTMLElement | null
|
|
505
|
-
): Promise<DOMRectReadOnly | false> =>
|
|
506
|
-
|
|
353
|
+
): Promise<DOMRectReadOnly | false> =>
|
|
354
|
+
new Promise((res) => {
|
|
507
355
|
if (!node || node.nodeType !== 1) return res(false)
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
(entries)
|
|
511
|
-
|
|
512
|
-
return res(entries[0].boundingClientRect)
|
|
513
|
-
},
|
|
514
|
-
{
|
|
515
|
-
threshold: 0,
|
|
516
|
-
}
|
|
517
|
-
)
|
|
356
|
+
const io = new IntersectionObserver((entries) => {
|
|
357
|
+
io.disconnect()
|
|
358
|
+
res(entries[0].boundingClientRect)
|
|
359
|
+
})
|
|
518
360
|
io.observe(node)
|
|
519
361
|
})
|
|
520
|
-
}
|
|
521
362
|
|
|
522
363
|
export const measureNode = async (
|
|
523
364
|
node: HTMLElement,
|
|
@@ -536,9 +377,13 @@ export const measureNode = async (
|
|
|
536
377
|
return null
|
|
537
378
|
}
|
|
538
379
|
|
|
539
|
-
type MeasureInWindowCb = (
|
|
540
|
-
|
|
541
|
-
|
|
380
|
+
export type MeasureInWindowCb = (
|
|
381
|
+
x: number,
|
|
382
|
+
y: number,
|
|
383
|
+
width: number,
|
|
384
|
+
height: number
|
|
385
|
+
) => void
|
|
386
|
+
export type MeasureCb = (
|
|
542
387
|
x: number,
|
|
543
388
|
y: number,
|
|
544
389
|
width: number,
|
|
@@ -546,45 +391,35 @@ type MeasureCb = (
|
|
|
546
391
|
pageX: number,
|
|
547
392
|
pageY: number
|
|
548
393
|
) => void
|
|
394
|
+
export type WindowLayout = { pageX: number; pageY: number; width: number; height: number }
|
|
549
395
|
|
|
550
396
|
export const measure = async (
|
|
551
397
|
node: HTMLElement,
|
|
552
398
|
callback: MeasureCb
|
|
553
399
|
): Promise<LayoutValue | null> => {
|
|
554
|
-
const out = await measureNode(
|
|
555
|
-
|
|
556
|
-
node.parentNode instanceof HTMLElement ? node.parentNode : null
|
|
557
|
-
)
|
|
558
|
-
if (out) {
|
|
559
|
-
callback?.(out.x, out.y, out.width, out.height, out.pageX, out.pageY)
|
|
560
|
-
}
|
|
400
|
+
const out = await measureNode(node, node.parentElement)
|
|
401
|
+
if (out) callback?.(out.x, out.y, out.width, out.height, out.pageX, out.pageY)
|
|
561
402
|
return out
|
|
562
403
|
}
|
|
563
404
|
|
|
564
|
-
export
|
|
565
|
-
node: HTMLElement
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
type WindowLayout = { pageX: number; pageY: number; width: number; height: number }
|
|
405
|
+
export const createMeasure =
|
|
406
|
+
(node: HTMLElement): ((callback: MeasureCb) => Promise<LayoutValue | null>) =>
|
|
407
|
+
(callback: MeasureCb) =>
|
|
408
|
+
measure(node, callback)
|
|
571
409
|
|
|
572
410
|
export const measureInWindow = async (
|
|
573
411
|
node: HTMLElement,
|
|
574
412
|
callback: MeasureInWindowCb
|
|
575
413
|
): Promise<WindowLayout | null> => {
|
|
576
414
|
const out = await measureNode(node, null)
|
|
577
|
-
if (out)
|
|
578
|
-
callback?.(out.pageX, out.pageY, out.width, out.height)
|
|
579
|
-
}
|
|
415
|
+
if (out) callback?.(out.pageX, out.pageY, out.width, out.height)
|
|
580
416
|
return out
|
|
581
417
|
}
|
|
582
418
|
|
|
583
|
-
export const createMeasureInWindow =
|
|
584
|
-
node: HTMLElement
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
}
|
|
419
|
+
export const createMeasureInWindow =
|
|
420
|
+
(node: HTMLElement): ((callback: MeasureInWindowCb) => Promise<WindowLayout | null>) =>
|
|
421
|
+
(callback: MeasureInWindowCb) =>
|
|
422
|
+
measureInWindow(node, callback)
|
|
588
423
|
|
|
589
424
|
export const measureLayout = async (
|
|
590
425
|
node: HTMLElement,
|
|
@@ -592,14 +427,13 @@ export const measureLayout = async (
|
|
|
592
427
|
callback: MeasureCb
|
|
593
428
|
): Promise<LayoutValue | null> => {
|
|
594
429
|
const out = await measureNode(node, relativeNode)
|
|
595
|
-
if (out)
|
|
596
|
-
callback?.(out.x, out.y, out.width, out.height, out.pageX, out.pageY)
|
|
597
|
-
}
|
|
430
|
+
if (out) callback?.(out.x, out.y, out.width, out.height, out.pageX, out.pageY)
|
|
598
431
|
return out
|
|
599
432
|
}
|
|
600
433
|
|
|
601
|
-
export
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
434
|
+
export const createMeasureLayout =
|
|
435
|
+
(
|
|
436
|
+
node: HTMLElement
|
|
437
|
+
): ((relativeTo: HTMLElement, callback: MeasureCb) => Promise<LayoutValue | null>) =>
|
|
438
|
+
(relativeTo: HTMLElement, callback: MeasureCb) =>
|
|
439
|
+
measureLayout(node, relativeTo, callback)
|
package/types/index.d.ts
CHANGED
|
@@ -27,20 +27,20 @@ export declare function registerLayoutNode(node: HTMLElement, onChange: () => vo
|
|
|
27
27
|
export declare function useElementLayout(ref: RefObject<TamaguiComponentStatePartial>, onLayout?: ((e: LayoutEvent) => void) | null): void;
|
|
28
28
|
export declare const getBoundingClientRectAsync: (node: HTMLElement | null) => Promise<DOMRectReadOnly | false>;
|
|
29
29
|
export declare const measureNode: (node: HTMLElement, relativeTo?: HTMLElement | null) => Promise<null | LayoutValue>;
|
|
30
|
-
type MeasureInWindowCb = (x: number, y: number, width: number, height: number) => void;
|
|
31
|
-
type MeasureCb = (x: number, y: number, width: number, height: number, pageX: number, pageY: number) => void;
|
|
32
|
-
export
|
|
33
|
-
export declare function createMeasure(node: HTMLElement): (callback: MeasureCb) => Promise<LayoutValue | null>;
|
|
34
|
-
type WindowLayout = {
|
|
30
|
+
export type MeasureInWindowCb = (x: number, y: number, width: number, height: number) => void;
|
|
31
|
+
export type MeasureCb = (x: number, y: number, width: number, height: number, pageX: number, pageY: number) => void;
|
|
32
|
+
export type WindowLayout = {
|
|
35
33
|
pageX: number;
|
|
36
34
|
pageY: number;
|
|
37
35
|
width: number;
|
|
38
36
|
height: number;
|
|
39
37
|
};
|
|
38
|
+
export declare const measure: (node: HTMLElement, callback: MeasureCb) => Promise<LayoutValue | null>;
|
|
39
|
+
export declare const createMeasure: (node: HTMLElement) => ((callback: MeasureCb) => Promise<LayoutValue | null>);
|
|
40
40
|
export declare const measureInWindow: (node: HTMLElement, callback: MeasureInWindowCb) => Promise<WindowLayout | null>;
|
|
41
41
|
export declare const createMeasureInWindow: (node: HTMLElement) => ((callback: MeasureInWindowCb) => Promise<WindowLayout | null>);
|
|
42
42
|
export declare const measureLayout: (node: HTMLElement, relativeNode: HTMLElement, callback: MeasureCb) => Promise<LayoutValue | null>;
|
|
43
|
-
export declare
|
|
43
|
+
export declare const createMeasureLayout: (node: HTMLElement) => ((relativeTo: HTMLElement, callback: MeasureCb) => Promise<LayoutValue | null>);
|
|
44
44
|
export {};
|
|
45
45
|
|
|
46
46
|
//# sourceMappingURL=index.d.ts.map
|