react-native-unistyles 3.4.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 (185) 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 +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 +102 -18
  12. package/cxx/core/UnistylesRegistry.h +42 -5
  13. package/cxx/hybridObjects/HybridShadowRegistry.cpp +124 -4
  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 +29 -1
  19. package/cxx/parser/Parser.h +1 -0
  20. package/cxx/shadowTree/NativeProps.h +46 -2
  21. package/cxx/shadowTree/ShadowTrafficController.h +4 -0
  22. package/cxx/shadowTree/ShadowTreeDiagnostics.cpp +339 -0
  23. package/cxx/shadowTree/ShadowTreeDiagnostics.h +18 -0
  24. package/cxx/shadowTree/ShadowTreeManager.cpp +7 -1
  25. package/cxx/shadowTree/ShadowTreeManager.h +1 -1
  26. package/diagnostics/package.json +6 -0
  27. package/ios/UnistylesModuleOnLoad.mm +9 -3
  28. package/lib/commonjs/components/native/ActivityIndicator.native.js +10 -0
  29. package/lib/commonjs/components/native/ActivityIndicator.native.js.map +1 -0
  30. package/lib/commonjs/components/native/NativeText.js +6 -0
  31. package/lib/commonjs/components/native/NativeText.js.map +1 -1
  32. package/lib/commonjs/components/native/NativeText.native.js +17 -1
  33. package/lib/commonjs/components/native/NativeText.native.js.map +1 -1
  34. package/lib/commonjs/components/native/NativeView.js +6 -0
  35. package/lib/commonjs/components/native/NativeView.js.map +1 -1
  36. package/lib/commonjs/components/native/NativeView.native.js +22 -3
  37. package/lib/commonjs/components/native/NativeView.native.js.map +1 -1
  38. package/lib/commonjs/components/native/Pressable.js +7 -3
  39. package/lib/commonjs/components/native/Pressable.js.map +1 -1
  40. package/lib/commonjs/components/native/TouchableHighlight.native.js +10 -0
  41. package/lib/commonjs/components/native/TouchableHighlight.native.js.map +1 -0
  42. package/lib/commonjs/core/createUnistylesActivityIndicator.js +41 -0
  43. package/lib/commonjs/core/createUnistylesActivityIndicator.js.map +1 -0
  44. package/lib/commonjs/core/createUnistylesElement.js +59 -14
  45. package/lib/commonjs/core/createUnistylesElement.js.map +1 -1
  46. package/lib/commonjs/core/createUnistylesTouchableHighlight.js +58 -0
  47. package/lib/commonjs/core/createUnistylesTouchableHighlight.js.map +1 -0
  48. package/lib/commonjs/core/index.js +14 -0
  49. package/lib/commonjs/core/index.js.map +1 -1
  50. package/lib/commonjs/diagnostics/index.js +13 -0
  51. package/lib/commonjs/diagnostics/index.js.map +1 -0
  52. package/lib/commonjs/diagnostics/types.js +2 -0
  53. package/lib/commonjs/diagnostics/types.js.map +1 -0
  54. package/lib/commonjs/diagnostics/verify.js +19 -0
  55. package/lib/commonjs/diagnostics/verify.js.map +1 -0
  56. package/lib/commonjs/diagnostics/verify.native.js +12 -0
  57. package/lib/commonjs/diagnostics/verify.native.js.map +1 -0
  58. package/lib/commonjs/index.js +38 -0
  59. package/lib/commonjs/index.js.map +1 -1
  60. package/lib/commonjs/mocks.js +41 -0
  61. package/lib/commonjs/mocks.js.map +1 -1
  62. package/lib/commonjs/reanimated/variant/useUpdateVariantColor.native.js +8 -0
  63. package/lib/commonjs/reanimated/variant/useUpdateVariantColor.native.js.map +1 -1
  64. package/lib/commonjs/specs/ShadowRegistry/index.js +97 -1
  65. package/lib/commonjs/specs/ShadowRegistry/index.js.map +1 -1
  66. package/lib/commonjs/specs/StyleSheet/index.js +5 -1
  67. package/lib/commonjs/specs/StyleSheet/index.js.map +1 -1
  68. package/lib/commonjs/specs/UnistylesRuntime/index.js.map +1 -1
  69. package/lib/commonjs/web/registry.js +3 -0
  70. package/lib/commonjs/web/registry.js.map +1 -1
  71. package/lib/module/components/native/ActivityIndicator.native.js +6 -0
  72. package/lib/module/components/native/ActivityIndicator.native.js.map +1 -0
  73. package/lib/module/components/native/NativeText.js +1 -1
  74. package/lib/module/components/native/NativeText.js.map +1 -1
  75. package/lib/module/components/native/NativeText.native.js +15 -1
  76. package/lib/module/components/native/NativeText.native.js.map +1 -1
  77. package/lib/module/components/native/NativeView.js +1 -0
  78. package/lib/module/components/native/NativeView.js.map +1 -1
  79. package/lib/module/components/native/NativeView.native.js +19 -2
  80. package/lib/module/components/native/NativeView.native.js.map +1 -1
  81. package/lib/module/components/native/Pressable.js +7 -3
  82. package/lib/module/components/native/Pressable.js.map +1 -1
  83. package/lib/module/components/native/TouchableHighlight.native.js +6 -0
  84. package/lib/module/components/native/TouchableHighlight.native.js.map +1 -0
  85. package/lib/module/core/createUnistylesActivityIndicator.js +35 -0
  86. package/lib/module/core/createUnistylesActivityIndicator.js.map +1 -0
  87. package/lib/module/core/createUnistylesElement.js +58 -13
  88. package/lib/module/core/createUnistylesElement.js.map +1 -1
  89. package/lib/module/core/createUnistylesTouchableHighlight.js +52 -0
  90. package/lib/module/core/createUnistylesTouchableHighlight.js.map +1 -0
  91. package/lib/module/core/index.js +2 -0
  92. package/lib/module/core/index.js.map +1 -1
  93. package/lib/module/diagnostics/index.js +5 -0
  94. package/lib/module/diagnostics/index.js.map +1 -0
  95. package/lib/module/diagnostics/types.js +2 -0
  96. package/lib/module/diagnostics/types.js.map +1 -0
  97. package/lib/module/diagnostics/verify.js +14 -0
  98. package/lib/module/diagnostics/verify.js.map +1 -0
  99. package/lib/module/diagnostics/verify.native.js +8 -0
  100. package/lib/module/diagnostics/verify.native.js.map +1 -0
  101. package/lib/module/index.js +2 -0
  102. package/lib/module/index.js.map +1 -1
  103. package/lib/module/mocks.js +41 -0
  104. package/lib/module/mocks.js.map +1 -1
  105. package/lib/module/reanimated/variant/useUpdateVariantColor.native.js +9 -1
  106. package/lib/module/reanimated/variant/useUpdateVariantColor.native.js.map +1 -1
  107. package/lib/module/specs/ShadowRegistry/index.js +97 -1
  108. package/lib/module/specs/ShadowRegistry/index.js.map +1 -1
  109. package/lib/module/specs/StyleSheet/index.js +6 -1
  110. package/lib/module/specs/StyleSheet/index.js.map +1 -1
  111. package/lib/module/specs/UnistylesRuntime/index.js.map +1 -1
  112. package/lib/module/web/registry.js +3 -0
  113. package/lib/module/web/registry.js.map +1 -1
  114. package/lib/typescript/src/components/native/ActivityIndicator.native.d.ts +2 -0
  115. package/lib/typescript/src/components/native/ActivityIndicator.native.d.ts.map +1 -0
  116. package/lib/typescript/src/components/native/NativeText.d.ts +1 -1
  117. package/lib/typescript/src/components/native/NativeText.d.ts.map +1 -1
  118. package/lib/typescript/src/components/native/NativeText.native.d.ts.map +1 -1
  119. package/lib/typescript/src/components/native/NativeView.d.ts +1 -0
  120. package/lib/typescript/src/components/native/NativeView.d.ts.map +1 -1
  121. package/lib/typescript/src/components/native/NativeView.native.d.ts +2 -2
  122. package/lib/typescript/src/components/native/NativeView.native.d.ts.map +1 -1
  123. package/lib/typescript/src/components/native/Pressable.d.ts.map +1 -1
  124. package/lib/typescript/src/components/native/TouchableHighlight.native.d.ts +2 -0
  125. package/lib/typescript/src/components/native/TouchableHighlight.native.d.ts.map +1 -0
  126. package/lib/typescript/src/core/createUnistylesActivityIndicator.d.ts +3 -0
  127. package/lib/typescript/src/core/createUnistylesActivityIndicator.d.ts.map +1 -0
  128. package/lib/typescript/src/core/createUnistylesElement.d.ts.map +1 -1
  129. package/lib/typescript/src/core/createUnistylesTouchableHighlight.d.ts +3 -0
  130. package/lib/typescript/src/core/createUnistylesTouchableHighlight.d.ts.map +1 -0
  131. package/lib/typescript/src/core/index.d.ts +2 -0
  132. package/lib/typescript/src/core/index.d.ts.map +1 -1
  133. package/lib/typescript/src/core/useUnistyles.d.ts +1 -1
  134. package/lib/typescript/src/diagnostics/index.d.ts +3 -0
  135. package/lib/typescript/src/diagnostics/index.d.ts.map +1 -0
  136. package/lib/typescript/src/diagnostics/types.d.ts +19 -0
  137. package/lib/typescript/src/diagnostics/types.d.ts.map +1 -0
  138. package/lib/typescript/src/diagnostics/verify.d.ts +3 -0
  139. package/lib/typescript/src/diagnostics/verify.d.ts.map +1 -0
  140. package/lib/typescript/src/diagnostics/verify.native.d.ts +3 -0
  141. package/lib/typescript/src/diagnostics/verify.native.d.ts.map +1 -0
  142. package/lib/typescript/src/index.d.ts +4 -1
  143. package/lib/typescript/src/index.d.ts.map +1 -1
  144. package/lib/typescript/src/reanimated/variant/useUpdateVariantColor.native.d.ts.map +1 -1
  145. package/lib/typescript/src/specs/ShadowRegistry/index.d.ts +5 -1
  146. package/lib/typescript/src/specs/ShadowRegistry/index.d.ts.map +1 -1
  147. package/lib/typescript/src/specs/ShadowRegistry/types.d.ts +3 -0
  148. package/lib/typescript/src/specs/ShadowRegistry/types.d.ts.map +1 -1
  149. package/lib/typescript/src/specs/StyleSheet/index.d.ts +4 -1
  150. package/lib/typescript/src/specs/StyleSheet/index.d.ts.map +1 -1
  151. package/lib/typescript/src/specs/UnistylesRuntime/index.d.ts +2 -2
  152. package/lib/typescript/src/specs/UnistylesRuntime/index.d.ts.map +1 -1
  153. package/lib/typescript/src/types/breakpoints.d.ts +1 -1
  154. package/lib/typescript/src/types/breakpoints.d.ts.map +1 -1
  155. package/lib/typescript/src/types/stylesheet.d.ts +3 -3
  156. package/lib/typescript/src/types/stylesheet.d.ts.map +1 -1
  157. package/lib/typescript/src/web/registry.d.ts.map +1 -1
  158. package/package.json +9 -1
  159. package/plugin/index.d.ts +4 -4
  160. package/plugin/index.js +21 -5
  161. package/src/components/native/ActivityIndicator.native.tsx +5 -0
  162. package/src/components/native/NativeText.native.tsx +18 -1
  163. package/src/components/native/NativeText.tsx +1 -1
  164. package/src/components/native/NativeView.native.tsx +22 -2
  165. package/src/components/native/NativeView.tsx +2 -0
  166. package/src/components/native/Pressable.tsx +8 -3
  167. package/src/components/native/TouchableHighlight.native.tsx +5 -0
  168. package/src/core/createUnistylesActivityIndicator.tsx +47 -0
  169. package/src/core/createUnistylesElement.tsx +45 -14
  170. package/src/core/createUnistylesTouchableHighlight.tsx +69 -0
  171. package/src/core/index.ts +2 -0
  172. package/src/diagnostics/index.ts +3 -0
  173. package/src/diagnostics/types.ts +25 -0
  174. package/src/diagnostics/verify.native.ts +7 -0
  175. package/src/diagnostics/verify.ts +13 -0
  176. package/src/index.ts +4 -1
  177. package/src/mocks.ts +41 -0
  178. package/src/reanimated/variant/useUpdateVariantColor.native.ts +7 -1
  179. package/src/specs/ShadowRegistry/index.ts +128 -1
  180. package/src/specs/ShadowRegistry/types.ts +3 -0
  181. package/src/specs/StyleSheet/index.ts +10 -2
  182. package/src/specs/UnistylesRuntime/index.ts +7 -2
  183. package/src/types/breakpoints.ts +5 -3
  184. package/src/types/stylesheet.ts +3 -3
  185. package/src/web/registry.ts +3 -0
