@symbiote-native/engine 1.3.0 → 1.3.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 (54) hide show
  1. package/build/accessibility-props.d.ts +0 -11
  2. package/build/accessibility-props.js +30 -68
  3. package/build/animated/graph.js +1 -1
  4. package/build/animated/leaf-lifecycle.js +2 -2
  5. package/build/asset-source-resolver.d.ts +2 -0
  6. package/build/asset-source-resolver.js +13 -0
  7. package/build/back-handler/index.d.ts +1 -5
  8. package/build/back-handler/index.js +0 -6
  9. package/build/debug.js +8 -22
  10. package/build/dispatch.js +3 -9
  11. package/build/events/index.js +2 -2
  12. package/build/fabric-props.js +74 -179
  13. package/build/fabric.d.ts +0 -13
  14. package/build/fabric.js +18 -38
  15. package/build/host-access.d.ts +1 -128
  16. package/build/host-access.js +96 -205
  17. package/build/host-behavior.d.ts +0 -100
  18. package/build/host-behavior.js +125 -311
  19. package/build/image-loader.js +10 -23
  20. package/build/image-source-resolver.js +3 -7
  21. package/build/image-source-write.d.ts +0 -11
  22. package/build/image-source-write.js +14 -34
  23. package/build/imperative.d.ts +0 -28
  24. package/build/imperative.js +42 -92
  25. package/build/index.d.ts +1 -0
  26. package/build/index.js +24 -37
  27. package/build/mutation-buffer.d.ts +0 -177
  28. package/build/mutation-buffer.js +137 -308
  29. package/build/native-engine.d.ts +0 -102
  30. package/build/native-engine.js +38 -98
  31. package/build/native-events.js +9 -18
  32. package/build/native-tree-host.d.ts +0 -21
  33. package/build/native-tree-host.js +14 -31
  34. package/build/node.d.ts +0 -212
  35. package/build/node.js +338 -774
  36. package/build/post-commit.js +3 -8
  37. package/build/process-aspect-ratio.js +3 -7
  38. package/build/process-background-longhands.js +10 -19
  39. package/build/process-filter.js +11 -19
  40. package/build/process-font-variant.js +3 -7
  41. package/build/registry.d.ts +0 -33
  42. package/build/registry.js +22 -57
  43. package/build/report-error.js +4 -18
  44. package/build/structured-style.d.ts +0 -9
  45. package/build/structured-style.js +16 -31
  46. package/build/styles.js +3 -6
  47. package/build/surface.d.ts +0 -26
  48. package/build/surface.js +29 -76
  49. package/build/text-input-state.js +4 -8
  50. package/build/touch-history.js +5 -11
  51. package/build/tree-host.d.ts +0 -270
  52. package/build/tree-host.js +63 -153
  53. package/build/view-config.js +17 -37
  54. package/package.json +2 -2
@@ -1,11 +1,6 @@
1
- // Post-commit hooks: run once after every native commit that changed the tree (after
2
- // completeRoot, when fresh Fabric tags have just been assigned). The seam exists so a
3
- // consumer that needs a node's committed tag — today only the Animated native driver,
4
- // which binds a props node to a view tag — can retry work that ran too early under an
5
- // async-batched commit (Vue/Svelte schedule completeRoot on a microtask, so lifecycle
6
- // code can reach a node before its tag exists; React commits synchronously and never
7
- // queues). A neutral module so commit.ts and animated/props.ts share it without a
8
- // dependency cycle.
1
+ // Run once after every commit that changed the tree, once fresh Fabric tags exist — lets a
2
+ // consumer needing a node's committed tag (the Animated native driver) retry work that ran too
3
+ // early under an async-batched commit. Shared by commit.ts and animated/props.ts, cycle-free.
9
4
  const hooks = new Set();
