@symbiote-native/components 1.0.0 → 3.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.
- package/README.md +11 -13
- package/build/accessibility-props.d.ts +1 -1
- package/build/accessibility-props.js +2 -2
- package/build/behaviors/activity-indicator/index.android.d.ts +1 -0
- package/build/behaviors/activity-indicator/index.android.js +16 -0
- package/build/behaviors/activity-indicator/index.d.ts +3 -0
- package/build/behaviors/activity-indicator/index.ios.d.ts +1 -0
- package/build/behaviors/activity-indicator/index.ios.js +14 -0
- package/build/behaviors/activity-indicator/index.js +5 -0
- package/build/behaviors/activity-indicator/shared.d.ts +18 -0
- package/build/behaviors/activity-indicator/shared.js +120 -0
- package/build/behaviors/button.d.ts +13 -0
- package/build/behaviors/button.js +340 -0
- package/build/behaviors/image-background.d.ts +3 -0
- package/build/behaviors/image-background.js +155 -0
- package/build/behaviors/image.d.ts +1 -2
- package/build/behaviors/image.js +25 -106
- package/build/behaviors/input-accessory-view.d.ts +1 -2
- package/build/behaviors/input-accessory-view.js +49 -55
- package/build/behaviors/pressable.d.ts +59 -1
- package/build/behaviors/pressable.js +142 -96
- package/build/behaviors/refresh-control.d.ts +2 -0
- package/build/behaviors/refresh-control.js +96 -0
- package/build/behaviors/scroll-view/index.android.d.ts +1 -0
- package/build/behaviors/scroll-view/index.android.js +40 -0
- package/build/behaviors/scroll-view/index.d.ts +3 -0
- package/build/behaviors/scroll-view/index.ios.d.ts +1 -0
- package/build/behaviors/scroll-view/index.ios.js +10 -0
- package/build/behaviors/scroll-view/index.js +8 -0
- package/build/behaviors/scroll-view/responder.d.ts +4 -0
- package/build/behaviors/scroll-view/responder.js +202 -0
- package/build/behaviors/scroll-view/shared.d.ts +9 -0
- package/build/behaviors/scroll-view/shared.js +291 -0
- package/build/behaviors/scroll-view/sticky.d.ts +18 -0
- package/build/behaviors/scroll-view/sticky.js +581 -0
- package/build/behaviors/switch.d.ts +1 -1
- package/build/behaviors/switch.js +49 -88
- package/build/behaviors/text-input.d.ts +2 -2
- package/build/behaviors/text-input.js +244 -104
- package/build/behaviors/touchable-highlight.d.ts +9 -0
- package/build/behaviors/touchable-highlight.js +205 -0
- package/build/behaviors/touchable-native-feedback.d.ts +20 -0
- package/build/behaviors/touchable-native-feedback.js +254 -0
- package/build/behaviors/touchable-opacity.d.ts +12 -0
- package/build/behaviors/touchable-opacity.js +239 -0
- package/build/behaviors/touchable-without-feedback.d.ts +2 -0
- package/build/behaviors/touchable-without-feedback.js +231 -0
- package/build/component-names/index.android.js +31 -25
- package/build/component-names/index.ios.js +28 -23
- package/build/component-names/shared.d.ts +2 -1
- package/build/component-names/shared.js +16 -6
- package/build/descriptor.js +4 -4
- package/build/index.d.ts +26 -26
- package/build/index.js +56 -27
- package/build/register.d.ts +1 -0
- package/build/register.js +55 -0
- package/build/resolve-intrinsic.js +3 -9
- package/build/scroll-view-commands.d.ts +1 -5
- package/build/scroll-view-commands.js +23 -85
- package/build/state/flat-list.d.ts +2 -2
- package/build/state/flat-list.js +10 -2
- package/build/state/pressable.d.ts +6 -1
- package/build/state/pressable.js +63 -28
- package/build/state/section-list.d.ts +2 -0
- package/build/state/section-list.js +14 -7
- package/build/state/text-input.d.ts +10 -40
- package/build/state/text-input.js +17 -186
- package/build/state/touchable.d.ts +1 -0
- package/build/state/touchable.js +11 -8
- package/build/state/virtualized-list-reducer.d.ts +2 -2
- package/build/state/virtualized-list.d.ts +6 -6
- package/build/state/virtualized-list.js +71 -37
- package/build/text-props.d.ts +0 -8
- package/build/text-props.js +14 -25
- package/build/view/render-button.d.ts +11 -4
- package/build/view/render-button.js +74 -22
- package/build/view/render-image/index.d.ts +14 -1
- package/build/view/render-image/index.js +22 -147
- package/build/view/render-input-accessory-view.d.ts +1 -5
- package/build/view/render-input-accessory-view.js +26 -48
- package/build/view/render-keyboard-avoiding-view.d.ts +7 -1
- package/build/view/render-keyboard-avoiding-view.js +40 -1
- package/build/view/render-modal.d.ts +1 -1
- package/build/view/render-modal.js +18 -8
- package/build/view/render-pressable/index.d.ts +3 -0
- package/build/view/render-pressable/index.js +28 -0
- package/build/view/render-scroll-view.d.ts +1 -4
- package/build/view/render-scroll-view.js +17 -54
- package/build/view/render-switch.d.ts +4 -15
- package/build/view/render-switch.js +4 -42
- package/build/view/render-touchable-highlight.d.ts +1 -0
- package/build/view/render-touchable-native-feedback.d.ts +19 -1
- package/build/view/render-touchable-native-feedback.js +34 -9
- package/host-primitives.cjs +178 -280
- package/host-primitives.d.cts +0 -3
- package/package.json +8 -21
- package/build/fold-host-bag.d.ts +0 -15
- package/build/fold-host-bag.js +0 -99
- package/build/state-style.d.ts +0 -15
- package/build/state-style.js +0 -47
- package/build/view/render-activity-indicator.d.ts +0 -25
- package/build/view/render-activity-indicator.js +0 -88
- package/build/view/render-image-background.d.ts +0 -9
- package/build/view/render-image-background.js +0 -48
- package/build/view/render-text-input.d.ts +0 -11
- package/build/view/render-text-input.js +0 -39
- package/lowering-fixtures.cjs +0 -259
- package/lowering-fixtures.d.cts +0 -17
- package/specialize-state-style.cjs +0 -219
- package/specialize-state-style.d.cts +0 -15
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
// TouchableOpacity as an ENGINE-NODE behavior, so it can be an intrinsic tag instead of a
|
|
2
|
+
// framework component (`.claude/rules/host-primitive-tier.md`, tier 2).
|
|
3
|
+
//
|
|
4
|
+
// ONE NODE, and that is a correction rather than a shortcut. Every adapter's wrapper builds two —
|
|
5
|
+
// a `pressable` holding a faded `view` — and RN builds one: `TouchableOpacity.js:302` renders a
|
|
6
|
+
// single `<Animated.View>` carrying the responder handlers AND
|
|
7
|
+
// `style={[props.style, {opacity: anim}]}`. The two-node shape is copied from
|
|
8
|
+
// TouchableNativeFeedback, which genuinely does clone onto a child; the same misreading is on
|
|
9
|
+
// record for `android_ripple` (`.claude/rules/adapter-parity-audit.md`). So the tag collapses the
|
|
10
|
+
// pair and lands on RN's own tree.
|
|
11
|
+
//
|
|
12
|
+
// WHAT IS SHARED AND WHAT IS NEW. The press machine is `./pressable`'s, composed through
|
|
13
|
+
// `createPressBehavior` — one node may hold exactly one press machine, so this tag must not also
|
|
14
|
+
// register the plain `pressable` behavior. The press-scheduling half (delayPressIn defer,
|
|
15
|
+
// flush-on-early-release, the minPressDuration hold) is `../state/touchable`, unchanged and still
|
|
16
|
+
// shared with the wrappers. New here is only WHERE the fade lives: on the engine node, through
|
|
17
|
+
// `setAnimatedBehaviorStyle`, instead of in a component's own Animated wiring.
|
|
18
|
+
//
|
|
19
|
+
// REGISTRATION IS THE HAZARD, not the machine — see `./pressable` for why each adapter's entry
|
|
20
|
+
// does a bare `import './register';` that the barrel does not re-export.
|
|
21
|
+
//
|
|
22
|
+
// TODO(rn-parity, low priority): `TouchableOpacity.js:197-220` gates the active/inactive opacity
|
|
23
|
+
// fade on `onFocus`/`onBlur` too (TV remote focus fires the same fade `onPress` triggers). Neither
|
|
24
|
+
// name is wired into the feedback machine here; they pass through as ordinary, un-intercepted
|
|
25
|
+
// event props. Deliberately not implemented — dead on every device this project targets (no tvOS
|
|
26
|
+
// build, no example app; iOS/Android are the only targets). Audit skill, "Found, NOT fixed: TV
|
|
27
|
+
// (Platform.isTV) focus/blur feedback on TouchableOpacity/Highlight".
|
|
28
|
+
import { AnimatedMock, AnimatedValue, Easing, Platform, dlog, getExplicitStyle, markPropsDirty, registerHostBehavior, requestCommitFor, setAnimatedBehaviorStyle, timing, propOf, propsOf, } from '@symbiote-native/engine';
|
|
29
|
+
import { resolveButtonDisabled } from '../view/render-button.js';
|
|
30
|
+
import { asAccessibilityState, booleanOr, createPressBehavior, } from './pressable.js';
|
|
31
|
+
import { createTouchableFeedbackHandlers, createTouchableFeedbackRuntime, DEFAULT_ACTIVE_OPACITY, OPACITY_ACTIVE_DURATION_MS, OPACITY_ACTIVE_GRANT_DURATION_MS, OPACITY_INACTIVE_DURATION_MS, RESTING_OPACITY, restingOpacityFromStyle, TOUCHABLE_MIN_PRESS_DURATION_MS, } from '../state/touchable.js';
|
|
32
|
+
export const TOUCHABLE_OPACITY_TAG = 'touchable-opacity';
|
|
33
|
+
const states = new WeakMap();
|
|
34
|
+
// `modules/animated`'s barrel swaps the whole driver namespace for the mock when the host reports
|
|
35
|
+
// reduced motion; the one driver used here is swapped on the same flag.
|
|
36
|
+
const startTiming = Platform.isDisableAnimations ? AnimatedMock.timing : timing;
|
|
37
|
+
function numberOr(value, fallback) {
|
|
38
|
+
return typeof value === 'number' ? value : fallback;
|
|
39
|
+
}
|
|
40
|
+
// From `parts.explicitStyle`, never `node.props.style`. A fade frame lands as `setNativeProps`,
|
|
41
|
+
// which merges its partial onto `node.props.style` in place — so reading the published slot would
|
|
42
|
+
// hand the resting-opacity math the fade's own output and `afterCommit` would chase itself.
|
|
43
|
+
//
|
|
44
|
+
// It therefore sees the AUTHOR's style and not a class-derived `opacity`, which is exactly RN's
|
|
45
|
+
// scope: `_getChildStyleOpacityWithDefault` reads `this.props.style` and nothing else.
|
|
46
|
+
function restingOpacityOf(node) {
|
|
47
|
+
return restingOpacityFromStyle(getExplicitStyle(node));
|
|
48
|
+
}
|
|
49
|
+
function fadeTo(state, toValue, ms) {
|
|
50
|
+
dlog(`touchable-opacity opacity -> ${toValue} over ${ms}ms`);
|
|
51
|
+
// useNativeDriver is RN's own (TouchableOpacity.js:242): opacity is natively drivable, so the
|
|
52
|
+
// fade survives a busy JS thread, which is the whole point of press feedback.
|
|
53
|
+
startTiming(state.opacity, {
|
|
54
|
+
toValue,
|
|
55
|
+
duration: ms,
|
|
56
|
+
easing: Easing.inOut(Easing.quad),
|
|
57
|
+
useNativeDriver: true,
|
|
58
|
+
}).start();
|
|
59
|
+
}
|
|
60
|
+
// Runs once per GESTURE (see IPressConfigRefinement), so the config it reads is whatever the props
|
|
61
|
+
// hold when a finger lands, and the handlers it builds are discarded with the gesture. The runtime
|
|
62
|
+
// that must outlive one — an in-flight delayPressIn timer, the activation clock — is on the node.
|
|
63
|
+
const refine = (node, config) => {
|
|
64
|
+
const state = states.get(node);
|
|
65
|
+
if (state === undefined)
|
|
66
|
+
return config;
|
|
67
|
+
const resting = restingOpacityOf(node);
|
|
68
|
+
const activeOpacity = numberOr(propOf(node, 'activeOpacity'), DEFAULT_ACTIVE_OPACITY);
|
|
69
|
+
const handlers = createTouchableFeedbackHandlers({
|
|
70
|
+
delayPressIn: numberOr(propOf(node, 'delayPressIn'), 0),
|
|
71
|
+
delayPressOut: numberOr(propOf(node, 'delayPressOut'), 0),
|
|
72
|
+
// Read raw rather than off `config`, which has already defaulted the absent case to the press
|
|
73
|
+
// machine's own 130 ms — a floor RN's Touchables never see (TOUCHABLE_MIN_PRESS_DURATION_MS).
|
|
74
|
+
minPressDuration: numberOr(propOf(node, 'minPressDuration'), TOUCHABLE_MIN_PRESS_DURATION_MS),
|
|
75
|
+
schedule: (callback, ms) => {
|
|
76
|
+
const id = setTimeout(() => {
|
|
77
|
+
state.timers.delete(id);
|
|
78
|
+
callback();
|
|
79
|
+
}, ms);
|
|
80
|
+
state.timers.add(id);
|
|
81
|
+
return () => {
|
|
82
|
+
clearTimeout(id);
|
|
83
|
+
state.timers.delete(id);
|
|
84
|
+
};
|
|
85
|
+
},
|
|
86
|
+
now: Date.now,
|
|
87
|
+
}, state.runtime, {
|
|
88
|
+
activate(event) {
|
|
89
|
+
// TouchableOpacity.js:215-220 picks the duration from WHERE the press-in came from: our
|
|
90
|
+
// engine's own 'pressIn' event (an ordinary grant, or its delayed replay) is the 0ms leg;
|
|
91
|
+
// a drift-out/drift-back-in reactivation arrives as 'responderMove'
|
|
92
|
+
// (`state/pressable.ts`'s `handleResponderMove`) and gets the 150ms ease-back-in instead.
|
|
93
|
+
const duration = event.type === 'responderMove'
|
|
94
|
+
? OPACITY_ACTIVE_DURATION_MS
|
|
95
|
+
: OPACITY_ACTIVE_GRANT_DURATION_MS;
|
|
96
|
+
fadeTo(state, activeOpacity, duration);
|
|
97
|
+
config.onPressIn?.(event);
|
|
98
|
+
},
|
|
99
|
+
deactivate(event) {
|
|
100
|
+
fadeTo(state, resting, OPACITY_INACTIVE_DURATION_MS);
|
|
101
|
+
config.onPressOut?.(event);
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
return {
|
|
105
|
+
...config,
|
|
106
|
+
// RN hands Pressability 0 (TouchableOpacity.js:195): the deactivation floor belongs to the
|
|
107
|
+
// feedback machine above, and a second one holds the fade for the press machine's default.
|
|
108
|
+
minPressDuration: 0,
|
|
109
|
+
onPressIn: handlers.handlePressIn,
|
|
110
|
+
onPressOut: handlers.handlePressOut,
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
// TouchableOpacity.js:186-189 — `disabled ?? aria-disabled ?? accessibilityState.disabled`, the
|
|
114
|
+
// same three-way answer Button resolves, just never wired to this tag's OWN registration before.
|
|
115
|
+
// Without it, a caller who sets only `accessibilityState={{disabled: true}}` (no `disabled` prop)
|
|
116
|
+
// gets the greyed-out RN look but a press that still fires here — RN suppresses it.
|
|
117
|
+
const touchableOpacityDisabled = props => resolveButtonDisabled(booleanOr(props.disabled), booleanOr(props['aria-disabled']), asAccessibilityState(props.accessibilityState));
|
|
118
|
+
/**
|
|
119
|
+
* The behavior as PARTS, so a tag that is a TouchableOpacity plus something — `button`, which RN
|
|
120
|
+
* builds as exactly that (Button.js:283) — composes the fade instead of re-implementing it.
|
|
121
|
+
*
|
|
122
|
+
* Safe to hand to two tags: every piece of runtime is keyed by NODE (`states`), and `press` is
|
|
123
|
+
* itself already shared that way.
|
|
124
|
+
*/
|
|
125
|
+
export function createTouchableOpacityBehavior(disabledOf) {
|
|
126
|
+
// Its own machine rather than the module-level `press`, so a composing tag can say what
|
|
127
|
+
// `disabled` MEANS to it — the answer feeds the machine, not a payload: the props half of that
|
|
128
|
+
// question is `foldPressableProps` in `SymbioteFabricProps.cpp` and reads the authored bag.
|
|
129
|
+
const machine = createPressBehavior(refine, disabledOf);
|
|
130
|
+
return {
|
|
131
|
+
...machine,
|
|
132
|
+
attach(node) {
|
|
133
|
+
const state = {
|
|
134
|
+
opacity: new AnimatedValue(RESTING_OPACITY),
|
|
135
|
+
runtime: createTouchableFeedbackRuntime(),
|
|
136
|
+
timers: new Set(),
|
|
137
|
+
settled: undefined,
|
|
138
|
+
};
|
|
139
|
+
states.set(node, state);
|
|
140
|
+
// Bound at ATTACH rather than at the first press, matching RN: the binding is what forces
|
|
141
|
+
// `collapsable: false`, and a view Fabric un-flattens mid-gesture would lose the responder.
|
|
142
|
+
// Nothing is published until a fade runs — the leaf writes only when its value moves.
|
|
143
|
+
setAnimatedBehaviorStyle(node, { opacity: state.opacity });
|
|
144
|
+
machine.attach(node);
|
|
145
|
+
},
|
|
146
|
+
// RN's componentDidUpdate: a changed `disabled` or a changed style opacity re-settles the view,
|
|
147
|
+
// so a Touchable disabled mid-press does not stay stuck at its active opacity.
|
|
148
|
+
afterCommit(node) {
|
|
149
|
+
const state = states.get(node);
|
|
150
|
+
if (state === undefined)
|
|
151
|
+
return;
|
|
152
|
+
const resting = restingOpacityOf(node);
|
|
153
|
+
// Through the SAME resolver the press machine uses, not the raw prop: on Button `disabled` is
|
|
154
|
+
// the three-way answer of `props.disabled ?? aria-disabled ?? accessibilityState.disabled`
|
|
155
|
+
// (Button.js:331,337), so reading the prop here left an aria-only flip un-settled — the view
|
|
156
|
+
// stayed at its active opacity while the press was already suppressed. RN's Button has no
|
|
157
|
+
// such gap because it passes the resolved value DOWN as its touchable's prop.
|
|
158
|
+
const props = propsOf(node);
|
|
159
|
+
const disabled = disabledOf?.(props) ?? props.disabled;
|
|
160
|
+
const previous = state.settled;
|
|
161
|
+
state.settled = { disabled, resting };
|
|
162
|
+
if (previous === undefined) {
|
|
163
|
+
// NOT vendor parity, despite this comment's old claim: `TouchableOpacity-itest.js` ("does
|
|
164
|
+
// not render explicit opacity when using default") proves a resting, untouched
|
|
165
|
+
// TouchableOpacity commits no `opacity` key at all. Vendor's native-animated leaf is a
|
|
166
|
+
// separate channel from the ordinary style diff, so forcing non-flattening never implies
|
|
167
|
+
// publishing a value. Ours publishes anyway: `setAnimatedBehaviorStyle`/`bindAnimatedValue`
|
|
168
|
+
// (`host-binding.ts`) force `collapsable: false` and resolve+write the leaf's value in the
|
|
169
|
+
// SAME call. Untangling that is a change to shared animation infrastructure, not a one-line
|
|
170
|
+
// fix here — deferred, see the audit skill. Set rather than animate: this is the mount, and
|
|
171
|
+
// RN re-settles on componentDidUpdate only.
|
|
172
|
+
//
|
|
173
|
+
// TODO(rn-parity): skip this initial publish when `resting === RESTING_OPACITY` (1, Fabric's
|
|
174
|
+
// own opacity default) and no authored `style.opacity` needs another route to Fabric, so a
|
|
175
|
+
// resting default TouchableOpacity commits no `opacity` key — matching vendor. Needs
|
|
176
|
+
// `bindAnimatedValue`/`host-binding.ts` to separate "force collapsable:false" from "publish
|
|
177
|
+
// the leaf's value" (shared by every animated-behavior consumer, not just this tag), plus a
|
|
178
|
+
// new test for the untouched-default case (no existing case in touchable-opacity.test.ts
|
|
179
|
+
// covers it — every case authors an explicit style.opacity first).
|
|
180
|
+
state.opacity.setValue(resting);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (previous.disabled === disabled && previous.resting === resting)
|
|
184
|
+
return;
|
|
185
|
+
fadeTo(state, resting, OPACITY_INACTIVE_DURATION_MS);
|
|
186
|
+
},
|
|
187
|
+
detach(node) {
|
|
188
|
+
machine.detach(node);
|
|
189
|
+
const state = states.get(node);
|
|
190
|
+
if (state === undefined)
|
|
191
|
+
return;
|
|
192
|
+
for (const id of state.timers)
|
|
193
|
+
clearTimeout(id);
|
|
194
|
+
state.timers.clear();
|
|
195
|
+
// RN's componentWillUnmount: stop the animation so a teardown mid-fade leaves no driver
|
|
196
|
+
// running against a node that is gone.
|
|
197
|
+
state.opacity.resetAnimation();
|
|
198
|
+
setAnimatedBehaviorStyle(node, undefined);
|
|
199
|
+
states.delete(node);
|
|
200
|
+
dlog('touchable-opacity behavior detached');
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
// THIS TAG NOW COSTS ZERO TRIPS INTO JS (2026-09-18). `focusable` was the last thing here, and it
|
|
205
|
+
// held out because its middle leg — `onPress !== undefined` (TouchableOpacity.js:338) — is an OWNED
|
|
206
|
+
// name that lives in the stash and never becomes a prop, so a props-only rule could not see it.
|
|
207
|
+
//
|
|
208
|
+
// What moved was not the closure but ONE BIT. `setEventListener` already knew the flip (it computes
|
|
209
|
+
// `wasWired !== isHandler` to fire `onOwnedListenerChange`); it now also records
|
|
210
|
+
// `OP_SET_OWNED_LISTENER`, and `foldPressableProps` resolves the whole three-leg expression off the
|
|
211
|
+
// node. The callback itself never left JS and never will — that is the split a browser draws too,
|
|
212
|
+
// where the UA knows which elements carry a click handler and the handler's body stays the page's.
|
|
213
|
+
//
|
|
214
|
+
// So "a rule cannot read an owned listener" was two claims wearing one sentence, and only the one
|
|
215
|
+
// about the FUNCTION was true. Contract:
|
|
216
|
+
// `core/engine/cpp/tests/js/touchable-focusable-payload.itest.ts`.
|
|
217
|
+
//
|
|
218
|
+
// `./button` keeps its own fold and its own resolution, deliberately: it resolves `disabled` three
|
|
219
|
+
// ways through the projection its derived children share, and it folds an Android view style and
|
|
220
|
+
// ripple regardless — so moving only its `focusable` would duplicate that precedence and buy back
|
|
221
|
+
// no crossing.
|
|
222
|
+
// STILL NEEDED, and for a reason the wire did not remove: `markDirty` on the host marks the node
|
|
223
|
+
// whose payload must be rebuilt, but the COMMIT still has to be asked for. A listener flip changes
|
|
224
|
+
// no prop on this side, so nothing else would schedule one and the new answer would sit in the host
|
|
225
|
+
// until some unrelated write happened to dirty the node.
|
|
226
|
+
function onOwnedListenerChange(node, name) {
|
|
227
|
+
if (name !== 'press')
|
|
228
|
+
return;
|
|
229
|
+
markPropsDirty(node);
|
|
230
|
+
requestCommitFor(node);
|
|
231
|
+
}
|
|
232
|
+
// Idempotent: an adapter entry may be imported more than once in a bundle.
|
|
233
|
+
export function registerTouchableOpacityBehavior() {
|
|
234
|
+
const touchable = createTouchableOpacityBehavior(touchableOpacityDisabled);
|
|
235
|
+
registerHostBehavior(TOUCHABLE_OPACITY_TAG, {
|
|
236
|
+
...touchable,
|
|
237
|
+
onOwnedListenerChange,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
// TouchableWithoutFeedback as an ENGINE-NODE behavior — the second primitive that commits NO NODE
|
|
2
|
+
// OF ITS OWN (`.claude/rules/host-primitive-tier.md`, tier 2), after `./touchable-native-feedback`.
|
|
3
|
+
//
|
|
4
|
+
// RN'S TWF RENDERS NOTHING, exactly like TNF: `React.Children.only(props.children)`
|
|
5
|
+
// (TouchableWithoutFeedback.js:229) then `cloneElement(element, elementProps, ...children)` (:286).
|
|
6
|
+
// All five of our adapters wrapped a `Pressable` around the children instead, so every TWF in every
|
|
7
|
+
// app committed a node RN does not.
|
|
8
|
+
//
|
|
9
|
+
// The SHAPE — anchor tag, `onChildInserted` adoption, a `cloneElement`-shaped `payloadFold` on the
|
|
10
|
+
// child, the press machine on the CHILD reading the OWNER — is `./touchable-native-feedback`'s and
|
|
11
|
+
// is documented there. Its three KNOWN LIMITS (one child only, a child removed from a live owner
|
|
12
|
+
// keeps its machine, `style` is not cloned) hold here verbatim: RN's TWF declares a `style` prop
|
|
13
|
+
// (:122, tagged "FIXME: not in doc") and then never puts it in `elementProps`, so it is dead
|
|
14
|
+
// upstream too.
|
|
15
|
+
//
|
|
16
|
+
// WHAT DIFFERS FROM TNF, since the two look like one primitive with a different name:
|
|
17
|
+
//
|
|
18
|
+
// 1. THE CLONE IS CONDITIONAL. TNF assigns its whole list unconditionally, so an absent prop CLEARS
|
|
19
|
+
// the child's (`cloneElement` copies undefined keys). TWF splits: eight names are computed and
|
|
20
|
+
// assigned unconditionally, and `PASSTHROUGH_PROPS` (:130-153) is copied only
|
|
21
|
+
// `if (props[prop] !== undefined)` (:281). So a TWF with no `testID` LEAVES the child's alone.
|
|
22
|
+
// 2. NO ANDROID RIPPLE. `background` / `useForeground` / `getBackgroundProp` and the
|
|
23
|
+
// `setPressed`/`hotspotUpdate` view commands are TNF's alone — they exist to drive the drawable
|
|
24
|
+
// TNF installs, and TWF installs none. `nativeFeedbackRefinement` is therefore NOT reused.
|
|
25
|
+
// 3. `onBlur` / `onFocus` ARE cloned (:152-153, in the passthrough list), where TNF drops them.
|
|
26
|
+
// Both are `BASE_EVENTS`, so they route to the stash and trampoline like the other two.
|
|
27
|
+
// 4. `accessibilityIgnoresInvertColors` is cloned; `hasTVPreferredFocus` and the five `nextFocus*`
|
|
28
|
+
// are NOT — the reverse of TNF on both counts. Read RN's two lists side by side rather than
|
|
29
|
+
// inheriting either.
|
|
30
|
+
// 5. TWF FORWARDS `delayPressIn` / `delayPressOut` to Pressability (:186-188), which the press
|
|
31
|
+
// machine does not own — so the tag layers `../state/touchable`'s scheduler on top, the same
|
|
32
|
+
// machine `./touchable-opacity` uses with the fade replaced by nothing. That is what "without
|
|
33
|
+
// feedback" means: no VISUAL, not no TIMING.
|
|
34
|
+
//
|
|
35
|
+
// REGISTRATION IS THE HAZARD, not the machine — see `./pressable`. Registered in the same commit
|
|
36
|
+
// that deletes the five wrappers, because the registry is keyed by TAG and a wrapper still emitting
|
|
37
|
+
// its own `pressable` would put two press machines on one tree.
|
|
38
|
+
import { SLOT_DERIVED_ALL, appListenerFor, markPropsDirty, registerHostBehavior, requestCommitFor, setBehaviorListener, propOf, } from '@symbiote-native/engine';
|
|
39
|
+
import { attachPressMachine, detachPressMachine, } from './pressable.js';
|
|
40
|
+
import { createTouchableFeedbackHandlers, createTouchableFeedbackRuntime, TOUCHABLE_MIN_PRESS_DURATION_MS, } from '../state/touchable.js';
|
|
41
|
+
export const TOUCHABLE_WITHOUT_FEEDBACK_TAG = 'touchable-without-feedback';
|
|
42
|
+
// WHAT THE OWNER'S WRITES DIRTY, and it is EVERY name rather than a list of thirty.
|
|
43
|
+
//
|
|
44
|
+
// RN's two clone lists (`TouchableWithoutFeedback.js:130-153` when-set, `:253-276` always) lived
|
|
45
|
+
// here until 2026-09-18, feeding `slotDerived` alongside the six computed names and the three
|
|
46
|
+
// scheduler ones. The clone is `foldCloneOntoChild` in `SymbioteFabricProps.cpp` now, which turned
|
|
47
|
+
// the list from the RULE into a MIRROR of `kWithoutFeedbackWhenSetKeys` +
|
|
48
|
+
// `kWithoutFeedbackAlwaysKeys` — two lists that must agree, failing silently when they drift.
|
|
49
|
+
//
|
|
50
|
+
// See `touchable-native-feedback.ts` for the same note and what the wildcard costs. The three
|
|
51
|
+
// scheduler names (`delayPressIn`, `delayPressOut`, `minPressDuration`) were the one entry here that
|
|
52
|
+
// was NEVER about the clone — they configure the press machine, which reads them live at gesture
|
|
53
|
+
// start — and a wildcard covers them for free where a list had to remember them.
|
|
54
|
+
const SLOT_DERIVED = [SLOT_DERIVED_ALL];
|
|
55
|
+
// :148-153. Owned, so the app's callback stashes on the owner and a trampoline on the child reads it
|
|
56
|
+
// at dispatch time. `layout` and `accessibilityAction` are Fabric BOOLEAN-GATED events
|
|
57
|
+
// (`.claude/rules/fabric-boolean-event-gates.md`), so the trampoline goes in only while the app has
|
|
58
|
+
// one wired; `blur` and `focus` are not gated but ride the same wiring, since the question — is
|
|
59
|
+
// there a callback to forward — is identical.
|
|
60
|
+
const FORWARDED_LISTENERS = [
|
|
61
|
+
'layout',
|
|
62
|
+
'accessibilityAction',
|
|
63
|
+
'blur',
|
|
64
|
+
'focus',
|
|
65
|
+
];
|
|
66
|
+
const PRESS_LISTENERS = [
|
|
67
|
+
'press',
|
|
68
|
+
'pressIn',
|
|
69
|
+
'pressOut',
|
|
70
|
+
'pressMove',
|
|
71
|
+
'longPress',
|
|
72
|
+
'startShouldSetResponder',
|
|
73
|
+
'responderMove',
|
|
74
|
+
'responderTerminationRequest',
|
|
75
|
+
];
|
|
76
|
+
const states = new WeakMap();
|
|
77
|
+
function numberOr(value, fallback) {
|
|
78
|
+
return typeof value === 'number' ? value : fallback;
|
|
79
|
+
}
|
|
80
|
+
// `cloneFold` LEFT THIS FILE ON 2026-09-18 — `foldCloneOntoChild` in `SymbioteFabricProps.cpp`,
|
|
81
|
+
// reached through the first rule keyed on the PARENT'S tag (`IOwner`). TWF's two lists are NOT
|
|
82
|
+
// TNF's: the passthrough half is copied only when SET (`:281`), and the C++ rule keeps that split
|
|
83
|
+
// rather than collapsing the two into one.
|
|
84
|
+
//
|
|
85
|
+
// One quirk of upstream's that IS reproduced, and it is NOT free: `:280-284`'s passthrough loop
|
|
86
|
+
// re-assigns a set `nativeID` over the `id` it just resolved, so an explicit `nativeID` wins here
|
|
87
|
+
// where TNF's `id` does. `routeProp` settles the two names into one value before any rule runs, so
|
|
88
|
+
// getting this right needed the collapse itself to know which of the two components it is running
|
|
89
|
+
// for — `node.nativeIdWinsOverId`, set only by this behavior's registration, flips it.
|
|
90
|
+
//
|
|
91
|
+
// Contract: `core/engine/cpp/tests/js/clone-onto-child-payload.itest.ts`.
|
|
92
|
+
/**
|
|
93
|
+
* `delayPressIn` / `delayPressOut` (:186-188) plus RN's unconditional `minPressDuration: 0` (:190).
|
|
94
|
+
*
|
|
95
|
+
* The scheduler is `../state/touchable`'s — the one `./touchable-opacity` drives its fade with,
|
|
96
|
+
* here with `activate` / `deactivate` forwarding and painting nothing. It reads the OWNER, so the
|
|
97
|
+
* owner is closed over rather than taken from the refinement's argument, which is the CHILD.
|
|
98
|
+
*/
|
|
99
|
+
function refinementFor(owner, state) {
|
|
100
|
+
return (_node, config) => {
|
|
101
|
+
const handlers = createTouchableFeedbackHandlers({
|
|
102
|
+
delayPressIn: numberOr(propOf(owner, 'delayPressIn'), 0),
|
|
103
|
+
delayPressOut: numberOr(propOf(owner, 'delayPressOut'), 0),
|
|
104
|
+
// Read raw rather than off `config`, which has already defaulted the absent case to the
|
|
105
|
+
// press machine's own 130 ms — a floor RN's Touchables never see.
|
|
106
|
+
minPressDuration: numberOr(propOf(owner, 'minPressDuration'), TOUCHABLE_MIN_PRESS_DURATION_MS),
|
|
107
|
+
schedule: (callback, ms) => {
|
|
108
|
+
const id = setTimeout(() => {
|
|
109
|
+
state.timers.delete(id);
|
|
110
|
+
callback();
|
|
111
|
+
}, ms);
|
|
112
|
+
state.timers.add(id);
|
|
113
|
+
return () => {
|
|
114
|
+
clearTimeout(id);
|
|
115
|
+
state.timers.delete(id);
|
|
116
|
+
};
|
|
117
|
+
},
|
|
118
|
+
now: Date.now,
|
|
119
|
+
}, state.runtime, {
|
|
120
|
+
activate(event) {
|
|
121
|
+
config.onPressIn?.(event);
|
|
122
|
+
},
|
|
123
|
+
deactivate(event) {
|
|
124
|
+
config.onPressOut?.(event);
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
return {
|
|
128
|
+
...config,
|
|
129
|
+
// :190, unconditional. The deactivation floor belongs to the scheduler above, and a second
|
|
130
|
+
// one would defer `onPressOut` by the press machine's 130 ms default on top of it.
|
|
131
|
+
minPressDuration: 0,
|
|
132
|
+
onPressIn: handlers.handlePressIn,
|
|
133
|
+
onPressOut: handlers.handlePressOut,
|
|
134
|
+
};
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
// The app's callback, read at dispatch time so a fresh closure per render costs nothing.
|
|
138
|
+
function trampolineFor(owner, name) {
|
|
139
|
+
return (event) => {
|
|
140
|
+
const listener = appListenerFor(owner, name);
|
|
141
|
+
if (typeof listener === 'function')
|
|
142
|
+
listener(event);
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function forwardListener(owner, child, name, wired) {
|
|
146
|
+
setBehaviorListener(child, name, wired ? trampolineFor(owner, name) : undefined);
|
|
147
|
+
}
|
|
148
|
+
// The machine plus the forwarded listeners. Split from adoption because a parked subtree comes back
|
|
149
|
+
// with its child and its fold intact but its machine torn down — `attach` re-runs this, and must
|
|
150
|
+
// NOT re-chain the fold.
|
|
151
|
+
function arm(owner, child) {
|
|
152
|
+
let state = states.get(owner);
|
|
153
|
+
if (state === undefined) {
|
|
154
|
+
state = { runtime: createTouchableFeedbackRuntime(), timers: new Set() };
|
|
155
|
+
states.set(owner, state);
|
|
156
|
+
}
|
|
157
|
+
attachPressMachine(child, {
|
|
158
|
+
source: owner,
|
|
159
|
+
refine: refinementFor(owner, state),
|
|
160
|
+
});
|
|
161
|
+
for (const name of FORWARDED_LISTENERS) {
|
|
162
|
+
forwardListener(owner, child, name, appListenerFor(owner, name) !== undefined);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function attach(node) {
|
|
166
|
+
// First attach has no child yet; a re-attach after the sweep does. See `arm`, which is also what
|
|
167
|
+
// seeds the scheduling cell — a TWF that never adopts a child never needs one.
|
|
168
|
+
if (node.childHost !== undefined)
|
|
169
|
+
arm(node, node.childHost);
|
|
170
|
+
}
|
|
171
|
+
function detach(node) {
|
|
172
|
+
const child = node.childHost;
|
|
173
|
+
if (child !== undefined)
|
|
174
|
+
detachPressMachine(child);
|
|
175
|
+
const state = states.get(node);
|
|
176
|
+
if (state === undefined)
|
|
177
|
+
return;
|
|
178
|
+
for (const id of state.timers)
|
|
179
|
+
clearTimeout(id);
|
|
180
|
+
state.timers.clear();
|
|
181
|
+
states.delete(node);
|
|
182
|
+
}
|
|
183
|
+
function onChildInserted(node, child) {
|
|
184
|
+
const previous = node.childHost;
|
|
185
|
+
if (previous === child)
|
|
186
|
+
return;
|
|
187
|
+
// A replaced child: release the machine still armed on the one that left. `removeChild` clears
|
|
188
|
+
// `childHost` (node.ts), so this only runs when a framework inserts without removing first.
|
|
189
|
+
if (previous !== undefined)
|
|
190
|
+
detachPressMachine(previous);
|
|
191
|
+
node.childHost = child;
|
|
192
|
+
// The owner's props were very likely written BEFORE this child existed (React and Solid set props
|
|
193
|
+
// at createInstance), so the CLONE owes a run even though nothing was written since. Witnessed by
|
|
194
|
+
// the MOVED-child case, not by a fresh one — `appendChild` already dirties a node it created.
|
|
195
|
+
// Still owed now that the rule is in C++: it runs on the child's commit, and a child nothing
|
|
196
|
+
// dirtied has no commit.
|
|
197
|
+
markPropsDirty(child);
|
|
198
|
+
arm(node, child);
|
|
199
|
+
}
|
|
200
|
+
// `focusable` is a function of a LISTENER, and a listener flip changes no payload by itself — so
|
|
201
|
+
// the commit after it is a no-op and no fold re-runs (`IHostBehavior.onOwnedListenerChange`).
|
|
202
|
+
function onOwnedListenerChange(node, name, wired) {
|
|
203
|
+
const child = node.childHost;
|
|
204
|
+
if (child === undefined)
|
|
205
|
+
return;
|
|
206
|
+
if (FORWARDED_LISTENERS.includes(name)) {
|
|
207
|
+
forwardListener(node, child, name, wired);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (name !== 'press')
|
|
211
|
+
return;
|
|
212
|
+
markPropsDirty(child);
|
|
213
|
+
requestCommitFor(child);
|
|
214
|
+
}
|
|
215
|
+
// Idempotent: an adapter entry may be imported more than once in a bundle.
|
|
216
|
+
export function registerTouchableWithoutFeedbackBehavior() {
|
|
217
|
+
const behavior = {
|
|
218
|
+
attach,
|
|
219
|
+
detach,
|
|
220
|
+
onChildInserted,
|
|
221
|
+
onOwnedListenerChange,
|
|
222
|
+
// Owned on the OWNER, which is where the app writes them, and read from there by the machine on
|
|
223
|
+
// the child (`attachPressMachine`'s `source`) and by the trampolines above.
|
|
224
|
+
ownedListeners: [...PRESS_LISTENERS, ...FORWARDED_LISTENERS],
|
|
225
|
+
slotDerived: SLOT_DERIVED,
|
|
226
|
+
// TouchableWithoutFeedback.js's clone overwrites `nativeID` with the raw authored value
|
|
227
|
+
// (`:279-282`), the reverse of every other component's `id ?? nativeID` — see `routeIdAlias`.
|
|
228
|
+
nativeIdWinsOverId: true,
|
|
229
|
+
};
|
|
230
|
+
registerHostBehavior(TOUCHABLE_WITHOUT_FEEDBACK_TAG, behavior);
|
|
231
|
+
}
|
|
@@ -2,33 +2,35 @@
|
|
|
2
2
|
// Each name is the ViewManager's REACT_CLASS in react-native/ReactAndroid/.../views/**.
|
|
3
3
|
// device-verify-pending: source-confirmed from RN's Android ViewManagers, proven on a
|
|
4
4
|
// real host by the absence of a "Can't find ViewManager '<name>'" red box.
|
|
5
|
+
import { ANCHOR_COMPONENT, VOID_COMPONENT } from '@symbiote-native/engine';
|
|
5
6
|
import { buildDescriptors, makeDescriptorFor, } from './shared.js';
|
|
6
7
|
const ANDROID_NAMES = {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
'
|
|
10
|
-
|
|
11
|
-
'
|
|
8
|
+
view: 'RCTView',
|
|
9
|
+
pressable: 'RCTView',
|
|
10
|
+
'touchable-opacity': 'RCTView',
|
|
11
|
+
// NOT a view name — TNF commits nothing at all, on either platform. See `shared.ts`.
|
|
12
|
+
'touchable-native-feedback': ANCHOR_COMPONENT,
|
|
13
|
+
'touchable-without-feedback': ANCHOR_COMPONENT,
|
|
14
|
+
'touchable-highlight': 'RCTView',
|
|
15
|
+
button: 'RCTView',
|
|
16
|
+
text: 'RCTText',
|
|
17
|
+
image: 'RCTImageView',
|
|
18
|
+
'image-background': 'RCTView',
|
|
19
|
+
'scroll-view': 'RCTScrollView',
|
|
12
20
|
// RN's VScrollContentViewNativeComponent is `Platform.OS === 'android' ? View : …`,
|
|
13
21
|
// so a vertical scroll's content is a plain RCTView on Android, not RCTScrollContentView.
|
|
14
|
-
'
|
|
22
|
+
'scroll-content': 'RCTView',
|
|
15
23
|
// Horizontal scroll on Android is its own ViewManager; RCTScrollView is vertical-only and
|
|
16
24
|
// ignores `horizontal`. RN routes it to AndroidHorizontalScrollView with a dedicated content
|
|
17
25
|
// view (HScrollViewNativeComponents.js: `Platform.OS === 'android' ? AndroidHorizontal… : …`).
|
|
18
|
-
'
|
|
19
|
-
'
|
|
26
|
+
'horizontal-scroll-view': 'AndroidHorizontalScrollView',
|
|
27
|
+
'horizontal-scroll-content': 'AndroidHorizontalScrollContentView',
|
|
20
28
|
// Android has one text-input ViewManager for both single- and multiline.
|
|
21
|
-
'
|
|
22
|
-
'
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
'
|
|
26
|
-
'symbiote-text-input-multiline-managed': 'AndroidTextInput',
|
|
27
|
-
'symbiote-switch': 'AndroidSwitch',
|
|
28
|
-
// The wrapper's tag — same native view, a tag the behavior registry does not carry. See
|
|
29
|
-
// `shared.ts` for why the wrapper may not share the lowered tag.
|
|
30
|
-
'symbiote-switch-managed': 'AndroidSwitch',
|
|
31
|
-
'symbiote-activity-indicator': 'AndroidProgressBar',
|
|
29
|
+
'text-input': 'AndroidTextInput',
|
|
30
|
+
'text-input-multiline': 'AndroidTextInput',
|
|
31
|
+
switch: 'AndroidSwitch',
|
|
32
|
+
'activity-indicator': 'RCTView',
|
|
33
|
+
'activity-indicator-spinner': 'AndroidProgressBar',
|
|
32
34
|
// KNOWN DIVERGENCE FROM REACT NATIVE, and it is in our favour — recorded 2026-09-01 because it
|
|
33
35
|
// was arrived at by accident, not decided. Upstream `SafeAreaView.js` is
|
|
34
36
|
// `Platform.select({ ios: RCTSafeAreaViewNativeComponent, default: View })`, so RN's own JS
|
|
@@ -43,12 +45,16 @@ const ANDROID_NAMES = {
|
|
|
43
45
|
// react-native-safe-area-context. Invisible to every audit we have, because both of our paths
|
|
44
46
|
// agree with each other and only disagree with RN — the boundary
|
|
45
47
|
// `.claude/rules/adapter-parity-audit.md` states about itself.
|
|
46
|
-
'
|
|
47
|
-
|
|
48
|
-
'
|
|
49
|
-
|
|
50
|
-
//
|
|
51
|
-
|
|
48
|
+
'safe-area-view': 'RCTSafeAreaView',
|
|
49
|
+
modal: 'RCTModalHostView',
|
|
50
|
+
'refresh-control': 'AndroidSwipeRefreshLayout',
|
|
51
|
+
'sticky-header': 'RCTView',
|
|
52
|
+
// `InputAccessoryView.js` on Android does `console.warn(...); return null` — the WHOLE component,
|
|
53
|
+
// children included, renders NOTHING. A plain `RCTView` container (this file's answer until
|
|
54
|
+
// 2026-09-20) committed a real, laid-out, potentially visible node plus its whole child subtree —
|
|
55
|
+
// a real divergence from vendor, not a harmless degrade. `VOID_COMPONENT` matches vendor exactly:
|
|
56
|
+
// no node, no children, on this platform only.
|
|
57
|
+
'input-accessory-view': VOID_COMPONENT,
|
|
52
58
|
};
|
|
53
59
|
export const COMPONENT_DESCRIPTORS = buildDescriptors(ANDROID_NAMES);
|
|
54
60
|
export const descriptorFor = makeDescriptorFor(COMPONENT_DESCRIPTORS);
|
|
@@ -2,33 +2,38 @@
|
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
'
|
|
10
|
-
|
|
11
|
-
'
|
|
12
|
-
|
|
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',
|
|
13
23
|
// iOS uses one scroll view for both axes; horizontal is RCTScrollView with the
|
|
14
24
|
// `horizontal` prop set, so these resolve identically to the vertical pair.
|
|
15
|
-
'
|
|
16
|
-
'
|
|
17
|
-
'
|
|
18
|
-
'
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
'
|
|
22
|
-
'
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
'
|
|
27
|
-
'symbiote-activity-indicator': 'ActivityIndicatorView',
|
|
28
|
-
'symbiote-safe-area-view': 'SafeAreaView',
|
|
29
|
-
'symbiote-modal': 'ModalHostView',
|
|
30
|
-
'symbiote-refresh-control': 'PullToRefreshView',
|
|
31
|
-
'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
|
+
switch: 'Switch',
|
|
30
|
+
'activity-indicator': 'RCTView',
|
|
31
|
+
'activity-indicator-spinner': 'ActivityIndicatorView',
|
|
32
|
+
'safe-area-view': 'SafeAreaView',
|
|
33
|
+
modal: 'ModalHostView',
|
|
34
|
+
'refresh-control': 'PullToRefreshView',
|
|
35
|
+
'sticky-header': 'RCTView',
|
|
36
|
+
'input-accessory-view': 'RCTInputAccessoryView',
|
|
32
37
|
};
|
|
33
38
|
export const COMPONENT_DESCRIPTORS = buildDescriptors(IOS_NAMES);
|
|
34
39
|
export const descriptorFor = makeDescriptorFor(COMPONENT_DESCRIPTORS);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export type ISymbioteIntrinsic = '
|
|
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' | 'switch' | '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;
|