react-native-unistyles 3.3.0 → 3.5.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.
Files changed (216) hide show
  1. package/README.md +26 -10
  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 +1 -0
  7. package/cxx/converters/TransformOriginConverter.cpp +106 -0
  8. package/cxx/core/StyleSheetRegistry.cpp +9 -9
  9. package/cxx/core/StyleSheetRegistry.h +3 -3
  10. package/cxx/core/UnistyleWrapper.h +13 -1
  11. package/cxx/core/UnistylesRegistry.cpp +226 -54
  12. package/cxx/core/UnistylesRegistry.h +64 -7
  13. package/cxx/hybridObjects/HybridShadowRegistry.cpp +128 -11
  14. package/cxx/hybridObjects/HybridShadowRegistry.h +20 -0
  15. package/cxx/hybridObjects/HybridStyleSheet.cpp +20 -12
  16. package/cxx/hybridObjects/HybridUnistylesRuntime.cpp +11 -1
  17. package/cxx/hybridObjects/HybridUnistylesRuntime.h +4 -2
  18. package/cxx/parser/Parser.cpp +30 -3
  19. package/cxx/parser/Parser.h +1 -0
  20. package/cxx/shadowTree/NativeProps.h +88 -0
  21. package/cxx/shadowTree/ShadowLeafUpdate.h +8 -0
  22. package/cxx/shadowTree/ShadowTrafficController.h +18 -35
  23. package/cxx/shadowTree/ShadowTreeDiagnostics.cpp +339 -0
  24. package/cxx/shadowTree/ShadowTreeDiagnostics.h +18 -0
  25. package/cxx/shadowTree/ShadowTreeManager.cpp +14 -148
  26. package/cxx/shadowTree/ShadowTreeManager.h +2 -6
  27. package/diagnostics/package.json +6 -0
  28. package/ios/UnistylesModuleOnLoad.mm +9 -3
  29. package/lib/commonjs/components/native/ActivityIndicator.native.js +10 -0
  30. package/lib/commonjs/components/native/ActivityIndicator.native.js.map +1 -0
  31. package/lib/commonjs/components/native/Animated.js +2 -0
  32. package/lib/commonjs/components/native/Animated.js.map +1 -1
  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 +9 -3
  42. package/lib/commonjs/components/native/Pressable.js.map +1 -1
  43. package/lib/commonjs/components/native/Pressable.native.js +2 -0
  44. package/lib/commonjs/components/native/Pressable.native.js.map +1 -1
  45. package/lib/commonjs/components/native/TouchableHighlight.native.js +10 -0
  46. package/lib/commonjs/components/native/TouchableHighlight.native.js.map +1 -0
  47. package/lib/commonjs/core/createUnistylesActivityIndicator.js +41 -0
  48. package/lib/commonjs/core/createUnistylesActivityIndicator.js.map +1 -0
  49. package/lib/commonjs/core/createUnistylesElement.js +59 -14
  50. package/lib/commonjs/core/createUnistylesElement.js.map +1 -1
  51. package/lib/commonjs/core/createUnistylesTouchableHighlight.js +58 -0
  52. package/lib/commonjs/core/createUnistylesTouchableHighlight.js.map +1 -0
  53. package/lib/commonjs/core/index.js +14 -0
  54. package/lib/commonjs/core/index.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 +41 -0
  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/convert/object/boxShadow.js +2 -2
  75. package/lib/commonjs/web/convert/object/boxShadow.js.map +1 -1
  76. package/lib/commonjs/web/index.js.map +1 -1
  77. package/lib/commonjs/web/registry.js +3 -0
  78. package/lib/commonjs/web/registry.js.map +1 -1
  79. package/lib/module/components/native/ActivityIndicator.native.js +6 -0
  80. package/lib/module/components/native/ActivityIndicator.native.js.map +1 -0
  81. package/lib/module/components/native/Animated.js +3 -0
  82. package/lib/module/components/native/Animated.js.map +1 -1
  83. package/lib/module/components/native/NativeText.js +1 -1
  84. package/lib/module/components/native/NativeText.js.map +1 -1
  85. package/lib/module/components/native/NativeText.native.js +15 -1
  86. package/lib/module/components/native/NativeText.native.js.map +1 -1
  87. package/lib/module/components/native/NativeView.js +1 -0
  88. package/lib/module/components/native/NativeView.js.map +1 -1
  89. package/lib/module/components/native/NativeView.native.js +19 -2
  90. package/lib/module/components/native/NativeView.native.js.map +1 -1
  91. package/lib/module/components/native/Pressable.js +9 -3
  92. package/lib/module/components/native/Pressable.js.map +1 -1
  93. package/lib/module/components/native/Pressable.native.js +2 -0
  94. package/lib/module/components/native/Pressable.native.js.map +1 -1
  95. package/lib/module/components/native/TouchableHighlight.native.js +6 -0
  96. package/lib/module/components/native/TouchableHighlight.native.js.map +1 -0
  97. package/lib/module/core/createUnistylesActivityIndicator.js +35 -0
  98. package/lib/module/core/createUnistylesActivityIndicator.js.map +1 -0
  99. package/lib/module/core/createUnistylesElement.js +58 -13
  100. package/lib/module/core/createUnistylesElement.js.map +1 -1
  101. package/lib/module/core/createUnistylesTouchableHighlight.js +52 -0
  102. package/lib/module/core/createUnistylesTouchableHighlight.js.map +1 -0
  103. package/lib/module/core/index.js +2 -0
  104. package/lib/module/core/index.js.map +1 -1
  105. package/lib/module/diagnostics/index.js +5 -0
  106. package/lib/module/diagnostics/index.js.map +1 -0
  107. package/lib/module/diagnostics/types.js +2 -0
  108. package/lib/module/diagnostics/types.js.map +1 -0
  109. package/lib/module/diagnostics/verify.js +14 -0
  110. package/lib/module/diagnostics/verify.js.map +1 -0
  111. package/lib/module/diagnostics/verify.native.js +8 -0
  112. package/lib/module/diagnostics/verify.native.js.map +1 -0
  113. package/lib/module/index.js +2 -0
  114. package/lib/module/index.js.map +1 -1
  115. package/lib/module/mocks.js +41 -0
  116. package/lib/module/mocks.js.map +1 -1
  117. package/lib/module/reanimated/variant/useUpdateVariantColor.native.js +9 -1
  118. package/lib/module/reanimated/variant/useUpdateVariantColor.native.js.map +1 -1
  119. package/lib/module/specs/ShadowRegistry/index.js +97 -1
  120. package/lib/module/specs/ShadowRegistry/index.js.map +1 -1
  121. package/lib/module/specs/StyleSheet/index.js +6 -1
  122. package/lib/module/specs/StyleSheet/index.js.map +1 -1
  123. package/lib/module/specs/UnistylesRuntime/index.js.map +1 -1
  124. package/lib/module/web/convert/object/boxShadow.js +2 -2
  125. package/lib/module/web/convert/object/boxShadow.js.map +1 -1
  126. package/lib/module/web/index.js.map +1 -1
  127. package/lib/module/web/registry.js +3 -0
  128. package/lib/module/web/registry.js.map +1 -1
  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/Animated.d.ts +16 -36
  132. package/lib/typescript/src/components/native/Animated.d.ts.map +1 -1
  133. package/lib/typescript/src/components/native/NativeText.d.ts +1 -1
  134. package/lib/typescript/src/components/native/NativeText.d.ts.map +1 -1
  135. package/lib/typescript/src/components/native/NativeText.native.d.ts.map +1 -1
  136. package/lib/typescript/src/components/native/NativeView.d.ts +1 -0
  137. package/lib/typescript/src/components/native/NativeView.d.ts.map +1 -1
  138. package/lib/typescript/src/components/native/NativeView.native.d.ts +2 -2
  139. package/lib/typescript/src/components/native/NativeView.native.d.ts.map +1 -1
  140. package/lib/typescript/src/components/native/Pressable.d.ts +4 -2
  141. package/lib/typescript/src/components/native/Pressable.d.ts.map +1 -1
  142. package/lib/typescript/src/components/native/Pressable.native.d.ts +5 -2
  143. package/lib/typescript/src/components/native/Pressable.native.d.ts.map +1 -1
  144. package/lib/typescript/src/components/native/TouchableHighlight.native.d.ts +2 -0
  145. package/lib/typescript/src/components/native/TouchableHighlight.native.d.ts.map +1 -0
  146. package/lib/typescript/src/core/createUnistylesActivityIndicator.d.ts +3 -0
  147. package/lib/typescript/src/core/createUnistylesActivityIndicator.d.ts.map +1 -0
  148. package/lib/typescript/src/core/createUnistylesElement.d.ts.map +1 -1
  149. package/lib/typescript/src/core/createUnistylesTouchableHighlight.d.ts +3 -0
  150. package/lib/typescript/src/core/createUnistylesTouchableHighlight.d.ts.map +1 -0
  151. package/lib/typescript/src/core/index.d.ts +2 -0
  152. package/lib/typescript/src/core/index.d.ts.map +1 -1
  153. package/lib/typescript/src/core/useUnistyles.d.ts +1 -1
  154. package/lib/typescript/src/diagnostics/index.d.ts +3 -0
  155. package/lib/typescript/src/diagnostics/index.d.ts.map +1 -0
  156. package/lib/typescript/src/diagnostics/types.d.ts +19 -0
  157. package/lib/typescript/src/diagnostics/types.d.ts.map +1 -0
  158. package/lib/typescript/src/diagnostics/verify.d.ts +3 -0
  159. package/lib/typescript/src/diagnostics/verify.d.ts.map +1 -0
  160. package/lib/typescript/src/diagnostics/verify.native.d.ts +3 -0
  161. package/lib/typescript/src/diagnostics/verify.native.d.ts.map +1 -0
  162. package/lib/typescript/src/index.d.ts +4 -1
  163. package/lib/typescript/src/index.d.ts.map +1 -1
  164. package/lib/typescript/src/reanimated/variant/useUpdateVariantColor.native.d.ts.map +1 -1
  165. package/lib/typescript/src/specs/ShadowRegistry/index.d.ts +5 -1
  166. package/lib/typescript/src/specs/ShadowRegistry/index.d.ts.map +1 -1
  167. package/lib/typescript/src/specs/ShadowRegistry/types.d.ts +3 -0
  168. package/lib/typescript/src/specs/ShadowRegistry/types.d.ts.map +1 -1
  169. package/lib/typescript/src/specs/StyleSheet/index.d.ts +4 -1
  170. package/lib/typescript/src/specs/StyleSheet/index.d.ts.map +1 -1
  171. package/lib/typescript/src/specs/UnistylesRuntime/index.d.ts +2 -2
  172. package/lib/typescript/src/specs/UnistylesRuntime/index.d.ts.map +1 -1
  173. package/lib/typescript/src/types/breakpoints.d.ts +1 -1
  174. package/lib/typescript/src/types/breakpoints.d.ts.map +1 -1
  175. package/lib/typescript/src/types/core.d.ts +7 -2
  176. package/lib/typescript/src/types/core.d.ts.map +1 -1
  177. package/lib/typescript/src/types/stylesheet.d.ts +3 -3
  178. package/lib/typescript/src/types/stylesheet.d.ts.map +1 -1
  179. package/lib/typescript/src/web/index.d.ts.map +1 -1
  180. package/lib/typescript/src/web/registry.d.ts.map +1 -1
  181. package/nitrogen/generated/android/kotlin/com/margelo/nitro/unistyles/Func_void_UnistylesNativeMiniRuntime.kt +0 -2
  182. package/nitrogen/generated/android/kotlin/com/margelo/nitro/unistyles/Func_void_std__vector_UnistyleDependency__UnistylesNativeMiniRuntime.kt +0 -2
  183. package/nitrogen/generated/android/kotlin/com/margelo/nitro/unistyles/HybridNativePlatformSpec.kt +2 -0
  184. package/package.json +35 -26
  185. package/plugin/index.d.ts +4 -4
  186. package/plugin/index.js +21 -5
  187. package/src/components/native/ActivityIndicator.native.tsx +5 -0
  188. package/src/components/native/Animated.tsx +18 -1
  189. package/src/components/native/NativeText.native.tsx +18 -1
  190. package/src/components/native/NativeText.tsx +1 -1
  191. package/src/components/native/NativeView.native.tsx +22 -2
  192. package/src/components/native/NativeView.tsx +2 -0
  193. package/src/components/native/Pressable.native.tsx +10 -2
  194. package/src/components/native/Pressable.tsx +15 -4
  195. package/src/components/native/TouchableHighlight.native.tsx +5 -0
  196. package/src/core/createUnistylesActivityIndicator.tsx +47 -0
  197. package/src/core/createUnistylesElement.tsx +45 -14
  198. package/src/core/createUnistylesTouchableHighlight.tsx +69 -0
  199. package/src/core/index.ts +2 -0
  200. package/src/diagnostics/index.ts +3 -0
  201. package/src/diagnostics/types.ts +25 -0
  202. package/src/diagnostics/verify.native.ts +7 -0
  203. package/src/diagnostics/verify.ts +13 -0
  204. package/src/index.ts +4 -1
  205. package/src/mocks.ts +41 -0
  206. package/src/reanimated/variant/useUpdateVariantColor.native.ts +7 -1
  207. package/src/specs/ShadowRegistry/index.ts +128 -1
  208. package/src/specs/ShadowRegistry/types.ts +3 -0
  209. package/src/specs/StyleSheet/index.ts +10 -2
  210. package/src/specs/UnistylesRuntime/index.ts +7 -2
  211. package/src/types/breakpoints.ts +5 -3
  212. package/src/types/core.ts +7 -28
  213. package/src/types/stylesheet.ts +3 -3
  214. package/src/web/convert/object/boxShadow.ts +2 -2
  215. package/src/web/index.ts +2 -1
  216. package/src/web/registry.ts +3 -0
