@symbiote-native/components 3.1.1 → 3.1.3

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 (40) hide show
  1. package/build/behaviors/activity-indicator/shared.js +29 -71
  2. package/build/behaviors/button.d.ts +0 -9
  3. package/build/behaviors/button.js +47 -233
  4. package/build/behaviors/image-background.js +21 -81
  5. package/build/behaviors/image.js +3 -10
  6. package/build/behaviors/input-accessory-view.js +10 -51
  7. package/build/behaviors/pressable.d.ts +5 -51
  8. package/build/behaviors/pressable.js +101 -161
  9. package/build/behaviors/refresh-control.js +8 -2
  10. package/build/behaviors/scroll-view/index.android.js +10 -30
  11. package/build/behaviors/scroll-view/responder.d.ts +3 -2
  12. package/build/behaviors/scroll-view/responder.js +19 -21
  13. package/build/behaviors/scroll-view/shared.js +69 -185
  14. package/build/behaviors/scroll-view/sticky.d.ts +0 -8
  15. package/build/behaviors/scroll-view/sticky.js +54 -142
  16. package/build/behaviors/switch.js +18 -4
  17. package/build/behaviors/text-input.d.ts +0 -8
  18. package/build/behaviors/text-input.js +77 -162
  19. package/build/behaviors/touchable-highlight.js +14 -54
  20. package/build/behaviors/touchable-native-feedback.js +9 -32
  21. package/build/behaviors/touchable-opacity.d.ts +0 -7
  22. package/build/behaviors/touchable-opacity.js +65 -81
  23. package/build/behaviors/touchable-without-feedback.js +25 -41
  24. package/build/component-names/index.android.js +6 -8
  25. package/build/component-names/shared.js +12 -42
  26. package/build/index.js +13 -19
  27. package/build/scroll-view-commands.js +3 -11
  28. package/build/state/pressable.js +18 -43
  29. package/build/state/sticky-header-reducer.js +103 -149
  30. package/build/state/touchable.js +3 -5
  31. package/build/state/virtualized-list-reducer.js +21 -48
  32. package/build/state/virtualized-list.js +63 -148
  33. package/build/text-props.js +3 -13
  34. package/build/view/render-button.js +13 -44
  35. package/build/view/render-input-accessory-view.js +8 -24
  36. package/build/view/render-pressable/index.js +3 -4
  37. package/build/view/render-scroll-view.js +13 -23
  38. package/build/view/render-touchable-native-feedback.js +5 -14
  39. package/host-primitives.cjs +33 -207
  40. package/package.json +3 -3
