@tamagui/core 1.126.17 → 1.127.0
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/helpers/getBoundingClientRect.cjs +16 -3
- package/dist/cjs/helpers/getBoundingClientRect.js +17 -2
- package/dist/cjs/helpers/getBoundingClientRect.js.map +1 -1
- package/dist/cjs/helpers/getBoundingClientRect.native.js +20 -2
- package/dist/cjs/helpers/getBoundingClientRect.native.js.map +2 -2
- package/dist/cjs/hooks/useElementLayout.cjs +90 -22
- package/dist/cjs/hooks/useElementLayout.js +72 -16
- package/dist/cjs/hooks/useElementLayout.js.map +1 -1
- package/dist/cjs/hooks/useElementLayout.native.js +82 -20
- package/dist/cjs/hooks/useElementLayout.native.js.map +2 -2
- package/dist/esm/helpers/getBoundingClientRect.js +17 -2
- package/dist/esm/helpers/getBoundingClientRect.js.map +1 -1
- package/dist/esm/helpers/getBoundingClientRect.mjs +15 -3
- package/dist/esm/helpers/getBoundingClientRect.mjs.map +1 -1
- package/dist/esm/helpers/getBoundingClientRect.native.js +22 -4
- package/dist/esm/helpers/getBoundingClientRect.native.js.map +1 -1
- package/dist/esm/hooks/useElementLayout.js +72 -16
- package/dist/esm/hooks/useElementLayout.js.map +1 -1
- package/dist/esm/hooks/useElementLayout.mjs +89 -23
- package/dist/esm/hooks/useElementLayout.mjs.map +1 -1
- package/dist/esm/hooks/useElementLayout.native.js +101 -36
- package/dist/esm/hooks/useElementLayout.native.js.map +1 -1
- package/dist/native.js +77 -21
- package/dist/native.js.map +2 -2
- package/dist/test.native.js +77 -21
- package/dist/test.native.js.map +2 -2
- package/package.json +7 -7
- package/src/helpers/getBoundingClientRect.tsx +26 -0
- package/src/hooks/useElementLayout.tsx +107 -8
- package/types/helpers/getBoundingClientRect.d.ts +1 -0
- package/types/helpers/getBoundingClientRect.d.ts.map +1 -1
- package/types/hooks/useElementLayout.d.ts +4 -2
- package/types/hooks/useElementLayout.d.ts.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tamagui/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.127.0",
|
|
4
4
|
"source": "src/index.tsx",
|
|
5
5
|
"main": "dist/cjs",
|
|
6
6
|
"module": "dist/esm",
|
|
@@ -35,14 +35,14 @@
|
|
|
35
35
|
"native-test.d.ts"
|
|
36
36
|
],
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@tamagui/react-native-media-driver": "1.
|
|
39
|
-
"@tamagui/react-native-use-pressable": "1.
|
|
40
|
-
"@tamagui/react-native-use-responder-events": "1.
|
|
41
|
-
"@tamagui/use-event": "1.
|
|
42
|
-
"@tamagui/web": "1.
|
|
38
|
+
"@tamagui/react-native-media-driver": "1.127.0",
|
|
39
|
+
"@tamagui/react-native-use-pressable": "1.127.0",
|
|
40
|
+
"@tamagui/react-native-use-responder-events": "1.127.0",
|
|
41
|
+
"@tamagui/use-event": "1.127.0",
|
|
42
|
+
"@tamagui/web": "1.127.0"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@tamagui/build": "1.
|
|
45
|
+
"@tamagui/build": "1.127.0",
|
|
46
46
|
"@testing-library/react": "^16.1.0",
|
|
47
47
|
"csstype": "^3.0.10",
|
|
48
48
|
"typescript": "^5.8.2",
|
|
@@ -2,3 +2,29 @@ export const getBoundingClientRect = (node: HTMLElement | null): undefined | DOM
|
|
|
2
2
|
if (!node || node.nodeType !== 1) return
|
|
3
3
|
return node.getBoundingClientRect?.()
|
|
4
4
|
}
|
|
5
|
+
|
|
6
|
+
export const getBoundingClientRectAsync = (
|
|
7
|
+
element: HTMLElement
|
|
8
|
+
): Promise<DOMRectReadOnly | undefined> => {
|
|
9
|
+
return new Promise((resolve) => {
|
|
10
|
+
let didFallback = false
|
|
11
|
+
function fallbackToSync() {
|
|
12
|
+
didFallback = true
|
|
13
|
+
resolve(getBoundingClientRect(element))
|
|
14
|
+
}
|
|
15
|
+
const tm = setTimeout(fallbackToSync, 32)
|
|
16
|
+
const observer = new IntersectionObserver(
|
|
17
|
+
(entries, ob) => {
|
|
18
|
+
clearTimeout(tm)
|
|
19
|
+
ob.disconnect()
|
|
20
|
+
if (!didFallback) {
|
|
21
|
+
resolve(entries[0]?.boundingClientRect)
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
threshold: 0.0001,
|
|
26
|
+
}
|
|
27
|
+
)
|
|
28
|
+
observer.observe(element)
|
|
29
|
+
})
|
|
30
|
+
}
|
|
@@ -9,11 +9,12 @@ import type { RefObject } from 'react'
|
|
|
9
9
|
const LayoutHandlers = new WeakMap<HTMLElement, Function>()
|
|
10
10
|
const Nodes = new Set<HTMLElement>()
|
|
11
11
|
|
|
12
|
-
type
|
|
12
|
+
type LayoutMeasurementStrategy = 'off' | 'sync' | 'async'
|
|
13
13
|
|
|
14
|
-
let
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
let strategy: LayoutMeasurementStrategy = 'async'
|
|
15
|
+
|
|
16
|
+
export function setOnLayoutStrategy(state: LayoutMeasurementStrategy) {
|
|
17
|
+
strategy = state
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
export type LayoutValue = {
|
|
@@ -35,8 +36,11 @@ export type LayoutEvent = {
|
|
|
35
36
|
|
|
36
37
|
const NodeRectCache = new WeakMap<HTMLElement, DOMRect>()
|
|
37
38
|
const ParentRectCache = new WeakMap<HTMLElement, DOMRect>()
|
|
39
|
+
const DebounceTimers = new WeakMap<HTMLElement, NodeJS.Timeout>()
|
|
40
|
+
const LastChangeTime = new WeakMap<HTMLElement, number>()
|
|
38
41
|
|
|
39
42
|
const rAF = typeof window !== 'undefined' ? window.requestAnimationFrame : undefined
|
|
43
|
+
const DEBOUNCE_DELAY = 32 // 32ms debounce (2 frames at 60fps)
|
|
40
44
|
|
|
41
45
|
if (isClient) {
|
|
42
46
|
if (rAF) {
|
|
@@ -56,7 +60,7 @@ if (isClient) {
|
|
|
56
60
|
}
|
|
57
61
|
})
|
|
58
62
|
|
|
59
|
-
function updateLayoutIfChanged(node: HTMLElement) {
|
|
63
|
+
async function updateLayoutIfChanged(node: HTMLElement) {
|
|
60
64
|
const nodeRect = node.getBoundingClientRect()
|
|
61
65
|
const parentNode = node.parentElement
|
|
62
66
|
const parentRect = parentNode?.getBoundingClientRect()
|
|
@@ -77,10 +81,48 @@ if (isClient) {
|
|
|
77
81
|
if (parentRect && parentNode) {
|
|
78
82
|
ParentRectCache.set(parentNode, parentRect)
|
|
79
83
|
}
|
|
80
|
-
|
|
84
|
+
|
|
81
85
|
if (avoidUpdates) {
|
|
86
|
+
// Use sync version for queued updates to avoid promise complications
|
|
87
|
+
const event = getElementLayoutEvent(node)
|
|
82
88
|
queuedUpdates.set(node, () => onLayout(event))
|
|
89
|
+
} else if (strategy === 'async') {
|
|
90
|
+
// For async strategy, debounce the layout update
|
|
91
|
+
const now = Date.now()
|
|
92
|
+
LastChangeTime.set(node, now)
|
|
93
|
+
|
|
94
|
+
// Clear existing debounce timer
|
|
95
|
+
const existingTimer = DebounceTimers.get(node)
|
|
96
|
+
if (existingTimer) {
|
|
97
|
+
clearTimeout(existingTimer)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Set new debounce timer
|
|
101
|
+
const timer = setTimeout(async () => {
|
|
102
|
+
const lastChange = LastChangeTime.get(node) || 0
|
|
103
|
+
const timeSinceChange = Date.now() - lastChange
|
|
104
|
+
|
|
105
|
+
// Only fire if at least DEBOUNCE_DELAY has passed since last change
|
|
106
|
+
if (timeSinceChange >= DEBOUNCE_DELAY) {
|
|
107
|
+
const event = await getElementLayoutEventAsync(node)
|
|
108
|
+
onLayout(event)
|
|
109
|
+
DebounceTimers.delete(node)
|
|
110
|
+
} else {
|
|
111
|
+
// Reschedule if not enough time has passed
|
|
112
|
+
const remainingDelay = DEBOUNCE_DELAY - timeSinceChange
|
|
113
|
+
const newTimer = setTimeout(async () => {
|
|
114
|
+
const event = await getElementLayoutEventAsync(node)
|
|
115
|
+
onLayout(event)
|
|
116
|
+
DebounceTimers.delete(node)
|
|
117
|
+
}, remainingDelay)
|
|
118
|
+
DebounceTimers.set(node, newTimer)
|
|
119
|
+
}
|
|
120
|
+
}, DEBOUNCE_DELAY)
|
|
121
|
+
|
|
122
|
+
DebounceTimers.set(node, timer)
|
|
83
123
|
} else {
|
|
124
|
+
// Sync strategy - use sync version
|
|
125
|
+
const event = getElementLayoutEvent(node)
|
|
84
126
|
onLayout(event)
|
|
85
127
|
}
|
|
86
128
|
}
|
|
@@ -93,7 +135,7 @@ if (isClient) {
|
|
|
93
135
|
const timeSinceLastFrame = now - lastFrameAt
|
|
94
136
|
lastFrameAt = now
|
|
95
137
|
|
|
96
|
-
if (
|
|
138
|
+
if (strategy !== 'off') {
|
|
97
139
|
// avoid updates if we've been dropping frames (indicates sync work happening)
|
|
98
140
|
const expectedFrameTime = 16.67 // ~60fps
|
|
99
141
|
const hasRecentSyncWork =
|
|
@@ -114,6 +156,7 @@ if (isClient) {
|
|
|
114
156
|
}
|
|
115
157
|
}
|
|
116
158
|
|
|
159
|
+
// Sync versions
|
|
117
160
|
export const getElementLayoutEvent = (target: HTMLElement): LayoutEvent => {
|
|
118
161
|
let res: LayoutEvent | null = null
|
|
119
162
|
measureLayout(target, null, (x, y, width, height, left, top) => {
|
|
@@ -131,7 +174,6 @@ export const getElementLayoutEvent = (target: HTMLElement): LayoutEvent => {
|
|
|
131
174
|
return res
|
|
132
175
|
}
|
|
133
176
|
|
|
134
|
-
// matching old RN callback API (can we remove?)
|
|
135
177
|
export const measureLayout = (
|
|
136
178
|
node: HTMLElement,
|
|
137
179
|
relativeTo: HTMLElement | null,
|
|
@@ -148,6 +190,55 @@ export const measureLayout = (
|
|
|
148
190
|
if (relativeNode instanceof HTMLElement) {
|
|
149
191
|
const nodeDim = node.getBoundingClientRect()
|
|
150
192
|
const relativeNodeDim = relativeNode.getBoundingClientRect()
|
|
193
|
+
|
|
194
|
+
if (relativeNodeDim && nodeDim) {
|
|
195
|
+
const { x, y, width, height, left, top } = getRelativeDimensions(
|
|
196
|
+
nodeDim,
|
|
197
|
+
relativeNodeDim
|
|
198
|
+
)
|
|
199
|
+
callback(x, y, width, height, left, top)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export const getElementLayoutEventAsync = async (
|
|
205
|
+
target: HTMLElement
|
|
206
|
+
): Promise<LayoutEvent> => {
|
|
207
|
+
let res: LayoutEvent | null = null
|
|
208
|
+
await measureLayoutAsync(target, null, (x, y, width, height, left, top) => {
|
|
209
|
+
res = {
|
|
210
|
+
nativeEvent: {
|
|
211
|
+
layout: { x, y, width, height, left, top },
|
|
212
|
+
target,
|
|
213
|
+
},
|
|
214
|
+
timeStamp: Date.now(),
|
|
215
|
+
}
|
|
216
|
+
})
|
|
217
|
+
if (!res) {
|
|
218
|
+
throw new Error(`‼️`) // impossible
|
|
219
|
+
}
|
|
220
|
+
return res
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export const measureLayoutAsync = async (
|
|
224
|
+
node: HTMLElement,
|
|
225
|
+
relativeTo: HTMLElement | null,
|
|
226
|
+
callback: (
|
|
227
|
+
x: number,
|
|
228
|
+
y: number,
|
|
229
|
+
width: number,
|
|
230
|
+
height: number,
|
|
231
|
+
left: number,
|
|
232
|
+
top: number
|
|
233
|
+
) => void
|
|
234
|
+
) => {
|
|
235
|
+
const relativeNode = relativeTo || node?.parentElement
|
|
236
|
+
if (relativeNode instanceof HTMLElement) {
|
|
237
|
+
const [nodeDim, relativeNodeDim] = await Promise.all([
|
|
238
|
+
node.getBoundingClientRect(),
|
|
239
|
+
relativeNode.getBoundingClientRect(),
|
|
240
|
+
])
|
|
241
|
+
|
|
151
242
|
if (relativeNodeDim && nodeDim) {
|
|
152
243
|
const { x, y, width, height, left, top } = getRelativeDimensions(
|
|
153
244
|
nodeDim,
|
|
@@ -188,6 +279,14 @@ export function useElementLayout(
|
|
|
188
279
|
Nodes.delete(node)
|
|
189
280
|
LayoutHandlers.delete(node)
|
|
190
281
|
NodeRectCache.delete(node)
|
|
282
|
+
|
|
283
|
+
// Clean up debounce timer and tracking
|
|
284
|
+
const timer = DebounceTimers.get(node)
|
|
285
|
+
if (timer) {
|
|
286
|
+
clearTimeout(timer)
|
|
287
|
+
DebounceTimers.delete(node)
|
|
288
|
+
}
|
|
289
|
+
LastChangeTime.delete(node)
|
|
191
290
|
}
|
|
192
291
|
}, [ref, !!onLayout])
|
|
193
292
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getBoundingClientRect.d.ts","sourceRoot":"","sources":["../../src/helpers/getBoundingClientRect.tsx"],"names":[],"mappings":"AAAA,eAAO,MAAM,qBAAqB,GAAI,MAAM,WAAW,GAAG,IAAI,KAAG,SAAS,GAAG,OAG5E,CAAA"}
|
|
1
|
+
{"version":3,"file":"getBoundingClientRect.d.ts","sourceRoot":"","sources":["../../src/helpers/getBoundingClientRect.tsx"],"names":[],"mappings":"AAAA,eAAO,MAAM,qBAAqB,GAAI,MAAM,WAAW,GAAG,IAAI,KAAG,SAAS,GAAG,OAG5E,CAAA;AAED,eAAO,MAAM,0BAA0B,GACrC,SAAS,WAAW,KACnB,OAAO,CAAC,eAAe,GAAG,SAAS,CAsBrC,CAAA"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type TamaguiComponentStateRef } from '@tamagui/web';
|
|
2
2
|
import type { RefObject } from 'react';
|
|
3
|
-
type
|
|
4
|
-
export declare function setOnLayoutStrategy(state:
|
|
3
|
+
type LayoutMeasurementStrategy = 'off' | 'sync' | 'async';
|
|
4
|
+
export declare function setOnLayoutStrategy(state: LayoutMeasurementStrategy): void;
|
|
5
5
|
export type LayoutValue = {
|
|
6
6
|
x: number;
|
|
7
7
|
y: number;
|
|
@@ -19,6 +19,8 @@ export type LayoutEvent = {
|
|
|
19
19
|
};
|
|
20
20
|
export declare const getElementLayoutEvent: (target: HTMLElement) => LayoutEvent;
|
|
21
21
|
export declare const measureLayout: (node: HTMLElement, relativeTo: HTMLElement | null, callback: (x: number, y: number, width: number, height: number, left: number, top: number) => void) => void;
|
|
22
|
+
export declare const getElementLayoutEventAsync: (target: HTMLElement) => Promise<LayoutEvent>;
|
|
23
|
+
export declare const measureLayoutAsync: (node: HTMLElement, relativeTo: HTMLElement | null, callback: (x: number, y: number, width: number, height: number, left: number, top: number) => void) => Promise<void>;
|
|
22
24
|
export declare function useElementLayout(ref: RefObject<TamaguiComponentStateRef>, onLayout?: ((e: LayoutEvent) => void) | null): void;
|
|
23
25
|
export {};
|
|
24
26
|
//# sourceMappingURL=useElementLayout.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useElementLayout.d.ts","sourceRoot":"","sources":["../../src/hooks/useElementLayout.tsx"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,wBAAwB,EAE9B,MAAM,cAAc,CAAA;AACrB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAKtC,KAAK,
|
|
1
|
+
{"version":3,"file":"useElementLayout.d.ts","sourceRoot":"","sources":["../../src/hooks/useElementLayout.tsx"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,wBAAwB,EAE9B,MAAM,cAAc,CAAA;AACrB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAKtC,KAAK,yBAAyB,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,CAAA;AAIzD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,yBAAyB,QAEnE;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;CACZ,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,WAAW,EAAE;QACX,MAAM,EAAE,WAAW,CAAA;QACnB,MAAM,EAAE,GAAG,CAAA;KACZ,CAAA;IACD,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AA6HD,eAAO,MAAM,qBAAqB,GAAI,QAAQ,WAAW,KAAG,WAe3D,CAAA;AAED,eAAO,MAAM,aAAa,GACxB,MAAM,WAAW,EACjB,YAAY,WAAW,GAAG,IAAI,EAC9B,UAAU,CACR,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,KACR,IAAI,SAeV,CAAA;AAED,eAAO,MAAM,0BAA0B,GACrC,QAAQ,WAAW,KAClB,OAAO,CAAC,WAAW,CAerB,CAAA;AAED,eAAO,MAAM,kBAAkB,GAC7B,MAAM,WAAW,EACjB,YAAY,WAAW,GAAG,IAAI,EAC9B,UAAU,CACR,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,KACR,IAAI,kBAiBV,CAAA;AASD,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,SAAS,CAAC,wBAAwB,CAAC,EACxC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,KAAK,IAAI,CAAC,GAAG,IAAI,QA+B7C"}
|