@tamagui/core 1.126.13 → 1.126.15

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.15",
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.15",
39
+ "@tamagui/react-native-use-pressable": "1.126.15",
40
+ "@tamagui/react-native-use-responder-events": "1.126.15",
41
+ "@tamagui/use-event": "1.126.15",
42
+ "@tamagui/web": "1.126.15"
43
43
  },
44
44
  "devDependencies": {
45
- "@tamagui/build": "1.126.13",
45
+ "@tamagui/build": "1.126.15",
46
46
  "@testing-library/react": "^16.1.0",
47
47
  "csstype": "^3.0.10",
48
48
  "typescript": "^5.8.2",
@@ -1,10 +1,20 @@
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>()
11
+
12
+ type LayoutMeasurementStatus = 'inactive' | 'active'
13
+
14
+ let status: LayoutMeasurementStatus = 'active'
15
+ export function setOnLayoutStrategy(state: LayoutMeasurementStatus) {
16
+ status = state
17
+ }
8
18
 
9
19
  export type LayoutValue = {
10
20
  x: number
@@ -23,48 +33,90 @@ export type LayoutEvent = {
23
33
  timeStamp: number
24
34
  }
25
35
 
26
- let resizeObserver: ResizeObserver | null = null
36
+ const NodeRectCache = new WeakMap<HTMLElement, DOMRect>()
37
+ const ParentRectCache = new WeakMap<HTMLElement, DOMRect>()
38
+
39
+ const rAF = typeof window !== 'undefined' ? window.requestAnimationFrame : undefined
40
+
41
+ if (isClient) {
42
+ if (rAF) {
43
+ // prevent thrashing during first hydration (somewhat, streaming gets trickier)
44
+ let avoidUpdates = true
45
+ const queuedUpdates = new Map<HTMLElement, Function>()
46
+
47
+ ___onDidFinishClientRender(() => {
48
+ avoidUpdates = false
49
+ if (queuedUpdates) {
50
+ queuedUpdates.forEach((cb) => cb())
51
+ queuedUpdates.clear()
52
+ }
53
+ })
54
+
55
+ function updateLayoutIfChanged(node: HTMLElement) {
56
+ const nodeRect = node.getBoundingClientRect()
57
+ const parentNode = node.parentElement
58
+ const parentRect = parentNode?.getBoundingClientRect()
27
59
 
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)
60
+ const onLayout = LayoutHandlers.get(node)
33
61
  if (typeof onLayout !== 'function') return
34
- measureElement(target as HTMLElement).then((event) => {
35
- onLayout(event)
36
- })
62
+
63
+ const cachedRect = NodeRectCache.get(node)
64
+ const cachedParentRect = parentNode ? NodeRectCache.get(parentNode) : null
65
+
66
+ if (
67
+ !cachedRect ||
68
+ // has changed one rect
69
+ (!isEqualShallow(cachedRect, nodeRect) &&
70
+ (!cachedParentRect || !isEqualShallow(cachedParentRect, parentRect)))
71
+ ) {
72
+ NodeRectCache.set(node, nodeRect)
73
+ if (parentRect && parentNode) {
74
+ ParentRectCache.set(parentNode, parentRect)
75
+ }
76
+ const event = getElementLayoutEvent(node)
77
+ if (avoidUpdates) {
78
+ queuedUpdates.set(node, () => onLayout(event))
79
+ } else {
80
+ onLayout(event)
81
+ }
82
+ }
37
83
  }
38
- })
39
84
 
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
- })
85
+ // note that getBoundingClientRect() does not thrash layout if its after an animation frame
86
+ rAF!(layoutOnAnimationFrame)
87
+ function layoutOnAnimationFrame() {
88
+ if (status !== 'inactive') {
89
+ Nodes.forEach(updateLayoutIfChanged)
90
+ }
91
+ rAF!(layoutOnAnimationFrame)
92
+ }
93
+ } else {
94
+ if (process.env.NODE_ENV === 'development') {
95
+ console.warn(
96
+ `No requestAnimationFrame - please polyfill for onLayout to work correctly`
97
+ )
98
+ }
49
99
  }
50
100
  }
51
101
 
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
- })
102
+ export const getElementLayoutEvent = (target: HTMLElement): LayoutEvent => {
103
+ let res: LayoutEvent | null = null
104
+ measureLayout(target, null, (x, y, width, height, left, top) => {
105
+ res = {
106
+ nativeEvent: {
107
+ layout: { x, y, width, height, left, top },
108
+ target,
109
+ },
110
+ timeStamp: Date.now(),
111
+ }
63
112
  })
113
+ if (!res) {
114
+ throw new Error(`‼️`) // impossible
115
+ }
116
+ return res
64
117
  }
65
118
 