@@ -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,12 +1,12 @@
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
 
@@ -18,28 +18,59 @@ type ComponentProps = {
18
18
  [K in StyleProp]?: UnistylesValues
19
19
  }
20
20
 
21
- const buildUnistylesProps = (Component: any, props: ComponentProps, forwardedRef: React.ForwardedRef<unknown>) => {
21
+ const buildUnistylesProps = (Component: any, props: ComponentProps) => {
22
22
  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
23
 
30
24
  componentStyleProps.forEach((styleProp) => {
31
25
  maybeWarnAboutMultipleUnistyles(props[styleProp] as ViewStyle, Component.displayName)
32
26
  })
33
27
 
34
- return {
35
- ...classNames,
36
- ref: isServer() ? undefined : (componentRef: any) => refs.forEach((ref) => ref?.(componentRef)),
28
+ return Object.fromEntries(componentStyleProps.map((styleProp) => [styleProp, getClassName(props[styleProp])]))
29
+ }
30
+
31
+ const assignRef = (ref: React.Ref<unknown> | undefined, value: unknown) => {
32
+ if (typeof ref === 'function') {
33
+ ref(value)
34
+ } else if (ref) {
35
+ ref.current = value
37
36
  }
38
37
  }
39
38
 
40
39
  export const createUnistylesElement = (Component: any) => {
41
- const UnistylesComponent = (props: any) => {
42
- return <Component {...props} {...buildUnistylesProps(Component, props, props.ref)} />
40
+ const UnistylesComponent = ({ ref, ...props }: any) => {
41
+ const classNames = buildUnistylesProps(Component, props)
42
+ const hashes = Object.values(classNames).map((className) => className?.[0].hash)
43
+ const stored = useRef({ node: null as unknown, hashes, ref })
44
+ // Stable for the component lifetime, as some components (e.g. ScrollView) call only the latest ref on unmount
45
+ const unistylesRef = useCallback((node: unknown) => {
46
+ const { shadowRegistry } = unistyles.services
47
+
48
+ stored.current.hashes.forEach((hash) =>
49
+ node ? shadowRegistry.add(node, hash) : shadowRegistry.remove(stored.current.node, hash),
50
+ )
51
+ stored.current.node = node
52
+ assignRef(stored.current.ref, node)
53
+ }, [])
54
+
55
+ // React won't call a stable ref again, so apply style and ref changes after commit
56
+ useLayoutEffect(() => {
57
+ const { shadowRegistry } = unistyles.services
58
+ const { node, hashes: previousHashes, ref: previousRef } = stored.current
59
+
60
+ if (previousHashes.join() !== hashes.join()) {
61
+ previousHashes.forEach((hash) => shadowRegistry.remove(node, hash))
62
+ hashes.forEach((hash) => shadowRegistry.add(node, hash))
63
+ }
64
+
65
+ if (previousRef !== ref) {
66
+ assignRef(previousRef, null)
67
+ assignRef(ref, node)
68
+ }
69
+
70
+ stored.current = { node, hashes, ref }
71
+ })
72
+
73
+ return <Component {...props} {...classNames} ref={isServer() ? undefined : unistylesRef} />
43
74
  }
44
75
 
45
76
  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'
@@ -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
@@ -146,6 +146,47 @@ jest.mock('react-native-unistyles', () => {
146
146
  }
147
147
 
148
148
  return {
149
+ ColorScheme: {
150
+ Light: 'light',
151
+ Dark: 'dark',
152
+ Unspecified: 'unspecified',
153
+ },
154
+ Orientation: {
155
+ Portrait: 'portrait',
156
+ Landscape: 'landscape',
157
+ },
158
+ StatusBarStyle: {
159
+ Default: 'default',
160
+ Light: 'light',
161
+ Dark: 'dark',
162
+ },
163
+ IOSContentSizeCategory: {
164
+ AccessibilityExtraExtraExtraLarge: 'accessibilityExtraExtraExtraLarge',
165
+ AccessibilityExtraExtraLarge: 'accessibilityExtraExtraLarge',
166
+ AccessibilityExtraLarge: 'accessibilityExtraLarge',
167
+ AccessibilityLarge: 'accessibilityLarge',
168
+ AccessibilityMedium: 'accessibilityMedium',
169
+ ExtraExtraExtraLarge: 'xxxLarge',
170
+ ExtraExtraLarge: 'xxLarge',
171
+ ExtraLarge: 'xLarge',
172
+ Large: 'Large',
173
+ Medium: 'Medium',
174
+ Small: 'Small',
175
+ ExtraSmall: 'xSmall',
176
+ Unspecified: 'unspecified',
177
+ },
178
+ AndroidContentSizeCategory: {
179
+ Small: 'Small',
180
+ Default: 'Default',
181
+ Large: 'Large',
182
+ ExtraLarge: 'ExtraLarge',
183
+ Huge: 'Huge',
184
+ ExtraHuge: 'ExtraHuge',
185
+ ExtraExtraHuge: 'ExtraExtraHuge',
186
+ },
187
+ WebContentSizeCategory: {
188
+ Unspecified: 'web-unspecified',
189
+ },
149
190
  Hide: () => null,
150
191
  Display: () => null,
151
192
  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])
@@ -1,8 +1,11 @@
1
1
  import { NitroModules } from 'react-native-nitro-modules'
2
2
 
3
+ import type { VerifyReport } from '../../diagnostics/types'
3
4
  import type { UnistylesShadowRegistry as UnistylesShadowRegistrySpec } from './ShadowRegistry.nitro'
4
5
  import type { ShadowNode, Unistyle, ViewHandle } from './types'
5
6
 
7
+ import { StyleSheet } from '../StyleSheet'
8
+
6
9
  interface ShadowRegistry extends UnistylesShadowRegistrySpec {
7
10
  // Babel API
8
11
  add(handle?: ViewHandle, styles?: Array<Unistyle>): void
@@ -14,10 +17,14 @@ interface ShadowRegistry extends UnistylesShadowRegistrySpec {
14
17
  flush(): void
15
18
  setScopedTheme(themeName?: string): void
16
19
  getScopedTheme(): string | undefined
20
+ verify(): VerifyReport
21
+ takeCommittedTags(): Array<number>
22
+ refreshReactNodes(nodes: Array<ShadowNode>): void
17
23
  }
18
24
 
19
25
  const HybridShadowRegistry = NitroModules.createHybridObject<ShadowRegistry>('UnistylesShadowRegistry')
20
26
 
27
+ const HOST_COMPONENT_TAG = 5
21
28
  const SUSPENSE_TAG = 13
22
29
  const OFFSCREEN_TAG = 22
23
30
 
@@ -65,6 +72,123 @@ const findShadowNodeForHandle = (handle: ViewHandle) => {
65
72
  return node
66
73
  }
67
74
 
75
+ const getNativeTag = (handle: ViewHandle): number | undefined =>
76
+ findFiberForHandle(handle)?.stateNode?.canonical?.nativeTag
77
+
78
+ // by native tag, weak: views unmounted while frozen never unlink, their dead refs are dropped when the map doubles
79
+ const linkedHandles = new Map<number, WeakRef<ViewHandle>>()
80
+ const MIN_SWEEP_SIZE = 256
81
+ let sweepSize = MIN_SWEEP_SIZE
82
+
83
+ const sweepUnmountedHandles = () => {
84
+ linkedHandles.forEach((ref, tag) => {
85
+ if (!ref.deref()) {
86
+ linkedHandles.delete(tag)
87
+ }
88
+ })
89
+
90
+ sweepSize = Math.max(MIN_SWEEP_SIZE, linkedHandles.size * 2)
91
+ }
92
+
93
+ const trackHandle = (handle: ViewHandle) => {
94
+ const tag = getNativeTag(handle)
95
+
96
+ // views re-link on every render
97
+ if (tag === undefined || linkedHandles.get(tag)?.deref() === handle) {
98
+ return
99
+ }
100
+
101
+ linkedHandles.set(tag, new WeakRef(handle))
102
+
103
+ if (linkedHandles.size >= sweepSize) {
104
+ sweepUnmountedHandles()
105
+ }
106
+ }
107
+
108
+ const untrackHandle = (handle: ViewHandle) => {
109
+ const tag = getNativeTag(handle)
110
+
111
+ if (tag !== undefined && linkedHandles.get(tag)?.deref() === handle) {
112
+ linkedHandles.delete(tag)
113
+ }
114
+ }
115
+
116
+ // React re-attaches an untouched subtree through its root's node and only follows clones made on the JS thread.
117
+ // Once a node was cloned elsewhere (state updates, Reanimated), React would commit a subtree from before a Unistyles
118
+ // update, so after a Unistyles commit the committed nodes and their host ancestors must point at the committed tree
119
+ const refreshReactNodes = () => {
120
+ const tags = HybridShadowRegistry.takeCommittedTags()
121
+
122
+ if (tags.length === 0) {
123
+ return
124
+ }
125
+
126
+ const nodes = new Set<ShadowNode>()
127
+ const visited = new Set<any>()
128
+
129
+ for (const tag of tags) {
130
+ const handle = linkedHandles.get(tag)?.deref()
131
+
132
+ if (!handle) {
133
+ continue
134
+ }
135
+
136
+ const fiber = findFiberForHandle(handle)
137
+
138
+ // hidden by Suspense or a frozen screen
139
+ if (!fiber || isInsideSuspendedBoundary(fiber)) {
140
+ continue
141
+ }
142
+
143
+ for (let current: any = fiber; current && !visited.has(current); current = current.return) {
144
+ visited.add(current)
145
+
146
+ if (current.alternate) {
147
+ visited.add(current.alternate)
148
+ }
149
+
150
+ if (current.tag !== HOST_COMPONENT_TAG) {
151
+ continue
152
+ }
153
+
154
+ for (const target of [current, current.alternate]) {
155
+ const node = target?.stateNode?.node
156
+
157
+ if (node) {
158
+ nodes.add(node)
159
+ }
160
+ }
161
+ }
162
+ }
163
+
164
+ if (nodes.size > 0) {
165
+ HybridShadowRegistry.refreshReactNodes(Array.from(nodes))
166
+ }
167
+ }
168
+
169
+ StyleSheet.addChangeListener(refreshReactNodes)
170
+
171
+ // runs for every style on every link, so it allocates only for the rare merged style (eg. flattened by Animated)
172
+ const splitMergedUnistyles = (style: any) => {
173
+ let hasUnistyle = false
174
+
175
+ for (const key in style) {
176
+ if (!key.startsWith('unistyles_') || !Object.prototype.hasOwnProperty.call(style, key)) {
177
+ continue
178
+ }
179
+
180
+ if (hasUnistyle) {
181
+ return Object.keys(style)
182
+ .filter((unistyleKey) => unistyleKey.startsWith('unistyles_'))
183
+ .map((unistyleKey) => ({ [unistyleKey]: style[unistyleKey] }))
184
+ }
185
+
186
+ hasUnistyle = true
187
+ }
188
+
189
+ return style
190
+ }
191
+
68
192
  HybridShadowRegistry.add = (handle, styles) => {
69
193
  // virtualized nodes can be null
70
194
  if (!handle || !styles) {
@@ -78,6 +202,7 @@ HybridShadowRegistry.add = (handle, styles) => {
78
202
  .filter((style) => style && Object.keys(style).length > 0)
79
203
  .flat()
80
204
  .filter(Boolean)
205
+ .flatMap(splitMergedUnistyles)
81
206
 
82
207
  if (filteredStyles.length > 0) {
83
208
  const node = findShadowNodeForHandle(handle)
@@ -89,6 +214,7 @@ HybridShadowRegistry.add = (handle, styles) => {
89
214
  }
90
215
 
91
216
  HybridShadowRegistry.link(node, filteredStyles)
217
+ trackHandle(handle)
92
218
  }
93
219
  }
94
220
 
@@ -106,10 +232,11 @@ HybridShadowRegistry.remove = (handle) => {
106
232
  HybridShadowRegistry.suspend(maybeNode)
107
233
  } else {
108
234
  HybridShadowRegistry.unlink(maybeNode)
235
+ untrackHandle(handle)
109
236
  }
110
237
  }
111
238
  }
112
239
 
113
- type PrivateMethods = 'add' | 'remove' | 'link' | 'unlink' | 'suspend'
240
+ type PrivateMethods = 'add' | 'remove' | 'link' | 'unlink' | 'suspend' | 'takeCommittedTags' | 'refreshReactNodes'
114
241
 
115
242
  export const UnistylesShadowRegistry = HybridShadowRegistry as Omit<ShadowRegistry, PrivateMethods>
@@ -1,5 +1,8 @@
1
1
  export type StateNode = {
2
2
  node?: ShadowNode
3
+ canonical?: {
4
+ nativeTag?: number
5
+ }
3
6
  }
4
7
 
5
8
  export type ShadowNode = {
@@ -34,8 +34,15 @@ export type UnistylesConfig = {
34
34
  breakpoints?: UnistylesBreakpoints
35
35
  }
36
36
 
37
+ // React Native 0.85 dropped `absoluteFillObject`, where `absoluteFill` is already a plain
38
+ // style object. Older versions still expose both, so the descriptor is read from React Native
39
+ // instead of being duplicated here.
40
+ type AbsoluteFillObject = typeof NativeStyleSheetType extends { absoluteFillObject: infer TStyle }
41
+ ? TStyle
42
+ : typeof NativeStyleSheetType.absoluteFill
43
+
37
44
  export interface UnistylesStyleSheet extends UnistylesStyleSheetSpec {
38
- absoluteFillObject: typeof NativeStyleSheetType.absoluteFill
45
+ absoluteFillObject: AbsoluteFillObject
39
46
  absoluteFill: typeof NativeStyleSheetType.absoluteFill
40
47
  compose: typeof NativeStyleSheetType.compose
41
48
  flatten: typeof NativeStyleSheetType.flatten
@@ -52,7 +59,8 @@ export interface UnistylesStyleSheet extends UnistylesStyleSheetSpec {
52
59
 
53
60
  const HybridUnistylesStyleSheet = NitroModules.createHybridObject<UnistylesStyleSheet>('UnistylesStyleSheet')
54
61
 
55
- HybridUnistylesStyleSheet.absoluteFillObject = NativeStyleSheet.absoluteFill
62
+ HybridUnistylesStyleSheet.absoluteFillObject = ((NativeStyleSheet as Record<string, unknown>).absoluteFillObject ??
63
+ NativeStyleSheet.absoluteFill) as AbsoluteFillObject
56
64
  HybridUnistylesStyleSheet.absoluteFill = NativeStyleSheet.absoluteFill
57
65
  HybridUnistylesStyleSheet.flatten = NativeStyleSheet.flatten
58
66
  HybridUnistylesStyleSheet.compose = NativeStyleSheet.compose
@@ -2,7 +2,12 @@ import { processColor } from 'react-native'
2
2
  import { NitroModules } from 'react-native-nitro-modules'
3
3
 
4
4
  import type { UnistylesThemes } from '../../global'
5
- import type { AndroidContentSizeCategory, IOSContentSizeCategory, UnistylesTheme } from '../../types'
5
+ import type {
6
+ AndroidContentSizeCategory,
7
+ IOSContentSizeCategory,
8
+ UnistylesTheme,
9
+ WebContentSizeCategory,
10
+ } from '../../types'
6
11
  import type { UnistylesNavigationBar } from '../NavigtionBar'
7
12
  import type { AppBreakpoint, AppTheme, AppThemeName, Color, ColorScheme, Orientation } from '../types'
8
13
  import type { UnistylesMiniRuntime, UnistylesRuntime as UnistylesRuntimeSpec } from './UnistylesRuntime.nitro'
@@ -13,7 +18,7 @@ import { type UnistylesStatusBar, attachStatusBarJSMethods } from '../StatusBar'
13
18
  export interface UnistylesRuntimePrivate extends Omit<UnistylesRuntimeSpec, 'setRootViewBackgroundColor'> {
14
19
  readonly colorScheme: ColorScheme
15
20
  readonly themeName?: AppThemeName
16
- readonly contentSizeCategory: IOSContentSizeCategory | AndroidContentSizeCategory
21
+ readonly contentSizeCategory: IOSContentSizeCategory | AndroidContentSizeCategory | WebContentSizeCategory
17
22
  readonly breakpoint?: AppBreakpoint
18
23
  readonly orientation: Orientation
19
24
 
@@ -9,9 +9,11 @@ type ExtractTransformArray<T> = T extends object ? { [K in keyof T]: ExtractBrea
9
9
  type ExtractBreakpoints<T> = T extends object
10
10
  ? keyof T extends BreakpointsOrMediaQueries
11
11
  ? T[keyof T]
12
- : T extends Array<ToDeepUnistyles<TransformStyles>>
13
- ? Array<ExtractTransformArray<T[number]>>
14
- : T extends Array<infer _U>
12
+ : T extends ReadonlyArray<ToDeepUnistyles<TransformStyles>>
13
+ ? T extends Array<infer _U>
14
+ ? Array<ExtractTransformArray<T[number]>>
15
+ : ReadonlyArray<ExtractTransformArray<T[number]>>
16
+ : T extends ReadonlyArray<infer _U>
15
17
  ? T
16
18
  : {
17
19
  [K in keyof T]: ExtractBreakpoints<T[K]>
package/src/types/core.ts CHANGED
@@ -1,19 +1,4 @@
1
1
  import type { ImageStyle, TextStyle, ViewStyle } from 'react-native'
2
- import type {
3
- MatrixTransform,
4
- PerspectiveTransform,
5
- RotateTransform,
6
- RotateXTransform,
7
- RotateYTransform,
8
- RotateZTransform,
9
- ScaleTransform,
10
- ScaleXTransform,
11
- ScaleYTransform,
12
- SkewXTransform,
13
- SkewYTransform,
14
- TranslateXTransform,
15
- TranslateYTransform,
16
- } from 'react-native/Libraries/StyleSheet/StyleSheetTypes'
17
2
 
18
3
  import type { UnistylesBreakpoints, UnistylesThemes } from '../global'
19
4
 
@@ -22,19 +7,13 @@ export type ShadowOffset = {
22
7
  height: number
23
8
  }
24
9
 
25
- export type TransformStyles = PerspectiveTransform &
26
- RotateTransform &
27
- RotateXTransform &
28
- RotateYTransform &
29
- RotateZTransform &
30
- ScaleTransform &
31
- ScaleXTransform &
32
- ScaleYTransform &
33
- TranslateXTransform &
34
- TranslateYTransform &
35
- SkewXTransform &
36
- SkewYTransform &
37
- MatrixTransform
10
+ type TransformEntry = Exclude<NonNullable<ViewStyle['transform']>, string>[number]
11
+ type TransformKeys<T> = T extends unknown ? keyof T : never
12
+ type TransformValue<T, K extends PropertyKey> = T extends unknown ? (K extends keyof T ? T[K] : never) : never
13
+
14
+ export type TransformStyles = {
15
+ [K in TransformKeys<TransformEntry>]: Exclude<TransformValue<TransformEntry, K>, void | undefined>
16
+ }
38
17
 
39
18
  export type ScreenSize = {
40
19
  width: number