react-native-unistyles 3.4.0 → 3.5.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.
Files changed (204) hide show
  1. package/README.md +23 -7
  2. package/android/build.gradle +12 -1
  3. package/android/src/main/cxx/NativeUnistylesModule.cpp +8 -4
  4. package/android/src/main/cxx/NativeUnistylesModule.h +5 -1
  5. package/components/native/ActivityIndicator/package.json +1 -1
  6. package/cxx/common/UnistylesConstants.h +4 -0
  7. package/cxx/converters/TransformOriginConverter.cpp +106 -0
  8. package/cxx/core/HostUnistyle.cpp +13 -1
  9. package/cxx/core/HostUnistyle.h +6 -2
  10. package/cxx/core/RNStyle.h +1 -1
  11. package/cxx/core/StyleSheetRegistry.cpp +9 -9
  12. package/cxx/core/StyleSheetRegistry.h +3 -3
  13. package/cxx/core/UnistyleWrapper.h +13 -1
  14. package/cxx/core/UnistylesRegistry.cpp +102 -18
  15. package/cxx/core/UnistylesRegistry.h +42 -5
  16. package/cxx/hybridObjects/HybridShadowRegistry.cpp +124 -4
  17. package/cxx/hybridObjects/HybridShadowRegistry.h +20 -0
  18. package/cxx/hybridObjects/HybridStyleSheet.cpp +20 -12
  19. package/cxx/hybridObjects/HybridUnistylesRuntime.cpp +11 -1
  20. package/cxx/hybridObjects/HybridUnistylesRuntime.h +4 -2
  21. package/cxx/parser/Parser.cpp +29 -1
  22. package/cxx/parser/Parser.h +1 -0
  23. package/cxx/shadowTree/NativeProps.h +46 -2
  24. package/cxx/shadowTree/ShadowTrafficController.h +4 -0
  25. package/cxx/shadowTree/ShadowTreeDiagnostics.cpp +344 -0
  26. package/cxx/shadowTree/ShadowTreeDiagnostics.h +18 -0
  27. package/cxx/shadowTree/ShadowTreeManager.cpp +7 -1
  28. package/cxx/shadowTree/ShadowTreeManager.h +1 -1
  29. package/diagnostics/package.json +6 -0
  30. package/ios/UnistylesModuleOnLoad.mm +9 -3
  31. package/lib/commonjs/components/native/ActivityIndicator.native.js +10 -0
  32. package/lib/commonjs/components/native/ActivityIndicator.native.js.map +1 -0
  33. package/lib/commonjs/components/native/NativeText.js +6 -0
  34. package/lib/commonjs/components/native/NativeText.js.map +1 -1
  35. package/lib/commonjs/components/native/NativeText.native.js +17 -1
  36. package/lib/commonjs/components/native/NativeText.native.js.map +1 -1
  37. package/lib/commonjs/components/native/NativeView.js +6 -0
  38. package/lib/commonjs/components/native/NativeView.js.map +1 -1
  39. package/lib/commonjs/components/native/NativeView.native.js +22 -3
  40. package/lib/commonjs/components/native/NativeView.native.js.map +1 -1
  41. package/lib/commonjs/components/native/Pressable.js +7 -3
  42. package/lib/commonjs/components/native/Pressable.js.map +1 -1
  43. package/lib/commonjs/components/native/TouchableHighlight.native.js +10 -0
  44. package/lib/commonjs/components/native/TouchableHighlight.native.js.map +1 -0
  45. package/lib/commonjs/core/createUnistylesActivityIndicator.js +41 -0
  46. package/lib/commonjs/core/createUnistylesActivityIndicator.js.map +1 -0
  47. package/lib/commonjs/core/createUnistylesElement.js +60 -15
  48. package/lib/commonjs/core/createUnistylesElement.js.map +1 -1
  49. package/lib/commonjs/core/createUnistylesTouchableHighlight.js +58 -0
  50. package/lib/commonjs/core/createUnistylesTouchableHighlight.js.map +1 -0
  51. package/lib/commonjs/core/index.js +14 -0
  52. package/lib/commonjs/core/index.js.map +1 -1
  53. package/lib/commonjs/core/passForwardRef.js +15 -3
  54. package/lib/commonjs/core/passForwardRef.js.map +1 -1
  55. package/lib/commonjs/diagnostics/index.js +13 -0
  56. package/lib/commonjs/diagnostics/index.js.map +1 -0
  57. package/lib/commonjs/diagnostics/types.js +2 -0
  58. package/lib/commonjs/diagnostics/types.js.map +1 -0
  59. package/lib/commonjs/diagnostics/verify.js +19 -0
  60. package/lib/commonjs/diagnostics/verify.js.map +1 -0
  61. package/lib/commonjs/diagnostics/verify.native.js +12 -0
  62. package/lib/commonjs/diagnostics/verify.native.js.map +1 -0
  63. package/lib/commonjs/index.js +38 -0
  64. package/lib/commonjs/index.js.map +1 -1
  65. package/lib/commonjs/mocks.js +42 -1
  66. package/lib/commonjs/mocks.js.map +1 -1
  67. package/lib/commonjs/reanimated/variant/useUpdateVariantColor.native.js +8 -0
  68. package/lib/commonjs/reanimated/variant/useUpdateVariantColor.native.js.map +1 -1
  69. package/lib/commonjs/specs/ShadowRegistry/index.js +97 -1
  70. package/lib/commonjs/specs/ShadowRegistry/index.js.map +1 -1
  71. package/lib/commonjs/specs/StyleSheet/index.js +5 -1
  72. package/lib/commonjs/specs/StyleSheet/index.js.map +1 -1
  73. package/lib/commonjs/specs/UnistylesRuntime/index.js.map +1 -1
  74. package/lib/commonjs/web/registry.js +3 -0
  75. package/lib/commonjs/web/registry.js.map +1 -1
  76. package/lib/commonjs/web/runtime.js +6 -0
  77. package/lib/commonjs/web/runtime.js.map +1 -1
  78. package/lib/module/components/native/ActivityIndicator.native.js +6 -0
  79. package/lib/module/components/native/ActivityIndicator.native.js.map +1 -0
  80. package/lib/module/components/native/NativeText.js +1 -1
  81. package/lib/module/components/native/NativeText.js.map +1 -1
  82. package/lib/module/components/native/NativeText.native.js +15 -1
  83. package/lib/module/components/native/NativeText.native.js.map +1 -1
  84. package/lib/module/components/native/NativeView.js +1 -0
  85. package/lib/module/components/native/NativeView.js.map +1 -1
  86. package/lib/module/components/native/NativeView.native.js +19 -2
  87. package/lib/module/components/native/NativeView.native.js.map +1 -1
  88. package/lib/module/components/native/Pressable.js +7 -3
  89. package/lib/module/components/native/Pressable.js.map +1 -1
  90. package/lib/module/components/native/TouchableHighlight.native.js +6 -0
  91. package/lib/module/components/native/TouchableHighlight.native.js.map +1 -0
  92. package/lib/module/core/createUnistylesActivityIndicator.js +35 -0
  93. package/lib/module/core/createUnistylesActivityIndicator.js.map +1 -0
  94. package/lib/module/core/createUnistylesElement.js +59 -14
  95. package/lib/module/core/createUnistylesElement.js.map +1 -1
  96. package/lib/module/core/createUnistylesTouchableHighlight.js +52 -0
  97. package/lib/module/core/createUnistylesTouchableHighlight.js.map +1 -0
  98. package/lib/module/core/index.js +2 -0
  99. package/lib/module/core/index.js.map +1 -1
  100. package/lib/module/core/passForwardRef.js +15 -3
  101. package/lib/module/core/passForwardRef.js.map +1 -1
  102. package/lib/module/diagnostics/index.js +5 -0
  103. package/lib/module/diagnostics/index.js.map +1 -0
  104. package/lib/module/diagnostics/types.js +2 -0
  105. package/lib/module/diagnostics/types.js.map +1 -0
  106. package/lib/module/diagnostics/verify.js +14 -0
  107. package/lib/module/diagnostics/verify.js.map +1 -0
  108. package/lib/module/diagnostics/verify.native.js +8 -0
  109. package/lib/module/diagnostics/verify.native.js.map +1 -0
  110. package/lib/module/index.js +2 -0
  111. package/lib/module/index.js.map +1 -1
  112. package/lib/module/mocks.js +42 -1
  113. package/lib/module/mocks.js.map +1 -1
  114. package/lib/module/reanimated/variant/useUpdateVariantColor.native.js +9 -1
  115. package/lib/module/reanimated/variant/useUpdateVariantColor.native.js.map +1 -1
  116. package/lib/module/specs/ShadowRegistry/index.js +97 -1
  117. package/lib/module/specs/ShadowRegistry/index.js.map +1 -1
  118. package/lib/module/specs/StyleSheet/index.js +6 -1
  119. package/lib/module/specs/StyleSheet/index.js.map +1 -1
  120. package/lib/module/specs/UnistylesRuntime/index.js.map +1 -1
  121. package/lib/module/web/registry.js +3 -0
  122. package/lib/module/web/registry.js.map +1 -1
  123. package/lib/module/web/runtime.js +6 -0
  124. package/lib/module/web/runtime.js.map +1 -1
  125. package/lib/typescript/src/__tests__/passForwardRef.spec.d.ts +2 -0
  126. package/lib/typescript/src/__tests__/passForwardRef.spec.d.ts.map +1 -0
  127. package/lib/typescript/src/__tests__/web-runtime.spec.d.ts +2 -0
  128. package/lib/typescript/src/__tests__/web-runtime.spec.d.ts.map +1 -0
  129. package/lib/typescript/src/components/native/ActivityIndicator.native.d.ts +2 -0
  130. package/lib/typescript/src/components/native/ActivityIndicator.native.d.ts.map +1 -0
  131. package/lib/typescript/src/components/native/NativeText.d.ts +1 -1
  132. package/lib/typescript/src/components/native/NativeText.d.ts.map +1 -1
  133. package/lib/typescript/src/components/native/NativeText.native.d.ts.map +1 -1
  134. package/lib/typescript/src/components/native/NativeView.d.ts +1 -0
  135. package/lib/typescript/src/components/native/NativeView.d.ts.map +1 -1
  136. package/lib/typescript/src/components/native/NativeView.native.d.ts +2 -2
  137. package/lib/typescript/src/components/native/NativeView.native.d.ts.map +1 -1
  138. package/lib/typescript/src/components/native/Pressable.d.ts.map +1 -1
  139. package/lib/typescript/src/components/native/TouchableHighlight.native.d.ts +2 -0
  140. package/lib/typescript/src/components/native/TouchableHighlight.native.d.ts.map +1 -0
  141. package/lib/typescript/src/core/createUnistylesActivityIndicator.d.ts +3 -0
  142. package/lib/typescript/src/core/createUnistylesActivityIndicator.d.ts.map +1 -0
  143. package/lib/typescript/src/core/createUnistylesElement.d.ts.map +1 -1
  144. package/lib/typescript/src/core/createUnistylesTouchableHighlight.d.ts +3 -0
  145. package/lib/typescript/src/core/createUnistylesTouchableHighlight.d.ts.map +1 -0
  146. package/lib/typescript/src/core/index.d.ts +2 -0
  147. package/lib/typescript/src/core/index.d.ts.map +1 -1
  148. package/lib/typescript/src/core/passForwardRef.d.ts.map +1 -1
  149. package/lib/typescript/src/core/useUnistyles.d.ts +1 -1
  150. package/lib/typescript/src/diagnostics/index.d.ts +3 -0
  151. package/lib/typescript/src/diagnostics/index.d.ts.map +1 -0
  152. package/lib/typescript/src/diagnostics/types.d.ts +19 -0
  153. package/lib/typescript/src/diagnostics/types.d.ts.map +1 -0
  154. package/lib/typescript/src/diagnostics/verify.d.ts +3 -0
  155. package/lib/typescript/src/diagnostics/verify.d.ts.map +1 -0
  156. package/lib/typescript/src/diagnostics/verify.native.d.ts +3 -0
  157. package/lib/typescript/src/diagnostics/verify.native.d.ts.map +1 -0
  158. package/lib/typescript/src/index.d.ts +4 -1
  159. package/lib/typescript/src/index.d.ts.map +1 -1
  160. package/lib/typescript/src/reanimated/variant/useUpdateVariantColor.native.d.ts.map +1 -1
  161. package/lib/typescript/src/specs/ShadowRegistry/index.d.ts +5 -1
  162. package/lib/typescript/src/specs/ShadowRegistry/index.d.ts.map +1 -1
  163. package/lib/typescript/src/specs/ShadowRegistry/types.d.ts +3 -0
  164. package/lib/typescript/src/specs/ShadowRegistry/types.d.ts.map +1 -1
  165. package/lib/typescript/src/specs/StyleSheet/index.d.ts +4 -1
  166. package/lib/typescript/src/specs/StyleSheet/index.d.ts.map +1 -1
  167. package/lib/typescript/src/specs/UnistylesRuntime/index.d.ts +2 -2
  168. package/lib/typescript/src/specs/UnistylesRuntime/index.d.ts.map +1 -1
  169. package/lib/typescript/src/types/breakpoints.d.ts +1 -1
  170. package/lib/typescript/src/types/breakpoints.d.ts.map +1 -1
  171. package/lib/typescript/src/types/stylesheet.d.ts +3 -3
  172. package/lib/typescript/src/types/stylesheet.d.ts.map +1 -1
  173. package/lib/typescript/src/web/registry.d.ts.map +1 -1
  174. package/lib/typescript/src/web/runtime.d.ts.map +1 -1
  175. package/package.json +9 -1
  176. package/plugin/index.d.ts +4 -4
  177. package/plugin/index.js +21 -5
  178. package/src/components/native/ActivityIndicator.native.tsx +5 -0
  179. package/src/components/native/NativeText.native.tsx +18 -1
  180. package/src/components/native/NativeText.tsx +1 -1
  181. package/src/components/native/NativeView.native.tsx +22 -2
  182. package/src/components/native/NativeView.tsx +2 -0
  183. package/src/components/native/Pressable.tsx +8 -3
  184. package/src/components/native/TouchableHighlight.native.tsx +5 -0
  185. package/src/core/createUnistylesActivityIndicator.tsx +47 -0
  186. package/src/core/createUnistylesElement.tsx +51 -15
  187. package/src/core/createUnistylesTouchableHighlight.tsx +69 -0
  188. package/src/core/index.ts +2 -0
  189. package/src/core/passForwardRef.ts +16 -4
  190. package/src/diagnostics/index.ts +3 -0
  191. package/src/diagnostics/types.ts +25 -0
  192. package/src/diagnostics/verify.native.ts +7 -0
  193. package/src/diagnostics/verify.ts +13 -0
  194. package/src/index.ts +4 -1
  195. package/src/mocks.ts +47 -2
  196. package/src/reanimated/variant/useUpdateVariantColor.native.ts +7 -1
  197. package/src/specs/ShadowRegistry/index.ts +128 -1
  198. package/src/specs/ShadowRegistry/types.ts +3 -0
  199. package/src/specs/StyleSheet/index.ts +10 -2
  200. package/src/specs/UnistylesRuntime/index.ts +7 -2
  201. package/src/types/breakpoints.ts +5 -3
  202. package/src/types/stylesheet.ts +3 -3
  203. package/src/web/registry.ts +3 -0
  204. package/src/web/runtime.ts +6 -0
