@tamagui/core 1.126.13-1747671613486 → 1.126.13-1747874018127

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tamagui/core",
3
- "version": "1.126.13-1747671613486",
3
+ "version": "1.126.13-1747874018127",
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.126.13-1747671613486",
39
- "@tamagui/react-native-use-pressable": "1.126.13-1747671613486",
40
- "@tamagui/react-native-use-responder-events": "1.126.13-1747671613486",
41
- "@tamagui/use-event": "1.126.13-1747671613486",
42
- "@tamagui/web": "1.126.13-1747671613486"
38
+ "@tamagui/react-native-media-driver": "1.126.13-1747874018127",
39
+ "@tamagui/react-native-use-pressable": "1.126.13-1747874018127",
40
+ "@tamagui/react-native-use-responder-events": "1.126.13-1747874018127",
41
+ "@tamagui/use-event": "1.126.13-1747874018127",
42
+ "@tamagui/web": "1.126.13-1747874018127"
43
43
  },
44
44
  "devDependencies": {
45
- "@tamagui/build": "1.126.13-1747671613486",
45
+ "@tamagui/build": "1.126.13-1747874018127",
46
46
  "@testing-library/react": "^16.1.0",
47
47
  "csstype": "^3.0.10",
48
48
  "typescript": "^5.8.2",
@@ -1,10 +1,13 @@
1
- import { useIsomorphicLayoutEffect } from '@tamagui/constants'
2
- import type { TamaguiComponentStateRef } from '@tamagui/web'
1
+ import { isClient, useIsomorphicLayoutEffect } from '@tamagui/constants'
2
+ import {
3
+ isEqualShallow,
4
+ type TamaguiComponentStateRef,
5
+ ___onDidFinishClientRender,
6
+ } from '@tamagui/web'
3
7
  import type { RefObject } from 'react'
4
- import { getBoundingClientRect } from '../helpers/getBoundingClientRect'
5
8
 
6
- const LayoutHandlers = new WeakMap<Element, Function>()
7
- const resizeListeners = new Set<Function>()
9
+ const LayoutHandlers = new WeakMap<HTMLElement, Function>()
10
+ const Nodes = new Set<HTMLElement>()
8
11
 
9
12
  export type LayoutValue = {
10
13
  x: number
@@ -23,48 +26,78 @@ export type LayoutEvent = {
23
26
  timeStamp: number
24
27
  }
25
28
 
26
- let resizeObserver: ResizeObserver | null = null
29
+ const NodeRectCache = new WeakMap<HTMLElement, DOMRect>()
30
+ const ParentRectCache = new WeakMap<HTMLElement, DOMRect>()
27
31
 
28
- if (typeof window !== 'undefined' && 'ResizeObserver' in window) {
29
- // node resize/move
30
- resizeObserver = new ResizeObserver((entries) => {
31
- for (const { target } of entries) {
32
- const onLayout = LayoutHandlers.get(target)
33
- if (typeof onLayout !== 'function') return
34
- measureElement(target as HTMLElement).then((event) => {
35
- onLayout(event)
36
- })
32
+ if (isClient && typeof requestAnimationFrame === 'function') {
33
+ // prevent thrashing during first hydration (somewhat, streaming gets trickier)
34
+ let avoidUpdates = true
35
+ const queuedUpdates = new Map<HTMLElement, Function>()
36
+
37
+ ___onDidFinishClientRender(() => {
38
+ avoidUpdates = false
39
+ if (queuedUpdates) {
40
+ queuedUpdates.forEach((cb) => cb())
41
+ queuedUpdates.clear()
37
42
  }
38
43
  })
39
44
 
40
- // window resize
41
- if (typeof window.addEventListener === 'function') {
42
- let tm
43
- window.addEventListener('resize', () => {
44
- clearTimeout(tm)
45
- tm = setTimeout(() => {
46
- resizeListeners.forEach((cb) => cb())
47
- }, 4)
48
- })
45
+ function updateLayoutIfChanged(node: HTMLElement) {
46
+ const nodeRect = node.getBoundingClientRect()
47
+ const parentNode = node.parentElement
48
+ const parentRect = parentNode?.getBoundingClientRect()
49
+
50
+ const onLayout = LayoutHandlers.get(node)
51
+ if (typeof onLayout !== 'function') return
52
+
53
+ const cachedRect = NodeRectCache.get(node)
54
+ const cachedParentRect = parentNode ? NodeRectCache.get(parentNode) : null
55
+
56
+ if (
57
+ !cachedRect ||
58
+ // has changed one rect
59
+ (!isEqualShallow(cachedRect, nodeRect) &&
60
+ (!cachedParentRect || !isEqualShallow(cachedParentRect, parentRect)))
61
+ ) {
62
+ NodeRectCache.set(node, nodeRect)
63
+ if (parentRect && parentNode) {
64
+ ParentRectCache.set(parentNode, parentRect)
65
+ }
66
+ const event = getElementLayoutEvent(node)
67
+ if (avoidUpdates) {
68
+ queuedUpdates.set(node, () => onLayout(event))
69
+ } else {
70
+ onLayout(event)
71
+ }
72
+ }
73
+ }
74
+
75
+ // note that getBoundingClientRect() does not thrash layout if its after an animation frame
76
+ requestAnimationFrame(layoutOnAnimationFrame)
77
+ function layoutOnAnimationFrame() {
78
+ Nodes.forEach(updateLayoutIfChanged)
79
+ requestAnimationFrame(layoutOnAnimationFrame)
49
80
  }
50
81
  }
51
82
 
52
- export const measureElement = async (target: HTMLElement): Promise<LayoutEvent> => {
53
- return new Promise((res) => {
54
- measureLayout(target, null, (x, y, width, height, left, top) => {
55
- res({
56
- nativeEvent: {
57
- layout: { x, y, width, height, left, top },
58
- target,
59
- },
60
- timeStamp: Date.now(),
61
- })
62
- })
83
+ export const getElementLayoutEvent = (target: HTMLElement): LayoutEvent => {
84
+ let res: LayoutEvent | null = null
85
+ measureLayout(target, null, (x, y, width, height, left, top) => {
86
+ res = {
87
+ nativeEvent: {
88
+ layout: { x, y, width, height, left, top },
89
+ target,
90
+ },
91
+ timeStamp: Date.now(),
92
+ }
63
93
  })
94
+ if (!res) {
95
+ throw new Error(`‼️`) // impossible
96
+ }
97
+ return res
64
98
  }
65
99
 
66
- const cache = new WeakMap()
67
-
100
+ // matching old RN callback API (can we remove?)
68
101
  export const measureLayout = (
69
102
  node: HTMLElement,
70
103
  relativeTo: HTMLElement | null,
@@ -77,22 +110,17 @@ export const measureLayout = (
77
110
  top: number
78
111
  ) => void
79
112
  ) => {
80
- const relativeNode = relativeTo || node?.parentNode
113
+ const relativeNode = relativeTo || node?.parentElement
81
114
  if (relativeNode instanceof HTMLElement) {
82
- const now = Date.now()
83
- cache.set(node, now)
84
- Promise.all([
85
- getBoundingClientRectAsync(node),
86
- getBoundingClientRectAsync(relativeNode),
87
- ]).then(([nodeDim, relativeNodeDim]) => {
88
- if (relativeNodeDim && nodeDim && cache.get(node) === now) {
89
- const { x, y, width, height, left, top } = getRelativeDimensions(
90
- nodeDim,
91
- relativeNodeDim
92
- )
93
- callback(x, y, width, height, left, top)
94
- }
95
- })
115
+ const nodeDim = node.getBoundingClientRect()
116
+ const relativeNodeDim = relativeNode.getBoundingClientRect()
117
+ if (relativeNodeDim && nodeDim) {
118
+ const { x, y, width, height, left, top } = getRelativeDimensions(
119
+ nodeDim,
120
+ relativeNodeDim
121
+ )
122
+ callback(x, y, width, height, left, top)
123
+ }
96
124
  }
97
125
  }
98
126
 
@@ -103,58 +131,29 @@ const getRelativeDimensions = (a: DOMRectReadOnly, b: DOMRectReadOnly) => {
103
131
  return { x, y, width, height, left, top }
104
132
  }
105
133
 
106
- const getBoundingClientRectAsync = (
107
- element: HTMLElement
108
- ): Promise<DOMRectReadOnly | undefined> => {
109
- return new Promise((resolve) => {
110
- function fallbackToSync() {
111
- resolve(getBoundingClientRect(element))
112
- }
113
- const tm = setTimeout(fallbackToSync, 10)
114
- const observer = new IntersectionObserver(
115
- (entries, ob) => {
116
- clearTimeout(tm)
117
- ob.disconnect()
118
- resolve(entries[0]?.boundingClientRect)
119
- },
120
- {
121
- threshold: 0.0001,
122
- }
123
- )
124
- observer.observe(element)
125
- })
126
- }
127
-
128
134
  export function useElementLayout(
129
135
  ref: RefObject<TamaguiComponentStateRef>,
130
136
  onLayout?: ((e: LayoutEvent) => void) | null
131
137
  ) {
132
- const node = ref.current?.host as Element
133
-
134
138
  // ensure always up to date so we can avoid re-running effect
139
+ const node = ref.current?.host as HTMLElement
135
140
  if (node && onLayout) {
136
141
  LayoutHandlers.set(node, onLayout)
137
142
  }
138
143
 
139
144
  useIsomorphicLayoutEffect(() => {
140
- if (!resizeObserver || !onLayout) return
141
- const node = ref.current?.host as Element
145
+ if (!onLayout) return
146
+ const node = ref.current?.host as HTMLElement
142
147
  if (!node) return
143
148
 
144
- // setup once
145
149
  LayoutHandlers.set(node, onLayout)
146
-
147
- const onResize = () => {
148
- measureElement(node as HTMLElement).then(onLayout)
149
- }
150
-
151
- resizeListeners.add(onResize)
152
- resizeObserver.observe(node)
150
+ Nodes.add(node)
151
+ onLayout(getElementLayoutEvent(node))
153
152
 
154
153
  return () => {
154
+ Nodes.delete(node)
155
155
  LayoutHandlers.delete(node)
156
- resizeListeners.delete(onResize)
157
- resizeObserver?.unobserve(node)
156
+ NodeRectCache.delete(node)
158
157
  }
159
158
  }, [ref, !!onLayout])
160
159
  }
package/src/index.tsx CHANGED
@@ -29,6 +29,12 @@ import type { RNTextProps, RNViewProps } from './reactNativeTypes'
29
29
  import { usePressability } from './vendor/Pressability'
30
30
  import { addNativeValidStyles } from './addNativeValidStyles'
31
31
 
32
+ // helpful for usage outside of tamagui
33
+ export {
34
+ getElementLayoutEvent,
35
+ type LayoutEvent,
36
+ } from './hooks/useElementLayout'
37
+
32
38
  // add newer style props based on react native version
33
39
  addNativeValidStyles()
34
40
 
@@ -1,4 +1,4 @@
1
- import type { TamaguiComponentStateRef } from '@tamagui/web';
1
+ import { type TamaguiComponentStateRef } from '@tamagui/web';
2
2
  import type { RefObject } from 'react';
3
3
  export type LayoutValue = {
4
4
  x: number;
@@ -15,7 +15,7 @@ export type LayoutEvent = {
15
15
  };
16
16
  timeStamp: number;
17
17
  };
18
- export declare const measureElement: (target: HTMLElement) => Promise<LayoutEvent>;
18
+ export declare const getElementLayoutEvent: (target: HTMLElement) => LayoutEvent;
19
19
  export declare const measureLayout: (node: HTMLElement, relativeTo: HTMLElement | null, callback: (x: number, y: number, width: number, height: number, left: number, top: number) => void) => void;
20
20
  export declare function useElementLayout(ref: RefObject<TamaguiComponentStateRef>, onLayout?: ((e: LayoutEvent) => void) | null): void;
21
21
  //# sourceMappingURL=useElementLayout.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useElementLayout.d.ts","sourceRoot":"","sources":["../../src/hooks/useElementLayout.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAA;AAC5D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAMtC,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;AA4BD,eAAO,MAAM,cAAc,GAAU,QAAQ,WAAW,KAAG,OAAO,CAAC,WAAW,CAY7E,CAAA;AAID,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,SAmBV,CAAA;AA+BD,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,SAAS,CAAC,wBAAwB,CAAC,EACxC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,KAAK,IAAI,CAAC,GAAG,IAAI,QA8B7C"}
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,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;AAwDD,eAAO,MAAM,qBAAqB,GAAI,QAAQ,WAAW,KAAG,WAe3D,CAAA;AAGD,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,SAcV,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,QAuB7C"}
package/types/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { StackNonStyleProps, StackStyleBase, TamaDefer, TamaguiComponent, TamaguiElement, TamaguiTextElement, TextNonStyleProps, TextProps, TextStylePropsBase } from '@tamagui/web';
2
2
  import { createTamagui as createTamaguiWeb } from '@tamagui/web';
3
3
  import type { RNTextProps, RNViewProps } from './reactNativeTypes';
4
+ export { getElementLayoutEvent, type LayoutEvent, } from './hooks/useElementLayout';
4
5
  type RNExclusiveViewProps = Omit<RNViewProps, keyof StackNonStyleProps>;
5
6
  export interface RNTamaguiViewNonStyleProps extends StackNonStyleProps, RNExclusiveViewProps {
6
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,kBAAkB,EAClB,cAAc,EACd,SAAS,EACT,gBAAgB,EAChB,cAAc,EACd,kBAAkB,EAClB,iBAAiB,EACjB,SAAS,EACT,kBAAkB,EACnB,MAAM,cAAc,CAAA;AACrB,OAAO,EAKL,aAAa,IAAI,gBAAgB,EAElC,MAAM,cAAc,CAAA;AAOrB,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AASlE,KAAK,oBAAoB,GAAG,IAAI,CAAC,WAAW,EAAE,MAAM,kBAAkB,CAAC,CAAA;AACvE,MAAM,WAAW,0BACf,SAAQ,kBAAkB,EACxB,oBAAoB;CAAG;AAE3B,KAAK,aAAa,GAAG,gBAAgB,CACnC,SAAS,EACT,cAAc,EACd,0BAA0B,EAC1B,cAAc,EACd,EAAE,CACH,CAAA;AAED,KAAK,oBAAoB,GAAG,IAAI,CAAC,WAAW,EAAE,MAAM,SAAS,CAAC,CAAA;AAC9D,MAAM,WAAW,0BACf,SAAQ,iBAAiB,EACvB,oBAAoB;CAAG;AAE3B,KAAK,aAAa,GAAG,gBAAgB,CACnC,SAAS,EACT,kBAAkB,EAClB,0BAA0B,EAC1B,kBAAkB,EAClB,EAAE,CACH,CAAA;AAGD,cAAc,cAAc,CAAA;AAG5B,cAAc,oBAAoB,CAAA;AAGlC,eAAO,MAAM,aAAa,EAAE,OAAO,gBAOlC,CAAA;AAmLD,eAAO,MAAM,IAAI,EAAqB,aAAa,CAAA;AACnD,eAAO,MAAM,KAAK,EAAsB,aAAa,CAAA;AACrD,eAAO,MAAM,IAAI,EAAqB,aAAa,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,kBAAkB,EAClB,cAAc,EACd,SAAS,EACT,gBAAgB,EAChB,cAAc,EACd,kBAAkB,EAClB,iBAAiB,EACjB,SAAS,EACT,kBAAkB,EACnB,MAAM,cAAc,CAAA;AACrB,OAAO,EAKL,aAAa,IAAI,gBAAgB,EAElC,MAAM,cAAc,CAAA;AAOrB,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAKlE,OAAO,EACL,qBAAqB,EACrB,KAAK,WAAW,GACjB,MAAM,0BAA0B,CAAA;AAOjC,KAAK,oBAAoB,GAAG,IAAI,CAAC,WAAW,EAAE,MAAM,kBAAkB,CAAC,CAAA;AACvE,MAAM,WAAW,0BACf,SAAQ,kBAAkB,EACxB,oBAAoB;CAAG;AAE3B,KAAK,aAAa,GAAG,gBAAgB,CACnC,SAAS,EACT,cAAc,EACd,0BAA0B,EAC1B,cAAc,EACd,EAAE,CACH,CAAA;AAED,KAAK,oBAAoB,GAAG,IAAI,CAAC,WAAW,EAAE,MAAM,SAAS,CAAC,CAAA;AAC9D,MAAM,WAAW,0BACf,SAAQ,iBAAiB,EACvB,oBAAoB;CAAG;AAE3B,KAAK,aAAa,GAAG,gBAAgB,CACnC,SAAS,EACT,kBAAkB,EAClB,0BAA0B,EAC1B,kBAAkB,EAClB,EAAE,CACH,CAAA;AAGD,cAAc,cAAc,CAAA;AAG5B,cAAc,oBAAoB,CAAA;AAGlC,eAAO,MAAM,aAAa,EAAE,OAAO,gBAOlC,CAAA;AAmLD,eAAO,MAAM,IAAI,EAAqB,aAAa,CAAA;AACnD,eAAO,MAAM,KAAK,EAAsB,aAAa,CAAA;AACrD,eAAO,MAAM,IAAI,EAAqB,aAAa,CAAA"}