66
- const cache = new WeakMap()
67
-
119
+ // matching old RN callback API (can we remove?)
68
120
  export const measureLayout = (
69
121
  node: HTMLElement,
70
122
  relativeTo: HTMLElement | null,
@@ -77,22 +129,17 @@ export const measureLayout = (
77
129
  top: number
78
130
  ) => void
79
131
  ) => {
80
- const relativeNode = relativeTo || node?.parentNode
132
+ const relativeNode = relativeTo || node?.parentElement
81
133
  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
- })
134
+ const nodeDim = node.getBoundingClientRect()
135
+ const relativeNodeDim = relativeNode.getBoundingClientRect()
136
+ if (relativeNodeDim && nodeDim) {
137
+ const { x, y, width, height, left, top } = getRelativeDimensions(
138
+ nodeDim,
139
+ relativeNodeDim
140
+ )
141
+ callback(x, y, width, height, left, top)
142
+ }
96
143
  }
97
144
  }
98
145
 
@@ -103,58 +150,29 @@ const getRelativeDimensions = (a: DOMRectReadOnly, b: DOMRectReadOnly) => {
103
150
  return { x, y, width, height, left, top }
104
151
  }
105
152
 
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
153
  export function useElementLayout(
129
154
  ref: RefObject<TamaguiComponentStateRef>,
130
155
  onLayout?: ((e: LayoutEvent) => void) | null
131
156
  ) {
132
- const node = ref.current?.host as Element
133
-
134
157
  // ensure always up to date so we can avoid re-running effect
158
+ const node = ref.current?.host as HTMLElement
135
159
  if (node && onLayout) {
136
160
  LayoutHandlers.set(node, onLayout)
137
161
  }
138
162
 
139
163
  useIsomorphicLayoutEffect(() => {
140
- if (!resizeObserver || !onLayout) return
141
- const node = ref.current?.host as Element
164
+ if (!onLayout) return
165
+ const node = ref.current?.host as HTMLElement
142
166
  if (!node) return
143
167
 
144
- // setup once
145
168
  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)
169
+ Nodes.add(node)
170
+ onLayout(getElementLayoutEvent(node))
153
171
 
154
172
  return () => {
173
+ Nodes.delete(node)
155
174
  LayoutHandlers.delete(node)
156
- resizeListeners.delete(onResize)
157
- resizeObserver?.unobserve(node)
175
+ NodeRectCache.delete(node)
158
176
  }
159
177
  }, [ref, !!onLayout])
160
178
  }
package/src/index.tsx CHANGED
@@ -21,13 +21,20 @@ import {
21
21
  } from '@tamagui/web'
22
22
  import React from 'react'
23
23
 
24
+ import { addNativeValidStyles } from './addNativeValidStyles'
24
25
  import { createOptimizedView } from './createOptimizedView'
25
26
  import { getBaseViews } from './getBaseViews'
26
27
  import { getRect } from './helpers/getRect'
27
28
  import { measureLayout, useElementLayout } from './hooks/useElementLayout'
28
29
  import type { RNTextProps, RNViewProps } from './reactNativeTypes'
29
30
  import { usePressability } from './vendor/Pressability'
30
- import { addNativeValidStyles } from './addNativeValidStyles'
31
+
32
+ // helpful for usage outside of tamagui
33
+ export {
34
+ getElementLayoutEvent,
35
+ setOnLayoutStrategy,
36
+ type LayoutEvent,
37
+ } from './hooks/useElementLayout'
31
38
 
32
39
  // add newer style props based on react native version
33
40
  addNativeValidStyles()
@@ -1,5 +1,7 @@
1
- import type { TamaguiComponentStateRef } from '@tamagui/web';
1
+ import { type TamaguiComponentStateRef } from '@tamagui/web';
2
2
  import type { RefObject } from 'react';
3
+ type LayoutMeasurementStatus = 'inactive' | 'active';
4
+ export declare function setOnLayoutStrategy(state: LayoutMeasurementStatus): void;
3
5
  export type LayoutValue = {
4
6
  x: number;
5
7
  y: number;
@@ -15,7 +17,8 @@ export type LayoutEvent = {
15
17
  };
16
18
  timeStamp: number;
17
19
  };
18
- export declare const measureElement: (target: HTMLElement) => Promise<LayoutEvent>;
20
+ export declare const getElementLayoutEvent: (target: HTMLElement) => LayoutEvent;
19
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;
20
22
  export declare function useElementLayout(ref: RefObject<TamaguiComponentStateRef>, onLayout?: ((e: LayoutEvent) => void) | null): void;
23
+ export {};
21
24
  //# 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,KAAK,uBAAuB,GAAG,UAAU,GAAG,QAAQ,CAAA;AAGpD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,uBAAuB,QAEjE;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;AAoED,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, setOnLayoutStrategy, 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;AAQrB,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAIlE,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,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"}