@symbiote-native/components 0.5.0 → 2.0.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 (88) hide show
  1. package/README.md +23 -10
  2. package/build/accessibility-props.d.ts +11 -0
  3. package/build/accessibility-props.js +30 -118
  4. package/build/behaviors/activity-indicator/index.android.d.ts +1 -0
  5. package/build/behaviors/activity-indicator/index.android.js +16 -0
  6. package/build/behaviors/activity-indicator/index.d.ts +3 -0
  7. package/build/behaviors/activity-indicator/index.ios.d.ts +1 -0
  8. package/build/behaviors/activity-indicator/index.ios.js +14 -0
  9. package/build/behaviors/activity-indicator/index.js +5 -0
  10. package/build/behaviors/activity-indicator/shared.d.ts +18 -0
  11. package/build/behaviors/activity-indicator/shared.js +149 -0
  12. package/build/behaviors/button.d.ts +2 -0
  13. package/build/behaviors/button.js +328 -0
  14. package/build/behaviors/image-background.d.ts +2 -0
  15. package/build/behaviors/image-background.js +139 -0
  16. package/build/behaviors/image.d.ts +3 -0
  17. package/build/behaviors/image.js +123 -0
  18. package/build/behaviors/input-accessory-view.d.ts +3 -0
  19. package/build/behaviors/input-accessory-view.js +70 -0
  20. package/build/behaviors/pressable.d.ts +60 -0
  21. package/build/behaviors/pressable.js +385 -0
  22. package/build/behaviors/refresh-control.d.ts +2 -0
  23. package/build/behaviors/refresh-control.js +83 -0
  24. package/build/behaviors/scroll-view/index.android.d.ts +1 -0
  25. package/build/behaviors/scroll-view/index.android.js +52 -0
  26. package/build/behaviors/scroll-view/index.d.ts +2 -0
  27. package/build/behaviors/scroll-view/index.ios.d.ts +1 -0
  28. package/build/behaviors/scroll-view/index.ios.js +10 -0
  29. package/build/behaviors/scroll-view/index.js +5 -0
  30. package/build/behaviors/scroll-view/shared.d.ts +11 -0
  31. package/build/behaviors/scroll-view/shared.js +291 -0
  32. package/build/behaviors/scroll-view/sticky.d.ts +17 -0
  33. package/build/behaviors/scroll-view/sticky.js +568 -0
  34. package/build/behaviors/switch.d.ts +2 -0
  35. package/build/behaviors/switch.js +186 -0
  36. package/build/behaviors/text-input.d.ts +14 -0
  37. package/build/behaviors/text-input.js +319 -0
  38. package/build/behaviors/touchable-highlight.d.ts +9 -0
  39. package/build/behaviors/touchable-highlight.js +192 -0
  40. package/build/behaviors/touchable-native-feedback.d.ts +20 -0
  41. package/build/behaviors/touchable-native-feedback.js +333 -0
  42. package/build/behaviors/touchable-opacity.d.ts +12 -0
  43. package/build/behaviors/touchable-opacity.js +227 -0
  44. package/build/behaviors/touchable-without-feedback.d.ts +2 -0
  45. package/build/behaviors/touchable-without-feedback.js +296 -0
  46. package/build/component-names/index.android.js +47 -15
  47. package/build/component-names/index.ios.js +35 -15
  48. package/build/component-names/shared.d.ts +2 -1
  49. package/build/component-names/shared.js +49 -3
  50. package/build/descriptor.js +4 -4
  51. package/build/fold-host-bag.d.ts +15 -0
  52. package/build/fold-host-bag.js +99 -0
  53. package/build/index.d.ts +23 -12
  54. package/build/index.js +50 -9
  55. package/build/register.d.ts +1 -0
  56. package/build/register.js +55 -0
  57. package/build/resolve-intrinsic.d.ts +7 -0
  58. package/build/resolve-intrinsic.js +49 -0
  59. package/build/scroll-view-commands.d.ts +4 -0
  60. package/build/scroll-view-commands.js +30 -31
  61. package/build/state/pressable.d.ts +9 -0
  62. package/build/state/pressable.js +120 -34
  63. package/build/state/text-input.d.ts +10 -2
  64. package/build/state/text-input.js +11 -0
  65. package/build/text-props.js +2 -1
  66. package/build/view/render-button.d.ts +36 -1
  67. package/build/view/render-button.js +101 -12
  68. package/build/view/render-image/index.d.ts +2 -0
  69. package/build/view/render-image/index.js +29 -2
  70. package/build/view/render-input-accessory-view.d.ts +2 -0
  71. package/build/view/render-input-accessory-view.js +37 -5
  72. package/build/view/render-modal.js +3 -3
  73. package/build/view/render-pressable/index.d.ts +2 -0
  74. package/build/view/render-pressable/index.js +24 -0
  75. package/build/view/render-scroll-view.d.ts +1 -0
  76. package/build/view/render-scroll-view.js +18 -9
  77. package/build/view/render-switch.d.ts +4 -1
  78. package/build/view/render-switch.js +8 -2
  79. package/build/view/render-text-input.js +6 -2
  80. package/build/view/render-touchable-native-feedback.d.ts +19 -0
  81. package/build/view/render-touchable-native-feedback.js +19 -0
  82. package/host-primitives.cjs +432 -0
  83. package/host-primitives.d.cts +33 -0
  84. package/package.json +33 -5
  85. package/build/view/render-activity-indicator.d.ts +0 -25
  86. package/build/view/render-activity-indicator.js +0 -62
  87. package/build/view/render-image-background.d.ts +0 -9
  88. package/build/view/render-image-background.js +0 -48
