@symbiote-native/engine 0.1.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 (195) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +124 -0
  3. package/build/accessibility-info/index.android.d.ts +3 -0
  4. package/build/accessibility-info/index.android.js +166 -0
  5. package/build/accessibility-info/index.d.ts +1 -0
  6. package/build/accessibility-info/index.ios.d.ts +3 -0
  7. package/build/accessibility-info/index.ios.js +219 -0
  8. package/build/accessibility-info/index.js +5 -0
  9. package/build/accessibility-info/shared.d.ts +34 -0
  10. package/build/accessibility-info/shared.js +13 -0
  11. package/build/action-sheet-ios/index.d.ts +36 -0
  12. package/build/action-sheet-ios/index.js +74 -0
  13. package/build/alert/index.android.d.ts +5 -0
  14. package/build/alert/index.android.js +117 -0
  15. package/build/alert/index.d.ts +1 -0
  16. package/build/alert/index.ios.d.ts +7 -0
  17. package/build/alert/index.ios.js +83 -0
  18. package/build/alert/index.js +8 -0
  19. package/build/alert/shared.d.ts +19 -0
  20. package/build/alert/shared.js +17 -0
  21. package/build/animated/animated-component-shared.d.ts +5 -0
  22. package/build/animated/animated-component-shared.js +54 -0
  23. package/build/animated/animation.d.ts +9 -0
  24. package/build/animated/animation.js +6 -0
  25. package/build/animated/animations/base.d.ts +27 -0
  26. package/build/animated/animations/base.js +90 -0
  27. package/build/animated/animations/composition.d.ts +38 -0
  28. package/build/animated/animations/composition.js +236 -0
  29. package/build/animated/animations/decay.d.ts +22 -0
  30. package/build/animated/animations/decay.js +65 -0
  31. package/build/animated/animations/raf.d.ts +5 -0
  32. package/build/animated/animations/raf.js +39 -0
  33. package/build/animated/animations/spring-config.d.ts +6 -0
  34. package/build/animated/animations/spring-config.js +55 -0
  35. package/build/animated/animations/spring.d.ts +50 -0
  36. package/build/animated/animations/spring.js +207 -0
  37. package/build/animated/animations/timing.d.ts +27 -0
  38. package/build/animated/animations/timing.js +101 -0
  39. package/build/animated/animations/tracking.d.ts +14 -0
  40. package/build/animated/animations/tracking.js +43 -0
  41. package/build/animated/bezier.d.ts +1 -0
  42. package/build/animated/bezier.js +101 -0
  43. package/build/animated/color.d.ts +37 -0
  44. package/build/animated/color.js +183 -0
  45. package/build/animated/easing.d.ts +20 -0
  46. package/build/animated/easing.js +96 -0
  47. package/build/animated/event.d.ts +36 -0
  48. package/build/animated/event.js +252 -0
  49. package/build/animated/graph.d.ts +38 -0
  50. package/build/animated/graph.js +227 -0
  51. package/build/animated/index.d.ts +20 -0
  52. package/build/animated/index.js +28 -0
  53. package/build/animated/interpolation-node.d.ts +16 -0
  54. package/build/animated/interpolation-node.js +57 -0
  55. package/build/animated/interpolation.d.ts +22 -0
  56. package/build/animated/interpolation.js +199 -0
  57. package/build/animated/mock.d.ts +56 -0
  58. package/build/animated/mock.js +127 -0
  59. package/build/animated/native/native-animated.d.ts +43 -0
  60. package/build/animated/native/native-animated.js +146 -0
  61. package/build/animated/operators.d.ts +80 -0
  62. package/build/animated/operators.js +266 -0
  63. package/build/animated/props.d.ts +20 -0
  64. package/build/animated/props.js +187 -0
  65. package/build/animated/style.d.ts +26 -0
  66. package/build/animated/style.js +187 -0
  67. package/build/animated/value-xy.d.ts +35 -0
  68. package/build/animated/value-xy.js +106 -0
  69. package/build/animated/value.d.ts +36 -0
  70. package/build/animated/value.js +185 -0
  71. package/build/app-registry/index.d.ts +40 -0
  72. package/build/app-registry/index.js +144 -0
  73. package/build/app-state/index.d.ts +16 -0
  74. package/build/app-state/index.js +105 -0
  75. package/build/appearance/index.d.ts +12 -0
  76. package/build/appearance/index.js +84 -0
  77. package/build/back-handler/index.d.ts +14 -0
  78. package/build/back-handler/index.js +106 -0
  79. package/build/commit.d.ts +16 -0
  80. package/build/commit.js +678 -0
  81. package/build/debug.d.ts +5 -0
  82. package/build/debug.js +18 -0
  83. package/build/dimensions/index.d.ts +28 -0
  84. package/build/dimensions/index.js +148 -0
  85. package/build/dispatch.d.ts +2 -0
  86. package/build/dispatch.js +18 -0
  87. package/build/events/index.d.ts +1 -0
  88. package/build/events/index.js +691 -0
  89. package/build/fabric.d.ts +32 -0
  90. package/build/fabric.js +59 -0
  91. package/build/host-instance/index.d.ts +11 -0
  92. package/build/host-instance/index.js +49 -0
  93. package/build/i18n-manager/index.d.ts +13 -0
  94. package/build/i18n-manager/index.js +91 -0
  95. package/build/index.d.ts +80 -0
  96. package/build/index.js +72 -0
  97. package/build/interaction-manager/index.d.ts +45 -0
  98. package/build/interaction-manager/index.js +222 -0
  99. package/build/keyboard/index.d.ts +31 -0
  100. package/build/keyboard/index.js +142 -0
  101. package/build/layout-animation/index.d.ts +66 -0
  102. package/build/layout-animation/index.js +183 -0
  103. package/build/linking/index.android.d.ts +2 -0
  104. package/build/linking/index.android.js +18 -0
  105. package/build/linking/index.d.ts +1 -0
  106. package/build/linking/index.ios.d.ts +2 -0
  107. package/build/linking/index.ios.js +9 -0
  108. package/build/linking/index.js +6 -0
  109. package/build/linking/shared.d.ts +32 -0
  110. package/build/linking/shared.js +98 -0
  111. package/build/native-events.d.ts +24 -0
  112. package/build/native-events.js +129 -0
  113. package/build/native-modules.d.ts +6 -0
  114. package/build/native-modules.js +57 -0
  115. package/build/node.d.ts +36 -0
  116. package/build/node.js +194 -0
  117. package/build/pan-responder/index.d.ts +53 -0
  118. package/build/pan-responder/index.js +353 -0
  119. package/build/permissions-android/index.d.ts +115 -0
  120. package/build/permissions-android/index.js +185 -0
  121. package/build/pixel-ratio/index.d.ts +8 -0
  122. package/build/pixel-ratio/index.js +27 -0
  123. package/build/platform/index.android.d.ts +22 -0
  124. package/build/platform/index.android.js +60 -0
  125. package/build/platform/index.d.ts +1 -0
  126. package/build/platform/index.ios.d.ts +18 -0
  127. package/build/platform/index.ios.js +62 -0
  128. package/build/platform/index.js +5 -0
  129. package/build/platform/shared.d.ts +25 -0
  130. package/build/platform/shared.js +41 -0
  131. package/build/platform-color.d.ts +19 -0
  132. package/build/platform-color.js +25 -0
  133. package/build/post-commit.d.ts +4 -0
  134. package/build/post-commit.js +16 -0
  135. package/build/process-aspect-ratio.d.ts +1 -0
  136. package/build/process-aspect-ratio.js +34 -0
  137. package/build/process-background-image/index.d.ts +28 -0
  138. package/build/process-background-image/index.js +557 -0
  139. package/build/process-box-shadow/index.d.ts +11 -0
  140. package/build/process-box-shadow/index.js +193 -0
  141. package/build/process-filter.d.ts +31 -0
  142. package/build/process-filter.js +304 -0
  143. package/build/process-font-variant.d.ts +1 -0
  144. package/build/process-font-variant.js +17 -0
  145. package/build/process-transform/index.d.ts +5 -0
  146. package/build/process-transform/index.js +120 -0
  147. package/build/process-transform-origin/index.d.ts +3 -0
  148. package/build/process-transform-origin/index.js +108 -0
  149. package/build/registry.d.ts +31 -0
  150. package/build/registry.js +145 -0
  151. package/build/settings/index.d.ts +8 -0
  152. package/build/settings/index.js +126 -0
  153. package/build/share/index.android.d.ts +3 -0
  154. package/build/share/index.android.js +56 -0
  155. package/build/share/index.d.ts +1 -0
  156. package/build/share/index.ios.d.ts +3 -0
  157. package/build/share/index.ios.js +47 -0
  158. package/build/share/index.js +6 -0
  159. package/build/share/shared.d.ts +32 -0
  160. package/build/share/shared.js +32 -0
  161. package/build/status-bar/index.android.d.ts +5 -0
  162. package/build/status-bar/index.android.js +83 -0
  163. package/build/status-bar/index.d.ts +1 -0
  164. package/build/status-bar/index.ios.d.ts +5 -0
  165. package/build/status-bar/index.ios.js +66 -0
  166. package/build/status-bar/index.js +4 -0
  167. package/build/status-bar/shared.d.ts +22 -0
  168. package/build/status-bar/shared.js +22 -0
  169. package/build/style/index.d.ts +1 -0
  170. package/build/style/index.js +30 -0
  171. package/build/style-registry/index.d.ts +11 -0
  172. package/build/style-registry/index.js +165 -0
  173. package/build/style-sheet/index.d.ts +20 -0
  174. package/build/style-sheet/index.js +121 -0
  175. package/build/styles.d.ts +220 -0
  176. package/build/styles.js +7 -0
  177. package/build/surface.d.ts +16 -0
  178. package/build/surface.js +67 -0
  179. package/build/tags.d.ts +1 -0
  180. package/build/tags.js +10 -0
  181. package/build/text-input-state.d.ts +5 -0
  182. package/build/text-input-state.js +29 -0
  183. package/build/toast-android/index.d.ts +10 -0
  184. package/build/toast-android/index.js +108 -0
  185. package/build/vibration/index.android.d.ts +2 -0
  186. package/build/vibration/index.android.js +18 -0
  187. package/build/vibration/index.d.ts +1 -0
  188. package/build/vibration/index.ios.d.ts +2 -0
  189. package/build/vibration/index.ios.js +54 -0
  190. package/build/vibration/index.js +6 -0
  191. package/build/vibration/shared.d.ts +15 -0
  192. package/build/vibration/shared.js +68 -0
  193. package/build/view-config.d.ts +1 -0
  194. package/build/view-config.js +114 -0
  195. package/package.json +41 -0