10
5
  export function registerPostCommit(hook) {
11
6
  hooks.add(hook);
@@ -1,10 +1,6 @@
1
- // JS-side port of RN's processAspectRatio (Libraries/StyleSheet/processAspectRatio.js).
2
- // Same root cause family as boxShadow/filter: RN registers `aspectRatio` with a JS
3
- // `process` because enableNativeCSSParsing() defaults to false, so the CSS ratio string
4
- // ('16 / 9') is resolved to a number IN JS before native. A plain number (the common,
5
- // already-working form) passes through untouched; this is a no-op for it.
6
- //
7
- // RN throws via invariant() in __DEV__ on a malformed value; we dlog and return undefined.
1
+ // JS-side port of RN's processAspectRatio: resolves a CSS ratio string ('16 / 9') to a number
2
+ // before native, same root cause as boxShadow/filter. A plain number passes through untouched.
3
+ // RN throws via invariant() on a malformed value; here we dlog and return undefined instead.
8
4
  import { dlog } from './debug.js';
9
5
  // RN processAspectRatio.js:15-63. number → number; ratio string → number; invalid → undefined.
10
6
  export function processAspectRatio(aspectRatio) {
@@ -1,20 +1,12 @@
1
1
  // RN's own processBackgroundSize / processBackgroundPosition / processBackgroundRepeat, imported
2
2
  // rather than ported.
3
- //
4
- // Why they are needed at all: ReactNativeStyleAttributes registers all three with
5
- // `nativeCSSParsing ? true : {process: processX}` (:50, :54, :58), and enableNativeCSSParsing()
6
- // defaults to false - so RN's stock path parses the CSS string into the per-axis structure
7
- // Fabric's C++ expects. We registered `experimental_backgroundImage` and not its three siblings,
8
- // so a string for any of them reached native unparsed: dropped on iOS, cast to the wrong
9
- // ReadableMap type on Android. Same shape as the process-transform incident.
10
- //
11
- // Why importing is free here even by this repo's strictest reading: all three upstream files
12
- // contain ONLY `import type` lines. Their value-import closure is empty - no processColor, no
13
- // Platform, no TurboModule floor, no renderer. There is nothing to weigh.
14
- //
15
- // Why no try/catch, unlike the other upstream wrappers: these three do not validate through
16
- // `invariant`. They answer `[]` for anything they cannot parse, which upstream's own comment calls
17
- // "do not apply any background" - so refusal is a RETURN here, not a throw.
3
+ // Needed because enableNativeCSSParsing() defaults to false, so RN's stock JS path parses the CSS
4
+ // string into the per-axis structure Fabric's C++ expects. Without it a string for any of these
5
+ // three reached native unparsed: dropped on iOS, cast to the wrong ReadableMap type on Android.
6
+ // Importing is free here: all three upstream files contain only `import type` lines, so their
7
+ // value-import closure is empty — no processColor, no Platform, no TurboModule floor, no renderer.
8
+ // No try/catch, unlike the other upstream wrappers: these three don't validate through invariant,
9
+ // they answer `[]` for anything unparseable — so refusal is a return here, not a throw.
18
10
  import { dlog } from './debug.js';
19
11
  // @ts-expect-error - untyped Flow source. Metro compiles it; vitest.config.ts strips the types.
20
12
  import processBackgroundSizeUpstream from 'react-native/Libraries/StyleSheet/processBackgroundSize';
@@ -22,10 +14,9 @@ import processBackgroundSizeUpstream from 'react-native/Libraries/StyleSheet/pro
22
14
  import processBackgroundPositionUpstream from 'react-native/Libraries/StyleSheet/processBackgroundPosition';
23
15
  // @ts-expect-error - untyped Flow source. Metro compiles it; vitest.config.ts strips the types.
24
16
  import processBackgroundRepeatUpstream from 'react-native/Libraries/StyleSheet/processBackgroundRepeat';
25
- // An empty result is upstream's way of saying it parsed nothing usable. The payload builder's
26
- // rule is that a refused processor leaves the key ABSENT, so it must not become `[]` - an empty
27
- // array is a real value, and committing one asks native to apply "no background size" rather than
28
- // leaving the declaration unset.
17
+ // An empty result means upstream parsed nothing usable. The payload builder's rule is that a
18
+ // refused processor leaves the key absent — `[]` is a real value, committing it would ask native
19
+ // to apply "no background size" rather than leave the declaration unset.
29
20
  function orAbsent(parsed, key, value) {
30
21
  if (!Array.isArray(parsed) || parsed.length === 0) {
31
22
  dlog(`${key}: RN parsed nothing usable from ${JSON.stringify(value)}`);
@@ -1,22 +1,14 @@
1
- // RN's own processFilter, imported rather than ported.
2
- //
3
- // Why it is needed at all: RN registers `filter` with `nativeCSSParsing ? true : {process:
4
- // processFilter}`, and enableNativeCSSParsing() defaults to false - so the stock path parses the
5
- // CSS string / structured array in JS and native never sees the raw string.
6
- //
7
- // Why the try/catch, unlike processBoxShadow's wrapper: upstream throws exactly once
8
- // (processFilter.js:120, a TypeError for an input that is neither string nor array), and a commit
9
- // path must never throw. fabric-props narrows before calling, but `processFilter` is on the public
10
- // barrel, so the guard is reachable rather than ceremonial.
11
- //
12
- // What changed by importing: a drop-shadow's colour now goes through RN's own `processColor`
13
- // (upstream imports it directly at processFilter.js:16) instead of our injected
14
- // `setColorProcessor` seam. On a real host those are the same function - bootstrapHost injects
15
- // exactly `processColor` - so the seam simply no longer governs the colour inside a filter.
16
- //
17
- // Platform caveat worth keeping in view when demoing this: on iOS RN paints only `brightness` and
18
- // `opacity` unless enableSwiftUIBasedFilters is on, so grayscale/blur/saturate/contrast/hueRotate
19
- // parse correctly here and still do nothing on screen.
1
+ // RN's own processFilter, imported rather than ported: enableNativeCSSParsing() defaults to
2
+ // false, so the stock path parses the CSS string / structured array in JS and native never sees
3
+ // the raw string.
4
+ // try/catch because upstream throws once (a TypeError for a non-string/array input) and a commit
5
+ // path must never throw; fabric-props narrows first, but processFilter is on the public barrel
6
+ // too, so the guard here stays reachable, not ceremonial.
7
+ // A drop-shadow's color goes through RN's own processColor here, not our injected
8
+ // setColorProcessor seam — the same function on a real host, since bootstrapHost injects exactly
9
+ // processColor, so the seam simply doesn't govern a filter's color.
10
+ // iOS caveat: RN paints only brightness/opacity unless enableSwiftUIBasedFilters is on, so
11
+ // grayscale/blur/saturate/contrast/hueRotate parse fine here and still paint nothing on screen.
20
12
  import { dlog } from './debug.js';
21
13
  // @ts-expect-error - untyped Flow source. Metro compiles it; vitest.config.ts strips the types.
22
14
  import processFilterUpstream from 'react-native/Libraries/StyleSheet/processFilter';
@@ -1,10 +1,6 @@
1
- // JS-side port of RN's processFontVariant (Libraries/StyleSheet/processFontVariant.js).
2
- // Same root cause family as boxShadow/filter: RN registers `fontVariant` with a JS
3
- // `process` because enableNativeCSSParsing() defaults to false, so a space-separated CSS
4
- // string ('small-caps tabular-nums') is split into the array native expects IN JS. An
5
- // array (the common, already-working form) passes through untouched; this is a no-op
6
- // for it.
7
- // RN processFontVariant.js:15-28. Array → array; space-separated string → array of variants.
1
+ // JS-side port of RN's processFontVariant: splits a space-separated CSS string
2
+ // ('small-caps tabular-nums') into the array native expects, same root cause as
3
+ // boxShadow/filter. An array (the common, already-working form) passes through as a no-op.
8
4
  export function processFontVariant(fontVariant) {
9
5
  if (Array.isArray(fontVariant)) {
10
6
  return fontVariant;
@@ -29,39 +29,6 @@ export declare function registerComponent(name: string, registration?: IComponen
29
29
  export declare function isRegisteredEvent(component: string, listener: string): boolean;
30
30
  export declare function registeredNativeEvent(component: string, raw: string): INativeEventBinding | undefined;
31
31
  export declare function registeredProcessor(component: string, key: string): IPropProcessor | undefined;
32
- /**
33
- * A component's ViewConfig processors, as a payload fold — or undefined when it has none.
34
- *
35
- * WHY A FOLD AND NOT A LOOKUP AT PAYLOAD-BUILD TIME. The payload is built in C++ on a device
36
- * (`core/engine/cpp/SymbioteFabricProps.cpp`) and only in JS headless, and this registry cannot
37
- * cross that boundary: it is populated lazily from an INJECTED `ReactNativeViewConfigRegistry`
38
- * lookup, holding JS closures. `payloadFold` is the one seam that already runs in JS on both paths
39
- * — the C++ probes it once per node and calls back — so putting the processors there is what makes
40
- * a third-party view behave the same on a device as it does in a test.
41
- *
42
- * The alternative was a list of prop NAMES in C++, and that is what this replaces. It cost a day:
43
- * `@symbiote-native/slider` declares `minimumTrackTintColor` / `maximumTrackTintColor` in its own
44
- * ViewConfig, neither name was in the C++ list, both reached Fabric as CSS strings, and iOS answers
45
- * a string colour with `clearColor()`. The slider dragged and reported values correctly with no
46
- * track drawn at all. Any list of names is a list somebody has to extend for a component we have
47
- * never seen — which would have meant editing C++ to add a native view, and that is exactly the
48
- * coupling `<native_core_is_untouched>` exists to prevent.
49
- *
50
- * Resolved ONCE per component and cached, because `createElement` asks per node. A built-in
51
- * short-circuits inside `resolve` before any of this.
52
- */
53
- /**
54
- * The prop names a component's OWN ViewConfig claims a processor for — i.e. exactly the keys
55
- * `configPayloadFold` has already converted by the time the payload builder's own passes run.
56
- *
57
- * It exists so "a colour is converted exactly once" can be a stated rule rather than a lucky one.
58
- * The overlap is real: `thumbTintColor` is claimed by @symbiote-native/slider's config AND by the
59
- * engine's built-in COLOR_PROPS, and for a while nothing broke only because a processed colour came
60
- * back as a NUMBER and the engine skipped numbers. That guard died when a numeric colour became
61
- * processable in its own right — an author writing `color: 0xff0000ff` means rrggbbaa and owes the
62
- * same rotation a string owes — and the second conversion then turned an already-correct int into
63
- * a different colour.
64
- */
65
32
  export declare function configProcessedKeys(component: string): ReadonlySet<string> | undefined;
66
33
  export declare function configPayloadFold(component: string): IPayloadFold | undefined;
67
34
  export {};
package/build/registry.js CHANGED
@@ -1,33 +1,19 @@
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, where 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.
1
+ // Runtime metadata for native Fabric views, derived by default. Any RN library ships its own
2
+ // ViewConfig via codegen, carrying everything the shared engine can't infer — which events a view
3
+ // emits, how to process its props. Nothing is transcribed; read from the config on first use.
4
+ // shared must stay react-native-free (the headless harness runs in plain Node), so the ViewConfig
5
+ // lookup is injected, exactly like the color processor: the adapter wires
6
+ // setNativeViewConfigSource(ReactNativeViewConfigRegistry.get) on a real host.
7
+ // The only explicit list is our own built-in primitives (BUILTIN_COMPONENTS): a finite set we own,
8
+ // which keep their hand-tuned tables and are never read from the source, so they can't drift. It
9
+ // grows only when we add a core primitive of our own, never for a community package.
21
10
  import { isRecord } from './type-guards.js';
22
11
  const EMPTY = {
23
12
  listeners: new Set(),
24
13
  byRaw: new Map(),
25
14
  processors: new Map(),
26
15
  };
27
- // OUR own primitives: the finite set shared hand-tunes (view-config events,
28
- // commit COLOR_PROPS). The source is never consulted for these, so they can't
29
- // drift. Everything else derives. This list grows only when WE add a core
30
- // primitive, never for a community package.
16
+ // Our own primitives — see the module header for why the source is never consulted for these.
31
17
  const BUILTIN_COMPONENTS = new Set([
32
18
  'RCTView',
33
19
  'RCTText',
@@ -156,39 +142,18 @@ export function registeredNativeEvent(component, raw) {
156
142
  export function registeredProcessor(component, key) {
157
143
  return resolve(component).processors.get(key);
158
144
  }
159
- /**
160
- * A component's ViewConfig processors, as a payload fold — or undefined when it has none.
161
- *
162
- * WHY A FOLD AND NOT A LOOKUP AT PAYLOAD-BUILD TIME. The payload is built in C++ on a device
163
- * (`core/engine/cpp/SymbioteFabricProps.cpp`) and only in JS headless, and this registry cannot
164
- * cross that boundary: it is populated lazily from an INJECTED `ReactNativeViewConfigRegistry`
165
- * lookup, holding JS closures. `payloadFold` is the one seam that already runs in JS on both paths
166
- * — the C++ probes it once per node and calls back — so putting the processors there is what makes
167
- * a third-party view behave the same on a device as it does in a test.
168
- *
169
- * The alternative was a list of prop NAMES in C++, and that is what this replaces. It cost a day:
170
- * `@symbiote-native/slider` declares `minimumTrackTintColor` / `maximumTrackTintColor` in its own
171
- * ViewConfig, neither name was in the C++ list, both reached Fabric as CSS strings, and iOS answers
172
- * a string colour with `clearColor()`. The slider dragged and reported values correctly with no
173
- * track drawn at all. Any list of names is a list somebody has to extend for a component we have
174
- * never seen — which would have meant editing C++ to add a native view, and that is exactly the
175
- * coupling `<native_core_is_untouched>` exists to prevent.
176
- *
177
- * Resolved ONCE per component and cached, because `createElement` asks per node. A built-in
178
- * short-circuits inside `resolve` before any of this.
179
- */
180
- /**
181
- * The prop names a component's OWN ViewConfig claims a processor for — i.e. exactly the keys
182
- * `configPayloadFold` has already converted by the time the payload builder's own passes run.
183
- *
184
- * It exists so "a colour is converted exactly once" can be a stated rule rather than a lucky one.
185
- * The overlap is real: `thumbTintColor` is claimed by @symbiote-native/slider's config AND by the
186
- * engine's built-in COLOR_PROPS, and for a while nothing broke only because a processed colour came
187
- * back as a NUMBER and the engine skipped numbers. That guard died when a numeric colour became
188
- * processable in its own right — an author writing `color: 0xff0000ff` means rrggbbaa and owes the
189
- * same rotation a string owes — and the second conversion then turned an already-correct int into
190
- * a different colour.
191
- */
145
+ // A component's ViewConfig processors, as a payload fold — or undefined when it has none. Not a
146
+ // lookup at payload-build time: the payload is built in C++ on a device and this registry can't
147
+ // cross that boundary (it's populated lazily from an injected JS-closure-holding lookup).
148
+ // payloadFold is the one seam that already runs in JS on both paths, so putting the processors
149
+ // there is what makes a third-party view behave the same on device as in a test. The alternative,
150
+ // a list of prop names hardcoded in C++, needs editing native code for every new component we see.
151
+ // Resolved once per component and cached, since createElement asks per node. A built-in
152
+ // short-circuits inside resolve before any of this.
153
+ // The prop names a component's own ViewConfig claims a processor for — exactly the keys
154
+ // configPayloadFold has already converted by the time the payload builder's own passes run.
155
+ // Exists so "a colour is converted exactly once" is a stated rule, not a lucky one: an overlap
156
+ // between a third-party config and the engine's own COLOR_PROPS would otherwise double-convert.
192
157
  export function configProcessedKeys(component) {
193
158
  const cached = keysCache.get(component);
194
159
  if (cached !== undefined)
@@ -1,21 +1,8 @@
1
1
  // Hand an error to the host the way React Native itself does, for the seams where an adapter
2
2
  // catches something its framework would otherwise have surfaced on its own.
3
- //
4
- // RN routes an uncaught render error through ReactFiberErrorDialog -> ExceptionsManager
5
- // (Libraries/Core/ReactFiberErrorDialog.js), which is reachable only by a deep path into RN's
6
- // Libraries. `global.ErrorUtils` is the same surface through the documented global that
7
- // @react-native/js-polyfills installs before any module evaluates - RN's own
8
- // Libraries/vendor/core/ErrorUtils.js is nothing but `export default global.ErrorUtils`. Off a
9
- // native host (vitest, node smokes) the global is absent and console.error is the whole channel.
10
- //
11
- // This is NOT the dlog channel. dlog is DEBUG-gated and therefore the developer's; an error that
12
- // blanked the screen has to reach the app whether or not anyone turned diagnostics on.
13
- // Read off globalThis through Reflect rather than named as an identifier, and deliberately NOT
14
- // declared in an ambient block: react-native ships its own `const ErrorUtils: ErrorUtils` in
15
- // src/types/globals.d.ts, so a `declare global { var ErrorUtils }` here redeclares it (TS2451) for
16
- // every package whose tsconfig pulls in RN's types - the engine's own does not, so it typechecks
17
- // clean here and breaks ngc downstream. RN's declared shape also lists only the two
18
- // get/setGlobalHandler members, not reportError, which the polyfill does install.
3
+ // Read off globalThis via Reflect, not a named identifier or an ambient declare: react-native
4
+ // ships its own `ErrorUtils` global type, so redeclaring it here breaks any package whose tsconfig
5
+ // pulls in RN's types. RN's declared shape also omits reportError, which the polyfill installs.
19
6
  function nativeReporter() {
20
7
  const utils = Reflect.get(globalThis, 'ErrorUtils');
21
8
  if (typeof utils !== 'object' || utils === null)
@@ -54,8 +41,7 @@ export function reportUncaughtError(value, info) {
54
41
  const report = nativeReporter();
55
42
  if (report !== null) {
56
43
  // Exactly one channel, never both: RN routes console.error into LogBox as well, so logging
57
- // here too would double-report every error. Upstream returns false from showErrorDialog for
58
- // this same reason (Libraries/Core/ReactFiberErrorDialog.js:57).
44
+ // here too would double-report every error.
59
45
  report(error);
60
46
  return;
61
47
  }
@@ -1,10 +1 @@
1
- /**
2
- * A style value with its structured keys resolved — the same value by identity when there was
3
- * nothing to resolve, which is nearly always.
4
- *
5
- * Accepts what a style slot can hold: one object, or a (nested) array of them. An array is not
6
- * memoized — `pushClassStyle` mints a fresh one on every publish, so a cache keyed on it could
7
- * never hit; its ENTRIES carry the memo instead, and the original array comes back untouched when
8
- * none of them moved.
9
- */
10
1
  export declare function resolveStructuredStyle(style: unknown): unknown;
@@ -1,22 +1,12 @@
1
1
  // The ten style keys RN parses in JS before native, and the one place they are resolved.
2
- //
3
- // WHY THEY RUN AT WRITE TIME AND NOT AT PAYLOAD-BUILD TIME. On a device the payload is built by
4
- // `core/engine/cpp/SymbioteFabricProps.cpp`, which does not carry these — they are pure JS. So a
5
- // `boxShadow: '0 2px 4px #000'` resolved only inside `fabric-props.ts` is resolved only headless,
6
- // and the device gets the raw CSS string. Fabric's C++ parses a style string ONLY under
7
- // `enableNativeCSSParsing()`, which defaults to FALSE, so the declaration is dropped in silence:
8
- // no warning, no wrong value, just a gradient or a shadow that is not there. Resolving on the way
9
- // IN puts the structured value in `node.props` itself, which is the one thing both payload builders
10
- // read.
11
- //
12
- // This is the same move `configPayloadFold` makes for a third-party view's own processors, one
13
- // layer down: anything the C++ half cannot do has to happen before the C++ half sees the value.
14
- //
15
- // IDENTITY IS PART OF THE CONTRACT. A style object that needs nothing comes back BY IDENTITY, and
16
- // so does an array whose every entry did. The host's `OP_SET_PROP` skips a same-identity write, and
17
- // `pushClassStyle` compares what it is about to publish against what it published last — hand
18
- // either of them a fresh object per write and an unchanged style becomes a write and a dirty node
19
- // on every render.
2
+ // They run at write time, not payload-build time: SymbioteFabricProps.cpp doesn't carry these
3
+ // processors, so resolving only in fabric-props.ts leaves the device its raw CSS string, silently
4
+ // dropped (C++ parses a style string only under enableNativeCSSParsing, default false).
5
+ // Same move `configPayloadFold` makes for a third-party view's own processors: anything the C++
6
+ // half can't do has to happen before the C++ half sees the value.
7
+ // Identity is part of the contract: an object/array needing no resolution comes back by identity,
8
+ // since OP_SET_PROP skips a same-identity write and pushClassStyle diffs by reference — a fresh
9
+ // object per call would turn an unchanged style into a write and a dirty node every render.
20
10
  import { processAspectRatio } from './process-aspect-ratio.js';
21
11
  import { processBackgroundImage } from './process-background-image/index.js';
22
12
  import { processBackgroundPosition, processBackgroundRepeat, processBackgroundSize, } from './process-background-longhands.js';
@@ -80,10 +70,9 @@ function asFontVariantInput(value) {
80
70
  return value.filter(isString);
81
71
  return '';
82
72
  }
83
- // transform accepts a CSS string (processTransform parses it) or an array of single-key
84
- // transform records (the hot animated / sticky-header path). A non-string non-array value is NOT
85
- // dropped: it may already be processed, so it passes through verbatim rather than being coerced to
86
- // [] (which would erase a valid transform).
73
+ // transform accepts a CSS string or an array of single-key records (the hot animated /
74
+ // sticky-header path). A non-string non-array value passes through verbatim — it may already be
75
+ // processed, so coercing it to [] would erase a valid transform.
87
76
  function processTransformValue(value) {
88
77
  if (typeof value === 'string')
89
78
  return processTransform(value);
@@ -154,15 +143,11 @@ function resolveRecord(style) {
154
143
  resolvedStyles.set(style, answer);
155
144
  return answer;
156
145
  }
157
- /**
158
- * A style value with its structured keys resolved — the same value by identity when there was
159
- * nothing to resolve, which is nearly always.
160
- *
161
- * Accepts what a style slot can hold: one object, or a (nested) array of them. An array is not
162
- * memoized — `pushClassStyle` mints a fresh one on every publish, so a cache keyed on it could
163
- * never hit; its ENTRIES carry the memo instead, and the original array comes back untouched when
164
- * none of them moved.
165
- */
146
+ // A style value with its structured keys resolved — same value by identity when nothing needed
147
+ // resolving, which is nearly always.
148
+ // Accepts one object or a (nested) array of them. An array itself isn't memoized (pushClassStyle
149
+ // mints a fresh one per publish, so a cache keyed on it would never hit); its entries carry the
150
+ // memo instead.
166
151
  export function resolveStructuredStyle(style) {
167
152
  if (Array.isArray(style)) {
168
153
  let out;
package/build/styles.js CHANGED
@@ -1,7 +1,4 @@
1
- // The typed style surface (ViewStyle / TextStyle and friends). Maps onto Yoga layout
2
- // props and RN's view/text props, which Fabric's C++ reads off the props payload.
3
- // A correctly-typed subset of RN's StyleSheet surface (see
4
- // react-native/Libraries/StyleSheet/StyleSheetTypes): the load-bearing layout / box /
5
- // shadow / transform / text props, not the full surface. Agnostic types, so they
6
- // live in the engine next to the style processors; every adapter re-exports them.
1
+ // The typed style surface (ViewStyle/TextStyle and friends). Maps onto Yoga layout props and RN's
2
+ // view/text props, which Fabric's C++ reads off the props payload. A correctly-typed subset of
3
+ // RN's StyleSheet surface — agnostic types, so every adapter re-exports them from the engine.
7
4
  export {};
@@ -6,38 +6,12 @@ export declare class SymbioteSurface {
6
6
  private readonly node;
7
7
  private commitScheduled;
8
8
  constructor(rootTag: IRootTag);
9
- /**
10
- * Every OTHER live surface, so one commit names every root — see `commitSurfaceOps`.
11
- *
12
- * Static because it reads a sibling instance's private handle, which only a member of this class
13
- * may do. One surface — the universal case — allocates nothing.
14
- *
15
- * `self` may already be OUT of the registry: a teardown unregisters and then commits, and that
16
- * commit is the one carrying the removals. So the fast path cannot be a size check — with one
17
- * live surface left, `size === 1` means either "only me" or "only the other one", and taking the
18
- * shortcut on the second reading is how a surface loses the very batch that empties it.
19
- */
20
9
  private static others;
21
- /**
22
- * The top-level nodes, asked of the host.
23
- *
24
- * A getter rather than an array this class maintains: a second copy of a child list is the thing
25
- * this design removes, and `nextSiblingOf` (host-access.ts) needs the same answer the host gives
26
- * for a parented node.
27
- */
28
10
  get children(): readonly ISymbioteNode[];
29
11
  appendChild(child: ISymbioteNode): void;
30
12
  insertBefore(child: ISymbioteNode, beforeChild: ISymbioteNode): void;
31
13
  removeChild(child: ISymbioteNode): void;
32
14
  clear(): void;
33
- /**
34
- * Release every host behavior still standing under this surface, at unmount.
35
- *
36
- * The sweep above cannot answer this: it only sees nodes a `removeChild` NOMINATED, and an
37
- * unmount removes nothing — the adapter drops the whole surface. Without it every node keeps its
38
- * `afterCommit` registration and its timers, and a restarted surface's commits drain the dead
39
- * one's hooks forever.
40
- */
41
15
  teardown(): void;
42
16
  commit(): void;
43
17
  requestCommit(): void;