@tamagui/native-registry 0.0.0-bootstrap.0 → 3.0.0-beta.643.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 (81) hide show
  1. package/README.md +58 -0
  2. package/TamaguiRegistry.podspec +32 -0
  3. package/android/CMakeLists.txt +25 -0
  4. package/android/build.gradle +83 -0
  5. package/android/src/main/AndroidManifest.xml +1 -0
  6. package/android/src/main/cpp/cpp-adapter.cpp +11 -0
  7. package/android/src/main/java/com/tamagui/registry/TamaguiRegistryPackage.kt +25 -0
  8. package/cpp/HybridTamaguiRegistry.cpp +369 -0
  9. package/cpp/HybridTamaguiRegistry.hpp +117 -0
  10. package/dist/cjs/index.cjs +73 -0
  11. package/dist/cjs/index.native.cjs +144 -0
  12. package/dist/cjs/index.native.js +146 -0
  13. package/dist/cjs/index.native.js.map +1 -0
  14. package/dist/cjs/mirror.cjs +44 -0
  15. package/dist/cjs/mirror.native.cjs +48 -0
  16. package/dist/cjs/mirror.native.js +50 -0
  17. package/dist/cjs/mirror.native.js.map +1 -0
  18. package/dist/cjs/processStyleColors.cjs +56 -0
  19. package/dist/cjs/processStyleColors.native.cjs +58 -0
  20. package/dist/cjs/processStyleColors.native.js +60 -0
  21. package/dist/cjs/processStyleColors.native.js.map +1 -0
  22. package/dist/cjs/specs/TamaguiRegistry.nitro.cjs +17 -0
  23. package/dist/cjs/specs/TamaguiRegistry.nitro.native.cjs +19 -0
  24. package/dist/cjs/specs/TamaguiRegistry.nitro.native.js +21 -0
  25. package/dist/cjs/specs/TamaguiRegistry.nitro.native.js.map +1 -0
  26. package/dist/cjs/types.cjs +17 -0
  27. package/dist/cjs/types.native.cjs +19 -0
  28. package/dist/cjs/types.native.js +21 -0
  29. package/dist/cjs/types.native.js.map +1 -0
  30. package/dist/esm/index.js +39 -0
  31. package/dist/esm/index.js.map +1 -0
  32. package/dist/esm/index.mjs +39 -0
  33. package/dist/esm/index.mjs.map +1 -0
  34. package/dist/esm/index.native.js +107 -0
  35. package/dist/esm/index.native.js.map +1 -0
  36. package/dist/esm/mirror.mjs +17 -0
  37. package/dist/esm/mirror.mjs.map +1 -0
  38. package/dist/esm/mirror.native.js +19 -0
  39. package/dist/esm/mirror.native.js.map +1 -0
  40. package/dist/esm/processStyleColors.mjs +36 -0
  41. package/dist/esm/processStyleColors.mjs.map +1 -0
  42. package/dist/esm/processStyleColors.native.js +36 -0
  43. package/dist/esm/processStyleColors.native.js.map +1 -0
  44. package/dist/esm/specs/TamaguiRegistry.nitro.mjs +0 -0
  45. package/dist/esm/specs/TamaguiRegistry.nitro.native.js +0 -0
  46. package/dist/esm/types.mjs +0 -0
  47. package/dist/esm/types.native.js +0 -0
  48. package/nitro.json +18 -0
  49. package/nitrogen/generated/.gitattributes +1 -0
  50. package/nitrogen/generated/android/kotlin/com/margelo/nitro/tamagui/registry/tamaguiregistryOnLoad.kt +35 -0
  51. package/nitrogen/generated/android/tamaguiregistry+autolinking.cmake +81 -0
  52. package/nitrogen/generated/android/tamaguiregistry+autolinking.gradle +27 -0
  53. package/nitrogen/generated/android/tamaguiregistryOnLoad.cpp +49 -0
  54. package/nitrogen/generated/android/tamaguiregistryOnLoad.hpp +34 -0
  55. package/nitrogen/generated/ios/TamaguiRegistry+autolinking.rb +62 -0
  56. package/nitrogen/generated/ios/TamaguiRegistry-Swift-Cxx-Bridge.cpp +17 -0
  57. package/nitrogen/generated/ios/TamaguiRegistry-Swift-Cxx-Bridge.hpp +27 -0
  58. package/nitrogen/generated/ios/TamaguiRegistry-Swift-Cxx-Umbrella.hpp +38 -0
  59. package/nitrogen/generated/ios/TamaguiRegistryAutolinking.mm +35 -0
  60. package/nitrogen/generated/ios/TamaguiRegistryAutolinking.swift +16 -0
  61. package/nitrogen/generated/shared/c++/HybridTamaguiRegistrySpec.cpp +27 -0
  62. package/nitrogen/generated/shared/c++/HybridTamaguiRegistrySpec.hpp +68 -0
  63. package/package.json +69 -6
  64. package/src/index.native.ts +174 -0
  65. package/src/index.ts +79 -0
  66. package/src/mirror.ts +31 -0
  67. package/src/processStyleColors.ts +42 -0
  68. package/src/specs/TamaguiRegistry.nitro.ts +51 -0
  69. package/src/types.ts +65 -0
  70. package/types/index.d.ts +20 -0
  71. package/types/index.d.ts.map +11 -0
  72. package/types/index.native.d.ts +37 -0
  73. package/types/index.native.d.ts.map +11 -0
  74. package/types/mirror.d.ts +7 -0
  75. package/types/mirror.d.ts.map +11 -0
  76. package/types/processStyleColors.d.ts +3 -0
  77. package/types/processStyleColors.d.ts.map +11 -0
  78. package/types/specs/TamaguiRegistry.nitro.d.ts +49 -0
  79. package/types/specs/TamaguiRegistry.nitro.d.ts.map +11 -0
  80. package/types/types.d.ts +61 -0
  81. package/types/types.d.ts.map +11 -0
