@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
@@ -2,25 +2,45 @@
2
2
  // (component-names.ts re-exports it) for headless tsx / tsc / web fallback.
3
3
  // Fabric names are the codegen spec's registered name (the new-arch name), not the legacy
4
4
  // paperComponentName (RCTSwitch, …).
5
+ import { ANCHOR_COMPONENT } from '@symbiote-native/engine';
5
6
  import { buildDescriptors, makeDescriptorFor, } from './shared.js';
6
7
  const IOS_NAMES = {
7
- 'symbiote-view': 'RCTView',
8
- 'symbiote-text': 'RCTText',
9
- 'symbiote-image': 'RCTImageView',
10
- 'symbiote-scroll-view': 'RCTScrollView',
11
- 'symbiote-scroll-content': 'RCTScrollContentView',
8
+ view: 'RCTView',
9
+ pressable: 'RCTView',
10
+ 'touchable-opacity': 'RCTView',
11
+ // NOT a view name — TNF commits nothing at all. See `shared.ts`. Platform-invariant, unlike
12
+ // every other entry here: RN's TNF renders no view on either platform, it only stops CLONING a
13
+ // background off Android (TouchableNativeFeedback.js:402).
14
+ 'touchable-native-feedback': ANCHOR_COMPONENT,
15
+ 'touchable-without-feedback': ANCHOR_COMPONENT,
16
+ 'touchable-highlight': 'RCTView',
17
+ button: 'RCTView',
18
+ text: 'RCTText',
19
+ image: 'RCTImageView',
20
+ 'image-background': 'RCTView',
21
+ 'scroll-view': 'RCTScrollView',
22
+ 'scroll-content': 'RCTScrollContentView',
12
23
  // iOS uses one scroll view for both axes; horizontal is RCTScrollView with the
13
24
  // `horizontal` prop set, so these resolve identically to the vertical pair.
14
- 'symbiote-horizontal-scroll-view': 'RCTScrollView',
15
- 'symbiote-horizontal-scroll-content': 'RCTScrollContentView',
16
- 'symbiote-text-input': 'RCTSinglelineTextInputView',
17
- 'symbiote-text-input-multiline': 'RCTMultilineTextInputView',
18
- 'symbiote-switch': 'Switch',
19
- 'symbiote-activity-indicator': 'ActivityIndicatorView',
20
- 'symbiote-safe-area-view': 'SafeAreaView',
21
- 'symbiote-modal': 'ModalHostView',
22
- 'symbiote-refresh-control': 'PullToRefreshView',
23
- 'symbiote-input-accessory-view': 'RCTInputAccessoryView',
25
+ 'horizontal-scroll-view': 'RCTScrollView',
26
+ 'horizontal-scroll-content': 'RCTScrollContentView',
27
+ 'text-input': 'RCTSinglelineTextInputView',
28
+ 'text-input-multiline': 'RCTMultilineTextInputView',
29
+ // The component path's pair — same native views, a tag the behavior registry does not
30
+ // carry. See `shared.ts` for why the wrapper may not share the lowered tag.
31
+ 'text-input-managed': 'RCTSinglelineTextInputView',
32
+ 'text-input-multiline-managed': 'RCTMultilineTextInputView',
33
+ switch: 'Switch',
34
+ // The wrapper's tag — same native view, a tag the behavior registry does not carry. See
35
+ // `shared.ts` for why the wrapper may not share the lowered tag.
36
+ 'switch-managed': 'Switch',
37
+ 'activity-indicator': 'RCTView',
38
+ 'activity-indicator-spinner': 'ActivityIndicatorView',
39
+ 'safe-area-view': 'SafeAreaView',
40
+ modal: 'ModalHostView',
41
+ 'refresh-control': 'PullToRefreshView',
42
+ 'sticky-header': 'RCTView',
43
+ 'input-accessory-view': 'RCTInputAccessoryView',
24
44
  };
25
45
  export const COMPONENT_DESCRIPTORS = buildDescriptors(IOS_NAMES);
26
46
  export const descriptorFor = makeDescriptorFor(COMPONENT_DESCRIPTORS);