@@ -1,12 +1,29 @@
1
1
  import type { TextProps } from 'react-native'
2
2
 
3
3
  import { type ComponentType, createElement, forwardRef } from 'react'
4
+ import * as ReactNative from 'react-native'
4
5
 
5
6
  import { createUnistylesElement } from '../../core'
6
7
 
8
+ const RN = ReactNative as typeof ReactNative & { unstable_NativeText?: unknown }
9
+
10
+ const resolveRCTText = () => {
11
+ // RN 0.83+ re-exports the host component, which at runtime is the 'RCTText' string itself
12
+ if (typeof RN.unstable_NativeText === 'string') {
13
+ return RN.unstable_NativeText
14
+ }
15
+
16
+ // older RN has no public handle on it, but reading Text evaluates the very same module
17
+ if (!RN.Text) {
18
+ throw new Error('Unistyles 🦄: react-native did not expose Text, unable to render NativeText')
19
+ }
20
+
21
+ return 'RCTText'
22
+ }
23
+
7
24
  // credits to @hirbod
8
25
  const LeanText = forwardRef((props, ref) => {
9
- return createElement('RCTText', { ...props, ref })
26
+ return createElement(resolveRCTText(), { ...props, ref })
10
27
  }) as ComponentType<TextProps>
11
28
 