@@ -0,0 +1,108 @@
1
+ // JS-side port of RN's processTransformOrigin (Libraries/StyleSheet/processTransformOrigin.js).
2
+ // Same root cause as boxShadow/filter: ReactNativeStyleAttributes registers `transformOrigin`
3
+ // with `nativeCSSParsing ? true : {process: processTransformOrigin}`, and enableNativeCSSParsing()
4
+ // DEFAULTS TO FALSE, so RN's stock path runs processTransformOrigin IN JS, turning the CSS
5
+ // string ('top left') into the [x, y, z] array native expects. symbiote forwarded the raw
6
+ // string: iOS native tolerated it, Android native casts it to ReadableArray and crashes with
7
+ // `java.lang.String cannot be cast to com.facebook.react.bridge.ReadableArray`. This restores
8
+ // the missing JS parse so native always receives the [x, y, z] array.
9
+ //
10
+ // RN throws via invariant() on a malformed value; we instead dlog and keep the partial array
11
+ // (matching the boxShadow/filter idiom of never throwing into the commit path).
12
+ import { dlog } from '../debug';
13
+ // RN processTransformOrigin.js:14, pre-compiled pattern matching each keyword / length token.
14
+ const TRANSFORM_ORIGIN_REGEX = /(top|bottom|left|right|center|\d+(?:%|px)|0)/gi;
15
+ // RN processTransformOrigin.js:16-18.
16
+ const INDEX_X = 0;
17
+ const INDEX_Y = 1;
18
+ const INDEX_Z = 2;
19
+ // RN processTransformOrigin.js:21-120. Parses the CSS string into the [x, y, z] array,
20
+ // or normalizes/passes through an array input unchanged.
21
+ export function processTransformOrigin(transformOrigin) {
22
+ if (transformOrigin == null) {
23
+ // RN never receives undefined here (the registry only calls the processor for a
24
+ // present value), but the commit path may; default to center/center/0.
25
+ return ['50%', '50%', 0];
26
+ }
27
+ if (typeof transformOrigin !== 'string') {
28
+ // Array input passes through, like RN (which only re-validates in __DEV__).
29
+ return transformOrigin;
30
+ }
31
+ const transformOriginString = transformOrigin;
32
+ TRANSFORM_ORIGIN_REGEX.lastIndex = 0;
33
+ const transformOriginArray = ['50%', '50%', 0];
34
+ let index = INDEX_X;
35
+ let matches;
36
+ outer: while ((matches = TRANSFORM_ORIGIN_REGEX.exec(transformOriginString))) {
37
+ let nextIndex = index + 1;
38
+ const value = matches[0];
39
+ const valueLower = value.toLowerCase();
40
+ switch (valueLower) {
41
+ case 'left':
42
+ case 'right': {
43
+ // RN processTransformOrigin.js:42-46, left/right are x-only.
44
+ if (index !== INDEX_X) {
45
+ dlog(`processTransformOrigin reject: "${value}" can only be used for x-position`);
46
+ return transformOriginArray;
47
+ }
48
+ transformOriginArray[INDEX_X] = valueLower === 'left' ? 0 : '100%';
49
+ break;
50
+ }
51
+ case 'top':
52
+ case 'bottom': {
53
+ // RN processTransformOrigin.js:52-56, top/bottom are not valid for z.
54
+ if (index === INDEX_Z) {
55
+ dlog(`processTransformOrigin reject: "${value}" can only be used for y-position`);
56
+ return transformOriginArray;
57
+ }
58
+ transformOriginArray[INDEX_Y] = valueLower === 'top' ? 0 : '100%';
59
+ // RN processTransformOrigin.js:59-86, handle [[ center | left | right ] &&
60
+ // [ center | top | bottom ]] <length>? When y came first, the next token is x.
61
+ if (index === INDEX_X) {
62
+ const horizontal = TRANSFORM_ORIGIN_REGEX.exec(transformOriginString);
63
+ if (horizontal == null) {
64
+ break outer;
65
+ }
66
+ switch (horizontal[0].toLowerCase()) {
67
+ case 'left':
68
+ transformOriginArray[INDEX_X] = 0;
69
+ break;
70
+ case 'right':
71
+ transformOriginArray[INDEX_X] = '100%';
72
+ break;
73
+ case 'center':
74
+ transformOriginArray[INDEX_X] = '50%';
75
+ break;
76
+ default:
77
+ dlog(`processTransformOrigin reject: could not parse "${transformOriginString}"`);
78
+ return transformOriginArray;
79
+ }
80
+ nextIndex = INDEX_Z;
81
+ }
82
+ break;
83
+ }
84
+ case 'center': {
85
+ // RN processTransformOrigin.js:91-95, center is invalid for z.
86
+ if (index === INDEX_Z) {
87
+ dlog(`processTransformOrigin reject: "${value}" cannot be used for z-position`);
88
+ return transformOriginArray;
89
+ }
90
+ transformOriginArray[index] = '50%';
91
+ break;
92
+ }
93
+ default: {
94
+ // RN processTransformOrigin.js:99-105, a percentage stays a string, a length
95
+ // drops its `px` and becomes a number.
96
+ if (value.endsWith('%')) {
97
+ transformOriginArray[index] = value;
98
+ }
99
+ else {
100
+ transformOriginArray[index] = parseFloat(value);
101
+ }
102
+ break;
103
+ }
104
+ }
105
+ index = nextIndex;
106
+ }
107
+ return transformOriginArray;
108
+ }
@@ -0,0 +1,31 @@
1
+ export type IPropProcessor = (value: unknown) => unknown;
2
+ export interface INativeEventBinding {
3
+ raw: string;
4
+ listener: string;
5
+ direct?: boolean;
6
+ }
7
+ export interface IComponentRegistration {
8
+ events?: readonly INativeEventBinding[];
9
+ processors?: Readonly<Record<string, IPropProcessor>>;
10
+ }
11
+ interface IPhasedRegistrationNames {
12
+ bubbled?: string;
13
+ }
14
+ interface IBubblingEventType {
15
+ phasedRegistrationNames?: IPhasedRegistrationNames;
16
+ }
17
+ interface IDirectEventType {
18
+ registrationName?: string;
19
+ }
20
+ export interface INativeViewConfig {
21
+ bubblingEventTypes?: Record<string, IBubblingEventType | null | undefined>;
22
+ directEventTypes?: Record<string, IDirectEventType | null | undefined>;
23
+ validAttributes?: Record<string, unknown>;
24
+ }
25
+ export type INativeViewConfigSource = (name: string) => INativeViewConfig | undefined;
26
+ export declare function setNativeViewConfigSource(source: INativeViewConfigSource): void;
27
+ export declare function registerComponent(name: string, registration?: IComponentRegistration): void;
28
+ export declare function isRegisteredEvent(component: string, listener: string): boolean;
29
+ export declare function registeredNativeEvent(component: string, raw: string): INativeEventBinding | undefined;
30
+ export declare function registeredProcessor(component: string, key: string): IPropProcessor | undefined;
31
+ export {};
@@ -0,0 +1,145 @@
1
+ // Runtime metadata for native Fabric views, DERIVED BY DEFAULT. Any RN library
2
+ // already ships its own ViewConfig: codegen registers it into RN's
3
+ // ReactNativeViewConfigRegistry the moment the library's native-component module is
4
+ // imported. That config carries everything the shared engine can't infer: which
5
+ // events the view emits (bubblingEventTypes / directEventTypes) and how to process
6
+ // its props (validAttributes[*].process, e.g. processColor). So we DON'T transcribe
7
+ // any of it, and we don't mark anything "third-party": there is no
8
+ // per-package registration to maintain. The engine reads the config for ANY
9
+ // component on first use. Install a community view library, render it, done.
10
+ //
11
+ // shared must stay react-native-free (the headless harness runs in plain Node), so
12
+ // the ViewConfig lookup is INJECTED, exactly like the color processor: the adapter
13
+ // wires `setNativeViewConfigSource(ReactNativeViewConfigRegistry.get)` on a real
14
+ // host (on a real host that one source covers BOTH RN core and every library).
15
+ //
16
+ // The ONLY explicit list is OUR OWN built-in primitives (BUILTIN_COMPONENTS): a
17
+ // finite set we own, which keep their hand-tuned tables (view-config events, commit
18
+ // COLOR_PROPS) and are never read from the source, so they can't drift. Everything
19
+ // NOT in that set derives. The list never grows with the community; it grows only
20
+ // when we add a core primitive of our own.
21
+ const EMPTY = { listeners: new Set(), byRaw: new Map(), processors: new Map() };
22
+ // OUR own primitives: the finite set shared hand-tunes (view-config events,
23
+ // commit COLOR_PROPS). The source is never consulted for these, so they can't
24
+ // drift. Everything else derives. This list grows only when WE add a core
25
+ // primitive, never for a community package.
26
+ const BUILTIN_COMPONENTS = new Set([
27
+ 'RCTView',
28
+ 'RCTText',
29
+ 'RCTRawText',
30
+ 'RCTVirtualText',
31
+ 'RCTImageView',
32
+ 'RCTScrollView',
33
+ 'RCTScrollContentView',
34
+ 'RCTSinglelineTextInputView',
35
+ 'RCTMultilineTextInputView',
36
+ 'Switch',
37
+ 'ActivityIndicatorView',
38
+ 'SafeAreaView',
39
+ 'ModalHostView',
40
+ 'PullToRefreshView',
41
+ 'RCTInputAccessoryView',
42
+ ]);
43
+ // Manual overrides per component (usually none): the escape hatch.
44
+ const overrides = new Map();
45
+ const resolvedCache = new Map();
46
+ let viewConfigSource;
47
+ // Wired once by the adapter on a real host: `name => ReactNativeViewConfigRegistry.get(name)`.
48
+ export function setNativeViewConfigSource(source) {
49
+ viewConfigSource = source;
50
+ resolvedCache.clear();
51
+ }
52
+ // Escape hatch: override a derived config, or supply one for a view with no codegen
53
+ // ViewConfig. NOT needed on the common path; views derive from the source.
54
+ export function registerComponent(name, registration = {}) {
55
+ const list = overrides.get(name);
56
+ if (list === undefined)
57
+ overrides.set(name, [registration]);
58
+ else
59
+ list.push(registration);
60
+ resolvedCache.delete(name);
61
+ }
62
+ function isRecord(value) {
63
+ return typeof value === 'object' && value !== null;
64
+ }
65
+ // onChange -> change (mirrors node.ts listenerName; the split of the handler prop).
66
+ function splitListener(handlerProp) {
67
+ return handlerProp.charAt(2).toLowerCase() + handlerProp.slice(3);
68
+ }
69
+ function addEvent(into, binding) {
70
+ into.listeners.add(binding.listener);
71
+ into.byRaw.set(binding.raw, binding);
72
+ }
73
+ function deriveFromConfig(config, into) {
74
+ const { bubblingEventTypes, directEventTypes, validAttributes } = config;
75
+ if (bubblingEventTypes) {
76
+ for (const raw in bubblingEventTypes) {
77
+ const bubbled = bubblingEventTypes[raw]?.phasedRegistrationNames?.bubbled;
78
+ if (typeof bubbled === 'string')
79
+ addEvent(into, { raw, listener: splitListener(bubbled) });
80
+ }
81
+ }
82
+ if (directEventTypes) {
83
+ for (const raw in directEventTypes) {
84
+ const registrationName = directEventTypes[raw]?.registrationName;
85
+ if (typeof registrationName === 'string') {
86
+ addEvent(into, { raw, listener: splitListener(registrationName), direct: true });
87
+ }
88
+ }
89
+ }
90
+ if (validAttributes) {
91
+ for (const key in validAttributes) {
92
+ const attribute = validAttributes[key];
93
+ if (isRecord(attribute)) {
94
+ const process = attribute.process;
95
+ // The codegen config already carries the right processor (processColor, …);
96
+ // wrap it so the typed Function becomes a PropProcessor without a cast.
97
+ if (typeof process === 'function')
98
+ into.processors.set(key, value => process(value));
99
+ }
100
+ }
101
+ }
102
+ }
103
+ function applyRegistration(registration, into) {
104
+ if (registration.events)
105
+ for (const binding of registration.events)
106
+ addEvent(into, binding);
107
+ if (registration.processors) {
108
+ for (const key of Object.keys(registration.processors)) {
109
+ into.processors.set(key, registration.processors[key]);
110
+ }
111
+ }
112
+ }
113
+ // Resolve a component's metadata. OUR built-ins short-circuit to EMPTY so the
114
+ // source is never read for them (their hand-tuned tables stand). Everything else
115
+ // derives from the injected source, then any manual override wins on top.
116
+ function resolve(name) {
117
+ if (BUILTIN_COMPONENTS.has(name))
118
+ return EMPTY;
119
+ let resolved = resolvedCache.get(name);
120
+ if (resolved !== undefined)
121
+ return resolved;
122
+ resolved = { listeners: new Set(), byRaw: new Map(), processors: new Map() };
123
+ const config = viewConfigSource?.(name);
124
+ if (config)
125
+ deriveFromConfig(config, resolved);
126
+ const registrations = overrides.get(name);
127
+ if (registrations)
128
+ for (const registration of registrations)
129
+ applyRegistration(registration, resolved);
130
+ resolvedCache.set(name, resolved);
131
+ return resolved;
132
+ }
133
+ // True when `listener` is an event the (third-party) component emits.
134
+ export function isRegisteredEvent(component, listener) {
135
+ return resolve(component).listeners.has(listener);
136
+ }
137
+ // The binding for a raw Fabric event on this component, for incoming dispatch.
138
+ export function registeredNativeEvent(component, raw) {
139
+ return resolve(component).byRaw.get(raw);
140
+ }
141
+ // The processor for a prop of this component (e.g. processColor for a tint), or
142
+ // undefined to leave the value untouched.
143
+ export function registeredProcessor(component, key) {
144
+ return resolve(component).processors.get(key);
145
+ }
@@ -0,0 +1,8 @@
1
+ declare class SettingsImpl {
2
+ get(key: string): unknown;
3
+ set(settings: Record<string, unknown>): void;
4
+ watchKeys(keys: string | string[], callback: () => void): number;
5
+ clearWatch(watchId: number): void;
6
+ }
7
+ export declare const Settings: SettingsImpl;
8
+ export {};
@@ -0,0 +1,126 @@
1
+ // Settings module: reads/writes the app's persisted defaults (iOS NSUserDefaults
2
+ // via the native SettingsManager). A JS-side snapshot seeded from the native module
3
+ // constants answers `get`; `set` writes through to native and updates the snapshot;
4
+ // `watchKeys`/`clearWatch` are a pure-JS subscription registry that fires when a
5
+ // watched key's value changes. Native re-broadcasts external edits through the
6
+ // device event `settingsUpdated`, which feeds the same change->fire path. Mirrors
7
+ // RN's Libraries/Settings/Settings.ios.js, iOS surface only.
8
+ import { installDeviceEventHub, NativeEventEmitter, } from '../native-events';
9
+ import { getNativeModule } from '../native-modules';
10
+ import { dlog } from '../debug';
11
+ // The iOS native module name RN registers this under. NOTE: this is the name the
12
+ // iOS JS wrapper resolves via `TurboModuleRegistry.getEnforcing('SettingsManager')`.
13
+ // The spec filename is `INativeSettingsManager`. Per the symbiote invariant, a
14
+ // module name is only provable on a real host (a headless fake answers to any
15
+ // name); this iOS name is device-verify-pending.
16
+ // See .docs/native-module-platform-routing.md.
17
+ const SETTINGS_MODULE = 'SettingsManager';
18
+ // The device event native emits when the app's defaults change out from under JS
19
+ // (e.g. a Settings.bundle edit). Its payload is a record of changed key->value.
20
+ const SETTINGS_UPDATED_EVENT = 'settingsUpdated';
21
+ // watchIds are handed out as the registry index, so the first watcher is 0. A
22
+ // cleared slot keeps its index (the array never shrinks) so later ids stay stable.
23
+ const FIRST_WATCH_ID = 0;
24
+ function isRecord(value) {
25
+ return typeof value === 'object' && value !== null;
26
+ }
27
+ // Lazily resolved so importing this module has no native side effect. `null` when
28
+ // the module isn't linked (headless): the snapshot then starts empty and `set`
29
+ // updates only JS state + fires watchers.
30
+ let settingsModule;
31
+ function getModule() {
32
+ if (settingsModule === undefined) {
33
+ settingsModule = getNativeModule(SETTINGS_MODULE);
34
+ dlog(`Settings: module ${settingsModule ? 'resolved' : 'NOT resolved (null)'}`);
35
+ }
36
+ return settingsModule;
37
+ }
38
+ // The JS-side mirror of native defaults: seeded once from the module constants,
39
+ // kept in sync by `set` and by the `settingsUpdated` device event.
40
+ let snapshot;
41
+ function getSnapshot() {
42
+ if (snapshot === undefined) {
43
+ const module = getModule();
44
+ const constants = module?.getConstants().settings;
45
+ snapshot = isRecord(constants) ? { ...constants } : {};
46
+ dlog(`Settings: snapshot seeded with ${Object.keys(snapshot).length} key(s)`);
47
+ }
48
+ return snapshot;
49
+ }
50
+ const subscriptions = [];
51
+ // Fire every watcher whose key set covers `key`. Called only for keys that changed.
52
+ function fireWatchers(key) {
53
+ for (const sub of subscriptions) {
54
+ if (sub.callback !== null && sub.keys.includes(key))
55
+ sub.callback();
56
+ }
57
+ }
58
+ // Fold a batch of new values into the snapshot, firing watchers for each key whose
59
+ // value actually changed. Shared by `set` (JS-originated) and the device event
60
+ // (native-originated), matching RN's `_sendObservations`.
61
+ function applyChanges(values) {
62
+ const current = getSnapshot();
63
+ for (const key of Object.keys(values)) {
64
+ const next = values[key];
65
+ const didChange = current[key] !== next;
66
+ current[key] = next;
67
+ if (didChange)
68
+ fireWatchers(key);
69
+ }
70
+ }
71
+ let emitter;
72
+ // Subscribe to native's `settingsUpdated` so external edits flow into the snapshot.
73
+ // WHY lazy + module-gated: importing this file must have no native side effect, and
74
+ // with no native module there is nothing native to observe, so a headless run that
75
+ // never installs the device hub doesn't crash. Idempotent.
76
+ function subscribeToNative() {
77
+ if (emitter !== undefined)
78
+ return;
79
+ const module = getModule();
80
+ if (module === null)
81
+ return;
82
+ installDeviceEventHub();
83
+ emitter = new NativeEventEmitter(module);
84
+ emitter.addListener(SETTINGS_UPDATED_EVENT, payload => {
85
+ if (!isRecord(payload))
86
+ return;
87
+ applyChanges(payload);
88
+ });
89
+ }
90
+ class SettingsImpl {
91
+ // Read the current value for `key` from the JS snapshot. `undefined` if unset.
92
+ get(key) {
93
+ subscribeToNative();
94
+ return getSnapshot()[key];
95
+ }
96
+ // Persist `settings` to native, update the snapshot, and fire watchers for the
97
+ // keys that changed. Without a native module (headless) only JS state updates.
98
+ set(settings) {
99
+ subscribeToNative();
100
+ const module = getModule();
101
+ if (module === null) {
102
+ dlog('Settings.set -> no module (JS snapshot + watchers only)');
103
+ }
104
+ else {
105
+ module.setValues(settings);
106
+ }
107
+ applyChanges(settings);
108
+ }
109
+ // Register a watcher for one or more keys. Returns a numeric watchId (the registry
110
+ // index) to pass to `clearWatch`.
111
+ watchKeys(keys, callback) {
112
+ subscribeToNative();
113
+ const watched = typeof keys === 'string' ? [keys] : keys;
114
+ const watchId = subscriptions.length + FIRST_WATCH_ID;
115
+ subscriptions.push({ keys: watched, callback });
116
+ return watchId;
117
+ }
118
+ // Disarm the watcher with the given id. Its slot is emptied (kept, not removed) so
119
+ // every other watchId stays valid.
120
+ clearWatch(watchId) {
121
+ if (watchId >= FIRST_WATCH_ID && watchId < subscriptions.length) {
122
+ subscriptions[watchId] = { keys: [], callback: null };
123
+ }
124
+ }
125
+ }
126
+ export const Settings = new SettingsImpl();
@@ -0,0 +1,3 @@
1
+ import type { IShareStatic } from './shared';
2
+ export type { IShareContent, IShareOptions, IShareAction } from './shared';
3
+ export declare const Share: IShareStatic;
@@ -0,0 +1,56 @@
1
+ // Share (Android build). The native module is `ShareModule`:
2
+ // `share(content, dialogTitle?) -> Promise<{ action }>`. We validate content, build the
3
+ // content dict (title/message), forward the dialog title, and map the resolved action
4
+ // onto the shared IShareAction shape; Android has no dismiss path, so RN fills the
5
+ // missing activityType with null. Metro picks this file on an Android host. See ADR 0019.
6
+ //
7
+ // device-verify-pending: the `ShareModule` name matches NativeShareModule's
8
+ // TurboModuleRegistry.get('ShareModule') from RN source, but headless fakes resolve any
9
+ // name, so it is only proven on a real Android host (a bridgeless resolution log there).
10
+ // See .docs/native-module-platform-routing.md, ADR 0012.
11
+ import { dlog } from '../debug';
12
+ import { getNativeModule } from '../native-modules';
13
+ import { validateContent, shareActions, SHARED_ACTION, DISMISSED_ACTION } from './shared';
14
+ const SHARE_MODULE = 'ShareModule';
15
+ // ShareModule.share resolves an untyped value at the native boundary; narrow it before
16
+ // reading `action` (no `as`).
17
+ function isShareResult(value) {
18
+ return (typeof value === 'object' &&
19
+ value !== null &&
20
+ 'action' in value &&
21
+ typeof value.action === 'string');
22
+ }
23
+ export const Share = {
24
+ ...shareActions,
25
+ // Open the Android share dialog for `content`. Resolves with the user's action
26
+ // (Android always resolves sharedAction); rejects on invalid content, an unexpected
27
+ // native result, or a missing module (explicit reject, never a hung Promise).
28
+ share(content, options = {}) {
29
+ const invalid = validateContent(content);
30
+ if (invalid !== null) {
31
+ dlog(`Share.share -> invalid content: ${invalid.message}`);
32
+ return Promise.reject(invalid);
33
+ }
34
+ dlog('Share.share (android)');
35
+ const shareModule = getNativeModule(SHARE_MODULE);
36
+ if (shareModule === null) {
37
+ dlog(`Share: "${SHARE_MODULE}" unresolved`);
38
+ return Promise.reject(new Error('Share: ShareModule native module unavailable'));
39
+ }
40
+ const newContent = {
41
+ title: content.title,
42
+ message: typeof content.message === 'string' ? content.message : undefined,
43
+ };
44
+ return shareModule.share(newContent, options.dialogTitle).then(result => {
45
+ if (!isShareResult(result)) {
46
+ dlog('Share.share -> android result missing action');
47
+ throw new Error('Share: ShareModule returned an unexpected result');
48
+ }
49
+ dlog(`Share.share -> android action=${result.action}`);
50
+ return {
51
+ action: result.action === DISMISSED_ACTION ? DISMISSED_ACTION : SHARED_ACTION,
52
+ activityType: null,
53
+ };
54
+ });
55
+ },
56
+ };
@@ -0,0 +1 @@
1
+ export * from './index.ios';
@@ -0,0 +1,3 @@
1
+ import type { IShareStatic } from './shared';
2
+ export type { IShareContent, IShareOptions, IShareAction } from './shared';
3
+ export declare const Share: IShareStatic;
@@ -0,0 +1,47 @@
1
+ // Share, iOS build. The native module is `ActionSheetManager` (there is NO ShareModule
2
+ // on iOS; that is the Android module); the share sheet is driven by its callback-style
3
+ // `showShareActionSheetWithOptions(options, failure, success)`. We validate content, map
4
+ // options onto the native share options, and resolve the IShareAction from the success
5
+ // callback. Metro picks this file on an iOS host; the base share.ts re-exports it for
6
+ // web/headless. See ADR 0019.
7
+ import { dlog } from '../debug';
8
+ import { getNativeModule } from '../native-modules';
9
+ import { validateContent, shareActions, SHARED_ACTION, DISMISSED_ACTION } from './shared';
10
+ const ACTION_SHEET_MANAGER = 'ActionSheetManager';
11
+ export const Share = {
12
+ ...shareActions,
13
+ // Open the iOS share sheet for `content`. Resolves with the user's action
14
+ // (sharedAction / dismissedAction); rejects on invalid content, a native failure,
15
+ // or a missing module (explicit reject rather than a Promise that never settles).
16
+ share(content, options = {}) {
17
+ const invalid = validateContent(content);
18
+ if (invalid !== null) {
19
+ dlog(`Share.share -> invalid content: ${invalid.message}`);
20
+ return Promise.reject(invalid);
21
+ }
22
+ dlog('Share.share (ios)');
23
+ const manager = getNativeModule(ACTION_SHEET_MANAGER);
24
+ if (manager === null) {
25
+ dlog(`Share: "${ACTION_SHEET_MANAGER}" unresolved`);
26
+ return Promise.reject(new Error('Share: ActionSheetManager native module unavailable'));
27
+ }
28
+ return new Promise((resolve, reject) => {
29
+ manager.showShareActionSheetWithOptions({
30
+ message: typeof content.message === 'string' ? content.message : undefined,
31
+ url: typeof content.url === 'string' ? content.url : undefined,
32
+ subject: options.subject,
33
+ tintColor: options.tintColor,
34
+ anchor: options.anchor,
35
+ excludedActivityTypes: options.excludedActivityTypes,
36
+ }, error => {
37
+ dlog('Share.share -> failure');
38
+ reject(new Error(error.message));
39
+ }, (completed, activityType) => {
40
+ dlog(`Share.share -> success completed=${completed}`);
41
+ resolve(completed
42
+ ? { action: SHARED_ACTION, activityType: activityType ?? null }
43
+ : { action: DISMISSED_ACTION, activityType: null });
44
+ });
45
+ });
46
+ },
47
+ };
@@ -0,0 +1,6 @@
1
+ // Share: base / default build (web, headless tsx, any target without a dedicated
2
+ // platform file). Metro overrides this with share.ios.ts / share.android.ts on a real
3
+ // iOS/Android host; off those, the iOS build is the fallback (its ActionSheetManager
4
+ // resolves null elsewhere → graceful reject). The barrel imports './share', which
5
+ // resolves here under tsc/tsx and to the platform file under Metro. See ADR 0019.
6
+ export * from './index.ios';
@@ -0,0 +1,32 @@
1
+ export type IShareContent = {
2
+ title?: string;
3
+ url: string;
4
+ message?: string;
5
+ } | {
6
+ title?: string;
7
+ url?: string;
8
+ message: string;
9
+ };
10
+ export interface IShareOptions {
11
+ dialogTitle?: string;
12
+ subject?: string;
13
+ excludedActivityTypes?: string[];
14
+ tintColor?: unknown;
15
+ anchor?: number;
16
+ }
17
+ export declare const SHARED_ACTION = "sharedAction";
18
+ export declare const DISMISSED_ACTION = "dismissedAction";
19
+ export interface IShareAction {
20
+ action: typeof SHARED_ACTION | typeof DISMISSED_ACTION;
21
+ activityType?: string | null;
22
+ }
23
+ export interface IShareStatic {
24
+ share(content: IShareContent, options?: IShareOptions): Promise<IShareAction>;
25
+ sharedAction: typeof SHARED_ACTION;
26
+ dismissedAction: typeof DISMISSED_ACTION;
27
+ }
28
+ export declare const shareActions: {
29
+ sharedAction: typeof SHARED_ACTION;
30
+ dismissedAction: typeof DISMISSED_ACTION;
31
+ };
32
+ export declare function validateContent(content: IShareContent): Error | null;
@@ -0,0 +1,32 @@
1
+ // Shared core of the Share module, only what does NOT differ by platform: the public
2
+ // types (IShareContent / IShareOptions / IShareAction), the public contract (IShareStatic),
3
+ // and the content invariant. Share is almost entirely divergent (one method, a totally
4
+ // different native call per platform), so there is no shared factory: each platform file
5
+ // (share.ios.ts / share.android.ts) implements `share()` fully against its own module.
6
+ //
7
+ // Metro selects the platform file on a real host (share.android.ts > share.ts); the base
8
+ // share.ts re-exports the iOS build for web/headless. There is no runtime `Platform.OS`
9
+ // read: the filename is the selector. See ADR 0019.
10
+ // RN's Share action constants (RN Share.js ~173/179). These back the documented
11
+ // `result.action === Share.dismissedAction` pattern. True statically-known literals,
12
+ // so CONSTANT_CASE; the public fields (Share.sharedAction / Share.dismissedAction) are
13
+ // lowerCamel, assigned from these below.
14
+ export const SHARED_ACTION = 'sharedAction';
15
+ export const DISMISSED_ACTION = 'dismissedAction';
16
+ // The constant fields every platform's Share exposes, spread onto the platform Share
17
+ // object so both builds carry them identically.
18
+ export const shareActions = {
19
+ sharedAction: SHARED_ACTION,
20
+ dismissedAction: DISMISSED_ACTION,
21
+ };
22
+ // RN's invariant: return an Error (caller rejects rather than throws) so a bad call
23
+ // can't unmount the tree on device. Shared because the rule is identical per platform.
24
+ export function validateContent(content) {
25
+ if (typeof content !== 'object' || content === null) {
26
+ return new Error('Content to share must be a valid object');
27
+ }
28
+ if (typeof content.url !== 'string' && typeof content.message !== 'string') {
29
+ return new Error('At least one of URL or message is required');
30
+ }
31
+ return null;
32
+ }
@@ -0,0 +1,5 @@
1
+ import { type IStatusBarImperative, type IStatusBarProps } from './shared';
2
+ export type { IStatusBarProps, IStatusBarStyle } from './shared';
3
+ export declare function applyStatusBarProps(props: IStatusBarProps): void;
4
+ export declare const statusBarImperative: IStatusBarImperative;
5
+ export declare function statusBarCurrentHeight(): number | undefined;