@@ -112,7 +112,31 @@ function readSourceUri(source) {
112
112
  }
113
113
  return undefined;
114
114
  }
115
- export function renderImage(view) {
115
+ // The names `renderImage` CONSUMES rather than forwards — the fold's input list, and the reason it
116
+ // is exported: an adapter that splits props before calling renderImage was copying this list by
117
+ // hand, and a name added here reached the copy only if someone remembered. The Image behavior's
118
+ // `foldPayload` reads it too, so the flat path and the view path cannot drift apart.
119
+ export const IMAGE_VIEW_PROP_NAMES = [
120
+ 'source',
121
+ 'defaultSource',
122
+ 'loadingIndicatorSource',
123
+ 'style',
124
+ 'resizeMode',
125
+ 'tintColor',
126
+ 'src',
127
+ 'srcSet',
128
+ 'alt',
129
+ 'width',
130
+ 'height',
131
+ 'crossOrigin',
132
+ 'referrerPolicy',
133
+ ];
134
+ // The whole of Image's prop mapping, with the `el()` wrap taken off. Split out because a LOWERED
135
+ // `<image>` has no component body to run it in: its behavior's `foldPayload` needs the same
136
+ // bag at commit time, and a second implementation of this is the exact drift this file already paid
137
+ // for once (`adapters/svelte/src/components/image/image-logic.ts` reproduces it by hand, with a
138
+ // comment saying it had to because nothing here was exported).
139
+ export function mapImageProps(view) {
116
140
  // `width` / `height` aliases fold into style (ImageProps.js:195,202); explicit
117
141
  // style keys win, matching RN's `{width, height}, ...style` ordering.
118
142
  const foldedStyle = view.width === undefined && view.height === undefined
@@ -138,6 +162,9 @@ export function renderImage(view) {
138
162
  if (view.loadingIndicatorSource !== undefined) {
139
163
  mapped.loadingIndicatorSrc = readSourceUri(view.loadingIndicatorSource);
140
164
  }
165
+ return mapped;
166
+ }
167
+ export function renderImage(view) {
141
168
  dlog('Image -> RCTImageView');
142
- return el('symbiote-image', mapped);
169
+ return el('image', mapImageProps(view));
143
170
  }
@@ -6,4 +6,6 @@ export type IInputAccessoryViewViewProps = {
6
6
  style?: IStyleProp<IViewStyle>;
7
7
  passthrough: Record<string, unknown>;
8
8
  };
9
+ export declare const INPUT_ACCESSORY_VIEW_PROP_NAMES: readonly ["nativeID", "backgroundColor", "style"];
10
+ export declare function mapInputAccessoryViewProps(view: IInputAccessoryViewViewProps): Record<string, unknown>;
9
11
  export declare function renderInputAccessoryView(view: IInputAccessoryViewViewProps): IDescriptor;
@@ -6,16 +6,48 @@
6
6
  // verbatim across adapters: React and Vue both bridge this Descriptor.
7
7
  import { dlog, } from '@symbiote-native/engine';
8
8
  import { el } from '../descriptor.js';
9
- export function renderInputAccessoryView(view) {
10
- const props = {
9
+ // The names this fold CONSUMES, exported so an adapter splitting props before calling the render fn
10
+ // reads the list instead of copying it — the drift `render-image.ts` records paying for once.
11
+ export const INPUT_ACCESSORY_VIEW_PROP_NAMES = [
12
+ 'nativeID',
13
+ 'backgroundColor',
14
+ 'style',
15
+ ];
16
+ // The whole mapping, so the wrapper path and the lowered path share ONE implementation. There is no
17
+ // aliasing here at all: every consumed name leaves under the same name, which is what makes the
18
+ // fold idempotent by construction (asserted in `behaviors/input-accessory-view.test.ts`, not
19
+ // assumed).
20
+ //
21
+ // The `undefined` guards on `nativeID` / `backgroundColor` are LOAD-BEARING, and the asymmetry with
22
+ // the unguarded `style` above is not the tell it looks like.
23
+ //
24
+ // They were removed on 2026-09-01 on the reasoning `.claude/rules/fabric-boolean-event-gates.md`
25
+ // states — `setProp` collapses an undefined value to an absent key, so a conditional write in a
26
+ // shared render fn is cosmetic — and that reasoning was checked against a payload carrying no `id`,
27
+ // which is exactly the case where it holds. The equivalence oracle caught it on its first run:
28
+ //
29
+ // authored <InputAccessoryView id="p" testID="p" />
30
+ // guarded RCTInputAccessoryView{testID, nativeID:"p"}
31
+ // unguarded RCTInputAccessoryView{testID} <- the alias result, deleted
32
+ //
33
+ // `nativeID` has an ALIAS SOURCE. The wrapper leaves `id` in `passthrough`, the renderer's
34
+ // PROP_ALIASES renames it to `nativeID`, and a `nativeID: undefined` written afterwards deletes what
35
+ // the rename just produced — last write wins, and `undefined` collapsing to "absent" is precisely
36
+ // what makes it destructive rather than inert. So the guard is cosmetic for a key nothing else can
37
+ // produce, and required for a key an alias also targets.
38
+ export function mapInputAccessoryViewProps(view) {
39
+ const out = {
11
40
  ...view.passthrough,
12
41
  style: view.style,
13
42
  };
14
43
  if (view.nativeID !== undefined)
15
- props.nativeID = view.nativeID;
44
+ out.nativeID = view.nativeID;
16
45
  if (view.backgroundColor !== undefined)
17
- props.backgroundColor = view.backgroundColor;
46
+ out.backgroundColor = view.backgroundColor;
47
+ return out;
48
+ }
49
+ export function renderInputAccessoryView(view) {
18
50
  dlog('InputAccessoryView -> RCTInputAccessoryView');
19
51
  // Empty structural children: the adapter appends the user children directly under the host.
20
- return el('symbiote-input-accessory-view', props, []);
52
+ return el('input-accessory-view', mapInputAccessoryViewProps(view), []);
21
53
  }
@@ -2,7 +2,7 @@
2
2
  // node: it lives in the SAME childSet and commits through the SAME completeRoot as the rest of
3
3
  // the tree. The native iOS/Android view presents its own window internally; there is no second
4
4
  // root or second surface on the JS side. So this is a thin render exactly like the others: it
5
- // maps to the `symbiote-modal` intrinsic the host config routes to ModalHostView, wrapping a
5
+ // maps to the `modal` intrinsic the host config routes to ModalHostView, wrapping a
6
6
  // full-screen container View that holds the user children (injected by the adapter). Shared
7
7
  // verbatim: React and Vue both bridge this Descriptor; the keep-alive state lives in state/modal.ts.
8
8
  import { dlog, } from '@symbiote-native/engine';
@@ -52,8 +52,8 @@ export function renderModal(view) {
52
52
  // collapsable:false keeps the container as a real shadow node (RN sets this so the wrapper is
53
53
  // never flattened away under the host). Empty structural children: the adapter injects the
54
54
  // user children UNDER this container, never as a direct sibling of the host.
55
- const container = el('symbiote-view', { style: containerStyle, collapsable: false }, []);
56
- return el('symbiote-modal', {
55
+ const container = el('view', { style: containerStyle, collapsable: false }, []);
56
+ return el('modal', {
57
57
  ...view.passthrough,
58
58
  style: MODAL_HOST_STYLE,
59
59
  transparent: view.transparent,
@@ -1,6 +1,8 @@
1
1
  import type { IAccessibilityStateValue } from '../../accessibility-props';
2
2
  import type { IPressHandlers } from '../../state/pressable';
3
3
  export declare function resolveDisabledAccessibilityState(accessibilityState: IAccessibilityStateValue | undefined, disabled: boolean | undefined): IAccessibilityStateValue | undefined;
4
+ export declare function resolvePressableFocusable(focusable: boolean | undefined): boolean;
5
+ export declare function resolveTouchableFocusable(focusable: boolean | undefined, hasOnPress: boolean, disabled: boolean | undefined): boolean;
4
6
  export declare function shouldSuppressPress(disabled: boolean | undefined): boolean;
5
7
  export declare function shouldClaimResponder(disabled: boolean | undefined): boolean;
6
8
  export declare function isTerminationAllowed(cancelable: boolean | undefined): boolean;
@@ -12,6 +12,30 @@ export function resolveDisabledAccessibilityState(accessibilityState, disabled)
12
12
  ? { ...accessibilityState, disabled }
13
13
  : accessibilityState;
14
14
  }
15
+ // RN computes `focusable` in all five touchables and we computed it nowhere until 2026-09-09, so a
16
+ // DISABLED control stayed focusable — a keyboard, a TV remote or switch control could land on
17
+ // something that cannot be pressed. There are TWO formulas, split by primitive and not by platform;
18
+ // do not collapse them.
19
+ //
20
+ // Pressable defaults it ON (Pressable.js:258). `!== false`, not `?? true`: only a literal `false`
21
+ // opts out, the same shape `accessible` uses one file over.
22
+ export function resolvePressableFocusable(focusable) {
23
+ return focusable !== false;
24
+ }
25
+ // The four Touchable* additionally require a press handler and a non-disabled state —
26
+ // TouchableOpacity.js:336-340, TouchableHighlight.js:370-374,
27
+ // TouchableWithoutFeedback.js:263-266, TouchableNativeFeedback.js:369-372, one expression verbatim.
28
+ //
29
+ // `hasOnPress` is RN's `onPress !== undefined` and NOT `hasTouchablePressHandler`, which also counts
30
+ // onPressIn/onPressOut/onLongPress — that gate is the underlay's, and reusing it here would make a
31
+ // press-in-only Touchable focusable where RN leaves it out of the focus order.
32
+ //
33
+ // A Touchable that composes our Pressable passes the result DOWN as the `focusable` prop; Pressable
34
+ // then applies `!== false` to a value that is already boolean, which is why the two compose without
35
+ // either side knowing about the other.
36
+ export function resolveTouchableFocusable(focusable, hasOnPress, disabled) {
37
+ return (resolvePressableFocusable(focusable) && hasOnPress && disabled !== true);
38
+ }
15
39
  // The 3 agnostic gating predicates behind the listener bag below. Angular has no bag to spread -
16
40
  // it binds these directly onto template event outputs (see adapters/angular/src/components/
17
41
  // pressable/index.ts), so they're exported and shared rather than folded back into
@@ -26,6 +26,7 @@ export interface IScrollForwarding {
26
26
  capturesViewportHeight: boolean;
27
27
  collapsableChildren: boolean;
28
28
  }
29
+ export declare function preservesContentChildren(maintainVisibleContentPosition: unknown, snapToAlignment: unknown): boolean;
29
30
  export declare function resolveScrollForwarding(inputs: IScrollForwardingInputs): IScrollForwarding;
30
31
  export type IContentSize = {
31
32
  width: number;
@@ -58,11 +58,11 @@ export function selectScrollIntrinsics(isHorizontal, contentContainerStyle) {
58
58
  // ViewManager, not RCTScrollView+flag); on iOS both intrinsics map back to RCTScrollView.
59
59
  // The name table does the per-platform mapping; here we only pick the intrinsic.
60
60
  const scrollViewIntrinsic = isHorizontal
61
- ? 'symbiote-horizontal-scroll-view'
62
- : 'symbiote-scroll-view';
61
+ ? 'horizontal-scroll-view'
62
+ : 'scroll-view';
63
63
  const contentIntrinsic = isHorizontal
64
- ? 'symbiote-horizontal-scroll-content'
65
- : 'symbiote-scroll-content';
64
+ ? 'horizontal-scroll-content'
65
+ : 'scroll-content';
66
66
  const scrollViewBaseStyle = isHorizontal
67
67
  ? SCROLL_VIEW_BASE_HORIZONTAL
68
68
  : SCROLL_VIEW_BASE_VERTICAL;
@@ -82,12 +82,21 @@ export function selectScrollIntrinsics(isHorizontal, contentContainerStyle) {
82
82
  // throttle passes through untouched. These two magic numbers were copied into all three adapters.
83
83
  const STICKY_NATIVE_SCROLL_THROTTLE = 1;
84
84
  const STICKY_JS_SCROLL_THROTTLE = 16;
85
+ // maintainVisibleContentPosition (and Android snapToAlignment) anchor against MOUNTED cell views;
86
+ // Android Fabric view-flattens layout-only cells away, so RN keeps them as real views with
87
+ // collapsableChildren={false} on the content container (ScrollView.js:1731 `preserveChildren`).
88
+ // No-op on iOS.
89
+ //
90
+ // Named and exported rather than inlined because the LOWERED path needs the same answer from a
91
+ // different place — the host behavior's slot fold, which has the owner's raw props and none of the
92
+ // sticky inputs `resolveScrollForwarding` also takes. A second inline copy would be invisible to
93
+ // `tests/lowered-primitive-fold-parity.test.ts`, whose oracle is shared value IMPORTS.
94
+ export function preservesContentChildren(maintainVisibleContentPosition, snapToAlignment) {
95
+ return (maintainVisibleContentPosition !== undefined ||
96
+ snapToAlignment !== undefined);
97
+ }
85
98
  export function resolveScrollForwarding(inputs) {
86
- // maintainVisibleContentPosition (and Android snapToAlignment) anchor against MOUNTED cell views;
87
- // Android Fabric view-flattens layout-only cells away, so RN keeps them as real views with
88
- // collapsableChildren={false} on the content container (ScrollView.js preserveChildren). No-op on iOS.
89
- const collapsableChildren = inputs.maintainVisibleContentPosition !== undefined ||
90
- inputs.snapToAlignment !== undefined;
99
+ const collapsableChildren = preservesContentChildren(inputs.maintainVisibleContentPosition, inputs.snapToAlignment);
91
100
  if (!inputs.hasStickyHeaders) {
92
101
  return {
93
102
  mode: 'plain',
@@ -5,9 +5,12 @@ export type ISwitchTrackColor = {
5
5
  false?: string;
6
6
  true?: string;
7
7
  };
8
+ export type ISwitchChangeEvent = ISymbioteEvent & {
9
+ value: boolean;
10
+ };
8
11
  export interface ISwitchProps extends IAccessibilityProps, IAriaProps {
9
12
  value?: boolean;
10
- onValueChange?: (value: boolean, event: ISymbioteEvent) => void;
13
+ onValueChange?: (event: ISwitchChangeEvent) => void;
11
14
  disabled?: boolean;
12
15
  trackColor?: ISwitchTrackColor;
13
16
  thumbColor?: string;
@@ -1,5 +1,5 @@
1
1
  // Switch: the render half (framework-agnostic). Maps the resolved props onto the single
2
- // `symbiote-switch` host node: the strict value fold lands as the `value` Fabric prop, the
2
+ // `switch` host node: the strict value fold lands as the `value` Fabric prop, the
3
3
  // track colors take platform-specific prop NAMES (iOS onTintColor/tintColor vs Android
4
4
  // trackColorFor*/trackTintColor, supplied via `platform`), thumbColor → thumbTintColor, and
5
5
  // ios_backgroundColor folds into the style as the pill that shows through the shrunken track.
@@ -8,6 +8,12 @@ import { dlog } from '@symbiote-native/engine';
8
8
  import { el } from '../descriptor.js';
9
9
  // RN rounds the iOS background pill to this radius when ios_backgroundColor is set.
10
10
  const IOS_BACKGROUND_BORDER_RADIUS = 16;
11
+ // The `-managed` spelling, NOT the plain one the engine's Switch behavior registers under
12
+ // (`core/components/src/behaviors/switch.ts`) — the wrapper still runs its own lifecycle
13
+ // (lastNativeReport, the snap-back effect), so it may not share a tag the behavior registry would
14
+ // also attach to; see that behavior's module header and `.claude/rules/host-primitive-tier.md`,
15
+ // "A lowered element and its wrapper must not share an intrinsic tag" (the TextInput precedent).
16
+ const SWITCH_MANAGED_INTRINSIC = 'switch-managed';
11
17
  // Fold ios_backgroundColor into the style, matching RN's iOS branch: it paints the
12
18
  // background that shows through the shrunken track. Untouched when unset, so a caller's own
13
19
  // backgroundColor wins by simply not passing ios_backgroundColor.
@@ -32,5 +38,5 @@ export function renderSwitch(view, platform) {
32
38
  thumbTintColor: view.thumbColor,
33
39
  style: foldIosBackground(view.style, view.ios_backgroundColor),
34
40
  };
35
- return el('symbiote-switch', props);
41
+ return el(SWITCH_MANAGED_INTRINSIC, props);
36
42
  }
@@ -8,8 +8,12 @@ import { dlog } from '@symbiote-native/engine';
8
8
  import { el } from '../descriptor.js';
9
9
  // One host element per native input class. Text carries the only non-trivial nesting elsewhere;
10
10
  // here the choice is binary and runtime (the `multiline` prop), so the module stays flat.
11
- const SINGLELINE_INTRINSIC = 'symbiote-text-input';
12
- const MULTILINE_INTRINSIC = 'symbiote-text-input-multiline';
11
+ // The `-managed` spelling, NOT the plain one the lowering transforms emit. Both resolve to the
12
+ // same native view; the tag is what decides whether the engine's TextInput machine attaches, and
13
+ // on this path the adapter's own lifecycle already owns that state. Sharing the tag would run both
14
+ // copies at once. Full reasoning in `component-names/shared.ts`.
15
+ const SINGLELINE_INTRINSIC = 'text-input-managed';
16
+ const MULTILINE_INTRINSIC = 'text-input-multiline-managed';
13
17
  export function renderTextInput(view) {
14
18
  const intrinsic = view.multiline ? MULTILINE_INTRINSIC : SINGLELINE_INTRINSIC;
15
19
  dlog(`TextInput render multiline=${String(view.multiline)} ` +
@@ -15,3 +15,22 @@ export declare function selectableBackground(rippleRadius?: number): IThemeAttrB
15
15
  export declare function selectableBackgroundBorderless(rippleRadius?: number): IThemeAttrBackground;
16
16
  export declare function rippleBackground(color: string, borderless: boolean, rippleRadius?: number): IRippleBackground;
17
17
  export declare function backgroundProps(background: INativeFeedbackBackground, useForeground: boolean): Record<string, INativeFeedbackBackground>;
18
+ /**
19
+ * RN's four statics, under RN's own spelling — `TouchableNativeFeedback.Ripple(color, borderless)`.
20
+ *
21
+ * A NAMESPACE OBJECT rather than a component value, and that is the whole of what survived the
22
+ * wrapper: the element is a tag now, and a tag is a string, which cannot carry properties. Shared
23
+ * rather than copied five times because every member is a pure dict producer with no framework in
24
+ * it; each adapter re-exports it verbatim, as it does the background types beside it.
25
+ *
26
+ * NOT the spelling for NEW code, and not redundant either. RN's docs point at `Pressable`'s
27
+ * declarative `android_ripple={{ color, borderless }}`, which the pressable behavior resolves
28
+ * (`behaviors/pressable.ts`, `asRippleConfig`). These four stay because the
29
+ * `touchable-native-feedback` tag folds `background` and nothing else produces that value.
30
+ */
31
+ export declare const TouchableNativeFeedback: {
32
+ SelectableBackground: typeof selectableBackground;
33
+ SelectableBackgroundBorderless: typeof selectableBackgroundBorderless;
34
+ Ripple: typeof rippleBackground;
35
+ canUseNativeForeground: typeof canUseNativeForeground;
36
+ };
@@ -41,3 +41,22 @@ export function backgroundProps(background, useForeground) {
41
41
  }
42
42
  return { nativeBackgroundAndroid: background };
43
43
  }
44
+ /**
45
+ * RN's four statics, under RN's own spelling — `TouchableNativeFeedback.Ripple(color, borderless)`.
46
+ *
47
+ * A NAMESPACE OBJECT rather than a component value, and that is the whole of what survived the
48
+ * wrapper: the element is a tag now, and a tag is a string, which cannot carry properties. Shared
49
+ * rather than copied five times because every member is a pure dict producer with no framework in
50
+ * it; each adapter re-exports it verbatim, as it does the background types beside it.
51
+ *
52
+ * NOT the spelling for NEW code, and not redundant either. RN's docs point at `Pressable`'s
53
+ * declarative `android_ripple={{ color, borderless }}`, which the pressable behavior resolves
54
+ * (`behaviors/pressable.ts`, `asRippleConfig`). These four stay because the
55
+ * `touchable-native-feedback` tag folds `background` and nothing else produces that value.
56
+ */
57
+ export const TouchableNativeFeedback = {
58
+ SelectableBackground: selectableBackground,
59
+ SelectableBackgroundBorderless: selectableBackgroundBorderless,
60
+ Ripple: rippleBackground,
61
+ canUseNativeForeground,
62
+ };