12
29
  LeanText.displayName = 'RCTText'
@@ -1 +1 @@
1
- export { Text } from './Text'
1
+ export { Text, Text as NativeText } from './Text'
@@ -1,15 +1,35 @@
1
1
  import type { ViewProps } from 'react-native'
2
2
 
3
3
  import { type ComponentType, createElement, forwardRef } from 'react'
4
+ import * as ReactNative from 'react-native'
4
5
 
5
6
  import { createUnistylesElement } from '../../core'
6
7
 
8
+ const RN = ReactNative as typeof ReactNative & { unstable_NativeView?: unknown }
9
+
10
+ const resolveRCTView = () => {
11
+ // RN 0.83+ re-exports the host component, which at runtime is the 'RCTView' string itself
12
+ if (typeof RN.unstable_NativeView === 'string') {
13
+ return RN.unstable_NativeView
14
+ }
15
+
16
+ // older RN has no public handle on it, but reading View evaluates the very same module
17
+ if (!RN.View) {
18
+ throw new Error('Unistyles 🦄: react-native did not expose View, unable to render NativeView')
19
+ }
20
+
21
+ return 'RCTView'
22
+ }
23
+
7
24
  // credits to @hirbod
8
25
  const LeanView = forwardRef((props, ref) => {
9
- return createElement('RCTView', { ...props, ref })
26
+ return createElement(resolveRCTView(), { ...props, ref })
10
27
  }) as ComponentType<ViewProps>
