@symbiote-native/react 1.0.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.
- package/README.md +12 -11
- package/build/components/activity-indicator-props.d.ts +4 -0
- package/build/components/{button.d.ts → button-props.d.ts} +0 -2
- package/build/components/button-props.js +1 -0
- package/build/components/flat-list/index.js +2 -2
- package/build/components/image/image-props.d.ts +4 -0
- package/build/components/image/image-props.js +1 -0
- package/build/components/image-background-props.d.ts +9 -0
- package/build/components/image-background-props.js +1 -0
- package/build/components/input-accessory-view-props.d.ts +10 -0
- package/build/components/input-accessory-view-props.js +1 -0
- package/build/components/keyboard-avoiding-view/index.js +4 -4
- package/build/components/modal/index.js +3 -3
- package/build/components/pressable/{index.d.ts → pressable-props.d.ts} +4 -5
- package/build/components/pressable/pressable-props.js +1 -0
- package/build/components/{refresh-control/index.d.ts → refresh-control-props.d.ts} +3 -3
- package/build/components/refresh-control-props.js +1 -0
- package/build/components/safe-area-view-props.d.ts +11 -0
- package/build/components/safe-area-view-props.js +1 -0
- package/build/components/scroll-view/{shared.d.ts → scroll-view-props.d.ts} +4 -26
- package/build/components/scroll-view/scroll-view-props.js +1 -0
- package/build/components/switch/switch-props.d.ts +4 -0
- package/build/components/switch/switch-props.js +1 -0
- package/build/components/text-input/text-input-props.d.ts +4 -0
- package/build/components/text-input/text-input-props.js +1 -0
- package/build/components/touchable/touchable-props.d.ts +17 -0
- package/build/components/touchable/touchable-props.js +1 -0
- package/build/components/touchable-native-feedback/touchable-native-feedback-props.d.ts +8 -0
- package/build/components/touchable-native-feedback/touchable-native-feedback-props.js +1 -0
- package/build/components/touchable-without-feedback/touchable-without-feedback-props.d.ts +6 -0
- package/build/components/touchable-without-feedback/touchable-without-feedback-props.js +1 -0
- package/build/components/virtualized-list/index.js +83 -38
- package/build/components.d.ts +1 -2
- package/build/components.js +5 -19
- package/build/create-portal/index.js +1 -1
- package/build/create-tunnel/index.js +1 -1
- package/build/descriptor-to-react/index.js +1 -1
- package/build/host-config.js +22 -4
- package/build/index.d.ts +23 -28
- package/build/index.js +14 -14
- package/build/jsx-dev-runtime.d.ts +3 -0
- package/build/jsx-dev-runtime.js +10 -0
- package/build/jsx-namespace.type-check.d.ts +1 -0
- package/build/jsx-namespace.type-check.js +7 -0
- package/build/jsx-runtime.d.ts +62 -0
- package/build/jsx-runtime.js +1 -0
- package/build/modules/animated/index.d.ts +0 -1
- package/build/modules/animated/index.js +16 -22
- package/build/modules/image/index.d.ts +3 -0
- package/build/modules/image/index.js +14 -0
- package/build/register.d.ts +1 -0
- package/build/register.js +15 -0
- package/metro-css-parser.cjs +2 -1
- package/package.json +12 -8
- package/build/components/activity-indicator/index.android.d.ts +0 -3
- package/build/components/activity-indicator/index.android.js +0 -17
- package/build/components/activity-indicator/index.d.ts +0 -1
- package/build/components/activity-indicator/index.ios.d.ts +0 -3
- package/build/components/activity-indicator/index.ios.js +0 -16
- package/build/components/activity-indicator/index.js +0 -4
- package/build/components/activity-indicator/shared.d.ts +0 -6
- package/build/components/activity-indicator/shared.js +0 -22
- package/build/components/button.js +0 -42
- package/build/components/image/index.d.ts +0 -9
- package/build/components/image/index.js +0 -28
- package/build/components/image-background/index.d.ts +0 -10
- package/build/components/image-background/index.js +0 -46
- package/build/components/input-accessory-view/index.d.ts +0 -11
- package/build/components/input-accessory-view/index.js +0 -19
- package/build/components/pressable/index.js +0 -112
- package/build/components/refresh-control/index.js +0 -21
- package/build/components/safe-area-view/index.d.ts +0 -12
- package/build/components/safe-area-view/index.js +0 -18
- package/build/components/scroll-view/index.android.d.ts +0 -3
- package/build/components/scroll-view/index.android.js +0 -61
- package/build/components/scroll-view/index.d.ts +0 -1
- package/build/components/scroll-view/index.ios.d.ts +0 -3
- package/build/components/scroll-view/index.ios.js +0 -29
- package/build/components/scroll-view/index.js +0 -4
- package/build/components/scroll-view/shared.js +0 -240
- package/build/components/scroll-view/sticky-header.d.ts +0 -18
- package/build/components/scroll-view/sticky-header.js +0 -227
- package/build/components/switch/index.android.d.ts +0 -3
- package/build/components/switch/index.android.js +0 -18
- package/build/components/switch/index.d.ts +0 -1
- package/build/components/switch/index.ios.d.ts +0 -3
- package/build/components/switch/index.ios.js +0 -16
- package/build/components/switch/index.js +0 -6
- package/build/components/switch/shared.d.ts +0 -9
- package/build/components/switch/shared.js +0 -60
- package/build/components/text-input/index.d.ts +0 -50
- package/build/components/text-input/index.js +0 -180
- package/build/components/touchable/index.d.ts +0 -22
- package/build/components/touchable/index.js +0 -165
- package/build/components/touchable-native-feedback/index.d.ts +0 -18
- package/build/components/touchable-native-feedback/index.js +0 -32
- package/build/jsx.d.ts +0 -18
- package/build/state-style.d.ts +0 -1
- package/build/state-style.js +0 -13
- /package/build/{jsx.js → components/activity-indicator-props.js} +0 -0
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { type ISymbioteEvent } from '@symbiote-native/engine';
|
|
2
|
-
import type { ITextInputProps as ITextInputBaseProps, ITextInputHandle } from '@symbiote-native/components';
|
|
3
|
-
export type { ITextInputHandle } from '@symbiote-native/components';
|
|
4
|
-
export type ITextInputProps = ITextInputBaseProps & {
|
|
5
|
-
className?: string;
|
|
6
|
-
};
|
|
7
|
-
export declare const TextInput: import("react").ForwardRefExoticComponent<import("@symbiote-native/components").IAccessibilityProps & import("@symbiote-native/components").IAriaProps & {
|
|
8
|
-
value?: string;
|
|
9
|
-
defaultValue?: string;
|
|
10
|
-
placeholder?: string;
|
|
11
|
-
placeholderTextColor?: string;
|
|
12
|
-
editable?: boolean;
|
|
13
|
-
keyboardType?: string;
|
|
14
|
-
secureTextEntry?: boolean;
|
|
15
|
-
maxLength?: number;
|
|
16
|
-
multiline?: boolean;
|
|
17
|
-
selection?: import("@symbiote-native/components").ITextInputSelection;
|
|
18
|
-
autoCapitalize?: "none" | "sentences" | "words" | "characters";
|
|
19
|
-
autoCorrect?: boolean;
|
|
20
|
-
autoComplete?: string;
|
|
21
|
-
textContentType?: string;
|
|
22
|
-
autoFocus?: boolean;
|
|
23
|
-
showSoftInputOnFocus?: boolean;
|
|
24
|
-
returnKeyType?: string;
|
|
25
|
-
selectTextOnFocus?: boolean;
|
|
26
|
-
scrollEnabled?: boolean;
|
|
27
|
-
numberOfLines?: number;
|
|
28
|
-
textAlign?: "left" | "center" | "right";
|
|
29
|
-
blurOnSubmit?: boolean;
|
|
30
|
-
inputMode?: import("@symbiote-native/components").IInputMode;
|
|
31
|
-
enterKeyHint?: import("@symbiote-native/components").IEnterKeyHint;
|
|
32
|
-
readOnly?: boolean;
|
|
33
|
-
submitBehavior?: import("@symbiote-native/components").ISubmitBehavior;
|
|
34
|
-
cursorColor?: string;
|
|
35
|
-
selectionColor?: string;
|
|
36
|
-
selectionHandleColor?: string;
|
|
37
|
-
underlineColorAndroid?: string;
|
|
38
|
-
inputAccessoryViewID?: string;
|
|
39
|
-
style?: import("@symbiote-native/engine").ITextStyle;
|
|
40
|
-
onValueChange?: (text: string, event: ISymbioteEvent) => void;
|
|
41
|
-
onFocus?: import("@symbiote-native/components").ITextInputEventHandler;
|
|
42
|
-
onBlur?: import("@symbiote-native/components").ITextInputEventHandler;
|
|
43
|
-
onEndEditing?: import("@symbiote-native/components").ITextInputEventHandler;
|
|
44
|
-
onSubmitEditing?: import("@symbiote-native/components").ITextInputEventHandler;
|
|
45
|
-
onKeyPress?: import("@symbiote-native/components").ITextInputEventHandler;
|
|
46
|
-
onSelectionChange?: import("@symbiote-native/components").ITextInputEventHandler;
|
|
47
|
-
onContentSizeChange?: import("@symbiote-native/components").ITextInputEventHandler;
|
|
48
|
-
} & {
|
|
49
|
-
className?: string;
|
|
50
|
-
} & import("react").RefAttributes<ITextInputHandle>>;
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
// TextInput: the React lifecycle half. The folds/maps (value->text, the W3C/alias resolution)
|
|
2
|
-
// and the controlled-write predicate live in @symbiote-native/components/state; the render (intrinsic
|
|
3
|
-
// + native-prop mapping) in @symbiote-native/components/view; both shared verbatim with the Vue
|
|
4
|
-
// adapter. Here React supplies only the lifecycle: useState for the acknowledged event count
|
|
5
|
-
// (it must re-render so the imperative handle echoes the latest), a ref for the host node and
|
|
6
|
-
// the last text native holds, the useLayoutEffect controlled-write, the mount autoFocus, and
|
|
7
|
-
// useImperativeHandle for focus/blur/clear/isFocused/setSelection.
|
|
8
|
-
//
|
|
9
|
-
// The controlled handshake hinges on commanding native back with the ACKNOWLEDGED event count
|
|
10
|
-
// (setTextAndSelection), never a plain prop re-push. See @symbiote-native/components/state/text-input.
|
|
11
|
-
import { forwardRef, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useRef, useState, } from 'react';
|
|
12
|
-
import { dispatchViewCommand, dlog, } from '@symbiote-native/engine';
|
|
13
|
-
import { blurTextInput, setInputBlurred, setInputFocused, } from '@symbiote-native/engine';
|
|
14
|
-
import { resolveAccessibilityProps, resolveTextInputProps, renderTextInput, foldText, textFromChange, eventCountFromChange, shouldCommandText, INITIAL_EVENT_COUNT, SELECTION_NONE, } from '@symbiote-native/components';
|
|
15
|
-
import { descriptorToReact } from '../../descriptor-to-react/index.js';
|
|
16
|
-
export const TextInput = forwardRef((rawProps, forwardedRef) => {
|
|
17
|
-
// TextInput is its own host element (not a View wrapper), so it folds aria/role here.
|
|
18
|
-
const props = resolveAccessibilityProps(rawProps);
|
|
19
|
-
// Pull out the fields the lifecycle owns (controlled value, the wrapped handlers, the folded
|
|
20
|
-
// aliases) so they don't reach Fabric raw; everything else rides down via `passthrough`.
|
|
21
|
-
const { value, defaultValue, multiline, selection, onValueChange, onFocus, onBlur, inputMode, enterKeyHint, readOnly, submitBehavior, blurOnSubmit, cursorColor, selectionColor, selectionHandleColor, keyboardType, returnKeyType, editable, autoComplete, textContentType, autoFocus, showSoftInputOnFocus, underlineColorAndroid, ...passthrough } = props;
|
|
22
|
-
const isMultiline = multiline === true;
|
|
23
|
-
const folded = resolveTextInputProps({
|
|
24
|
-
inputMode,
|
|
25
|
-
keyboardType,
|
|
26
|
-
enterKeyHint,
|
|
27
|
-
returnKeyType,
|
|
28
|
-
readOnly,
|
|
29
|
-
editable,
|
|
30
|
-
submitBehavior,
|
|
31
|
-
blurOnSubmit,
|
|
32
|
-
multiline: isMultiline,
|
|
33
|
-
cursorColor,
|
|
34
|
-
selectionColor,
|
|
35
|
-
selectionHandleColor,
|
|
36
|
-
autoComplete,
|
|
37
|
-
textContentType,
|
|
38
|
-
showSoftInputOnFocus,
|
|
39
|
-
underlineColorAndroid,
|
|
40
|
-
});
|
|
41
|
-
const ref = useRef(null);
|
|
42
|
-
// JS-side focus state, mirrored from the focus/blur events for isFocused(). RN's
|
|
43
|
-
// TextInputState holds the same; native exposes no synchronous focus getter.
|
|
44
|
-
const focused = useRef(false);
|
|
45
|
-
// The count native last acknowledged. We echo it back on every controlled write so native's
|
|
46
|
-
// eventLag lands on 0 and the write applies.
|
|
47
|
-
const [mostRecentEventCount, setMostRecentEventCount] = useState(INITIAL_EVENT_COUNT);
|
|
48
|
-
// The last text native holds, as far as JS knows. Seeded from the mount-time value because the
|
|
49
|
-
// `text` prop already carries that value down via createNode, so the FIRST controlled value is
|
|
50
|
-
// not a divergence and must NOT re-command. Only later, post-keystroke divergences flow through
|
|
51
|
-
// setTextAndSelection.
|
|
52
|
-
const lastNativeText = useRef(foldText(value, defaultValue));
|
|
53
|
-
const handleChange = useCallback((event) => {
|
|
54
|
-
// Event seam: the controlled handshake hinges on the change payload carrying `text`
|
|
55
|
-
// (+ `eventCount`). iOS and Android Fabric can key these differently, so log the actual
|
|
56
|
-
// shape here; a missing `text` means onValueChange never fires.
|
|
57
|
-
dlog(`TextInput change keys=[${Object.keys(event.nativeEvent).join(',')}] ` +
|
|
58
|
-
`text=${JSON.stringify(event.nativeEvent.text)} count=${JSON.stringify(event.nativeEvent.eventCount)}`);
|
|
59
|
-
const text = textFromChange(event);
|
|
60
|
-
if (text !== undefined) {
|
|
61
|
-
lastNativeText.current = text;
|
|
62
|
-
onValueChange?.(text, event);
|
|
63
|
-
}
|
|
64
|
-
// Ordering matters: record the text first, then bump the acknowledged count, so the count
|
|
65
|
-
// never runs ahead of the text it stands for.
|
|
66
|
-
const count = eventCountFromChange(event);
|
|
67
|
-
if (count !== undefined)
|
|
68
|
-
setMostRecentEventCount(count);
|
|
69
|
-
}, [onValueChange]);
|
|
70
|
-
const text = foldText(value, defaultValue);
|
|
71
|
-
// Controlled write: when JS-side `value` diverges from what native reported, command the new
|
|
72
|
-
// text down with the acknowledged count. A plain prop re-push would race the user's keystrokes;
|
|
73
|
-
// the command is the only stale-safe path. No deps; it runs after every render and the
|
|
74
|
-
// predicate makes it a no-op unless the text diverged.
|
|
75
|
-
useLayoutEffect(() => {
|
|
76
|
-
const node = ref.current;
|
|
77
|
-
if (node === null)
|
|
78
|
-
return;
|
|
79
|
-
if (!shouldCommandText(lastNativeText.current, value))
|
|
80
|
-
return;
|
|
81
|
-
const selStart = selection?.start ?? SELECTION_NONE;
|
|
82
|
-
const selEnd = selection?.end ?? selection?.start ?? SELECTION_NONE;
|
|
83
|
-
dlog(`TextInput setTextAndSelection count=${mostRecentEventCount} text=${JSON.stringify(value)}`);
|
|
84
|
-
dispatchViewCommand(node, 'setTextAndSelection', [
|
|
85
|
-
mostRecentEventCount,
|
|
86
|
-
value,
|
|
87
|
-
selStart,
|
|
88
|
-
selEnd,
|
|
89
|
-
]);
|
|
90
|
-
lastNativeText.current = value;
|
|
91
|
-
});
|
|
92
|
-
// autoFocus is driven in JS, not as a native prop: on mount, command `focus` down once (RN does
|
|
93
|
-
// the same via TextInputState.focusInput, TextInput.js:538). Empty deps so it fires only on
|
|
94
|
-
// mount; the native `focus` command is idempotent if already focused.
|
|
95
|
-
useEffect(() => {
|
|
96
|
-
if (autoFocus !== true)
|
|
97
|
-
return;
|
|
98
|
-
const node = ref.current;
|
|
99
|
-
if (node === null)
|
|
100
|
-
return;
|
|
101
|
-
dlog('TextInput autoFocus -> focus command');
|
|
102
|
-
dispatchViewCommand(node, 'focus', []);
|
|
103
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
104
|
-
}, []);
|
|
105
|
-
const handleFocus = useCallback((event) => {
|
|
106
|
-
focused.current = true;
|
|
107
|
-
// Track focus app-wide so Keyboard.dismiss can blur this input without a ref.
|
|
108
|
-
if (ref.current !== null)
|
|
109
|
-
setInputFocused(ref.current);
|
|
110
|
-
onFocus?.(event);
|
|
111
|
-
}, [onFocus]);
|
|
112
|
-
const handleBlur = useCallback((event) => {
|
|
113
|
-
focused.current = false;
|
|
114
|
-
if (ref.current !== null)
|
|
115
|
-
setInputBlurred(ref.current);
|
|
116
|
-
onBlur?.(event);
|
|
117
|
-
}, [onBlur]);
|
|
118
|
-
// The imperative API RN exposes on the ref. focus/blur drive native view commands; clear and
|
|
119
|
-
// setSelection reuse setTextAndSelection (the same stale-safe path as a controlled write),
|
|
120
|
-
// echoing the acknowledged event count so native applies them.
|
|
121
|
-
useImperativeHandle(forwardedRef, () => ({
|
|
122
|
-
// Forwarded from the node so a TextInput ref is not poorer than any other host ref. An
|
|
123
|
-
// `expose`/`useImperativeHandle` that lists only the five TextInput methods CLOSES the
|
|
124
|
-
// node off, and the lowered path loses the opposite four — two different surfaces an app
|
|
125
|
-
// crosses between without touching its ref code. `ITextInputHandle` carries the account.
|
|
126
|
-
measure: (callback) => ref.current?.measure(callback),
|
|
127
|
-
measureInWindow: (callback) => ref.current?.measureInWindow(callback),
|
|
128
|
-
measureLayout: (relativeToNativeNode, onSuccess, onFail) => ref.current?.measureLayout(relativeToNativeNode, onSuccess, onFail),
|
|
129
|
-
setNativeProps: (nativeProps) => ref.current?.setNativeProps(nativeProps),
|
|
130
|
-
focus: () => {
|
|
131
|
-
const node = ref.current;
|
|
132
|
-
if (node !== null)
|
|
133
|
-
dispatchViewCommand(node, 'focus', []);
|
|
134
|
-
},
|
|
135
|
-
blur: () => {
|
|
136
|
-
// Routes through TextInputState so the app-wide focus tracking clears too.
|
|
137
|
-
blurTextInput(ref.current);
|
|
138
|
-
},
|
|
139
|
-
clear: () => {
|
|
140
|
-
const node = ref.current;
|
|
141
|
-
if (node === null)
|
|
142
|
-
return;
|
|
143
|
-
dispatchViewCommand(node, 'setTextAndSelection', [
|
|
144
|
-
mostRecentEventCount,
|
|
145
|
-
'',
|
|
146
|
-
0,
|
|
147
|
-
0,
|
|
148
|
-
]);
|
|
149
|
-
lastNativeText.current = '';
|
|
150
|
-
},
|
|
151
|
-
isFocused: () => focused.current,
|
|
152
|
-
setSelection: (start, end) => {
|
|
153
|
-
const node = ref.current;
|
|
154
|
-
if (node === null)
|
|
155
|
-
return;
|
|
156
|
-
const current = lastNativeText.current ?? '';
|
|
157
|
-
dispatchViewCommand(node, 'setTextAndSelection', [
|
|
158
|
-
mostRecentEventCount,
|
|
159
|
-
current,
|
|
160
|
-
start,
|
|
161
|
-
end,
|
|
162
|
-
]);
|
|
163
|
-
},
|
|
164
|
-
}), [mostRecentEventCount]);
|
|
165
|
-
return descriptorToReact(renderTextInput({
|
|
166
|
-
multiline: isMultiline,
|
|
167
|
-
text,
|
|
168
|
-
mostRecentEventCount,
|
|
169
|
-
selection,
|
|
170
|
-
folded,
|
|
171
|
-
passthrough: {
|
|
172
|
-
...passthrough,
|
|
173
|
-
ref,
|
|
174
|
-
onChange: handleChange,
|
|
175
|
-
onFocus: handleFocus,
|
|
176
|
-
onBlur: handleBlur,
|
|
177
|
-
},
|
|
178
|
-
}));
|
|
179
|
-
});
|
|
180
|
-
TextInput.displayName = 'TextInput';
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { type FC, type ReactNode } from 'react';
|
|
2
|
-
import { type IPressTimingProps } from '@symbiote-native/components';
|
|
3
|
-
import { type IPressableProps } from '../pressable';
|
|
4
|
-
import type { IStyleProp, IViewStyle } from '../../utils/styles';
|
|
5
|
-
type ITouchableBaseProps = Omit<IPressableProps, 'style' | 'children'> & IPressTimingProps & {
|
|
6
|
-
style?: IStyleProp<IViewStyle>;
|
|
7
|
-
children?: ReactNode;
|
|
8
|
-
};
|
|
9
|
-
export interface ITouchableOpacityProps extends ITouchableBaseProps {
|
|
10
|
-
activeOpacity?: number;
|
|
11
|
-
}
|
|
12
|
-
export declare const TouchableOpacity: FC<ITouchableOpacityProps>;
|
|
13
|
-
export interface ITouchableHighlightProps extends ITouchableBaseProps {
|
|
14
|
-
activeOpacity?: number;
|
|
15
|
-
underlayColor?: string;
|
|
16
|
-
onShowUnderlay?: () => void;
|
|
17
|
-
onHideUnderlay?: () => void;
|
|
18
|
-
}
|
|
19
|
-
export declare const TouchableHighlight: FC<ITouchableHighlightProps>;
|
|
20
|
-
export type ITouchableWithoutFeedbackProps = ITouchableBaseProps;
|
|
21
|
-
export declare const TouchableWithoutFeedback: FC<ITouchableWithoutFeedbackProps>;
|
|
22
|
-
export {};
|
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
// The Touchable* family, all built on Pressable. Ported against RN's own sources
|
|
2
|
-
// (.vendors/react-native/.../Components/Touchable/*.js) after the 2026-08-19 audit found ten
|
|
3
|
-
// divergences; the shared halves live in @symbiote-native/components and React owns only the
|
|
4
|
-
// lifecycle — refs, effects, the real timers, the Animated wiring, and the child clone:
|
|
5
|
-
// TouchableOpacity: an Animated.Value fades toward activeOpacity on press-in and back to the
|
|
6
|
-
// style's own resting opacity on press-out, driven from onPressIn/onPressOut.
|
|
7
|
-
// TouchableHighlight: the underlay machine drives a `shown` flag (NOT `pressed` — RN holds the
|
|
8
|
-
// underlay for delayPressOut past the tap); the container takes the underlay color and the
|
|
9
|
-
// child takes the lowered opacity, RN's split.
|
|
10
|
-
// TouchableWithoutFeedback: the same press-timing machine with the visual half left empty —
|
|
11
|
-
// "without feedback" means no VISUAL, not no timing.
|
|
12
|
-
import { Children, cloneElement, createElement, isValidElement, useEffect, useRef, useState, } from 'react';
|
|
13
|
-
import { createHighlightUnderlayHandlers, createHighlightUnderlayRuntime, createTouchableFeedbackHandlers, createTouchableFeedbackRuntime, hasTouchablePressHandler, resolveHighlightExtraStyles, restingOpacityFromStyle, DEFAULT_ACTIVE_OPACITY, OPACITY_ACTIVE_GRANT_DURATION_MS, OPACITY_INACTIVE_DURATION_MS, TOUCHABLE_MIN_PRESS_DURATION_MS, } from '@symbiote-native/components';
|
|
14
|
-
import { TouchablePressable as Pressable, } from '../pressable/index.js';
|
|
15
|
-
import { Animated } from '../../modules/animated/index.js';
|
|
16
|
-
// The real setTimeout the shared machines schedule on (core/components has no timer globals).
|
|
17
|
-
// Returns a canceller so an early release flushes the timer.
|
|
18
|
-
function scheduleTimeout(callback, ms) {
|
|
19
|
-
const id = setTimeout(callback, ms);
|
|
20
|
-
return () => clearTimeout(id);
|
|
21
|
-
}
|
|
22
|
-
export const TouchableOpacity = props => {
|
|
23
|
-
const { activeOpacity = DEFAULT_ACTIVE_OPACITY, style,
|
|
24
|
-
// className is pulled out here, like style, and applied to the inner Animated.View below —
|
|
25
|
-
// left in ...rest it would land on the outer Pressable instead, which is not where `style`
|
|
26
|
-
// (the opacity-fade node) goes.
|
|
27
|
-
className, children, disabled, onPressIn, onPressOut, delayPressIn = 0, delayPressOut = 0,
|
|
28
|
-
// RN's Touchables override Pressability's 130ms floor with 0 (TouchableOpacity.js:195); the
|
|
29
|
-
// active visual is held by the fade's own duration instead.
|
|
30
|
-
minPressDuration = TOUCHABLE_MIN_PRESS_DURATION_MS, ...rest } = props;
|
|
31
|
-
// RN's _getChildStyleOpacityWithDefault: the fade settles at the opacity the CALLER's style
|
|
32
|
-
// asks for, not at a hard 1 — and must START there, or the first paint jumps to fully opaque.
|
|
33
|
-
const restingOpacity = restingOpacityFromStyle(style);
|
|
34
|
-
// One Animated.Value per mount. The Animated.View leaf commits its current value every frame,
|
|
35
|
-
// so timing it animates the real view.
|
|
36
|
-
const opacity = useRef(new Animated.Value(restingOpacity)).current;
|
|
37
|
-
// The shared press-scheduling cell (delayPressIn timer + activation clock), held across renders.
|
|
38
|
-
const runtime = useRef(createTouchableFeedbackRuntime()).current;
|
|
39
|
-
function setOpacityTo(toValue, duration) {
|
|
40
|
-
// useNativeDriver: true is RN's own (TouchableOpacity.js:242). opacity is natively drivable
|
|
41
|
-
// and the fade then survives a busy JS thread, which is the whole point of the feedback.
|
|
42
|
-
Animated.timing(opacity, {
|
|
43
|
-
toValue,
|
|
44
|
-
duration,
|
|
45
|
-
easing: Animated.Easing.inOut(Animated.Easing.quad),
|
|
46
|
-
useNativeDriver: true,
|
|
47
|
-
}).start();
|
|
48
|
-
}
|
|
49
|
-
// RN's componentDidUpdate: a changed `disabled` or a changed style opacity re-settles the view,
|
|
50
|
-
// so a Touchable disabled mid-press does not stay stuck at its active opacity.
|
|
51
|
-
const hasRendered = useRef(false);
|
|
52
|
-
useEffect(() => {
|
|
53
|
-
// RN runs this on UPDATE only. Firing at mount would animate over the value the
|
|
54
|
-
// Animated.Value was just constructed with.
|
|
55
|
-
if (!hasRendered.current) {
|
|
56
|
-
hasRendered.current = true;
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
setOpacityTo(restingOpacity, OPACITY_INACTIVE_DURATION_MS);
|
|
60
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps -- setOpacityTo is re-made per render
|
|
61
|
-
}, [disabled, restingOpacity]);
|
|
62
|
-
// RN's componentWillUnmount: stop the fade so a teardown mid-animation leaves no driver running
|
|
63
|
-
// against a node that is gone.
|
|
64
|
-
useEffect(() => () => {
|
|
65
|
-
opacity.resetAnimation();
|
|
66
|
-
}, [opacity]);
|
|
67
|
-
// The shared machine owns the delayPressIn/minPressDuration scheduling; the adapter supplies only
|
|
68
|
-
// the native seam — the Animated opacity fade + the framework emit — as activate/deactivate.
|
|
69
|
-
const { handlePressIn, handlePressOut } = createTouchableFeedbackHandlers({
|
|
70
|
-
delayPressIn,
|
|
71
|
-
delayPressOut,
|
|
72
|
-
minPressDuration,
|
|
73
|
-
schedule: scheduleTimeout,
|
|
74
|
-
now: Date.now,
|
|
75
|
-
}, runtime, {
|
|
76
|
-
activate(event) {
|
|
77
|
-
setOpacityTo(activeOpacity, OPACITY_ACTIVE_GRANT_DURATION_MS);
|
|
78
|
-
onPressIn?.(event);
|
|
79
|
-
},
|
|
80
|
-
deactivate(event) {
|
|
81
|
-
setOpacityTo(restingOpacity, OPACITY_INACTIVE_DURATION_MS);
|
|
82
|
-
onPressOut?.(event);
|
|
83
|
-
},
|
|
84
|
-
});
|
|
85
|
-
return createElement(Pressable, { ...rest, disabled, onPressIn: handlePressIn, onPressOut: handlePressOut }, createElement(Animated.View, { style: [style, { opacity }], className }, children));
|
|
86
|
-
};
|
|
87
|
-
// RN's render: the lowered opacity is cloned onto the CHILD while the container keeps an opaque
|
|
88
|
-
// underlay (TouchableHighlight.js). Fold both onto the container instead and the opacity fades the
|
|
89
|
-
// very underlay it is meant to reveal — `underlayColor: 'black'` paints grey. React is the one
|
|
90
|
-
// adapter that can reach the child without a wrapper view perturbing the flex chain.
|
|
91
|
-
// A non-element child (text, a fragment, nothing at all) has no style prop to compose onto, so it
|
|
92
|
-
// passes through untouched rather than throwing the way RN's Children.only would.
|
|
93
|
-
function withChildStyle(children, extra) {
|
|
94
|
-
const child = Children.count(children) === 1 ? children : undefined;
|
|
95
|
-
if (!isValidElement(child))
|
|
96
|
-
return children;
|
|
97
|
-
return cloneElement(child, { style: [child.props.style, extra] });
|
|
98
|
-
}
|
|
99
|
-
export const TouchableHighlight = props => {
|
|
100
|
-
const { activeOpacity, underlayColor, style, children, onPress, onPressIn, onPressOut, onShowUnderlay, onHideUnderlay, delayPressOut = 0, ...rest } = props;
|
|
101
|
-
// `shown` is NOT `pressed`: RN holds the underlay for delayPressOut past the tap so a press too
|
|
102
|
-
// fast to see still flashes, and the pressed flag is already false by then.
|
|
103
|
-
const [shown, setShown] = useState(false);
|
|
104
|
-
const runtime = useRef(createHighlightUnderlayRuntime()).current;
|
|
105
|
-
// RN's _hasPressHandler gate: no press handler, no underlay. onLongPress is only READ here —
|
|
106
|
-
// it stays in `rest` for Pressable to fire directly.
|
|
107
|
-
const hasPressHandler = hasTouchablePressHandler({
|
|
108
|
-
onPress,
|
|
109
|
-
onPressIn,
|
|
110
|
-
onPressOut,
|
|
111
|
-
onLongPress: rest.onLongPress,
|
|
112
|
-
});
|
|
113
|
-
const underlay = createHighlightUnderlayHandlers({ delayPressOut, hasPressHandler, schedule: scheduleTimeout }, runtime, { setShown, onShowUnderlay, onHideUnderlay });
|
|
114
|
-
const extraStyles = resolveHighlightExtraStyles({
|
|
115
|
-
shown,
|
|
116
|
-
hasPressHandler,
|
|
117
|
-
underlayColor,
|
|
118
|
-
activeOpacity,
|
|
119
|
-
});
|
|
120
|
-
// Visual first, then the caller's callback — RN's order in _createPressabilityConfig.
|
|
121
|
-
function handlePressIn(event) {
|
|
122
|
-
underlay.handlePressIn(event);
|
|
123
|
-
onPressIn?.(event);
|
|
124
|
-
}
|
|
125
|
-
function handlePress(event) {
|
|
126
|
-
underlay.handlePress(event);
|
|
127
|
-
onPress?.(event);
|
|
128
|
-
}
|
|
129
|
-
function handlePressOut(event) {
|
|
130
|
-
underlay.handlePressOut(event);
|
|
131
|
-
onPressOut?.(event);
|
|
132
|
-
}
|
|
133
|
-
return createElement(Pressable, {
|
|
134
|
-
...rest,
|
|
135
|
-
style: extraStyles === undefined ? style : [style, extraStyles.underlay],
|
|
136
|
-
onPress: handlePress,
|
|
137
|
-
onPressIn: handlePressIn,
|
|
138
|
-
onPressOut: handlePressOut,
|
|
139
|
-
}, extraStyles === undefined
|
|
140
|
-
? children
|
|
141
|
-
: withChildStyle(children, extraStyles.child));
|
|
142
|
-
};
|
|
143
|
-
export const TouchableWithoutFeedback = props => {
|
|
144
|
-
const { children, onPressIn, onPressOut, delayPressIn = 0, delayPressOut = 0, minPressDuration = TOUCHABLE_MIN_PRESS_DURATION_MS, ...rest } = props;
|
|
145
|
-
// RN's TouchableWithoutFeedback builds a FULL Pressability config with delayPressIn /
|
|
146
|
-
// delayPressOut / minPressDuration: 0 — the same shared machine TouchableOpacity uses, with the
|
|
147
|
-
// visual half left empty. Spreading the delay props onto Pressable instead did nothing: it does
|
|
148
|
-
// not read them, and they leaked to the host as unknown props.
|
|
149
|
-
const runtime = useRef(createTouchableFeedbackRuntime()).current;
|
|
150
|
-
const { handlePressIn, handlePressOut } = createTouchableFeedbackHandlers({
|
|
151
|
-
delayPressIn,
|
|
152
|
-
delayPressOut,
|
|
153
|
-
minPressDuration,
|
|
154
|
-
schedule: scheduleTimeout,
|
|
155
|
-
now: Date.now,
|
|
156
|
-
}, runtime, {
|
|
157
|
-
activate(event) {
|
|
158
|
-
onPressIn?.(event);
|
|
159
|
-
},
|
|
160
|
-
deactivate(event) {
|
|
161
|
-
onPressOut?.(event);
|
|
162
|
-
},
|
|
163
|
-
});
|
|
164
|
-
return createElement(Pressable, { ...rest, onPressIn: handlePressIn, onPressOut: handlePressOut }, children);
|
|
165
|
-
};
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { type FC, type ReactNode } from 'react';
|
|
2
|
-
import { type INativeFeedbackBackground, type IThemeAttrBackground, type IRippleBackground } from '@symbiote-native/components';
|
|
3
|
-
import { type IPressableProps } from '../pressable';
|
|
4
|
-
export type { INativeFeedbackBackground, IThemeAttrBackground, IRippleBackground, } from '@symbiote-native/components';
|
|
5
|
-
type ITouchableNativeFeedbackBaseProps = Omit<IPressableProps, 'style' | 'children'> & {
|
|
6
|
-
background?: INativeFeedbackBackground;
|
|
7
|
-
useForeground?: boolean;
|
|
8
|
-
children?: ReactNode;
|
|
9
|
-
};
|
|
10
|
-
export type ITouchableNativeFeedbackProps = ITouchableNativeFeedbackBaseProps;
|
|
11
|
-
interface ITouchableNativeFeedbackComponent extends FC<ITouchableNativeFeedbackProps> {
|
|
12
|
-
SelectableBackground: (rippleRadius?: number) => IThemeAttrBackground;
|
|
13
|
-
SelectableBackgroundBorderless: (rippleRadius?: number) => IThemeAttrBackground;
|
|
14
|
-
Ripple: (color: string, borderless: boolean, rippleRadius?: number) => IRippleBackground;
|
|
15
|
-
canUseNativeForeground: () => boolean;
|
|
16
|
-
}
|
|
17
|
-
declare const TouchableNativeFeedback: ITouchableNativeFeedbackComponent;
|
|
18
|
-
export { TouchableNativeFeedback };
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
// TouchableNativeFeedback: Android's ripple/state-drawable touchable, built on Pressable like the
|
|
2
|
-
// rest of the family. RN realizes its feedback by cloning the child into an RCTView carrying native
|
|
3
|
-
// ripple props; we instead nest the child under a feedback View that carries those props, inside a
|
|
4
|
-
// Pressable that owns the press wiring. The native props (nativeBackgroundAndroid /
|
|
5
|
-
// nativeForegroundAndroid) are read by Android's ReactViewManager; on iOS they are inert props, so
|
|
6
|
-
// the component still renders its child with working press wiring. The static factories +
|
|
7
|
-
// background mapping are shared in @symbiote-native/components/view. React only attaches them onto the
|
|
8
|
-
// component value and nests the feedback View.
|
|
9
|
-
import { createElement } from 'react';
|
|
10
|
-
import { dlog } from '@symbiote-native/engine';
|
|
11
|
-
import { backgroundProps, canUseNativeForeground, rippleBackground, selectableBackground, selectableBackgroundBorderless, } from '@symbiote-native/components';
|
|
12
|
-
import { View } from '../../components.js';
|
|
13
|
-
import { TouchablePressable as Pressable, } from '../pressable/index.js';
|
|
14
|
-
const TouchableNativeFeedbackImpl = props => {
|
|
15
|
-
const { background, useForeground = false, children, ...rest } = props;
|
|
16
|
-
// RN defaults a missing background to SelectableBackground() so the touchable always has
|
|
17
|
-
// feedback; mirror that here.
|
|
18
|
-
const resolved = background ?? selectableBackground();
|
|
19
|
-
dlog(`TouchableNativeFeedback render ${resolved.type} useForeground ${useForeground}`);
|
|
20
|
-
const nativeProps = backgroundProps(resolved, useForeground);
|
|
21
|
-
// The native ripple props ride a dedicated feedback View nested under the Pressable; the
|
|
22
|
-
// Pressable owns the press wiring, the feedback View owns the drawable.
|
|
23
|
-
const feedback = createElement(View, nativeProps, children);
|
|
24
|
-
return createElement(Pressable, rest, feedback);
|
|
25
|
-
};
|
|
26
|
-
const TouchableNativeFeedback = Object.assign(TouchableNativeFeedbackImpl, {
|
|
27
|
-
SelectableBackground: selectableBackground,
|
|
28
|
-
SelectableBackgroundBorderless: selectableBackgroundBorderless,
|
|
29
|
-
Ripple: rippleBackground,
|
|
30
|
-
canUseNativeForeground,
|
|
31
|
-
});
|
|
32
|
-
export { TouchableNativeFeedback };
|
package/build/jsx.d.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import type { ISymbioteIntrinsic } from '@symbiote-native/components';
|
|
2
|
-
import type { IViewProps, ITextProps } from './components';
|
|
3
|
-
interface HostProps {
|
|
4
|
-
style?: unknown;
|
|
5
|
-
children?: import('react').ReactNode;
|
|
6
|
-
[key: string]: unknown;
|
|
7
|
-
}
|
|
8
|
-
type ILooseIntrinsics = Omit<Record<ISymbioteIntrinsic, HostProps>, 'symbiote-view' | 'symbiote-text'>;
|
|
9
|
-
declare module 'react' {
|
|
10
|
-
namespace JSX {
|
|
11
|
-
interface IntrinsicElements extends ILooseIntrinsics {
|
|
12
|
-
'symbiote-view': IViewProps;
|
|
13
|
-
'symbiote-text': ITextProps;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
export type ISymbioteIntrinsicTag = keyof import('react').JSX.IntrinsicElements;
|
|
18
|
-
export {};
|
package/build/state-style.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { resolveStateStyle, type IPressStateArgument, type IResolvedStateStyle, } from '@symbiote-native/components/state-style';
|
package/build/state-style.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
// Re-export only. The implementation is shared (`@symbiote-native/components`) because two adapters
|
|
2
|
-
// had written byte-identical copies; this file exists so the code a transform EMITS keeps importing
|
|
3
|
-
// from the adapter package the app already depends on, rather than reaching past it.
|
|
4
|
-
//
|
|
5
|
-
// Imported from the SUBPATH, not the barrel: every adapter barrel re-exports
|
|
6
|
-
// `@symbiote-native/components` wholesale, so a name on that barrel silently becomes public API on
|
|
7
|
-
// all five — which is how a transform internal nearly shipped as a supported export.
|
|
8
|
-
//
|
|
9
|
-
// Present on every adapter, including those with no lowering transform yet: a per-framework subpath
|
|
10
|
-
// that four of five declare is exactly the silent gap `tests/package-subpath-parity.test.ts` exists
|
|
11
|
-
// to catch, and the file costs a re-export. Deliberately NOT on the public barrel — the emitted
|
|
12
|
-
// code names it, app code does not.
|
|
13
|
-
export { resolveStateStyle, } from '@symbiote-native/components/state-style';
|
|
File without changes
|