@@ -1,4 +1,5 @@
1
- export type ISymbioteIntrinsic = 'symbiote-view' | 'symbiote-text' | 'symbiote-image' | 'symbiote-scroll-view' | 'symbiote-scroll-content' | 'symbiote-horizontal-scroll-view' | 'symbiote-horizontal-scroll-content' | 'symbiote-text-input' | 'symbiote-text-input-multiline' | 'symbiote-switch' | 'symbiote-activity-indicator' | 'symbiote-safe-area-view' | 'symbiote-modal' | 'symbiote-refresh-control' | 'symbiote-input-accessory-view';
1
+ export type ISymbioteIntrinsic = 'view' | 'pressable' | 'touchable-opacity' | 'touchable-native-feedback' | 'touchable-without-feedback' | 'touchable-highlight' | 'button' | 'text' | 'image' | 'image-background' | 'scroll-view' | 'scroll-content' | 'horizontal-scroll-view' | 'horizontal-scroll-content' | 'text-input' | 'text-input-multiline' | 'text-input-managed' | 'text-input-multiline-managed' | 'switch' | 'switch-managed' | 'activity-indicator' | 'activity-indicator-spinner' | 'safe-area-view' | 'modal' | 'refresh-control' | 'sticky-header' | 'input-accessory-view';
2
+ export type ICrossTypedIntrinsics<LooseProps, Crossed extends Partial<Record<ISymbioteIntrinsic, unknown>>> = Omit<Record<ISymbioteIntrinsic, LooseProps>, keyof Crossed> & Crossed;
2
3
  export interface IComponentDescriptor {
3
4
  component: string;
4
5
  isText: boolean;
@@ -7,7 +7,7 @@
7
7
  // The only text-laying intrinsic; drives the RCTText / RCTVirtualText nesting choice
8
8
  // (a <Text> inside another <Text> becomes a virtual span). Platform-invariant, so it is
9
9
  // not part of the per-platform name table.
10
- const TEXT_INTRINSICS = new Set(['symbiote-text']);
10
+ const TEXT_INTRINSICS = new Set(['text']);
11
11
  // Assemble the descriptor map a platform file exports: each intrinsic's Fabric name from
12
12
  // the platform table, paired with its invariant isText flag.
13
13
  export function buildDescriptors(names) {
@@ -27,13 +27,59 @@ export function buildDescriptors(names) {
27
27
  // is a raw Fabric view name from a library's codegen component and flows through untouched
28
28
  // (the engine derives its events/processors from the view's ViewConfig, no per-library glue).
29
29
  export function makeDescriptorFor(descriptors) {
30
+ const unrewritten = publicNamesThatAreNotViewNames(descriptors);
30
31
  return type => {
31
32
  const descriptor = descriptors[type];
32
33
  if (descriptor !== undefined)
33
34
  return descriptor;
34
- if (type.startsWith('symbiote-')) {
35
- throw new Error(`Unknown symbiote component type: ${type}`);
35
+ // NO "unknown tag of ours" throw any more, and the reason is the whole cost of dropping the
36
+ // prefix: `symbiote-*` was a MARKER, so a miss carrying it could only be our own typo. Without
37
+ // it this namespace holds three populations that a string cannot tell apart —
38
+ //
39
+ // view · scroll-view ours
40
+ // counter-child · app-root an Angular app's OWN component selectors, kebab by convention
41
+ // RCTView · RNCSlider a Fabric view name, resolving through the fallthrough below
42
+ //
43
+ // — and the first attempt at a replacement ("lowercase is ours") threw on the second row, which
44
+ // is most of an Angular app. Measured: 222 tests, the primary failure being `counter-child`.
45
+ //
46
+ // What replaces it is stronger and is not runtime at all: `ISymbioteIntrinsic` is a closed
47
+ // union, so a misspelled tag in our own source is a compile error at every call site that names
48
+ // one. The guard was only ever a backstop for a name built dynamically.
49
+ if (unrewritten.has(type)) {
50
+ throw new Error(`"${type}" is a primitive's PUBLIC name, not a Fabric view name — a rewrite was missed. ` +
51
+ `Expected the intrinsic tag (e.g. "view"). Falling through would have committed ` +
52
+ `a Fabric view literally named "${type}", which fails on device only.`);
36
53
  }
37
54
  return { component: type, isText: false };
38
55
  };
39
56
  }
57
+ // A primitive's public name reaching here means an adapter's rewrite missed a call site. Without
58
+ // this the name falls through as a raw Fabric view name and commits a view literally called `View`
59
+ // — no error at any layer, wrong only on a device.
60
+ //
61
+ // Both halves are DERIVED, because a hand-written list of either would be wrong within a release.
62
+ // The names come from the intrinsic union (kebab -> Pascal); the exclusions come from the platform's
63
+ // own table, and deriving them is not tidiness — two public names ARE real Fabric view names, so a
64
+ // hand-written block list would break an adapter's thin wrapper over a third-party native view,
65
+ // which resolves by view name through this same function.
66
+ //
67
+ // AND THE EXCLUSION IS PER PLATFORM, which is why it must be computed from the table rather than
68
+ // stated. `Switch` and `SafeAreaView` are iOS view names; Android spells them `AndroidSwitch` and
69
+ // `RCTSafeAreaView`, so the same public name is EXCLUDED on iOS and BLOCKED on Android. That
70
+ // asymmetry is correct — nothing legitimate resolves by the bare name on Android — but a reader who
71
+ // takes "these two are real view names" as platform-invariant will conclude the guard is broken on
72
+ // one side or the other.
73
+ function publicNamesThatAreNotViewNames(descriptors) {
74
+ const viewNames = new Set(Object.values(descriptors).map(descriptor => descriptor.component));
75
+ const names = new Set();
76
+ for (const intrinsic of Object.keys(descriptors)) {
77
+ const publicName = intrinsic
78
+ .split('-')
79
+ .map(part => part.charAt(0).toUpperCase() + part.slice(1))
80
+ .join('');
81
+ if (!viewNames.has(publicName))
82
+ names.add(publicName);
83
+ }
84
+ return names;
85
+ }
@@ -4,14 +4,14 @@
4
4
  // adapter's host element then flows on through its reconciler → engine → Fabric.
5
5
  //
6
6
  // `type` is an open host-component string, not a closed two-member union, because
7
- // symbiote paints one host element PER native component (`symbiote-activity-indicator`,
8
- // `symbiote-switch`, …), not just a generic box/text pair.
9
- // el(): a host element of any type. txt(): shorthand for the `symbiote-text` primitive.
7
+ // symbiote paints one host element PER native component (`activity-indicator`,
8
+ // `switch`, …), not just a generic box/text pair.
9
+ // el(): a host element of any type. txt(): shorthand for the `text` primitive.
10
10
  export function el(type, props = {}, children = [], key) {
11
11
  return { type, props, children, key };
12
12
  }
13
13
  export function txt(props = {}, children = []) {
14
- return { type: 'symbiote-text', props, children };
14
+ return { type: 'text', props, children };
15
15
  }
16
16
  // `bridge` names the caller (`descriptorToSolid`, `descriptorToSvelte`) so a device log says which
17
17
  // one tripped without a stack to read.
@@ -0,0 +1,15 @@
1
+ import { type IFoldOp } from '../host-primitives.cjs';
2
+ export type IHostBag = Record<string, unknown>;
3
+ interface IFoldPlan {
4
+ readonly aliases: ReadonlyArray<readonly [string, string]>;
5
+ readonly defaults: ReadonlyArray<readonly [string, IFoldOp]>;
6
+ }
7
+ export declare const FOLD_PLAN_BY_TAG: ReadonlyMap<string, IFoldPlan>;
8
+ /**
9
+ * Apply a primitive's aliases and defaults to a bag, copy-on-write.
10
+ *
11
+ * Never mutates the input: the bag belongs to whoever built it, and a framework may hand the same
12
+ * object back on a re-render, so writing into it would leak a fold into the author's own state.
13
+ */
14
+ export declare function foldHostBag(tagName: string, bag: IHostBag): IHostBag;
15
+ export {};
@@ -0,0 +1,99 @@
1
+ // The RUNTIME half of `HOST_PRIMITIVES` — RN's prop folds applied to a props bag on its way into
2
+ // the engine, keyed by intrinsic tag.
3
+ //
4
+ // Why it exists at all. A primitive's host node is produced by THREE paths, and the compile-time
5
+ // fold in a lowering transform covers exactly one of them:
6
+ //
7
+ // lowered `<View>` the transform folded it covered by the transform
8
+ // `<View>` it refused the wrapper folds it covered by the wrapper
9
+ // a hand-authored tag nobody folds it covered by NOTHING
10
+ //
11
+ // The third path is not hypothetical: Svelte's `components/button.svelte` writes `<text>`
12
+ // directly rather than composing `Text.svelte`, so its title reached Fabric with no
13
+ // `ellipsizeMode` and clipped mid-word where every other adapter ellipsised (2026-08-31). Angular
14
+ // shipped the identical defect from the identical cause, and both fixed it the same way — a seed
15
+ // in the layer every path crosses.
16
+ //
17
+ // Why it is SHARED rather than one copy per adapter: an adapter that lowers has a transform doing
18
+ // this at compile time, an adapter that does not has only this, and a primitive with no wrapper at
19
+ // all (a bare intrinsic tag) has only this everywhere. Three producers of one answer is the shape
20
+ // `<adapters_stay_thin>` exists to stop. Both halves read the same spec, so they cannot drift into
21
+ // two different answers — only into one answer applied twice, which every operation here is
22
+ // idempotent under (`(x ?? 'tail') ?? 'tail'`, `(x !== false) !== false`).
23
+ import { HOST_PRIMITIVES, } from '../host-primitives.cjs';
24
+ const planFor = (primitive) => ({
25
+ aliases: Object.entries(primitive.aliases),
26
+ defaults: Object.entries(primitive.defaults),
27
+ });
28
+ // The wrapper's spelling of a tag whose behavior must attach to the LOWERED path only
29
+ // (`component-names/shared.ts`). A suffix rather than a spec field because it cannot go stale: the
30
+ // next primitive to grow a `-managed` twin is covered the day it is named, with no edit here.
31
+ // `managed-tags-fold.test.ts` pins the convention against the platform tables, so a twin named some
32
+ // other way fails rather than silently losing its folds.
33
+ const managedSpellingOf = (tag) => `${tag}-managed`;
34
+ // EVERY spelling a primitive commits under, and the two axes are independent.
35
+ //
36
+ // `intrinsicWhen` lets one primitive commit two different tags (`TextInput` ->
37
+ // `text-input` / `…-multiline`). `-managed` is the wrapper's twin of each of those. A map
38
+ // keyed on the lowered spellings alone folds the lowered path and silently skips the component one
39
+ // — which is exactly what shipped: TextInput and Switch committed a raw `id`, a key no ViewConfig
40
+ // declares, so Fabric dropped it and the nativeID was lost on device with nothing red. Found by
41
+ // Svelte's equivalence arm, 2026-09-01, and it is the two paths of ONE adapter disagreeing.
42
+ //
43
+ // Folding a bag the wrapper already folded is a no-op — an alias deletes its source key, so the
44
+ // second pass finds nothing to rename — which is what makes covering both paths from one plan safe.
45
+ function tagsOf(primitive) {
46
+ const alternate = primitive.intrinsicWhen?.intrinsic;
47
+ const lowered = alternate === undefined
48
+ ? [primitive.intrinsic]
49
+ : [primitive.intrinsic, alternate];
50
+ return [...lowered, ...lowered.map(managedSpellingOf)];
51
+ }
52
+ export const FOLD_PLAN_BY_TAG = new Map(Object.values(HOST_PRIMITIVES).flatMap(primitive => {
53
+ const plan = planFor(primitive);
54
+ return tagsOf(primitive).map((tag) => [tag, plan]);
55
+ }));
56
+ // The runtime twin of a transform's `foldExpression`, which emits these same two operations as
57
+ // SOURCE. `notFalse` is RN's own encoding (`allowFontScaling !== false`): an explicit `undefined`
58
+ // reads as "not set" and only a literal `false` opts out, which is why it is not a `??`.
59
+ function fold(op, authored) {
60
+ return op.op === 'notFalse' ? authored !== false : (authored ?? op.value);
61
+ }
62
+ /**
63
+ * Apply a primitive's aliases and defaults to a bag, copy-on-write.
64
+ *
65
+ * Never mutates the input: the bag belongs to whoever built it, and a framework may hand the same
66
+ * object back on a re-render, so writing into it would leak a fold into the author's own state.
67
+ */
68
+ export function foldHostBag(tagName, bag) {
69
+ const plan = FOLD_PLAN_BY_TAG.get(tagName);
70
+ if (plan === undefined)
71
+ return bag;
72
+ let next = bag;
73
+ for (const [from, to] of plan.aliases) {
74
+ if (!(from in next))
75
+ continue;
76
+ if (next === bag)
77
+ next = { ...bag };
78
+ // RN gives the alias unconditional priority when both are set (View.js:77-79,
79
+ // `processedProps.nativeID = id`), and the raw key must not survive — no ViewConfig declares
80
+ // `id`, so Fabric would drop it silently.
81
+ next[to] = next[from];
82
+ delete next[from];
83
+ }
84
+ // Seeded whether or not the key was authored: a default that only applies to a key already
85
+ // present is not a default.
86
+ //
87
+ // A bag a transform already folded takes the `continue` on every key and never reaches the copy —
88
+ // which is what keeps the compile-time fold worth having once this one exists. It is no longer
89
+ // the mechanism, but it does keep the hot path allocation-free.
90
+ for (const [key, op] of plan.defaults) {
91
+ const folded = fold(op, next[key]);
92
+ if (key in next && Object.is(next[key], folded))
93
+ continue;
94
+ if (next === bag)
95
+ next = { ...bag };
96
+ next[key] = folded;
97
+ }
98
+ return next;
99
+ }
package/build/index.d.ts CHANGED
@@ -8,17 +8,15 @@ export type { IEllipsizeMode, ITextDefaultableProps } from './text-props';
8
8
  export type { IAccessibilityProps, IAriaProps, IAccessibilityRole, IRole, IAccessibilityStateValue, IAccessibilityValue, IAccessibilityActionInfo, } from './accessibility-props';
9
9
  export { descriptorFor, COMPONENT_DESCRIPTORS } from './component-names';
10
10
  export { buildDescriptors, makeDescriptorFor } from './component-names/shared';
11
- export type { ISymbioteIntrinsic, IComponentDescriptor, } from './component-names/shared';
12
- export { renderActivityIndicator } from './view/render-activity-indicator';
13
- export type { IActivityIndicatorProps, IActivityIndicatorViewProps, IActivityIndicatorPlatform, IActivityIndicatorSize, } from './view/render-activity-indicator';
11
+ export type { ISymbioteIntrinsic, IComponentDescriptor, ICrossTypedIntrinsics, } from './component-names/shared';
14
12
  export { renderSwitch } from './view/render-switch';
15
- export type { ISwitchProps, ISwitchViewProps, ISwitchPlatform, ISwitchTrackColor, } from './view/render-switch';
13
+ export type { ISwitchProps, ISwitchViewProps, ISwitchPlatform, ISwitchTrackColor, ISwitchChangeEvent, } from './view/render-switch';
16
14
  export type { IResponderProps } from './responder-props';
17
15
  export { switchReducer, createInitialSwitchState, shouldSnapBack, valueFromChange, } from './state/switch';
18
16
  export type { ISwitchState, ISwitchAction } from './state/switch';
19
17
  export { resolveDecelerationRate, selectScrollIntrinsics, readLayoutDimension, didContentSizeChange, resolveScrollForwarding, SCROLL_VIEW_BASE_HORIZONTAL, SCROLL_VIEW_BASE_VERTICAL, } from './view/render-scroll-view';
20
18
  export type { IScrollIntrinsics, IContentSize, IScrollForwarding, IScrollForwardingInputs, IScrollForwardMode, } from './view/render-scroll-view';
21
- export { buildScrollViewHandle, splitLayoutProps, attachStickyScroll, isSymbioteEvent, forwardScrollEvent, } from './scroll-view-commands';
19
+ export { buildScrollViewHandle, splitLayoutProps, splitScrollViewStyle, attachStickyScroll, isSymbioteEvent, forwardScrollEvent, } from './scroll-view-commands';
22
20
  export type { IScrollViewHandle } from './scroll-view-commands';
23
21
  export { computeStickyInterpolation, nextStickyHeaderY, stickyDebounceMs, readLayoutNumber, STICKY_HEADER_Z_INDEX, } from './view/render-scroll-sticky';
24
22
  export type { IStickyHeaderProps, IStickyInterpolationParams, } from './view/render-scroll-sticky';
@@ -28,8 +26,6 @@ export type { IImageSource, IImageSourceProp, IResizeMode, IImageProps, IImageVi
28
26
  export { renderImage } from './view/render-image';
29
27
  export type { IImageStatics, IImageSize, IImageCacheStatus, } from '@symbiote-native/engine';
30
28
  export { imageStatics, setImageSourceResolver } from '@symbiote-native/engine';
31
- export { renderImageBackground } from './view/render-image-background';
32
- export type { IImageBackgroundViewProps } from './view/render-image-background';
33
29
  export { renderInputAccessoryView } from './view/render-input-accessory-view';
34
30
  export type { IInputAccessoryViewViewProps } from './view/render-input-accessory-view';
35
31
  export { renderModal } from './view/render-modal';
@@ -38,19 +34,19 @@ export { modalReducer, createInitialModalState, shouldRenderModal, } from './sta
38
34
  export type { IModalState, IModalAction } from './state/modal';
39
35
  export { computeInset, keyboardAvoidingEventNamesFor, readPrefersCrossFadeTransitions, readKeyboardFrame, readLayoutFrame, resolveKeyboardAvoidingLayout, DEFAULT_VERTICAL_OFFSET, } from './view/render-keyboard-avoiding-view';
40
36
  export type { IComputeInsetOptions, IKeyboardAvoidingBehavior, IKeyboardAvoidingEventNames, IMeasuredFrame, IKeyboardFrame, IKeyboardAvoidingLayout, IResolveKeyboardAvoidingLayoutParams, } from './view/render-keyboard-avoiding-view';
41
- export { createPressHandlers, createPressRuntime, normalizeRect, maxEdge, isTouchWithinRegion, readPoint, computeRegion, rippleProps, DEFAULT_DELAY_LONG_PRESS_MS, DEFAULT_PRESS_RECT_OFFSETS, } from './state/pressable';
37
+ export { createPressHandlers, createPressRuntime, disposePressRuntime, normalizeRect, maxEdge, isTouchWithinRegion, readPoint, computeRegion, rippleProps, DEFAULT_DELAY_LONG_PRESS_MS, DEFAULT_MIN_PRESS_DURATION_MS, DEFAULT_PRESS_RECT_OFFSETS, } from './state/pressable';
42
38
  export type { IPressState, IPressHandler, IPressHandlers, IPressHost, IPressRuntime, IPressMachineConfig, IFrameCallback, IRectOffset, IEdgeInsets, IResponderRegion, IPressableAndroidRippleConfig, IRippleBackground as IPressableRippleBackground, } from './state/pressable';
43
- export { buildPressableListeners, resolveDisabledAccessibilityState, noteHoverNoop, shouldSuppressPress, shouldClaimResponder, isTerminationAllowed, } from './view/render-pressable';
39
+ export { buildPressableListeners, resolveDisabledAccessibilityState, noteHoverNoop, shouldSuppressPress, shouldClaimResponder, isTerminationAllowed, resolvePressableFocusable, resolveTouchableFocusable, } from './view/render-pressable';
44
40
  export { computePressOutWait, createTouchableFeedbackRuntime, createTouchableFeedbackHandlers, DEFAULT_ACTIVE_OPACITY, OPACITY_ACTIVE_GRANT_DURATION_MS, OPACITY_ACTIVE_DURATION_MS, OPACITY_INACTIVE_DURATION_MS, RESTING_OPACITY, DEFAULT_HIGHLIGHT_CHILD_OPACITY, DEFAULT_UNDERLAY_COLOR, TOUCHABLE_MIN_PRESS_DURATION_MS, restingOpacityFromStyle, hasTouchablePressHandler, createHighlightUnderlayRuntime, createHighlightUnderlayHandlers, } from './state/touchable';
45
41
  export type { IPressTimingProps, ITouchableHandler, ITouchableFeedbackRuntime, ITouchableFeedbackConfig, ITouchableFeedbackCallbacks, ITouchableFeedbackHandlers, ITouchablePressHandlerProps, IHighlightUnderlayRuntime, IHighlightUnderlayConfig, IHighlightUnderlayCallbacks, IHighlightUnderlayHandlers, } from './state/touchable';
46
42
  export { resolveHighlightExtraStyles } from './view/render-touchable-highlight';
47
43
  export type { ITouchableHighlightExtraStyles, ITouchableHighlightUnderlayView, } from './view/render-touchable-highlight';
48
- export { backgroundProps, canUseNativeForeground, selectableBackground, selectableBackgroundBorderless, rippleBackground, } from './view/render-touchable-native-feedback';
44
+ export { backgroundProps, canUseNativeForeground, selectableBackground, selectableBackgroundBorderless, rippleBackground, TouchableNativeFeedback, } from './view/render-touchable-native-feedback';
49
45
  export type { INativeFeedbackBackground, IThemeAttrBackground, IRippleBackground, } from './view/render-touchable-native-feedback';
50
- export { BUTTON_ACCESSIBILITY_ROLE, buttonTextStyle, resolveButtonTextStyle, } from './view/render-button';
46
+ export { BUTTON_ACCESSIBILITY_ROLE, buttonTextStyle, buttonViewStyle, resolveButtonDisabled, resolveButtonImportantForAccessibility, resolveButtonTextStyle, resolveButtonTitle, resolveButtonViewStyle, } from './view/render-button';
51
47
  export type { IButtonProps } from './view/render-button';
52
48
  export { resolveTextInputProps, foldText, foldAutoComplete, foldSubmitBehavior, mapAutoComplete, textFromChange, eventCountFromChange, shouldCommandText, INITIAL_EVENT_COUNT, SELECTION_NONE, } from './state/text-input';
53
- export type { ITextInputProps, ITextInputHandle, ITextInputSelection, ITextInputEventHandler, ITextInputFoldInput, IFoldedTextInputProps, IInputMode, IEnterKeyHint, ISubmitBehavior, } from './state/text-input';
49
+ export type { ITextInputProps, ITextInputHandle, ITextInputSelection, ITextInputEventHandler, ITextInputFoldInput, IFoldedTextInputProps, IInputMode, IEnterKeyHint, ISubmitBehavior, ITextInputChangeEvent, } from './state/text-input';
54
50
  export { keyboardTypeForInputMode } from './state/text-input';
55
51
  export { renderTextInput } from './view/render-text-input';
56
52
  export type { ITextInputViewProps } from './view/render-text-input';
@@ -64,3 +60,18 @@ export { SINGLE_COLUMN, chunkIntoRows, rowKeyExtractor, expandRowToken, expandRo
64
60
  export type { IRow } from './state/flat-list';
65
61
  export { flattenSections, unwrapEntryItem, sectionEntryKey, scrollLocationToFlatIndex, } from './state/section-list';
66
62
  export type { ISection, ISectionEntry, IVirtualizedSectionListHandle, } from './state/section-list';
63
+ export { registerPressableBehavior, PRESSABLE_TAG, } from './behaviors/pressable';
64
+ export { registerTouchableOpacityBehavior, TOUCHABLE_OPACITY_TAG, } from './behaviors/touchable-opacity';
65
+ export { registerTouchableHighlightBehavior, TOUCHABLE_HIGHLIGHT_TAG, } from './behaviors/touchable-highlight';
66
+ export { registerButtonBehavior, BUTTON_TAG } from './behaviors/button';
67
+ export { registerTouchableNativeFeedbackBehavior, TOUCHABLE_NATIVE_FEEDBACK_TAG, } from './behaviors/touchable-native-feedback';
68
+ export { registerTouchableWithoutFeedbackBehavior, TOUCHABLE_WITHOUT_FEEDBACK_TAG, } from './behaviors/touchable-without-feedback';
69
+ export { ACTIVITY_INDICATOR_SPINNER_TAG, ACTIVITY_INDICATOR_TAG, registerActivityIndicatorBehavior, } from './behaviors/activity-indicator';
70
+ export type { IActivityIndicatorProps, IActivityIndicatorSize, } from './behaviors/activity-indicator';
71
+ export { foldImagePayload, IMAGE_TAG, registerImageBehavior, } from './behaviors/image';
72
+ export { IMAGE_BACKGROUND_TAG, registerImageBackgroundBehavior, } from './behaviors/image-background';
73
+ export { foldInputAccessoryViewPayload, INPUT_ACCESSORY_VIEW_TAG, registerInputAccessoryViewBehavior, } from './behaviors/input-accessory-view';
74
+ export { registerTextInputBehavior, buildTextInputHandle, TEXT_INPUT_TAG, TEXT_INPUT_MULTILINE_TAG, } from './behaviors/text-input';
75
+ export { registerSwitchBehavior, SWITCH_TAG } from './behaviors/switch';
76
+ export { registerRefreshControlBehavior, REFRESH_CONTROL_TAG, } from './behaviors/refresh-control';
77
+ export { HORIZONTAL_SCROLL_VIEW_TAG, registerScrollViewBehavior, SCROLL_VIEW_TAG, } from './behaviors/scroll-view';
package/build/index.js CHANGED
@@ -18,7 +18,6 @@ export { resolveTextProps } from './text-props.js';
18
18
  // re-exports iOS for headless. descriptorFor is the per-platform-bound resolver.
19
19
  export { descriptorFor, COMPONENT_DESCRIPTORS } from './component-names';
20
20
  export { buildDescriptors, makeDescriptorFor } from './component-names/shared.js';
21
- export { renderActivityIndicator } from './view/render-activity-indicator.js';
22
21
  export { renderSwitch } from './view/render-switch.js';
23
22
  // Switch is the first STATE machine off this layer (ActivityIndicator was render-only):
24
23
  // the reducer + the two pure predicates are the logic half, the adapter supplies the hook.
@@ -27,7 +26,7 @@ export { switchReducer, createInitialSwitchState, shouldSnapBack, valueFromChang
27
26
  // owns the refs/effects/element assembly and the sticky-header component; these supply the
28
27
  // platform-invariant math and plumbing every adapter shares.
29
28
  export { resolveDecelerationRate, selectScrollIntrinsics, readLayoutDimension, didContentSizeChange, resolveScrollForwarding, SCROLL_VIEW_BASE_HORIZONTAL, SCROLL_VIEW_BASE_VERTICAL, } from './view/render-scroll-view.js';
30
- export { buildScrollViewHandle, splitLayoutProps, attachStickyScroll, isSymbioteEvent, forwardScrollEvent, } from './scroll-view-commands.js';
29
+ export { buildScrollViewHandle, splitLayoutProps, splitScrollViewStyle, attachStickyScroll, isSymbioteEvent, forwardScrollEvent, } from './scroll-view-commands.js';
31
30
  export { computeStickyInterpolation, nextStickyHeaderY, stickyDebounceMs, readLayoutNumber, STICKY_HEADER_Z_INDEX, } from './view/render-scroll-sticky.js';
32
31
  // The per-header sticky effect machine: the one pure state machine every sticky-header consumer drives
33
32
  // (React/Vue header component, Angular header component, Angular projection wrapper), folding the
@@ -37,7 +36,8 @@ export { reduceSticky, createInitialStickyState, stickyEffectSignature, } from '
37
36
  export { renderImage } from './view/render-image/index.js';
38
37
  export { imageStatics, setImageSourceResolver } from '@symbiote-native/engine';
39
38
  // ImageBackground: render-only composition (absolute-fill image behind, children on top).
40
- export { renderImageBackground } from './view/render-image-background.js';
39
+ // No render fn: the only composition this primitive had is the two nodes the behavior builds, so
40
+ // `view/render-image-background.ts` went with the wrappers — same as ActivityIndicator's.
41
41
  // InputAccessoryView: render-only host assembly (nativeID/backgroundColor).
42
42
  export { renderInputAccessoryView } from './view/render-input-accessory-view.js';
43
43
  // Modal: full 3-layer split (state machine gates the iOS keep-alive frame).
@@ -49,16 +49,20 @@ export { computeInset, keyboardAvoidingEventNamesFor, readPrefersCrossFadeTransi
49
49
  // geometry, drift test, suppression flags) lives in state/pressable; the responder-listener +
50
50
  // accessibilityState + ripple render decisions in view/render-pressable. Every adapter supplies
51
51
  // only its reactive `pressed` cell + the View's measure handle (see the 3-layer split).
52
- export { createPressHandlers, createPressRuntime, normalizeRect, maxEdge, isTouchWithinRegion, readPoint, computeRegion, rippleProps, DEFAULT_DELAY_LONG_PRESS_MS, DEFAULT_PRESS_RECT_OFFSETS, } from './state/pressable.js';
53
- export { buildPressableListeners, resolveDisabledAccessibilityState, noteHoverNoop, shouldSuppressPress, shouldClaimResponder, isTerminationAllowed, } from './view/render-pressable/index.js';
52
+ export { createPressHandlers, createPressRuntime, disposePressRuntime, normalizeRect, maxEdge, isTouchWithinRegion, readPoint, computeRegion, rippleProps, DEFAULT_DELAY_LONG_PRESS_MS, DEFAULT_MIN_PRESS_DURATION_MS, DEFAULT_PRESS_RECT_OFFSETS, } from './state/pressable.js';
53
+ export { buildPressableListeners, resolveDisabledAccessibilityState, noteHoverNoop, shouldSuppressPress, shouldClaimResponder, isTerminationAllowed, resolvePressableFocusable, resolveTouchableFocusable, } from './view/render-pressable/index.js';
54
54
  // Touchable*: shared press-timing constants + the deactivation-floor math (the Animated feedback
55
55
  // itself stays per-adapter), and TouchableNativeFeedback's pure static factories + background map.
56
56
  export { computePressOutWait, createTouchableFeedbackRuntime, createTouchableFeedbackHandlers, DEFAULT_ACTIVE_OPACITY, OPACITY_ACTIVE_GRANT_DURATION_MS, OPACITY_ACTIVE_DURATION_MS, OPACITY_INACTIVE_DURATION_MS, RESTING_OPACITY, DEFAULT_HIGHLIGHT_CHILD_OPACITY, DEFAULT_UNDERLAY_COLOR, TOUCHABLE_MIN_PRESS_DURATION_MS, restingOpacityFromStyle, hasTouchablePressHandler, createHighlightUnderlayRuntime, createHighlightUnderlayHandlers, } from './state/touchable.js';
57
57
  export { resolveHighlightExtraStyles } from './view/render-touchable-highlight.js';
58
- export { backgroundProps, canUseNativeForeground, selectableBackground, selectableBackgroundBorderless, rippleBackground, } from './view/render-touchable-native-feedback.js';
59
- // Button: shared base text style, role constant, and color fold (the adapter composes its own
60
- // TouchableOpacity + Text).
61
- export { BUTTON_ACCESSIBILITY_ROLE, buttonTextStyle, resolveButtonTextStyle, } from './view/render-button.js';
58
+ export { backgroundProps, canUseNativeForeground, selectableBackground, selectableBackgroundBorderless, rippleBackground,
59
+ // RN's own static namespace, the only part of the deleted wrappers with nowhere else to go: the
60
+ // element is a tag now, and a tag is a string that cannot carry `.Ripple`.
61
+ TouchableNativeFeedback, } from './view/render-touchable-native-feedback.js';
62
+ // Button: the role constant plus every platform fold RN's Button.js performs (the adapter composes
63
+ // its own touchable + view + text around them). The VIEW style is the half that was missing until
64
+ // 2026-09-09 and it is the whole Android look.
65
+ export { BUTTON_ACCESSIBILITY_ROLE, buttonTextStyle, buttonViewStyle, resolveButtonDisabled, resolveButtonImportantForAccessibility, resolveButtonTextStyle, resolveButtonTitle, resolveButtonViewStyle, } from './view/render-button.js';
62
66
  // TextInput: the controlled-value / event-count handshake. The logic half is the pure
63
67
  // folds/maps + the controlled-write predicate (not a single reducer: count must re-render the
64
68
  // imperative handle, lastNativeText must not); the view half picks the intrinsic and maps the
@@ -79,3 +83,40 @@ export { reduceList, createInitialListState, listEffectSignature, } from './stat
79
83
  export { subscribeListDiagnostics } from './state/virtualized-list-diagnostics.js';
80
84
  export { SINGLE_COLUMN, chunkIntoRows, rowKeyExtractor, expandRowToken, expandRowViewability, lastItemOfRow, firstItemOfRow, } from './state/flat-list.js';
81
85
  export { flattenSections, unwrapEntryItem, sectionEntryKey, scrollLocationToFlatIndex, } from './state/section-list.js';
86
+ export { registerPressableBehavior, PRESSABLE_TAG, } from './behaviors/pressable.js';
87
+ export { registerTouchableOpacityBehavior, TOUCHABLE_OPACITY_TAG, } from './behaviors/touchable-opacity.js';
88
+ export { registerTouchableHighlightBehavior, TOUCHABLE_HIGHLIGHT_TAG, } from './behaviors/touchable-highlight.js';
89
+ export { registerButtonBehavior, BUTTON_TAG } from './behaviors/button.js';
90
+ // Registered by ALL FIVE adapters since 2026-09-09, in the same commit that deleted the five
91
+ // wrappers — which is what makes it safe. While those wrappers still rendered a `Pressable` around
92
+ // a feedback `View`, registering would have put a second responder on every existing
93
+ // TouchableNativeFeedback. The ActivityIndicator and ScrollView blocks below are still in the
94
+ // withheld state this one just left.
95
+ export { registerTouchableNativeFeedbackBehavior, TOUCHABLE_NATIVE_FEEDBACK_TAG, } from './behaviors/touchable-native-feedback.js';
96
+ // The second tag that commits no node, and registered by all five adapters in the same commit that
97
+ // deleted their five wrappers — while those wrappers still rendered a `Pressable` around the app's
98
+ // children, registering would have put a second responder on every TouchableWithoutFeedback.
99
+ export { registerTouchableWithoutFeedbackBehavior, TOUCHABLE_WITHOUT_FEEDBACK_TAG, } from './behaviors/touchable-without-feedback.js';
100
+ // Registered by all five adapters since 2026-09-09, in the same commit that deleted the five
101
+ // wrappers — the tag builds its own spinner, so a surviving wrapper would have painted a second one.
102
+ // The prop type rides along: there is no `view/render-activity-indicator.ts` any more, because the
103
+ // only render this primitive had was the two nodes the behavior now builds.
104
+ export { ACTIVITY_INDICATOR_SPINNER_TAG, ACTIVITY_INDICATOR_TAG, registerActivityIndicatorBehavior, } from './behaviors/activity-indicator';
105
+ export { foldImagePayload, IMAGE_TAG, registerImageBehavior, } from './behaviors/image.js';
106
+ // Registered by all five adapters since 2026-09-09, in the same commit that deleted the five
107
+ // wrappers — the tag builds the background image itself, so a surviving wrapper would have
108
+ // committed a second one under it.
109
+ export { IMAGE_BACKGROUND_TAG, registerImageBackgroundBehavior, } from './behaviors/image-background.js';
110
+ export { foldInputAccessoryViewPayload, INPUT_ACCESSORY_VIEW_TAG, registerInputAccessoryViewBehavior, } from './behaviors/input-accessory-view.js';
111
+ export { registerTextInputBehavior, buildTextInputHandle, TEXT_INPUT_TAG, TEXT_INPUT_MULTILINE_TAG, } from './behaviors/text-input.js';
112
+ export { registerSwitchBehavior, SWITCH_TAG } from './behaviors/switch.js';
113
+ export { registerRefreshControlBehavior, REFRESH_CONTROL_TAG, } from './behaviors/refresh-control.js';
114
+ // Exported as a FUNCTION, not as a side-effect module, which is what keeps it safe on a barrel:
115
+ // Metro's inlineRequires only defers a re-export until its binding is named as a VALUE, and a
116
+ // caller of `registerScrollViewBehavior()` names it. The shape CLAUDE.md forbids is a module whose
117
+ // evaluation alone registers.
118
+ //
119
+ // EVERY ADAPTER CALLS IT since 2026-09-11, through `@symbiote-native/components/register`. It was
120
+ // withheld while the wrappers built their own content node — registering then would have
121
+ // double-nested every ScrollView. They are gone; the engine is the single owner.
122
+ export { HORIZONTAL_SCROLL_VIEW_TAG, registerScrollViewBehavior, SCROLL_VIEW_TAG, } from './behaviors/scroll-view';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,55 @@
1
+ // Side-effect ONLY. Exports nothing, and must never be re-exported from any barrel.
2
+ //
3
+ // The engine's host-behavior registry is what makes a bare `<pressable>` / `<text-input>` /
4
+ // `<switch>` carry its machine; an unregistered tag commits inert. Every adapter needs the same
5
+ // fourteen calls, so they live here once — an adapter's own `src/register.ts` is a single bare
6
+ // `import '@symbiote-native/components/register';`.
7
+ //
8
+ // WHY A SIDE-EFFECT MODULE AND NOT A BARREL EXPORT. Metro turns on `inlineRequires` for PRODUCTION
9
+ // only: it moves a `require` down to the first place its binding is used as a VALUE, and
10
+ // `export { X } from './x'` compiles to a lazy getter. A registration reached only through a
11
+ // re-export therefore never evaluates in a Release build — invisible to tsc, to vitest, and to
12
+ // grepping the bundle. A bare `import` placed NEXT TO a re-export of the same specifier does not
13
+ // help either: Babel merges the two into one dependency and the merged dependency stays lazy.
14
+ // `register.test.ts` is what stops a later tidy-up from turning this back into an export.
15
+ import { registerActivityIndicatorBehavior } from './behaviors/activity-indicator';
16
+ import { registerButtonBehavior } from './behaviors/button.js';
17
+ import { registerImageBackgroundBehavior } from './behaviors/image-background.js';
18
+ import { registerImageBehavior } from './behaviors/image.js';
19
+ import { registerInputAccessoryViewBehavior } from './behaviors/input-accessory-view.js';
20
+ import { registerPressableBehavior } from './behaviors/pressable.js';
21
+ import { registerRefreshControlBehavior } from './behaviors/refresh-control.js';
22
+ import { registerScrollViewBehavior } from './behaviors/scroll-view';
23
+ import { registerSwitchBehavior } from './behaviors/switch.js';
24
+ import { registerTextInputBehavior } from './behaviors/text-input.js';
25
+ import { registerTouchableHighlightBehavior } from './behaviors/touchable-highlight.js';
26
+ import { registerTouchableNativeFeedbackBehavior } from './behaviors/touchable-native-feedback.js';
27
+ import { registerTouchableOpacityBehavior } from './behaviors/touchable-opacity.js';
28
+ import { registerTouchableWithoutFeedbackBehavior } from './behaviors/touchable-without-feedback.js';
29
+ registerPressableBehavior();
30
+ registerTouchableNativeFeedbackBehavior();
31
+ registerTouchableWithoutFeedbackBehavior();
32
+ // Their own tags rather than `pressable`: one node holds exactly one press machine, and each of
33
+ // these is a pressable PLUS a second machine — the opacity fade, the underlay show/hide.
34
+ registerTouchableOpacityBehavior();
35
+ registerTouchableHighlightBehavior();
36
+ // The whole subtree is the behavior's — RN's Button takes no children and builds
37
+ // touchable > view > text > raw text itself (Button.js:363-388).
38
+ registerButtonBehavior();
39
+ // Two nodes, both the behavior's: RN wraps its native spinner in a centering View
40
+ // (ActivityIndicator.js:112), so the tag is that View and `buildStructure` builds the spinner.
41
+ registerActivityIndicatorBehavior();
42
+ registerTextInputBehavior();
43
+ registerSwitchBehavior();
44
+ registerRefreshControlBehavior();
45
+ // Fold-only, both of them: no node of their own, just the prop fold the wrapper used to run.
46
+ registerImageBehavior();
47
+ registerInputAccessoryViewBehavior();
48
+ registerImageBackgroundBehavior();
49
+ // The ENGINE is the single owner of a ScrollView's content node: `buildStructure` builds the
50
+ // `RCTScrollContentView`, `slotProps` carries `contentContainerStyle` onto it, the claim on a
51
+ // `refresh-control` child places it per platform, and the sticky seam is
52
+ // `behaviors/scroll-view/sticky.ts`. EXACTLY ONE THING MAY BUILD THAT NODE — a second owner is a
53
+ // silent `RCTScrollContentView` inside the first, which each adapter's
54
+ // `scroll-view-content-owner` test is what guards.
55
+ registerScrollViewBehavior();
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Resolve a primitive tag to the intrinsic it should actually commit, given its creation props.
3
+ *
4
+ * A tag with no `intrinsicWhen` — every primitive but `TextInput` today — passes through by
5
+ * identity, so a renderer may call this unconditionally without paying a lookup per prop.
6
+ */
7
+ export declare function resolveIntrinsicTag(tag: string, props: Readonly<Record<string, unknown>> | undefined): string;
@@ -0,0 +1,49 @@
1
+ // The RUNTIME half of `HOST_PRIMITIVES.intrinsicWhen` — choosing WHICH native view a primitive
2
+ // commits, from the props it was created with.
3
+ //
4
+ // Why this has to exist at runtime at all. `intrinsicWhen` lets one primitive map to two native
5
+ // views (`TextInput` -> `RCTSinglelineTextInputView` / `RCTMultilineTextInputView`), and until now
6
+ // only the lowering transforms read it — so the choice was made at COMPILE time, from the source
7
+ // text. That is why `dynamicIntrinsicChoice` had to be a refusal category: a transform seeing
8
+ // `multiline={isLong}` cannot know the value, so it refused to lower the element at all.
9
+ //
10
+ // A public primitive TAG has no transform in front of it on three of the five adapters, so the
11
+ // choice has to be made where the value is actually known — here, at element creation. That is a
12
+ // capability the compiler never had, and it is why the refusal category disappears WITH the
13
+ // transform rather than migrating into it.
14
+ //
15
+ // WHAT THIS DELIBERATELY DOES NOT DO: react to the prop CHANGING later. A node's native view is
16
+ // fixed at `createNode` and no prop write moves a node between view types, so flipping `multiline`
17
+ // on a mounted input needs the node re-created — exactly as a browser re-initialises on an
18
+ // `<input type>` change. Re-creation is the renderer's business, not this function's; until a
19
+ // renderer implements it, a runtime flip keeps the view it was created with.
20
+ import { HOST_PRIMITIVES } from '../host-primitives.cjs';
21
+ const CHOICE_BY_BASE_TAG = new Map(Object.values(HOST_PRIMITIVES).flatMap((primitive) => primitive.intrinsicWhen === undefined
22
+ ? []
23
+ : [
24
+ [
25
+ primitive.intrinsic,
26
+ {
27
+ prop: primitive.intrinsicWhen.prop,
28
+ alternate: primitive.intrinsicWhen.intrinsic,
29
+ },
30
+ ],
31
+ ]));
32
+ /**
33
+ * Resolve a primitive tag to the intrinsic it should actually commit, given its creation props.
34
+ *
35
+ * A tag with no `intrinsicWhen` — every primitive but `TextInput` today — passes through by
36
+ * identity, so a renderer may call this unconditionally without paying a lookup per prop.
37
+ */
38
+ export function resolveIntrinsicTag(tag, props) {
39
+ const choice = CHOICE_BY_BASE_TAG.get(tag);
40
+ if (choice === undefined || props === undefined)
41
+ return tag;
42
+ // Explicit rather than truthy, and the string form is not decoration: a template-driven adapter
43
+ // can deliver `multiline="true"` as an ATTRIBUTE STRING (Angular's `multiline="true"` vs
44
+ // `[multiline]="true"`), and plain truthiness would then read `multiline="false"` — also a
45
+ // non-empty string — as multiline. An absent prop keeps the base tag.
46
+ return props[choice.prop] === true || props[choice.prop] === 'true'
47
+ ? choice.alternate
48
+ : tag;
49
+ }
@@ -16,6 +16,10 @@ export declare function splitLayoutProps(style: IStyleProp<IViewStyle> | undefin
16
16
  outer: Record<string, unknown>;
17
17
  inner: Record<string, unknown>;
18
18
  };
19
+ export declare function splitScrollViewStyle(base: IStyleProp<IViewStyle> | undefined, style: unknown): {
20
+ outer: IStyleProp<IViewStyle>;
21
+ inner: IStyleProp<IViewStyle>;
22
+ };
19
23
  export { isSymbioteEvent };
20
24
  export declare function forwardScrollEvent(handler: IScrollHandler, args: readonly unknown[]): void;
21
25
  export declare function buildScrollViewHandle(getNode: () => ISymbioteNode | null): IScrollViewHandle;