@@ -1,35 +1,11 @@
1
- // ImageBackground's host behavior: the composition and the prop split the wrapper component did,
2
- // moved below the framework so the primitive can be a bare tag.
3
- //
4
- // THE TWO-NODE SHAPE IS RN'S. `ImageBackground.js:74-103` opens a `<View>` carrying the app's
5
- // `style`, puts an absolutely-filled `<Image>` inside it, and lays the app's `{children}` AFTER
6
- // that image so they paint on top. The tag commits the same two nodes — `image-background` (an
7
- // RCTView, the tag an app writes) with an RCTImageView built under it.
8
- //
9
- // WHY THE SLOT TAKES NO CHILDREN, which is the one thing this primitive needed that ScrollView,
10
- // ActivityIndicator and Button did not. `childHost` answers two questions at once — which node an
11
- // owner prop redirects onto, and which node the app's children go under — and those had the same
12
- // answer for every primitive until this one. Here they differ: the image takes `imageStyle` and the
13
- // whole `...props` spread, while the children belong beside it. `slotTakesNoChildren` is what
14
- // splits them (`IHostBehavior`, and it is not a JSX nicety upstream could have collapsed — an
15
- // Android `<Image>` is an `ImageView`, not a `ViewGroup`).
16
- //
17
- // WHERE THE APP'S PROPS GO. RN destructures `children, style, imageStyle, imageRef,
18
- // importantForAccessibility, ...props` and spreads `...props` onto the Image
19
- // (`ImageBackground.js:62-81`), so the set that moves is OPEN — every event, every accessibility
20
- // prop, `testID`, `id`, whatever an app writes next — and only a complement can express it.
21
- // `IMAGE_BACKGROUND_HOST_PROPS` is that complement, and `importantForAccessibility` is IN it despite
22
- // never being part of the spread: RN reapplies it explicitly to both nodes (`:76,82`), so it stays on
23
- // the owner here too and the image gets its own copy derived from `ownerProps` (FIXED 2026-09-20 —
24
- // it used to redirect to the image alone and never reach the owner at all, which the module's own
25
- // comment rationalized as "the divergence from RN predates all of it").
26
- //
27
- // WHAT THE IMAGE'S FOLD OWES. Everything on the image arrives as a real prop write, so its payload
28
- // is built by the shared `foldImagePayload` like any other `image`. Two things cannot arrive that
29
- // way and are folded here: the style, which is DERIVED from the owner's own `style` (RN proxies the
30
- // wrapper's width/height onto the image so it fills the box rather than collapsing to the source's
31
- // intrinsic size), and `id`, whose rename to `nativeID` is applied per adapter on the tag THEY
32
- // create and so never reaches a node a behavior built.
1
+ // ImageBackground's host behavior. RN's `ImageBackground.js:74-103` opens a `<View>` with the
2
+ // app's `style`, an absolutely-filled `<Image>` inside, children painted AFTER on top — this tag
3
+ // commits the same two nodes (`image-background` + its inner image).
4
+ // `childHost` normally answers both "where do owner props redirect" and "where do children go" —
5
+ // here they differ (image takes the spread, children stay on the owner), so
6
+ // `slotTakesNoChildren` splits them; not a JSX nicety, an Android `<Image>` is not a ViewGroup.
7
+ // RN spreads an OPEN prop set onto the Image (`ImageBackground.js:62-81`);
8
+ // `IMAGE_BACKGROUND_HOST_PROPS` below is the complement RN keeps on the wrapper instead.
33
9
  import { appendChild, createElement, registerHostBehavior, } from '@symbiote-native/engine';
34
10
  import { descriptorFor } from '../component-names';
35
11
  import { IMAGE_TAG, registerImageBehavior } from './image.js';
@@ -37,12 +13,9 @@ export const IMAGE_BACKGROUND_TAG = 'image-background';
37
13
  // The inner image's own tag. Distinct from `image` because the absolute fill must NOT reach a bare
38
14
  // `<image>`, and a tag is the only thing a per-node rule can branch on.
39
15
  export const IMAGE_BACKGROUND_IMAGE_TAG = 'image-background-image';