11
28
 
12
29
  LeanView.displayName = 'RCTView'
13
30
 
31
+ // named export matches unstable_NativeView from react-native
32
+ export const NativeView = createUnistylesElement(LeanView)
33
+
14
34
  // this will match default export from react-native
15
- export default createUnistylesElement(LeanView)
35
+ export default NativeView
@@ -1,3 +1,5 @@
1
1
  import { View } from './View'
2
2
 
3
+ export { View as NativeView }
4
+
3
5
  export default View
@@ -41,9 +41,14 @@ export const Pressable: React.ForwardRefExoticComponent<PressableProps & React.R
41
41
  isServer()
42
42
  ? undefined
43
43
  : (ref) => {
44
+ if (!ref) {
45
+ // @ts-expect-error hidden from TS
46
+ UnistylesShadowRegistry.remove(storedRef, classNames?.[0].hash)
47
+ }
48
+
44
49
  storedRef = ref as unknown as HTMLElement
45
50
  // @ts-expect-error hidden from TS
46
- UnistylesShadowRegistry.add(storedRef, classNames?.hash)
51
+ UnistylesShadowRegistry.add(storedRef, classNames?.[0].hash)
47
52
 
48
53
  if (typeof forwardedRef === 'function') {
49
54
  return forwardedRef(ref)
@@ -61,10 +66,10 @@ export const Pressable: React.ForwardRefExoticComponent<PressableProps & React.R
61
66
  UnistylesShadowRegistry.setScopedTheme(scopedTheme)
62
67
 
63
68
  // @ts-expect-error hidden from TS
64
- UnistylesShadowRegistry.remove(storedRef, classNames?.hash)
69
+ UnistylesShadowRegistry.remove(storedRef, classNames?.[0].hash)
65
70
  classNames = getClassName(styleResult as UnistylesValues)
66
71
  // @ts-expect-error hidden from TS
67
- UnistylesShadowRegistry.add(storedRef, classNames?.hash)
72
+ UnistylesShadowRegistry.add(storedRef, classNames?.[0].hash)
68
73
 
69
74
  UnistylesShadowRegistry.setScopedTheme(previousScopedTheme)
70
75
 
@@ -0,0 +1,5 @@
1
+ import { TouchableHighlight as NativeTouchableHighlight } from 'react-native'
2
+
3
+ import { createUnistylesTouchableHighlight } from '../../core'
4
+
5
+ export const TouchableHighlight = createUnistylesTouchableHighlight(NativeTouchableHighlight)
@@ -0,0 +1,47 @@
1
+ import type { ActivityIndicator } from 'react-native'
2
+
3
+ import React from 'react'
4
+
5
+ import { isAndroid } from '../common'
6
+ import { UnistylesShadowRegistry } from '../specs'
7
+ import { copyComponentProperties } from '../utils'
8
+ import { passForwardedRef } from './passForwardRef'
9
+ import { maybeWarnAboutMultipleUnistyles } from './warn'
10
+
11
+ const getWrapperHandle = (ref: any) => {
12
+ const spinner = ref?.__internalInstanceHandle
13
+
14
+ return {
15
+ __internalInstanceHandle: isAndroid ? spinner?.return?.return : spinner?.return,
16
+ }
17
+ }
18
+
19
+ export const createUnistylesActivityIndicator = (Component: typeof ActivityIndicator) => {
20
+ const UnistylesActivityIndicator = (props: any) => {
21
+ return (
22
+ <Component
23
+ {...props}
24
+ ref={(ref: unknown) => {
25
+ maybeWarnAboutMultipleUnistyles(props.style, 'ActivityIndicator')
26
+
27
+ const wrapperHandle = getWrapperHandle(ref)
28
+
29
+ return passForwardedRef(
30
+ ref,
31
+ props.ref,
32
+ () => {
33
+ // @ts-ignore this is hidden from TS
34
+ UnistylesShadowRegistry.add(wrapperHandle, props.style)
35
+ },
36
+ () => {
37
+ // @ts-ignore this is hidden from TS
38
+ UnistylesShadowRegistry.remove(wrapperHandle)
39
+ },
40
+ )
41
+ }}
42
+ />
43
+ )
44
+ }
45
+
46
+ return copyComponentProperties(Component, UnistylesActivityIndicator)
47
+ }
@@ -1,16 +1,21 @@
1
1
  import type { ViewStyle } from 'react-native'
2
2
 
3
- import React from 'react'
3
+ import React, { useCallback, useLayoutEffect, useRef } from 'react'
4
4
 
5
5
  import type { UnistylesValues } from '../types'
6
6
 
7
7
  import { copyComponentProperties } from '../utils'
8
+ import * as unistyles from '../web/services'
8
9
  import { isServer } from '../web/utils'
9
- import { createUnistylesRef } from '../web/utils/createUnistylesRef'
10
10
  import { getClassName } from './getClassname'
11
11
  import { maybeWarnAboutMultipleUnistyles } from './warn'
12
12
 
13
- const STYLE_PROPS = ['contentContainerStyle', 'columnWrapperStyle'] as const
13
+ const STYLE_PROPS = [
14
+ 'contentContainerStyle',
15
+ 'columnWrapperStyle',
16
+ 'ListHeaderComponentStyle',
17
+ 'ListFooterComponentStyle',
18
+ ] as const
14
19
 
15
20
  type StyleProp = (typeof STYLE_PROPS)[number] | 'style'
16
21
 
@@ -18,28 +23,59 @@ type ComponentProps = {
18
23
  [K in StyleProp]?: UnistylesValues
19
24
  }
20
25
 
21
- const buildUnistylesProps = (Component: any, props: ComponentProps, forwardedRef: React.ForwardedRef<unknown>) => {
26
+ const buildUnistylesProps = (Component: any, props: ComponentProps) => {
22
27
  const componentStyleProps = ['style' as const, ...STYLE_PROPS.filter((styleProp) => styleProp in props)]
23
- const classNames = Object.fromEntries(
24
- componentStyleProps.map((styleProp) => [styleProp, getClassName(props[styleProp])]),
25
- )
26
- const refs = componentStyleProps.map((styleProp) => {
27
- return createUnistylesRef(classNames[styleProp], styleProp === 'style' ? forwardedRef : undefined)
28
- })
29
28
 
30
29
  componentStyleProps.forEach((styleProp) => {
31
30
  maybeWarnAboutMultipleUnistyles(props[styleProp] as ViewStyle, Component.displayName)
32
31
  })
33
32
 
34
- return {
35
- ...classNames,
36
- ref: isServer() ? undefined : (componentRef: any) => refs.forEach((ref) => ref?.(componentRef)),
33
+ return Object.fromEntries(componentStyleProps.map((styleProp) => [styleProp, getClassName(props[styleProp])]))
34
+ }
35
+
36
+ const assignRef = (ref: React.Ref<unknown> | undefined, value: unknown) => {
37
+ if (typeof ref === 'function') {
38
+ ref(value)
39
+ } else if (ref) {
40
+ ref.current = value
37
41
  }
38
42
  }
39
43
 
40
44
  export const createUnistylesElement = (Component: any) => {
41
- const UnistylesComponent = (props: any) => {
42
- return <Component {...props} {...buildUnistylesProps(Component, props, props.ref)} />
45
+ const UnistylesComponent = ({ ref, ...props }: any) => {
46
+ const classNames = buildUnistylesProps(Component, props)
47
+ const hashes = Object.values(classNames).map((className) => className?.[0].hash)
48
+ const stored = useRef({ node: null as unknown, hashes, ref })
49
+ // Stable for the component lifetime, as some components (e.g. ScrollView) call only the latest ref on unmount
50
+ const unistylesRef = useCallback((node: unknown) => {
51
+ const { shadowRegistry } = unistyles.services
52
+
53
+ stored.current.hashes.forEach((hash) =>
54
+ node ? shadowRegistry.add(node, hash) : shadowRegistry.remove(stored.current.node, hash),
55
+ )
56
+ stored.current.node = node
57
+ assignRef(stored.current.ref, node)
58
+ }, [])
59
+
60
+ // React won't call a stable ref again, so apply style and ref changes after commit
61
+ useLayoutEffect(() => {
62
+ const { shadowRegistry } = unistyles.services
63
+ const { node, hashes: previousHashes, ref: previousRef } = stored.current
64
+
65
+ if (previousHashes.join() !== hashes.join()) {
66
+ previousHashes.forEach((hash) => shadowRegistry.remove(node, hash))
67
+ hashes.forEach((hash) => shadowRegistry.add(node, hash))
68
+ }
69
+
70
+ if (previousRef !== ref) {
71
+ assignRef(previousRef, null)
72
+ assignRef(ref, node)
73
+ }
74
+
75
+ stored.current = { node, hashes, ref }
76
+ })
77
+
78
+ return <Component {...props} {...classNames} ref={isServer() ? undefined : unistylesRef} />
43
79
  }
44
80
 
45
81
  return copyComponentProperties(Component, UnistylesComponent)
@@ -0,0 +1,69 @@
1
+ import type { TouchableHighlight } from 'react-native'
2
+
3
+ import React, { useCallback, useLayoutEffect, useRef, useState } from 'react'
4
+
5
+ import { UnistylesShadowRegistry } from '../specs'
6
+ import { copyComponentProperties } from '../utils'
7
+ import { passForwardedRef } from './passForwardRef'
8
+ import { maybeWarnAboutMultipleUnistyles } from './warn'
9
+
10
+ export const createUnistylesTouchableHighlight = (Component: typeof TouchableHighlight) => {
11
+ const UnistylesTouchableHighlight = (props: any) => {
12
+ const relinkRef = useRef<() => void>(undefined)
13
+ const [hiddenUnderlays, setHiddenUnderlays] = useState(0)
14
+
15
+ useLayoutEffect(() => {
16
+ if (hiddenUnderlays > 0) {
17
+ relinkRef.current?.()
18
+ }
19
+ }, [hiddenUnderlays])
20
+
21
+ // new props come with a parent render, re-renders for the underlay keep the ref and the scoped theme it captured
22
+ const ref = useCallback(
23
+ (ref: unknown) => {
24
+ maybeWarnAboutMultipleUnistyles(props.style, 'TouchableHighlight')
25
+
26
+ const scopedTheme = UnistylesShadowRegistry.getScopedTheme()
27
+
28
+ relinkRef.current = () => {
29
+ const previousScopedTheme = UnistylesShadowRegistry.getScopedTheme()
30
+
31
+ UnistylesShadowRegistry.setScopedTheme(scopedTheme)
32
+ // @ts-ignore this is hidden from TS
33
+ UnistylesShadowRegistry.remove(ref)
34
+ // @ts-ignore this is hidden from TS
35
+ UnistylesShadowRegistry.add(ref, props.style)
36
+ UnistylesShadowRegistry.setScopedTheme(previousScopedTheme)
37
+ }
38
+
39
+ return passForwardedRef(
40
+ ref,
41
+ props.ref,
42
+ () => {
43
+ // @ts-ignore this is hidden from TS
44
+ UnistylesShadowRegistry.add(ref, props.style)
45
+ },
46
+ () => {
47
+ relinkRef.current = undefined
48
+ // @ts-ignore this is hidden from TS
49
+ UnistylesShadowRegistry.remove(ref)
50
+ },
51
+ )
52
+ },
53
+ [props],
54
+ )
55
+
56
+ return (
57
+ <Component
58
+ {...props}
59
+ ref={ref}
60
+ onHideUnderlay={() => {
61
+ setHiddenUnderlays((count) => count + 1)
62
+ props.onHideUnderlay?.()
63
+ }}
64
+ />
65
+ )
66
+ }
67
+
68
+ return copyComponentProperties(Component, UnistylesTouchableHighlight)
69
+ }
package/src/core/index.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  export { createUnistylesElement } from './createUnistylesElement'
2
+ export { createUnistylesActivityIndicator } from './createUnistylesActivityIndicator'
2
3
  export { createUnistylesImageBackground } from './createUnistylesImageBackground'
4
+ export { createUnistylesTouchableHighlight } from './createUnistylesTouchableHighlight'
3
5
  export { withUnistyles } from './withUnistyles'
4
6
  export { passForwardedRef } from './passForwardRef'
5
7
  export { getClassName } from './getClassname'
@@ -6,7 +6,7 @@ export const passForwardedRef = <T>(
6
6
  onMount?: () => void,
7
7
  onUnmount?: () => void,
8
8
  ) => {
9
- const passForwardedRef = () => {
9
+ const passForwardedRef = (): unknown => {
10
10
  if (typeof forwardedRef === 'function') {
11
11
  return forwardedRef(ref)
12
12
  }
@@ -15,14 +15,26 @@ export const passForwardedRef = <T>(
15
15
  forwardedRef.current = ref
16
16
  }
17
17
 
18
- return () => {}
18
+ return undefined
19
19
  }
20
20
  const forwardedRefReturnFn = passForwardedRef()
21
21
 
22
22
  onMount?.()
23
23
 
24
24
  return () => {
25
- forwardedRefReturnFn?.()
26
- onUnmount?.()
25
+ try {
26
+ // React 19 allows a callback ref to return a cleanup function, but returning
27
+ // any other value is still valid, so it can't be called unconditionally.
28
+ // Without a cleanup function React detaches the ref with null, so do we.
29
+ if (typeof forwardedRefReturnFn === 'function') {
30
+ forwardedRefReturnFn()
31
+ } else if (typeof forwardedRef === 'function') {
32
+ forwardedRef(null)
33
+ } else if (forwardedRef) {
34
+ forwardedRef.current = null
35
+ }
36
+ } finally {
37
+ onUnmount?.()
38
+ }
27
39
  }
28
40
  }
@@ -0,0 +1,3 @@
1
+ // package exports resolve this file as is, so the platform split lives in ./verify
2
+ export { verify } from './verify'
3
+ export type { VerifyMismatch, VerifyReport } from './types'
@@ -0,0 +1,25 @@
1
+ export type VerifyMismatch = {
2
+ tag: number
3
+ component: string
4
+ styleKey: string
5
+ prop: string
6
+ expected: string
7
+ actual: string
8
+ }
9
+
10
+ export type VerifyReport = {
11
+ // all nodes linked with Unistyles
12
+ linked: number
13
+ // nodes compared with the committed shadow tree
14
+ checked: number
15
+ // linked nodes that are not part of the committed shadow tree
16
+ detached: number
17
+ // nodes hidden by Suspense or frozen screens
18
+ suspended: number
19
+ // nodes unmounted without unlink, waiting for the sweep
20
+ orphans: number
21
+ // shadow tree updates that were not committed yet
22
+ pendingUpdates: number
23
+ mismatches: Array<VerifyMismatch>
24
+ errors: Array<string>
25
+ }
@@ -0,0 +1,7 @@
1
+ import type { VerifyReport } from './types'
2
+
3
+ import { UnistylesShadowRegistry } from '../specs'
4
+
5
+ // Compares props committed to the shadow tree with styles rebuilt for every linked node
6
+ // (its own arguments, variants and the current theme). Meant for tests, it never commits anything.
7
+ export const verify = (): VerifyReport => UnistylesShadowRegistry.verify()
@@ -0,0 +1,13 @@
1
+ import type { VerifyReport } from './types'
2
+
3
+ // There is no shadow tree on web
4
+ export const verify = (): VerifyReport => ({
5
+ linked: 0,
6
+ checked: 0,
7
+ detached: 0,
8
+ suspended: 0,
9
+ orphans: 0,
10
+ pendingUpdates: 0,
11
+ mismatches: [],
12
+ errors: [],
13
+ })
package/src/index.ts CHANGED
@@ -8,9 +8,12 @@ if (majorReactVersions === undefined || majorReactVersions < 19) {
8
8
 
9
9
  export { StyleSheet, UnistylesRuntime } from './specs'
10
10
  export { UnistyleDependency } from './specs'
11
+ export type { UnistylesMiniRuntime } from './specs'
12
+ export { ColorScheme, Orientation, StatusBarStyle } from './specs/types'
11
13
  export { mq } from './mq'
12
14
  export type { UnistylesThemes, UnistylesBreakpoints } from './global'
13
15
  export { withUnistyles, useUnistyles, createUnistylesElement } from './core'
14
- export type { UnistylesVariants, UnistylesValues, IOSContentSizeCategory, AndroidContentSizeCategory } from './types'
16
+ export type { UnistylesVariants, UnistylesValues } from './types'
17
+ export { IOSContentSizeCategory, AndroidContentSizeCategory, WebContentSizeCategory } from './types'
15
18
  export { Display, Hide, ScopedTheme } from './components'
16
19
  export { useServerUnistyles, hydrateServerUnistyles, getServerUnistyles, resetServerUnistyles } from './server'
package/src/mocks.ts CHANGED
@@ -11,6 +11,10 @@ type Registry = {
11
11
  breakpoints: UnistylesBreakpoints
12
12
  }
13
13
 
14
+ type GlobalWithRegistry = typeof globalThis & {
15
+ __UNISTYLES_MOCK_REGISTRY__?: Registry
16
+ }
17
+
14
18
  jest.mock('react-native-nitro-modules', () => ({
15
19
  NitroModules: {
16
20
  createHybridObject: () => ({
@@ -26,10 +30,10 @@ jest.mock('react-native-nitro-modules', () => ({
26
30
 
27
31
  jest.mock('react-native-unistyles', () => {
28
32
  const React = require('react')
29
- const _REGISTRY: Registry = {
33
+ const _REGISTRY = ((globalThis as GlobalWithRegistry).__UNISTYLES_MOCK_REGISTRY__ ??= {
30
34
  themes: {},
31
35
  breakpoints: {},
32
- }
36
+ })
33
37
  const miniRuntime = {
34
38
  themeName: undefined,
35
39
  breakpoint: undefined,
@@ -146,6 +150,47 @@ jest.mock('react-native-unistyles', () => {
146
150
  }
147
151
 
148
152
  return {
153
+ ColorScheme: {
154
+ Light: 'light',
155
+ Dark: 'dark',
156
+ Unspecified: 'unspecified',
157
+ },
158
+ Orientation: {
159
+ Portrait: 'portrait',
160
+ Landscape: 'landscape',
161
+ },
162
+ StatusBarStyle: {
163
+ Default: 'default',
164
+ Light: 'light',
165
+ Dark: 'dark',
166
+ },
167
+ IOSContentSizeCategory: {
168
+ AccessibilityExtraExtraExtraLarge: 'accessibilityExtraExtraExtraLarge',
169
+ AccessibilityExtraExtraLarge: 'accessibilityExtraExtraLarge',
170
+ AccessibilityExtraLarge: 'accessibilityExtraLarge',
171
+ AccessibilityLarge: 'accessibilityLarge',
172
+ AccessibilityMedium: 'accessibilityMedium',
173
+ ExtraExtraExtraLarge: 'xxxLarge',
174
+ ExtraExtraLarge: 'xxLarge',
175
+ ExtraLarge: 'xLarge',
176
+ Large: 'Large',
177
+ Medium: 'Medium',
178
+ Small: 'Small',
179
+ ExtraSmall: 'xSmall',
180
+ Unspecified: 'unspecified',
181
+ },
182
+ AndroidContentSizeCategory: {
183
+ Small: 'Small',
184
+ Default: 'Default',
185
+ Large: 'Large',
186
+ ExtraLarge: 'ExtraLarge',
187
+ Huge: 'Huge',
188
+ ExtraHuge: 'ExtraHuge',
189
+ ExtraExtraHuge: 'ExtraExtraHuge',
190
+ },
191
+ WebContentSizeCategory: {
192
+ Unspecified: 'web-unspecified',
193
+ },
149
194
  Hide: () => null,
150
195
  Display: () => null,
151
196
  ScopedTheme: () => null,
@@ -1,4 +1,4 @@
1
- import { useEffect, useLayoutEffect } from 'react'
1
+ import { useEffect, useLayoutEffect, useRef } from 'react'
2
2
  import { useSharedValue } from 'react-native-reanimated'
3
3
 
4
4
  import type { UseUpdateVariantColorConfig } from './types'
@@ -12,6 +12,7 @@ export const useUpdateVariantColor = <T extends Record<string, any>>({
12
12
  }: UseUpdateVariantColorConfig<T>) => {
13
13
  const fromValue = useSharedValue<string>(style[colorKey])
14
14
  const toValue = useSharedValue<string>(style[colorKey])
15
+ const applied = useRef<{ style: T; colorKey: typeof colorKey }>(undefined)
15
16
 
16
17
  useEffect(() => {
17
18
  const dispose = StyleSheet.addChangeListener((changedDependencies) => {
@@ -31,6 +32,11 @@ export const useUpdateVariantColor = <T extends Record<string, any>>({
31
32
  }, [style, colorKey])
32
33
 
33
34
  useLayoutEffect(() => {
35
+ if (applied.current?.style === style && applied.current.colorKey === colorKey) {
36
+ return
37
+ }
38
+
39
+ applied.current = { style, colorKey }
34
40
  fromValue.set(toValue.value)
35
41
  toValue.set(style[colorKey])
36
42
  }, [style, colorKey])