@@ -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]>
@@ -18,9 +18,9 @@ export type UnistyleImage = Omit<ImageStyle, NestedKeys>
18
18
  type UnistyleNestedStyles = {
19
19
  shadowOffset?: ToDeepUnistyles<ShadowOffset>
20
20
  textShadowOffset?: ToDeepUnistyles<ShadowOffset>
21
- transform?: Array<ToDeepUnistyles<TransformStyles>>
22
- boxShadow?: Array<ToDeepUnistyles<BoxShadowValue>> | string
23
- filter?: Array<ToDeepUnistyles<FilterFunction>> | string
21
+ transform?: ReadonlyArray<ToDeepUnistyles<TransformStyles>>
22
+ boxShadow?: ReadonlyArray<ToDeepUnistyles<BoxShadowValue>> | string
23
+ filter?: ReadonlyArray<ToDeepUnistyles<FilterFunction>> | string
24
24
  }
25
25
 
26
26
  type VariantsObject = {
@@ -90,6 +90,9 @@ export class UnistylesRegistry {
90
90
  stylesCounter.delete(ref)
91
91
 
92
92
  if (stylesCounter.size === 0) {
93
+ // Safe to drop now: connect creates a new Set and the cleanup below only checks the DOM
94
+ this.stylesCounter.delete(hash)
95
+
93
96
  // Move this to the end of the event loop so the element is removed from the DOM
94
97
  return Promise.resolve().then(() => {
95
98
  // Check if element is still in the DOM