40
- // The props RN keeps on the wrapper View (`ImageBackground.js:74-78`), plus the two spellings of a
41
- // class name — `routeProp`'s slot redirect runs above its own class branch, so an unlisted `class`
42
- // would style the image instead of the box. `importantForAccessibility` stays too: RN destructures
43
- // it out of `...props` and reapplies it explicitly to BOTH the wrapper (:76) and the image (:82), so
44
- // it is never part of the spread — the engine derives the image's own copy from this node
45
- // (`foldImageBackgroundImageProps` in `SymbioteFabricProps.cpp`), the same seam the box proxy uses.
16
+ // Kept on the wrapper (`ImageBackground.js:74-78`), plus both class spellings — the slot redirect
17
+ // runs above the class branch, so an unlisted `class` would style the image instead. RN reapplies
18
+ // `importantForAccessibility` to BOTH nodes (:76,82); the image gets its own derived copy.
46
19
  const IMAGE_BACKGROUND_HOST_PROPS = [
47
20
  'style',
48
21
  'class',
@@ -56,49 +29,16 @@ const IMAGE_BACKGROUND_SLOT_DERIVED = ['style', 'importantForAccessibility'];
56
29
  // value for it — every adapter's wrapper resolved one — and `routeProp` routes a string landing on
57
30
  // `style` as `class` instead, so the registry resolves it on the image with nothing needed here.
58
31
  const IMAGE_BACKGROUND_SLOT_PROPS = { imageStyle: 'style' };
59
- // The owner's fold is GONE (2026-09-18), not moved into this file under another name: its whole
60
- // content was `accessibilityIgnoresInvertColors: true`, a function of the tag and nothing else, and
61
- // it is `foldImageBackgroundProps` in `SymbioteFabricProps.cpp` now. The owner therefore pays no
62
- // trip into JS at all, and a committed ImageBackground is down from two crossings to one — the
63
- // remaining one is the inner image's, below, which derives its style from THIS node and so cannot be
64
- // a per-node rule. Contract: `core/engine/cpp/tests/js/image-background-payload.itest.ts`.
65
- // THE IMAGE'S FOLD IS GONE TOO (2026-09-18) — `foldImageBackgroundImageProps` in
66
- // `SymbioteFabricProps.cpp`, and with it this primitive costs ZERO trips into JS on both nodes.
67
- //
68
- // It outlived every other fold in this file because both of its inputs live on the node ABOVE: the
69
- // app writes `style` on the `<image-background>` and `IMAGE_BACKGROUND_HOST_PROPS` keeps it there.
70
- // "A per-node rule cannot reach another node" is what this file used to say, and it was a fact about
71
- // the JS FOLD rather than the engine — the tree is in C++, so `fabricProps` takes `ownerProps` from
72
- // `node.parent` and the proxy reads it there.
73
- //
74
- // The `id -> nativeID` half went earlier still and was DEAD before this port: `foldIdAlias` applies
75
- // to every tagged node and runs ahead of any fold, so by the time this ran the key was already
76
- // renamed. Worth naming, because a fold that still spells a rule someone else now applies reads as
77
- // load-bearing and is not.
78
- //
79
- // Contract: `core/engine/cpp/tests/js/image-background-image-payload.itest.ts`.
80
- // Returns the image, so `slotProps` / `slotPropsExcept` / `slotDerived` all point at it — and the
81
- // app's children stay on the owner because of `slotTakesNoChildren`, not because of what this
82
- // returns. The image is appended FIRST and nothing else is ever placed in front of it, which is
83
- // what makes the children paint over it.
84
- //
85
- // Built WITH `IMAGE_TAG` since 2026-09-18, which is the reverse of what it used to do and for a
86
- // reason that reversed with it. It used to withhold the tag so the node would not get Image's
87
- // `payloadFold` — a single slot this primitive needed for its own derived style — and call the
88
- // shared mapping by hand at the end. The mapping is the ENGINE's now, reached off the tag, so the
89
- // tag is how this node gets the platform half at all; the JS slot is free for the composition.
90
- //
91
- // ONE ORDERING DIFFERENCE FALLS OUT, and it is deliberate rather than overlooked. The image rule
92
- // folds the image's own `width`/`height` PROPS under its style, and the background rule then layers
93
- // the box's dimensions over that — where RN nests it the other way (`ImageBackground.js:83-98` puts
94
- // the props under the proxied box size). So when an app sets BOTH a `width` prop on the
95
- // ImageBackground and a conflicting width in its `style`, RN gives the style's and we give the
96
- // prop's.
97
- //
98
- // It is left this way rather than reproduced: RN's own comment calls that nesting a "Temporary
99
- // Workaround" for an Image that overwrites its own dimensions, and an explicit prop winning over an
100
- // inherited box is the less surprising of the two. Pinned in
101
- // `core/components/src/behaviors/image-background.test.ts` so it stays a decision.
32
+ // The owner's fold is GONE: its one job (`accessibilityIgnoresInvertColors: true`) is
33
+ // `foldImageBackgroundProps` in C++ now (`image-background-payload.itest.ts`).
34
+ // The image's fold is GONE too — `foldImageBackgroundImageProps` in C++, reading `ownerProps`
35
+ // from `node.parent` for the style proxy (`image-background-image-payload.itest.ts`).
36
+ // Returns the image, so `slotProps`/`slotPropsExcept`/`slotDerived` point at it; children stay on
37
+ // the owner via `slotTakesNoChildren`. Built WITH `IMAGE_TAG` so the engine's Image mapping still
38
+ // applies to it — the JS slot is free for composition only.
39
+ // ONE ORDERING DIFFERENCE, deliberate: the image rule folds its own `width`/`height` under its
40
+ // style, then the box's dimensions layer over that — RN nests it the other way
41
+ // (`ImageBackground.js:83-98`). Pinned in `image-background.test.ts` so it stays a decision.
102
42
  //
103
43
  // THE TAG IS ITS OWN, and that is what the C++ port needed. The node used to carry plain `image`,
104
44
  // which is right for everything the ordinary image rule does and wrong for the fill — a bare
@@ -1,13 +1,6 @@
1
- // Image's host behavior, and it carries no runtime at all — no listeners, no timers, no commit
2
- // hook, and since 2026-09-18 no fold either. What is left is ONE declaration: that this tag's three
3
- // source props are resolved on the way in.
4
- //
5
- // WHERE THE FOLD WENT: `foldImageProps` in `SymbioteFabricProps.cpp`, with
6
- // `core/engine/cpp/tests/js/image-payload.itest.ts` as its contract. The `srcSet`/`src`/`source`
7
- // precedence, the W3C header decoration, the `width`/`height` fold into style, `alt` becoming
8
- // `accessibilityLabel` + `accessible`, `resizeMode`/`tintColor` falling back to style keys, and
9
- // `loadingIndicatorSource` being plucked down to a bare uri — all of it is a function of the tag,
10
- // which is what makes it the platform's.
1
+ // Image's host behavior carries no runtime — no listeners, no timers, no fold. `foldImageProps`
2
+ // in C++ resolves the `srcSet`/`src`/`source` precedence, style folds and a11y mapping, asserted
3
+ // in `core/engine/cpp/tests/js/image-payload.itest.ts`.
11
4
  //
12
5
  // WHY THIS ONE DID NOT MOVE WHOLE, and it is the first that did not. `resolveAssetSource` turns the
13
6
  // number `require('./logo.png')` returns into a `{uri, width, height, scale}` by asking METRO'S
@@ -1,54 +1,13 @@
1
- // InputAccessoryView's host behavior. This file itself still owns no per-node runtime — the
2
- // primitive's real rule lives in `foldInputAccessoryViewProps` (`SymbioteFabricProps.cpp`) now.
3
- //
4
- // THE OLD FOLD WAS GONE, not moved (2026-09-18): read end to end it split the bag into
5
- // consumed/passthrough and reassembled it — there was no aliasing at all, every consumed name left
6
- // under the same name, and the one thing it actually changed was dropping a `nativeID` or
7
- // `backgroundColor` that was not a `string`. That is defensive narrowing of a typed view object,
8
- // not a rule native cares about — and it was WRONG on `backgroundColor`, whose validAttributes
9
- // entry carries `processColor` and therefore accepts a number.
10
- //
11
- // A REAL RULE WAS FOUND WHILE READING VENDOR FOR THE PORT (2026-09-20):
12
- // `InputAccessoryView.js`'s `styles.container = {position: 'absolute'}`, composed AFTER the app's
13
- // own style (`[props.style, styles.container]`) so it wins over any `position` an app authors.
14
- // Every InputAccessoryView is positioned absolutely; nothing about it is per-instance, so it is
15
- // `foldInputAccessoryViewProps` now rather than anything here. Contract:
16
- // `core/engine/cpp/tests/js/touchable-payload.itest.ts`.
17
- //
18
- // NOT YET PORTED, and recorded honestly rather than silently skipped: vendor also wraps the app's
19
- // children in an inner `SafeAreaView` sized to `useWindowDimensions().width` (`InputAccessoryView.js`
20
- // render body) and returns nothing at all when it has zero children. Both are real structural gaps
21
- // — a composed child node plus a live window-dimension binding — genuinely larger than a tag-only
22
- // rule, and are NOT covered by this pass.
23
- //
24
- // TODO(rn-parity): port both. Needs (a) a `buildStructure` composing an inner wrapper node styled
25
- // `{flex: 1, width}` around the app's children, with `width` read from a live window-dimensions
26
- // subscription (no `SafeAreaView` primitive exists in this codebase yet — it would need building),
27
- // and (b) suppressing the whole node (both platforms, not just Android's void case already fixed
28
- // here) when it has zero children, matching `React.Children.count(props.children) === 0`.
29
- //
30
- // The `id -> nativeID` alias — is `foldIdAlias` in `SymbioteFabricProps.cpp`, applied to every
31
- // tagged node rather than per primitive.
32
- //
33
- // PLATFORM. This is the only primitive in its group that is not platform-invariant in what it
34
- // COMMITS TO: `input-accessory-view` resolves to `RCTInputAccessoryView` on iOS and to the void
35
- // component on Android. The fold itself is platform-invariant on purpose.
36
- //
37
- // FIXED (2026-09-20). `InputAccessoryView.js` on Android does `console.warn('<InputAccessoryView>
38
- // is only supported on iOS.'); return null` — the WHOLE component, children included, renders
39
- // NOTHING. We used to commit a real `RCTView` and its whole children subtree (the toolbar content
40
- // an app wrapped in it) — an extra, laid-out, potentially visible view where a real device shows
41
- // none. `backgroundColor` being a declared iOS prop vs an Android style key is the SAME already-
42
- // narrow gap, unaffected by this.
43
- //
44
- // The fix needed a new engine primitive, because every existing "commits no node of its own"
45
- // tier-2 primitive (`touchable-native-feedback`, `touchable-without-feedback`) is an ANCHOR — it
46
- // hoists its single child up in its own place, which is the opposite of what this tag needs: its
47
- // whole subtree must vanish. `VOID_COMPONENT` (`core/engine/src/node.ts`, `OP_CREATE_VOID` in
48
- // `mutation-buffer.ts`) is that primitive — a node the commit walk stops at, recursively,
49
- // contributing neither itself nor its children. Wired purely through
50
- // `core/components/src/component-names/index.android.ts`'s per-platform table, the same seam
51
- // `ANCHOR_COMPONENT` already used — this behavior file needed no change at all.
1
+ // InputAccessoryView's host behavior owns no per-node runtime — the rule is
2
+ // `foldInputAccessoryViewProps` in C++, including the container's absolute-position style RN
3
+ // always composes over the app's own (`core/engine/cpp/tests/js/touchable-payload.itest.ts`).
4
+ // NOT YET PORTED: vendor also wraps children in an inner SafeAreaView sized to
5
+ // `useWindowDimensions().width` and renders nothing with zero children.
6
+ // TODO(rn-parity): needs a `buildStructure` wrapper node, a live window-dimensions subscription,
7
+ // and suppressing the whole node on both platforms when childless.
8
+ // Android resolves to `VOID_COMPONENT` (`core/engine/src/node.ts`, `OP_CREATE_VOID`): the commit
9
+ // walk stops there recursively, contributing neither the node nor its children — wired through
10
+ // `component-names/index.android.ts`, this behavior file needed no change.
52
11
  import { Platform, registerHostBehavior, } from '@symbiote-native/engine';
53
12
  export const INPUT_ACCESSORY_VIEW_TAG = 'input-accessory-view';
54
13
  // `InputAccessoryView.js:110-113` — off iOS it warns and renders nothing (the void component).
@@ -1,69 +1,23 @@
1
- import { type IHostBehavior, type ISymbioteNode } from '@symbiote-native/engine';
1
+ import { type IEventDispatch, type IHostBehavior, type ISymbioteNode } from '@symbiote-native/engine';
2
2
  import { type IPressMachineConfig } from '../state/pressable';
3
3
  import type { IAccessibilityStateValue } from '../accessibility-props';
4
4
  export declare const PRESSABLE_TAG = "pressable";
5
- /**
6
- * A last look at the machine's config before its handlers are built, for a tag that IS a pressable
7
- * plus something — TouchableOpacity, whose fade has to run between the machine and the app's own
8
- * `onPressIn`.
9
- *
10
- * Called from `rebuild`, so once per gesture rather than once per mount: it sees the config the
11
- * props actually hold by the time a finger lands, and anything it captures is discarded with the
12
- * gesture. Per-node state that must OUTLIVE a gesture belongs on the caller's own WeakMap.
13
- */
14
5
  export type IPressConfigRefinement = (node: ISymbioteNode, config: IPressMachineConfig) => IPressMachineConfig;
15
- /**
16
- * What the machine reads as `disabled`, for a tag whose spelling of it is not the raw prop.
17
- *
18
- * There is no resolver by default because RN's Pressable hands Pressability the RAW prop
19
- * (`Pressable.js:266`) — `aria-disabled` there changes only what is ANNOUNCED. Button is the one
20
- * primitive that differs: it resolves `disabled ?? aria-disabled ?? accessibilityState.disabled` in
21
- * the component and passes the ANSWER down as the touchable's own prop (`Button.js:337` -> `:386`),
22
- * which a single tag has no second node to pass to.
23
- *
24
- * Reads the bag and returns the answer; it must never write one back. `resolveButtonDisabled`
25
- * short-circuits on an authored `disabled`, so a resolved value stored in `node.props.disabled`
26
- * would answer the NEXT resolution as if the app had written it and the tag could never re-enable.
27
- */
28
6
  export type IDisabledResolver = (props: Readonly<Record<string, unknown>>) => boolean | undefined;
29
7
  export type ICancelableResolver = (source: ISymbioteNode) => boolean | undefined;
30
8
  export declare function booleanOr(value: unknown): boolean | undefined;
31
9
  export declare function asAccessibilityState(value: unknown): IAccessibilityStateValue | undefined;
32
10
  export declare function accessibleUnlessOptedOut(props: Readonly<Record<string, unknown>>): boolean;
33
- /**
34
- * The Android ripple's three view commands around the app's callbacks (TNF :230-252,
35
- * useAndroidRippleForView.js:77-104). The JS responder takes the touch before Android's own
36
- * pressed handling, so without them the ripple never animates. Hotspot first: it starts under
37
- * the finger.
38
- */
39
11
  export declare function withNativeFeedbackCommands(node: ISymbioteNode, config: IPressMachineConfig): IPressMachineConfig;
40
- /**
41
- * The machine on `node`, reading its props and the app's callbacks off `options.source` when that
42
- * is a different node.
43
- *
44
- * Exported for a behavior whose responder is not its own node — `./touchable-native-feedback`,
45
- * whose tag commits nothing and adopts the app's single child as the responder. Every other caller
46
- * goes through `createPressBehavior`, where source and node are the same.
47
- *
48
- * Re-callable on the same node: a second call replaces the state and the dispatchers, which is what
49
- * a re-arm after `detachPressMachine` needs.
50
- */
51
- export declare function attachPressMachine(node: ISymbioteNode, options?: {
12
+ export declare const PRESS_DISPATCH: IEventDispatch;
13
+ export type IAttachOptions = {
52
14
  readonly refine?: IPressConfigRefinement;
53
15
  readonly disabledOf?: IDisabledResolver;
54
16
  readonly cancelableOf?: ICancelableResolver;
55
17
  readonly source?: ISymbioteNode;
56
- }): void;
18
+ };
19
+ export declare function attachPressMachine(node: ISymbioteNode, options?: IAttachOptions): void;
57
20
  /** See `attachPressMachine`: the same teardown `createPressBehavior` registers as its `detach`. */
58
21
  export declare function detachPressMachine(node: ISymbioteNode): void;
59
- /**
60
- * The press machine as behavior parts, so a tag that is a pressable PLUS something can compose it
61
- * instead of re-implementing it.
62
- *
63
- * Spread into the caller's own behavior and wrap `attach`/`detach` around these — the touchable
64
- * family needs a per-node Animated value opened before the machine and closed after it. The
65
- * WeakMap holding the machine's own state is keyed by node, so one node may hold exactly one of
66
- * these; a tag composing it therefore must not also register the plain `pressable` behavior.
67
- */
68
22
  export declare function createPressBehavior(refine?: IPressConfigRefinement, disabledOf?: IDisabledResolver): Pick<IHostBehavior, 'attach' | 'detach' | 'ownedListeners'>;
69
23
  export declare function registerPressableBehavior(): void;