package/package.json CHANGED
@@ -1,15 +1,78 @@
1
1
  {
2
2
  "name": "@tamagui/native-registry",
3
- "version": "0.0.0-bootstrap.0",
4
- "description": "Tamagui v3 package bootstrap",
3
+ "version": "3.0.0-beta.643.1",
4
+ "description": "Generic native style registry: zero-re-render style updates via Fabric ShadowTree commits",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "types": "./types/index.d.ts",
8
+ "main": "dist/cjs",
9
+ "module": "dist/esm",
10
+ "react-native": "src/index.native.ts",
11
+ "files": [
12
+ "src",
13
+ "!src/__tests__",
14
+ "types",
15
+ "dist",
16
+ "nitrogen",
17
+ "android",
18
+ "cpp",
19
+ "nitro.json",
20
+ "TamaguiRegistry.podspec"
21
+ ],
22
+ "scripts": {
23
+ "build": "tamagui-build",
24
+ "watch": "tamagui-build --watch",
25
+ "clean": "tamagui-build clean",
26
+ "clean:build": "tamagui-build clean:build",
27
+ "codegen": "nitrogen",
28
+ "test": "vitest run"
29
+ },
30
+ "exports": {
31
+ "./package.json": "./package.json",
32
+ ".": {
33
+ "react-native": {
34
+ "types": "./types/index.d.ts",
35
+ "module": "./dist/esm/index.native.js",
36
+ "import": "./dist/esm/index.native.js",
37
+ "require": "./dist/cjs/index.native.js"
38
+ },
39
+ "types": "./types/index.d.ts",
40
+ "browser": "./dist/esm/index.mjs",
41
+ "module": "./dist/esm/index.mjs",
42
+ "import": "./dist/esm/index.mjs",
43
+ "require": "./dist/cjs/index.cjs",
44
+ "default": "./dist/esm/index.mjs"
45
+ }
46
+ },
47
+ "devDependencies": {
48
+ "@tamagui/build": "3.0.0-beta.643.1",
49
+ "react": ">=19",
50
+ "react-native": "0.86.2",
51
+ "react-native-nitro-modules": "^0.36.5",
52
+ "typescript": "~6.0.3",
53
+ "vitest": "4.0.4",
54
+ "nitrogen": "^0.36.5"
55
+ },
56
+ "peerDependencies": {
57
+ "react": "*",
58
+ "react-native": ">=0.81.0",
59
+ "react-native-nitro-modules": ">=0.36.0",
60
+ "react-native-web": "*"
61
+ },
62
+ "peerDependenciesMeta": {
63
+ "react-native-nitro-modules": {
64
+ "optional": true
65
+ },
66
+ "react-native-web": {
67
+ "optional": true
68
+ }
69
+ },
5
70
  "repository": {
6
71
  "type": "git",
7
- "url": "git+https://github.com/tamagui/tamagui.git"
72
+ "url": "git+https://github.com/tamagui/tamagui.git",
73
+ "directory": "code/core/native-registry"
8
74
  },
9
75
  "license": "MIT",
10
- "files": [
11
- "README.md"
12
- ],
13
76
  "publishConfig": {
14
77
  "access": "public"
15
78
  }
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Native implementation backed by the TamaguiRegistry Nitro HybridObject.
3
+ * The C++ engine commits style updates directly to Fabric's ShadowTree via
4
+ * UIManager::updateShadowTree (RN >= 0.81), with zero React re-renders.
5
+ */
6
+ import { NitroModules } from 'react-native-nitro-modules'
7
+ import type { TamaguiRegistry } from './specs/TamaguiRegistry.nitro'
8
+ import type {
9
+ LinkHandle,
10
+ RegistryStats,
11
+ Unlink,
12
+ ViewSlots,
13
+ ViewStateSnapshot,
14
+ ViewStateTableUpdate,
15
+ ViewStateUpdate,
16
+ } from './types'
17
+ import {
18
+ ROOT_SCOPE,
19
+ getMirroredStateName,
20
+ removeMirroredScope,
21
+ setMirroredStateName,
22
+ } from './mirror'
23
+ import { processStyleColors } from './processStyleColors'
24
+
25
+ export type {
26
+ LinkHandle,
27
+ RegistryStats,
28
+ Unlink,
29
+ ViewSlots,
30
+ ViewStateSnapshot,
31
+ ViewStateTableUpdate,
32
+ ViewStateUpdate,
33
+ } from './types'
34
+ export { ROOT_SCOPE, getMirroredStateName } from './mirror'
35
+ export { processStyleColors } from './processStyleColors'
36
+
37
+ /** Full engine surface: typed Nitro methods plus the raw-JSI methods. */
38
+ interface Engine extends TamaguiRegistry {
39
+ link(shadowNode: unknown, slots: object, scopeId: string): number
40
+ applyViewStates(entries: ViewStateUpdate[]): void
41
+ updateViewStateTables(entries: ViewStateTableUpdate[]): void
42
+ getViewState(id: number): ViewStateSnapshot | null
43
+ }
44
+
45
+ let engine: Engine | null = null
46
+ let initError: unknown = null
47
+ try {
48
+ engine = NitroModules.createHybridObject<TamaguiRegistry>('TamaguiRegistry') as Engine
49
+ } catch (error) {
50
+ initError = error
51
+ }
52
+
53
+ export function isAvailable(): boolean {
54
+ return engine !== null
55
+ }
56
+
57
+ function getEngine(): Engine {
58
+ if (engine) return engine
59
+ const detail = initError instanceof Error ? ` ${initError.message}` : ''
60
+ throw new Error(
61
+ `[@tamagui/native-registry] TamaguiRegistry failed to initialize; is react-native-nitro-modules installed and the app rebuilt?${detail}`
62
+ )
63
+ }
64
+
65
+ /**
66
+ * Extract the opaque ShadowNode JSI wrapper from a Fabric host component ref.
67
+ * Same internals access as Unistyles; verified against RN 0.86.
68
+ */
69
+ function getShadowNode(ref: unknown): unknown | null {
70
+ const handle = (ref as Record<string, unknown>)?.__internalInstanceHandle as
71
+ | { stateNode?: { node?: unknown } }
72
+ | undefined
73
+ return handle?.stateNode?.node ?? null
74
+ }
75
+
76
+ /**
77
+ * Link a mounted view to the engine. Captures the ShadowNode once, returns
78
+ * a handle keyed by the engine-issued id: unlink never re-derives anything
79
+ * from the ref, so a torn-down ref cannot leave a stale entry behind.
80
+ */
81
+ export function link(
82
+ ref: unknown,
83
+ slots: ViewSlots,
84
+ scopeId: string = ROOT_SCOPE
85
+ ): LinkHandle | null {
86
+ const node = getShadowNode(ref)
87
+ if (!node) return null
88
+
89
+ const e = getEngine()
90
+ const prepared: Record<string, unknown> = {}
91
+ if (slots.base) prepared.base = processStyleColors(slots.base)
92
+ if (slots.state) {
93
+ const state: Record<string, unknown> = {}
94
+ for (const name in slots.state) {
95
+ state[name] = processStyleColors(slots.state[name])
96
+ }
97
+ prepared.state = state
98
+ }
99
+
100
+ const id = e.link(node, prepared, scopeId)
101
+ let unlinked = false
102
+ return {
103
+ id,
104
+ unlink: () => {
105
+ if (unlinked) return
106
+ unlinked = true
107
+ e.unlink(id)
108
+ },
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Batched per-view state selection: cold entries carry `props` (computed by
114
+ * the caller, colors already processed via processStyleColors), warm entries
115
+ * just name the state. One native commit for the whole batch.
116
+ */
117
+ export function applyViewStates(entries: ViewStateUpdate[]): void {
118
+ if (entries.length === 0) return
119
+ getEngine().applyViewStates(entries)
120
+ }
121
+
122
+ /**
123
+ * Fill lazily resolved state-table entries without switching a view to the
124
+ * per-view runtime controller. The native engine commits an entry immediately
125
+ * only when that state is already active for the view's scope.
126
+ */
127
+ export function updateViewStateTables(entries: ViewStateTableUpdate[]): void {
128
+ if (entries.length === 0) return
129
+ getEngine().updateViewStateTables(
130
+ entries.map((entry) => ({ ...entry, props: processStyleColors(entry.props) }))
131
+ )
132
+ }
133
+
134
+ /**
135
+ * Set the active state name (e.g. theme name) for a scope and commit the
136
+ * update natively. The JS mirror updates first so any concurrent React
137
+ * render paints the same styles the engine commits.
138
+ */
139
+ export function setStateName(stateName: string, scopeId: string = ROOT_SCOPE): void {
140
+ setMirroredStateName(scopeId, stateName)
141
+ getEngine().setStateName(scopeId, stateName)
142
+ }
143
+
144
+ export function removeScope(scopeId: string): void {
145
+ removeMirroredScope(scopeId)
146
+ getEngine().removeScope(scopeId)
147
+ }
148
+
149
+ /** Resolve the props a view should render right now, mirror-consistent. */
150
+ export function resolveSlots(
151
+ slots: ViewSlots,
152
+ scopeId: string = ROOT_SCOPE
153
+ ): Record<string, unknown> {
154
+ const name = getMirroredStateName(scopeId)
155
+ return {
156
+ ...slots.base,
157
+ ...(name ? slots.state?.[name] : undefined),
158
+ }
159
+ }
160
+
161
+ /** Debug/test introspection of a linked view's engine tables. Not a hot path. */
162
+ export function getViewState(id: number): ViewStateSnapshot | null {
163
+ if (!engine) return null
164
+ return engine.getViewState(id)
165
+ }
166
+
167
+ export function getStats(): RegistryStats {
168
+ if (!engine) return { viewCount: 0, commitCount: 0, missCount: 0 }
169
+ return {
170
+ viewCount: engine.getViewCount(),
171
+ commitCount: engine.getCommitCount(),
172
+ missCount: engine.getMissCount(),
173
+ }
174
+ }
package/src/index.ts ADDED
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Web/no-native fallback. The registry is a native-only optimization; on web
3
+ * (or when the native module is absent) the mirror still tracks scope state
4
+ * so shared code can resolve slots, but nothing links and nothing commits.
5
+ */
6
+ import type {
7
+ LinkHandle,
8
+ RegistryStats,
9
+ ViewSlots,
10
+ ViewStateSnapshot,
11
+ ViewStateTableUpdate,
12
+ ViewStateUpdate,
13
+ } from './types'
14
+ import {
15
+ ROOT_SCOPE,
16
+ getMirroredStateName,
17
+ removeMirroredScope,
18
+ setMirroredStateName,
19
+ } from './mirror'
20
+
21
+ export type {
22
+ LinkHandle,
23
+ RegistryStats,
24
+ Unlink,
25
+ ViewSlots,
26
+ ViewStateSnapshot,
27
+ ViewStateTableUpdate,
28
+ ViewStateUpdate,
29
+ } from './types'
30
+ export { ROOT_SCOPE, getMirroredStateName } from './mirror'
31
+
32
+ export function isAvailable(): boolean {
33
+ return false
34
+ }
35
+
36
+ export function link(
37
+ _ref: unknown,
38
+ _slots: ViewSlots,
39
+ _scopeId?: string
40
+ ): LinkHandle | null {
41
+ return null
42
+ }
43
+
44
+ export function applyViewStates(_entries: ViewStateUpdate[]): void {}
45
+
46
+ export function updateViewStateTables(_entries: ViewStateTableUpdate[]): void {}
47
+
48
+ export function getViewState(_id: number): ViewStateSnapshot | null {
49
+ return null
50
+ }
51
+
52
+ export function setStateName(stateName: string, scopeId: string = ROOT_SCOPE): void {
53
+ setMirroredStateName(scopeId, stateName)
54
+ }
55
+
56
+ export function removeScope(scopeId: string): void {
57
+ removeMirroredScope(scopeId)
58
+ }
59
+
60
+ export function resolveSlots(
61
+ slots: ViewSlots,
62
+ scopeId: string = ROOT_SCOPE
63
+ ): Record<string, unknown> {
64
+ const name = getMirroredStateName(scopeId)
65
+ return {
66
+ ...slots.base,
67
+ ...(name ? slots.state?.[name] : undefined),
68
+ }
69
+ }
70
+
71
+ export function getStats(): RegistryStats {
72
+ return { viewCount: 0, commitCount: 0, missCount: 0 }
73
+ }
74
+
75
+ export function processStyleColors(
76
+ props: Record<string, unknown>
77
+ ): Record<string, unknown> {
78
+ return props
79
+ }
package/src/mirror.ts ADDED
@@ -0,0 +1,31 @@
1
+ /**
2
+ * JS mirror of the engine's scope state.
3
+ *
4
+ * React re-renders of a linked component must never revert a native commit,
5
+ * so components read their current state name from this mirror at render
6
+ * time (without subscribing) and always paint styles that match what the
7
+ * engine last committed. The mirror is updated synchronously before the
8
+ * native call in setStateName, so render and engine can never disagree.
9
+ */
10
+ const scopeStates = new Map<string, string>()
11
+
12
+ export const ROOT_SCOPE = ''
13
+
14
+ export function getMirroredStateName(scopeId: string = ROOT_SCOPE): string | undefined {
15
+ return (
16
+ scopeStates.get(scopeId) ??
17
+ (scopeId === ROOT_SCOPE ? undefined : scopeStates.get(ROOT_SCOPE))
18
+ )
19
+ }
20
+
21
+ export function setMirroredStateName(scopeId: string, stateName: string): void {
22
+ scopeStates.set(scopeId, stateName)
23
+ }
24
+
25
+ export function removeMirroredScope(scopeId: string): void {
26
+ scopeStates.delete(scopeId)
27
+ }
28
+
29
+ export function resetMirror(): void {
30
+ scopeStates.clear()
31
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Fabric's C++ prop parser takes colors as ARGB ints, not CSS strings, so
3
+ * every color prop must be converted before props reach the engine. The
4
+ * compiler mappings and runtime-mode pushes both use this exact runtime
5
+ * processor before props cross into the engine.
6
+ */
7
+ import { processColor } from 'react-native'
8
+
9
+ const COLOR_PROPS = new Set([
10
+ 'color',
11
+ 'backgroundColor',
12
+ 'borderColor',
13
+ 'borderTopColor',
14
+ 'borderBottomColor',
15
+ 'borderLeftColor',
16
+ 'borderRightColor',
17
+ 'borderStartColor',
18
+ 'borderEndColor',
19
+ 'borderBlockColor',
20
+ 'borderBlockStartColor',
21
+ 'borderBlockEndColor',
22
+ 'shadowColor',
23
+ 'textShadowColor',
24
+ 'textDecorationColor',
25
+ 'tintColor',
26
+ 'overlayColor',
27
+ ])
28
+
29
+ export function processStyleColors(
30
+ props: Record<string, unknown>
31
+ ): Record<string, unknown> {
32
+ let out: Record<string, unknown> | null = null
33
+ for (const key in props) {
34
+ const value = props[key]
35
+ if (COLOR_PROPS.has(key) && typeof value === 'string') {
36
+ out ??= { ...props }
37
+ const processed = processColor(value)
38
+ if (processed != null) out[key] = processed
39
+ }
40
+ }
41
+ return out ?? props
42
+ }
@@ -0,0 +1,51 @@
1
+ import type { HybridObject } from 'react-native-nitro-modules'
2
+
3
+ /**
4
+ * Typed surface of the native registry.
5
+ *
6
+ * The engine is deliberately generic: it knows nothing about Tamagui themes
7
+ * beyond "a named state per scope selects which slot props apply". Tamagui
8
+ * semantics (theme names, scope wiring, compiler output) live in JS.
9
+ *
10
+ * Two methods are NOT in this spec because they take values Nitro cannot
11
+ * type: `link(shadowNode, slots, scopeId) -> number` and internal raw-JSI
12
+ * registration. They are registered as raw hybrid methods in C++ and typed
13
+ * on the JS side in index.native.ts.
14
+ *
15
+ * Threading: every method must be called from the JS thread. The engine is
16
+ * single-threaded by contract and holds no locks.
17
+ */
18
+ export interface TamaguiRegistry extends HybridObject<{ ios: 'c++'; android: 'c++' }> {
19
+ /**
20
+ * Set the active state name for a scope ('' = root scope) and commit the
21
+ * matching slot props for every affected linked view in one ShadowTree
22
+ * transaction.
23
+ */
24
+ setStateName(scopeId: string, stateName: string): void
25
+
26
+ /** Read the active state name for a scope ('' = root). */
27
+ getStateName(scopeId: string): string
28
+
29
+ /** Drop a scope entry (cleanup when a scope provider unmounts). */
30
+ removeScope(scopeId: string): void
31
+
32
+ /** Unlink by the id returned from link(). Safe to call twice. */
33
+ unlink(id: number): void
34
+
35
+ /** Number of currently linked views. */
36
+ getViewCount(): number
37
+
38
+ /**
39
+ * Number of ShadowTree commits performed. Benchmark honesty counter: a
40
+ * measurement where this does not increase never exercised the fast path.
41
+ */
42
+ getCommitCount(): number
43
+
44
+ /**
45
+ * Number of linked views skipped during commits because their slots had no
46
+ * entry for the active state name. Always 0 when the compiler emitted
47
+ * exhaustive keys; anything else is an upstream bug, not a runtime
48
+ * condition to recover from.
49
+ */
50
+ getMissCount(): number
51
+ }
package/src/types.ts ADDED
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Slot props for one linked view.
3
+ *
4
+ * v1 implements two slot kinds: `base` (state-independent props, applied
5
+ * under every state) and `state` (props keyed by state name, e.g. theme
6
+ * name; the scope's active name selects which entry applies). Future kinds
7
+ * (media, press, group, container) extend this shape; the engine merge
8
+ * order is fixed: base, then state, then future kinds.
9
+ *
10
+ * Keys must be exhaustive for every state name the scope can take. The
11
+ * engine does exact lookups only: no fallback chains, no name parsing. A
12
+ * miss increments the engine's miss counter and is a bug in the emitter.
13
+ */
14
+ export interface ViewSlots {
15
+ base?: Record<string, unknown>
16
+ state?: Record<string, Record<string, unknown>>
17
+ }
18
+
19
+ export interface RegistryStats {
20
+ viewCount: number
21
+ commitCount: number
22
+ missCount: number
23
+ }
24
+
25
+ /** Returned by link(); call to unlink. Safe to call more than once. */
26
+ export type Unlink = () => void
27
+
28
+ /**
29
+ * Handle for a linked view. `id` addresses the view in batched per-view
30
+ * operations (applyViewStates); `unlink` removes it. null when the ref had
31
+ * no shadow node (never mounted) or the engine is unavailable (web).
32
+ */
33
+ export interface LinkHandle {
34
+ id: number
35
+ unlink: Unlink
36
+ }
37
+
38
+ /**
39
+ * One entry of a batched per-view state update. When `props` is present it
40
+ * is merged into the view's state table under `state` first (lazy warm-up
41
+ * for runtime integrations); the view's active state then becomes `state`
42
+ * and all entries commit natively in one transaction.
43
+ */
44
+ export interface ViewStateUpdate {
45
+ id: number
46
+ state: string
47
+ props?: Record<string, unknown>
48
+ }
49
+
50
+ /** add or replace one resolved state-table entry without changing its view's controller */
51
+ export interface ViewStateTableUpdate {
52
+ id: number
53
+ state: string
54
+ props: Record<string, unknown>
55
+ }
56
+
57
+ /** Debug/test snapshot of a linked view's engine tables (getViewState). */
58
+ export interface ViewStateSnapshot {
59
+ scopeId: string
60
+ activeState: string
61
+ base: Record<string, unknown> | null
62
+ states: Record<string, Record<string, unknown>> | null
63
+ /** the family's sticky nativeProps_DEPRECATED merge, null when unset */
64
+ nativeProps: Record<string, unknown> | null
65
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Web/no-native fallback. The registry is a native-only optimization; on web
3
+ * (or when the native module is absent) the mirror still tracks scope state
4
+ * so shared code can resolve slots, but nothing links and nothing commits.
5
+ */
6
+ import type { LinkHandle, RegistryStats, ViewSlots, ViewStateSnapshot, ViewStateTableUpdate, ViewStateUpdate } from "./types";
7
+ export type { LinkHandle, RegistryStats, Unlink, ViewSlots, ViewStateSnapshot, ViewStateTableUpdate, ViewStateUpdate } from "./types";
8
+ export { ROOT_SCOPE, getMirroredStateName } from "./mirror";
9
+ export declare function isAvailable(): boolean;
10
+ export declare function link(_ref: unknown, _slots: ViewSlots, _scopeId?: string): LinkHandle | null;
11
+ export declare function applyViewStates(_entries: ViewStateUpdate[]): void;
12
+ export declare function updateViewStateTables(_entries: ViewStateTableUpdate[]): void;
13
+ export declare function getViewState(_id: number): ViewStateSnapshot | null;
14
+ export declare function setStateName(stateName: string, scopeId?: string): void;
15
+ export declare function removeScope(scopeId: string): void;
16
+ export declare function resolveSlots(slots: ViewSlots, scopeId?: string): Record<string, unknown>;
17
+ export declare function getStats(): RegistryStats;
18
+ export declare function processStyleColors(props: Record<string, unknown>): Record<string, unknown>;
19
+
20
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,11 @@
1
+ {
2
+ "mappings": ";;;;;AAKA,cACE,YACA,eACA,WACA,mBACA,sBACA,uBACK;AAQP,cACE,YACA,eACA,QACA,WACA,mBACA,sBACA,uBACK;AACP,SAAS,YAAY,4BAA4B;AAEjD,OAAO,iBAAS;AAIhB,OAAO,iBAAS,KACd,eACA,QAAQ,WACR,oBACC;AAIH,OAAO,iBAAS,gBAAgB,UAAU;AAE1C,OAAO,iBAAS,sBAAsB,UAAU;AAEhD,OAAO,iBAAS,aAAa,cAAc;AAI3C,OAAO,iBAAS,aAAa,mBAAmB;AAIhD,OAAO,iBAAS,YAAY;AAI5B,OAAO,iBAAS,aACd,OAAO,WACP,mBACC;AAQH,OAAO,iBAAS,YAAY;AAI5B,OAAO,iBAAS,mBACd,OAAO,0BACN",
3
+ "names": [],
4
+ "sources": [
5
+ "src/index.ts"
6
+ ],
7
+ "version": 3,
8
+ "sourcesContent": [
9
+ "/**\n * Web/no-native fallback. The registry is a native-only optimization; on web\n * (or when the native module is absent) the mirror still tracks scope state\n * so shared code can resolve slots, but nothing links and nothing commits.\n */\nimport type {\n LinkHandle,\n RegistryStats,\n ViewSlots,\n ViewStateSnapshot,\n ViewStateTableUpdate,\n ViewStateUpdate,\n} from './types'\nimport {\n ROOT_SCOPE,\n getMirroredStateName,\n removeMirroredScope,\n setMirroredStateName,\n} from './mirror'\n\nexport type {\n LinkHandle,\n RegistryStats,\n Unlink,\n ViewSlots,\n ViewStateSnapshot,\n ViewStateTableUpdate,\n ViewStateUpdate,\n} from './types'\nexport { ROOT_SCOPE, getMirroredStateName } from './mirror'\n\nexport function isAvailable(): boolean {\n return false\n}\n\nexport function link(\n _ref: unknown,\n _slots: ViewSlots,\n _scopeId?: string\n): LinkHandle | null {\n return null\n}\n\nexport function applyViewStates(_entries: ViewStateUpdate[]): void {}\n\nexport function updateViewStateTables(_entries: ViewStateTableUpdate[]): void {}\n\nexport function getViewState(_id: number): ViewStateSnapshot | null {\n return null\n}\n\nexport function setStateName(stateName: string, scopeId: string = ROOT_SCOPE): void {\n setMirroredStateName(scopeId, stateName)\n}\n\nexport function removeScope(scopeId: string): void {\n removeMirroredScope(scopeId)\n}\n\nexport function resolveSlots(\n slots: ViewSlots,\n scopeId: string = ROOT_SCOPE\n): Record<string, unknown> {\n const name = getMirroredStateName(scopeId)\n return {\n ...slots.base,\n ...(name ? slots.state?.[name] : undefined),\n }\n}\n\nexport function getStats(): RegistryStats {\n return { viewCount: 0, commitCount: 0, missCount: 0 }\n}\n\nexport function processStyleColors(\n props: Record<string, unknown>\n): Record<string, unknown> {\n return props\n}\n"
10
+ ]
11
+ }
@@ -0,0 +1,37 @@
1
+ import type { LinkHandle, RegistryStats, ViewSlots, ViewStateSnapshot, ViewStateTableUpdate, ViewStateUpdate } from "./types";
2
+ export type { LinkHandle, RegistryStats, Unlink, ViewSlots, ViewStateSnapshot, ViewStateTableUpdate, ViewStateUpdate } from "./types";
3
+ export { ROOT_SCOPE, getMirroredStateName } from "./mirror";
4
+ export { processStyleColors } from "./processStyleColors";
5
+ export declare function isAvailable(): boolean;
6
+ /**
7
+ * Link a mounted view to the engine. Captures the ShadowNode once, returns
8
+ * a handle keyed by the engine-issued id: unlink never re-derives anything
9
+ * from the ref, so a torn-down ref cannot leave a stale entry behind.
10
+ */
11
+ export declare function link(ref: unknown, slots: ViewSlots, scopeId?: string): LinkHandle | null;
12
+ /**
13
+ * Batched per-view state selection: cold entries carry `props` (computed by
14
+ * the caller, colors already processed via processStyleColors), warm entries
15
+ * just name the state. One native commit for the whole batch.
16
+ */
17
+ export declare function applyViewStates(entries: ViewStateUpdate[]): void;
18
+ /**
19
+ * Fill lazily resolved state-table entries without switching a view to the
20
+ * per-view runtime controller. The native engine commits an entry immediately
21
+ * only when that state is already active for the view's scope.
22
+ */
23
+ export declare function updateViewStateTables(entries: ViewStateTableUpdate[]): void;
24
+ /**
25
+ * Set the active state name (e.g. theme name) for a scope and commit the
26
+ * update natively. The JS mirror updates first so any concurrent React
27
+ * render paints the same styles the engine commits.
28
+ */
29
+ export declare function setStateName(stateName: string, scopeId?: string): void;
30
+ export declare function removeScope(scopeId: string): void;
31
+ /** Resolve the props a view should render right now, mirror-consistent. */
32
+ export declare function resolveSlots(slots: ViewSlots, scopeId?: string): Record<string, unknown>;
33
+ /** Debug/test introspection of a linked view's engine tables. Not a hot path. */
34
+ export declare function getViewState(id: number): ViewStateSnapshot | null;
35
+ export declare function getStats(): RegistryStats;
36
+
37
+ //# sourceMappingURL=index.native.d.ts.map
@@ -0,0 +1,11 @@
1
+ {
2
+ "mappings": "AAOA,cACE,YACA,eAEA,WACA,mBACA,sBACA,uBACK;AASP,cACE,YACA,eACA,QACA,WACA,mBACA,sBACA,uBACK;AACP,SAAS,YAAY,4BAA4B;AACjD,SAAS,0BAA0B;AAkBnC,OAAO,iBAAS;;;;;;AA4BhB,OAAO,iBAAS,KACd,cACA,OAAO,WACP,mBACC;;;;;;AAgCH,OAAO,iBAAS,gBAAgB,SAAS;;;;;;AAUzC,OAAO,iBAAS,sBAAsB,SAAS;;;;;;AAY/C,OAAO,iBAAS,aAAa,mBAAmB;AAKhD,OAAO,iBAAS,YAAY;;AAM5B,OAAO,iBAAS,aACd,OAAO,WACP,mBACC;;AASH,OAAO,iBAAS,aAAa,aAAa;AAK1C,OAAO,iBAAS,YAAY",
3
+ "names": [],
4
+ "sources": [
5
+ "src/index.native.ts"
6
+ ],
7
+ "version": 3,
8
+ "sourcesContent": [
9
+ "/**\n * Native implementation backed by the TamaguiRegistry Nitro HybridObject.\n * The C++ engine commits style updates directly to Fabric's ShadowTree via\n * UIManager::updateShadowTree (RN >= 0.81), with zero React re-renders.\n */\nimport { NitroModules } from 'react-native-nitro-modules'\nimport type { TamaguiRegistry } from './specs/TamaguiRegistry.nitro'\nimport type {\n LinkHandle,\n RegistryStats,\n Unlink,\n ViewSlots,\n ViewStateSnapshot,\n ViewStateTableUpdate,\n ViewStateUpdate,\n} from './types'\nimport {\n ROOT_SCOPE,\n getMirroredStateName,\n removeMirroredScope,\n setMirroredStateName,\n} from './mirror'\nimport { processStyleColors } from './processStyleColors'\n\nexport type {\n LinkHandle,\n RegistryStats,\n Unlink,\n ViewSlots,\n ViewStateSnapshot,\n ViewStateTableUpdate,\n ViewStateUpdate,\n} from './types'\nexport { ROOT_SCOPE, getMirroredStateName } from './mirror'\nexport { processStyleColors } from './processStyleColors'\n\n/** Full engine surface: typed Nitro methods plus the raw-JSI methods. */\ninterface Engine extends TamaguiRegistry {\n link(shadowNode: unknown, slots: object, scopeId: string): number\n applyViewStates(entries: ViewStateUpdate[]): void\n updateViewStateTables(entries: ViewStateTableUpdate[]): void\n getViewState(id: number): ViewStateSnapshot | null\n}\n\nlet engine: Engine | null = null\nlet initError: unknown = null\ntry {\n engine = NitroModules.createHybridObject<TamaguiRegistry>('TamaguiRegistry') as Engine\n} catch (error) {\n initError = error\n}\n\nexport function isAvailable(): boolean {\n return engine !== null\n}\n\nfunction getEngine(): Engine {\n if (engine) return engine\n const detail = initError instanceof Error ? ` ${initError.message}` : ''\n throw new Error(\n `[@tamagui/native-registry] TamaguiRegistry failed to initialize; is react-native-nitro-modules installed and the app rebuilt?${detail}`\n )\n}\n\n/**\n * Extract the opaque ShadowNode JSI wrapper from a Fabric host component ref.\n * Same internals access as Unistyles; verified against RN 0.86.\n */\nfunction getShadowNode(ref: unknown): unknown | null {\n const handle = (ref as Record<string, unknown>)?.__internalInstanceHandle as\n | { stateNode?: { node?: unknown } }\n | undefined\n return handle?.stateNode?.node ?? null\n}\n\n/**\n * Link a mounted view to the engine. Captures the ShadowNode once, returns\n * a handle keyed by the engine-issued id: unlink never re-derives anything\n * from the ref, so a torn-down ref cannot leave a stale entry behind.\n */\nexport function link(\n ref: unknown,\n slots: ViewSlots,\n scopeId: string = ROOT_SCOPE\n): LinkHandle | null {\n const node = getShadowNode(ref)\n if (!node) return null\n\n const e = getEngine()\n const prepared: Record<string, unknown> = {}\n if (slots.base) prepared.base = processStyleColors(slots.base)\n if (slots.state) {\n const state: Record<string, unknown> = {}\n for (const name in slots.state) {\n state[name] = processStyleColors(slots.state[name])\n }\n prepared.state = state\n }\n\n const id = e.link(node, prepared, scopeId)\n let unlinked = false\n return {\n id,\n unlink: () => {\n if (unlinked) return\n unlinked = true\n e.unlink(id)\n },\n }\n}\n\n/**\n * Batched per-view state selection: cold entries carry `props` (computed by\n * the caller, colors already processed via processStyleColors), warm entries\n * just name the state. One native commit for the whole batch.\n */\nexport function applyViewStates(entries: ViewStateUpdate[]): void {\n if (entries.length === 0) return\n getEngine().applyViewStates(entries)\n}\n\n/**\n * Fill lazily resolved state-table entries without switching a view to the\n * per-view runtime controller. The native engine commits an entry immediately\n * only when that state is already active for the view's scope.\n */\nexport function updateViewStateTables(entries: ViewStateTableUpdate[]): void {\n if (entries.length === 0) return\n getEngine().updateViewStateTables(\n entries.map((entry) => ({ ...entry, props: processStyleColors(entry.props) }))\n )\n}\n\n/**\n * Set the active state name (e.g. theme name) for a scope and commit the\n * update natively. The JS mirror updates first so any concurrent React\n * render paints the same styles the engine commits.\n */\nexport function setStateName(stateName: string, scopeId: string = ROOT_SCOPE): void {\n setMirroredStateName(scopeId, stateName)\n getEngine().setStateName(scopeId, stateName)\n}\n\nexport function removeScope(scopeId: string): void {\n removeMirroredScope(scopeId)\n getEngine().removeScope(scopeId)\n}\n\n/** Resolve the props a view should render right now, mirror-consistent. */\nexport function resolveSlots(\n slots: ViewSlots,\n scopeId: string = ROOT_SCOPE\n): Record<string, unknown> {\n const name = getMirroredStateName(scopeId)\n return {\n ...slots.base,\n ...(name ? slots.state?.[name] : undefined),\n }\n}\n\n/** Debug/test introspection of a linked view's engine tables. Not a hot path. */\nexport function getViewState(id: number): ViewStateSnapshot | null {\n if (!engine) return null\n return engine.getViewState(id)\n}\n\nexport function getStats(): RegistryStats {\n if (!engine) return { viewCount: 0, commitCount: 0, missCount: 0 }\n return {\n viewCount: engine.getViewCount(),\n commitCount: engine.getCommitCount(),\n missCount: engine.getMissCount(),\n }\n}\n"
10
+ ]
11
+ }
@@ -0,0 +1,7 @@
1
+ export declare const ROOT_SCOPE = "";
2
+ export declare function getMirroredStateName(scopeId?: string): string | undefined;
3
+ export declare function setMirroredStateName(scopeId: string, stateName: string): void;
4
+ export declare function removeMirroredScope(scopeId: string): void;
5
+ export declare function resetMirror(): void;
6
+
7
+ //# sourceMappingURL=mirror.d.ts.map
@@ -0,0 +1,11 @@
1
+ {
2
+ "mappings": "AAWA,OAAO,cAAM,aAAa;AAE1B,OAAO,iBAAS,qBAAqB;AAOrC,OAAO,iBAAS,qBAAqB,iBAAiB;AAItD,OAAO,iBAAS,oBAAoB;AAIpC,OAAO,iBAAS",
3
+ "names": [],
4
+ "sources": [
5
+ "src/mirror.ts"
6
+ ],
7
+ "version": 3,
8
+ "sourcesContent": [
9
+ "/**\n * JS mirror of the engine's scope state.\n *\n * React re-renders of a linked component must never revert a native commit,\n * so components read their current state name from this mirror at render\n * time (without subscribing) and always paint styles that match what the\n * engine last committed. The mirror is updated synchronously before the\n * native call in setStateName, so render and engine can never disagree.\n */\nconst scopeStates = new Map<string, string>()\n\nexport const ROOT_SCOPE = ''\n\nexport function getMirroredStateName(scopeId: string = ROOT_SCOPE): string | undefined {\n return (\n scopeStates.get(scopeId) ??\n (scopeId === ROOT_SCOPE ? undefined : scopeStates.get(ROOT_SCOPE))\n )\n}\n\nexport function setMirroredStateName(scopeId: string, stateName: string): void {\n scopeStates.set(scopeId, stateName)\n}\n\nexport function removeMirroredScope(scopeId: string): void {\n scopeStates.delete(scopeId)\n}\n\nexport function resetMirror(): void {\n scopeStates.clear()\n}\n"
10
+ ]
11
+ }
@@ -0,0 +1,3 @@
1
+ export declare function processStyleColors(props: Record<string, unknown>): Record<string, unknown>;
2
+
3
+ //# sourceMappingURL=processStyleColors.d.ts.map
@@ -0,0 +1,11 @@
1
+ {
2
+ "mappings": "AA4BA,OAAO,iBAAS,mBACd,OAAO,0BACN",
3
+ "names": [],
4
+ "sources": [
5
+ "src/processStyleColors.ts"
6
+ ],
7
+ "version": 3,
8
+ "sourcesContent": [
9
+ "/**\n * Fabric's C++ prop parser takes colors as ARGB ints, not CSS strings, so\n * every color prop must be converted before props reach the engine. The\n * compiler mappings and runtime-mode pushes both use this exact runtime\n * processor before props cross into the engine.\n */\nimport { processColor } from 'react-native'\n\nconst COLOR_PROPS = new Set([\n 'color',\n 'backgroundColor',\n 'borderColor',\n 'borderTopColor',\n 'borderBottomColor',\n 'borderLeftColor',\n 'borderRightColor',\n 'borderStartColor',\n 'borderEndColor',\n 'borderBlockColor',\n 'borderBlockStartColor',\n 'borderBlockEndColor',\n 'shadowColor',\n 'textShadowColor',\n 'textDecorationColor',\n 'tintColor',\n 'overlayColor',\n])\n\nexport function processStyleColors(\n props: Record<string, unknown>\n): Record<string, unknown> {\n let out: Record<string, unknown> | null = null\n for (const key in props) {\n const value = props[key]\n if (COLOR_PROPS.has(key) && typeof value === 'string') {\n out ??= { ...props }\n const processed = processColor(value)\n if (processed != null) out[key] = processed\n }\n }\n return out ?? props\n}\n"
10
+ ]
11
+ }