@tamagui/core 1.126.13 → 1.126.14

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",
3
+ "version": "1.126.14",
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",
39
- "@tamagui/react-native-use-pressable": "1.126.13",
40
- "@tamagui/react-native-use-responder-events": "1.126.13",
41
- "@tamagui/use-event": "1.126.13",
42
- "@tamagui/web": "1.126.13"
38
+ "@tamagui/react-native-media-driver": "1.126.14",
39
+ "@tamagui/react-native-use-pressable": "1.126.14",
40
+ "@tamagui/react-native-use-responder-events": "1.126.14",
41
+ "@tamagui/use-event": "1.126.14",
42
+ "@tamagui/web": "1.126.14"
43
43
  },
44
44
  "devDependencies": {
45
- "@tamagui/build": "1.126.13",
45
+ "@tamagui/build": "1.126.14",
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,88 @@ 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)
32
+ const rAF = typeof window !== 'undefined' ? window.requestAnimationFrame : undefined
33
+
34
+ if (isClient) {
35
+ if (rAF) {
36
+ // prevent thrashing during first hydration (somewhat, streaming gets trickier)
37
+ let avoidUpdates = true
38
+ const queuedUpdates = new Map<HTMLElement, Function>()
39
+
40
+ ___onDidFinishClientRender(() => {
41
+ avoidUpdates = false
42
+ if (queuedUpdates) {
43
+ queuedUpdates.forEach((cb) => cb())
44
+ queuedUpdates.clear()
45
+ }
46
+ })
47
+
48
+ function updateLayoutIfChanged(node: HTMLElement) {
49
+ const nodeRect = node.getBoundingClientRect()
50
+ const parentNode = node.parentElement
51
+ const parentRect = parentNode?.getBoundingClientRect()
52
+
53
+ const onLayout = LayoutHandlers.get(node)
33
54
  if (typeof onLayout !== 'function') return
34
- measureElement(target as HTMLElement).then((event) => {
35
- onLayout(event)
36
- })
55
+
56
+ const cachedRect = NodeRectCache.get(node)
57
+ const cachedParentRect = parentNode ? NodeRectCache.get(parentNode) : null
58
+
59
+ if (
60
+ !cachedRect ||
61
+ // has changed one rect
62
+ (!isEqualShallow(cachedRect, nodeRect) &&
63
+ (!cachedParentRect || !isEqualShallow(cachedParentRect, parentRect)))
64
+ ) {
65
+ NodeRectCache.set(node, nodeRect)
66
+ if (parentRect && parentNode) {
67
+ ParentRectCache.set(parentNode, parentRect)
68
+ }
69
+ const event = getElementLayoutEvent(node)
70
+ if (avoidUpdates) {
71
+ queuedUpdates.set(node, () => onLayout(event))
72
+ } else {
73
+ onLayout(event)
74
+ }
75
+ }
37
76
  }
38
- })
39
77
 
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
- })
78
+ // note that getBoundingClientRect() does not thrash layout if its after an animation frame
79
+ rAF!(layoutOnAnimationFrame)
80
+ function layoutOnAnimationFrame() {
81
+ Nodes.forEach(updateLayoutIfChanged)
82
+ rAF!(layoutOnAnimationFrame)
83
+ }
84
+ } else {
85
+ if (process.env.NODE_ENV === 'development') {
86
+ console.warn(
87
+ `No requestAnimationFrame - please polyfill for onLayout to work correctly`
88
+ )
89
+ }
49
90
  }
50
91
  }
51
92
 
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
- })
93
+ export const getElementLayoutEvent = (target: HTMLElement): LayoutEvent => {
94
+ let res: LayoutEvent | null = null
95
+ measureLayout(target, null, (x, y, width, height, left, top) => {
96
+ res = {
97
+ nativeEvent: {
98
+ layout: { x, y, width, height, left, top },
99
+ target,
100
+ },
101
+ timeStamp: Date.now(),
102
+ }
63
103
  })
104
+ if (!res) {
105
+ throw new Error(`‼️`) // impossible
106
+ }
107
+ return res
64
108
  }
65
109
 
66
- const cache = new WeakMap()
67
-
110
+ // matching old RN callback API (can we remove?)
68
111
  export const measureLayout = (
69
112
  node: HTMLElement,
70
113
  relativeTo: HTMLElement | null,
@@ -77,22 +120,17 @@ export const measureLayout = (
77
120
  top: number
78
121
  ) => void
79
122
  ) => {
80
- const relativeNode = relativeTo || node?.parentNode
123
+ const relativeNode = relativeTo || node?.parentElement
81
124
  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
- })
125
+ const nodeDim = node.getBoundingClientRect()
126
+ const relativeNodeDim = relativeNode.getBoundingClientRect()
127
+ if (relativeNodeDim && nodeDim) {
128
+ const { x, y, width, height, left, top } = getRelativeDimensions(
129
+ nodeDim,
130
+ relativeNodeDim
131
+ )
132
+ callback(x, y, width, height, left, top)
133
+ }
96
134
  }
97
135
  }
98
136
 
@@ -103,58 +141,29 @@ const getRelativeDimensions = (a: DOMRectReadOnly, b: DOMRectReadOnly) => {
103
141
  return { x, y, width, height, left, top }
104
142
  }
105
143
 
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
144
  export function useElementLayout(
129
145
  ref: RefObject<TamaguiComponentStateRef>,
130
146
  onLayout?: ((e: LayoutEvent) => void) | null
131
147
  ) {
132
- const node = ref.current?.host as Element
133
-
134
148
  // ensure always up to date so we can avoid re-running effect
149
+ const node = ref.current?.host as HTMLElement
135
150
  if (node && onLayout) {
136
151
  LayoutHandlers.set(node, onLayout)
137
152
  }
138
153
 
139
154
  useIsomorphicLayoutEffect(() => {
140
- if (!resizeObserver || !onLayout) return
141
- const node = ref.current?.host as Element
155
+ if (!onLayout) return
156
+ const node = ref.current?.host as HTMLElement
142
157
  if (!node) return
143
158
 
144
- // setup once
145
159
  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)
160
+ Nodes.add(node)
161
+ onLayout(getElementLayoutEvent(node))
153
162
 
154
163
  return () => {
164
+ Nodes.delete(node)
155
165
  LayoutHandlers.delete(node)
156
- resizeListeners.delete(onResize)
157
- resizeObserver?.unobserve(node)
166
+ NodeRectCache.delete(node)
158
167
  }
159
168
  }, [ref, !!onLayout])
160
169
  }
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;AAkED,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"}