@symbiote-native/components 0.1.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/LICENSE +21 -0
- package/README.md +110 -0
- package/build/accessibility-props.d.ts +64 -0
- package/build/accessibility-props.js +150 -0
- package/build/component-names/index.android.d.ts +3 -0
- package/build/component-names/index.android.js +32 -0
- package/build/component-names/index.d.ts +1 -0
- package/build/component-names/index.ios.d.ts +3 -0
- package/build/component-names/index.ios.js +26 -0
- package/build/component-names/index.js +5 -0
- package/build/component-names/shared.d.ts +7 -0
- package/build/component-names/shared.js +36 -0
- package/build/descriptor.d.ts +11 -0
- package/build/descriptor.js +17 -0
- package/build/index.d.ts +51 -0
- package/build/index.js +63 -0
- package/build/responder-props.d.ts +18 -0
- package/build/responder-props.js +9 -0
- package/build/scroll-view-commands.d.ts +23 -0
- package/build/scroll-view-commands.js +123 -0
- package/build/state/drawer-layout-android.d.ts +22 -0
- package/build/state/drawer-layout-android.js +53 -0
- package/build/state/flat-list.d.ts +12 -0
- package/build/state/flat-list.js +40 -0
- package/build/state/modal.d.ts +11 -0
- package/build/state/modal.js +28 -0
- package/build/state/pressable.d.ts +90 -0
- package/build/state/pressable.js +236 -0
- package/build/state/section-list.d.ts +46 -0
- package/build/state/section-list.js +51 -0
- package/build/state/switch.d.ts +12 -0
- package/build/state/switch.js +31 -0
- package/build/state/text-input.d.ts +103 -0
- package/build/state/text-input.js +205 -0
- package/build/state/touchable.d.ts +15 -0
- package/build/state/touchable.js +22 -0
- package/build/state/virtualized-list.d.ts +161 -0
- package/build/state/virtualized-list.js +306 -0
- package/build/view/render-activity-indicator.d.ts +25 -0
- package/build/view/render-activity-indicator.js +54 -0
- package/build/view/render-button.d.ts +19 -0
- package/build/view/render-button.js +24 -0
- package/build/view/render-drawer-layout-android.d.ts +23 -0
- package/build/view/render-drawer-layout-android.js +56 -0
- package/build/view/render-image-background.d.ts +9 -0
- package/build/view/render-image-background.js +48 -0
- package/build/view/render-image.d.ts +86 -0
- package/build/view/render-image.js +298 -0
- package/build/view/render-input-accessory-view.d.ts +9 -0
- package/build/view/render-input-accessory-view.js +18 -0
- package/build/view/render-keyboard-avoiding-view.d.ts +30 -0
- package/build/view/render-keyboard-avoiding-view.js +75 -0
- package/build/view/render-modal.d.ts +23 -0
- package/build/view/render-modal.js +70 -0
- package/build/view/render-pressable.d.ts +8 -0
- package/build/view/render-pressable.js +42 -0
- package/build/view/render-scroll-sticky.d.ts +24 -0
- package/build/view/render-scroll-sticky.js +81 -0
- package/build/view/render-scroll-view.d.ts +18 -0
- package/build/view/render-scroll-view.js +85 -0
- package/build/view/render-switch.d.ts +29 -0
- package/build/view/render-switch.js +33 -0
- package/build/view/render-text-input.d.ts +11 -0
- package/build/view/render-text-input.js +35 -0
- package/build/view/render-touchable-native-feedback.d.ts +17 -0
- package/build/view/render-touchable-native-feedback.js +39 -0
- package/package.json +38 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// Sticky headers: the framework-agnostic math behind the JS layer RN implements in
|
|
2
|
+
// ScrollView.js / ScrollViewStickyHeader.js. RN does stickiness PURELY IN JS: a single
|
|
3
|
+
// scroll AnimatedValue drives each flagged header's translateY through an interpolation that
|
|
4
|
+
// keeps it pinned to the top (or bottom, inverted) until the next header collides with it.
|
|
5
|
+
// The native Fabric scroll view does NOT honor stickyHeaderIndices on its own. The load-bearing
|
|
6
|
+
// piece, the top/inverted inputRange/outputRange math (computeStickyInterpolation), is ported
|
|
7
|
+
// byte-for-byte from ScrollViewStickyHeader.js's effect. The adapter owns the component shell,
|
|
8
|
+
// the layout state, and building the interpolation onto its Animated value.
|
|
9
|
+
// RN gives the sticky wrapper zIndex:10 (ScrollViewStickyHeader.js styles.header) so the
|
|
10
|
+
// pinned header paints OVER the rows that scroll up under it. Without it the next rows (later
|
|
11
|
+
// siblings) paint on top and bleed through the header.
|
|
12
|
+
export const STICKY_HEADER_Z_INDEX = 10;
|
|
13
|
+
// RN debounces the Fabric ShadowTree transform sync (ScrollViewStickyHeader.js): the smooth pin
|
|
14
|
+
// rides the native driver, but the committed transform must catch up for hit-testing. Android
|
|
15
|
+
// needs the tighter window because its tap hit-detection moves to JS on finger-move.
|
|
16
|
+
const STICKY_DEBOUNCE_ANDROID_MS = 15;
|
|
17
|
+
const STICKY_DEBOUNCE_IOS_MS = 64;
|
|
18
|
+
// The debounce window for pushing the settled translateY into the committed transform, by host.
|
|
19
|
+
export function stickyDebounceMs(os) {
|
|
20
|
+
return os === 'android' ? STICKY_DEBOUNCE_ANDROID_MS : STICKY_DEBOUNCE_IOS_MS;
|
|
21
|
+
}
|
|
22
|
+
export function readLayoutNumber(event, key) {
|
|
23
|
+
const layout = event.nativeEvent.layout;
|
|
24
|
+
if (typeof layout !== 'object' || layout === null)
|
|
25
|
+
return undefined;
|
|
26
|
+
const value = Reflect.get(layout, key);
|
|
27
|
+
return typeof value === 'number' ? value : undefined;
|
|
28
|
+
}
|
|
29
|
+
// Build the scroll-offset → translateY interpolation ranges that keep the header pinned.
|
|
30
|
+
// Ported byte-for-byte from ScrollViewStickyHeader.js's effect (both branches). The base
|
|
31
|
+
// [-1, 0] → [0, 0] stub is the un-measured identity; once measured, the top branch pins at
|
|
32
|
+
// layoutY and tracks 1:1 until the next header pushes it off, while the inverted branch pins
|
|
33
|
+
// at the viewport bottom (stickStartPoint) and tracks up to the collision point.
|
|
34
|
+
export function computeStickyInterpolation(params) {
|
|
35
|
+
const { measured, inverted, scrollViewHeight, layoutY, layoutHeight, nextHeaderLayoutY } = params;
|
|
36
|
+
const inputRange = [-1, 0];
|
|
37
|
+
const outputRange = [0, 0];
|
|
38
|
+
if (measured) {
|
|
39
|
+
if (inverted === true) {
|
|
40
|
+
// Inverted: the header sticks at the BOTTOM of the viewport. It starts sticking once
|
|
41
|
+
// its bottom edge reaches the viewport bottom (stickStartPoint), then tracks scroll up
|
|
42
|
+
// to the next header's collision point.
|
|
43
|
+
if (scrollViewHeight !== undefined) {
|
|
44
|
+
const stickStartPoint = layoutY + layoutHeight - scrollViewHeight;
|
|
45
|
+
if (stickStartPoint > 0) {
|
|
46
|
+
inputRange.push(stickStartPoint, stickStartPoint + 1);
|
|
47
|
+
outputRange.push(0, 1);
|
|
48
|
+
const collisionPoint = (nextHeaderLayoutY ?? 0) - layoutHeight - scrollViewHeight;
|
|
49
|
+
if (collisionPoint > stickStartPoint) {
|
|
50
|
+
inputRange.push(collisionPoint, collisionPoint + 1);
|
|
51
|
+
outputRange.push(collisionPoint - stickStartPoint, collisionPoint - stickStartPoint);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
// Top: no translation until the header reaches the top (layoutY), then it tracks the
|
|
58
|
+
// scroll 1:1 to stay pinned, until the next header pushes it back off.
|
|
59
|
+
inputRange.push(layoutY);
|
|
60
|
+
outputRange.push(0);
|
|
61
|
+
const collisionPoint = (nextHeaderLayoutY ?? 0) - layoutHeight;
|
|
62
|
+
if (collisionPoint >= layoutY) {
|
|
63
|
+
inputRange.push(collisionPoint, collisionPoint + 1);
|
|
64
|
+
outputRange.push(collisionPoint - layoutY, collisionPoint - layoutY);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
inputRange.push(layoutY + 1);
|
|
68
|
+
outputRange.push(1);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return { inputRange, outputRange };
|
|
73
|
+
}
|
|
74
|
+
// The cross-talk lookup (RN's _headerLayoutYs, ScrollView.js:1695 nextIndex): given this
|
|
75
|
+
// header's position in `stickyHeaderIndices` (indexOfIndex), find the measured y of the NEXT
|
|
76
|
+
// flagged header, its collision point. undefined until that header has measured (or for the
|
|
77
|
+
// last flagged header, which has no successor and so sticks indefinitely).
|
|
78
|
+
export function nextStickyHeaderY(stickyHeaderIndices, indexOfIndex, headerLayoutYs) {
|
|
79
|
+
const nextIndex = stickyHeaderIndices[indexOfIndex + 1];
|
|
80
|
+
return nextIndex === undefined ? undefined : headerLayoutYs.get(nextIndex);
|
|
81
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { IStyleProp, ISymbioteEvent, IViewStyle } from '@symbiote-native/engine';
|
|
2
|
+
import type { ISymbioteIntrinsic } from '../component-names/shared';
|
|
3
|
+
export declare function readLayoutDimension(event: ISymbioteEvent, key: 'width' | 'height'): number | undefined;
|
|
4
|
+
export declare function resolveDecelerationRate(rate: 'normal' | 'fast' | number): number;
|
|
5
|
+
export declare const SCROLL_VIEW_BASE_HORIZONTAL: IViewStyle;
|
|
6
|
+
export declare const SCROLL_VIEW_BASE_VERTICAL: IViewStyle;
|
|
7
|
+
export type IScrollIntrinsics = {
|
|
8
|
+
scrollViewIntrinsic: ISymbioteIntrinsic;
|
|
9
|
+
contentIntrinsic: ISymbioteIntrinsic;
|
|
10
|
+
scrollViewBaseStyle: IViewStyle;
|
|
11
|
+
contentStyle: IStyleProp<IViewStyle>;
|
|
12
|
+
};
|
|
13
|
+
export declare function selectScrollIntrinsics(isHorizontal: boolean, contentContainerStyle: IStyleProp<IViewStyle> | undefined): IScrollIntrinsics;
|
|
14
|
+
export type IContentSize = {
|
|
15
|
+
width: number;
|
|
16
|
+
height: number;
|
|
17
|
+
};
|
|
18
|
+
export declare function didContentSizeChange(last: IContentSize | null, next: IContentSize): boolean;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// ScrollView: the render half (framework-agnostic). The Fabric tree is nested: the scroll
|
|
2
|
+
// view wraps a content view that holds the children (RN's own ScrollView.js shape). Resolving
|
|
3
|
+
// decelerationRate, picking the per-axis intrinsics/base style, reading layout dimensions, and
|
|
4
|
+
// the content-size dedupe are all platform- and framework-invariant, so they live here. The
|
|
5
|
+
// adapter owns the lifecycle (refs/state/effects) and the element assembly; it calls these pure
|
|
6
|
+
// helpers from prepareScrollView. What diverges per platform (ADR 0020), how a RefreshControl
|
|
7
|
+
// integrates, stays in the adapter's .ios/.android files.
|
|
8
|
+
import { Platform } from '@symbiote-native/engine';
|
|
9
|
+
// Pull a numeric field out of an onLayout event's nativeEvent.layout without a cast:
|
|
10
|
+
// SymbioteEvent.nativeEvent is Record<string, unknown>, so the layout box and its
|
|
11
|
+
// width/height are narrowed at runtime. A malformed event yields undefined (no-op).
|
|
12
|
+
export function readLayoutDimension(event, key) {
|
|
13
|
+
const layout = event.nativeEvent.layout;
|
|
14
|
+
if (typeof layout !== 'object' || layout === null)
|
|
15
|
+
return undefined;
|
|
16
|
+
const value = Reflect.get(layout, key);
|
|
17
|
+
return typeof value === 'number' ? value : undefined;
|
|
18
|
+
}
|
|
19
|
+
// 'normal'/'fast' resolve to DIFFERENT friction constants per platform: RN's
|
|
20
|
+
// processDecelerationRate.js Platform.select()s them: iOS glides longer (0.998/0.99),
|
|
21
|
+
// Android sooner (0.985/0.9). Hardcoding the iOS pair made Android momentum scroll
|
|
22
|
+
// glide far too long on 'fast'. This is the file's one Platform read: the header's
|
|
23
|
+
// "no Platform.OS" rule governs component-intrinsic selection, not a value transform
|
|
24
|
+
// RN itself platform-branches. `default` mirrors iOS so any non-ios/android host stays
|
|
25
|
+
// defined (select would otherwise yield undefined). Numeric rates pass through unchanged.
|
|
26
|
+
export function resolveDecelerationRate(rate) {
|
|
27
|
+
if (typeof rate === 'number')
|
|
28
|
+
return rate;
|
|
29
|
+
// select() types as `number | undefined`; the always-present `default` makes the
|
|
30
|
+
// `??` fallback unreachable, but it narrows the return to a plain `number` (no cast).
|
|
31
|
+
if (rate === 'normal')
|
|
32
|
+
return Platform.select({ ios: 0.998, android: 0.985, default: 0.998 }) ?? 0.998;
|
|
33
|
+
return Platform.select({ ios: 0.99, android: 0.9, default: 0.99 }) ?? 0.99;
|
|
34
|
+
}
|
|
35
|
+
// RN applies a base style to the scroll-view NODE itself, per axis (ScrollView.js
|
|
36
|
+
// styles.baseHorizontal/baseVertical). Two parts carry weight:
|
|
37
|
+
// - `overflow: 'scroll'`: clips content to the scroll view's frame. On iOS Fabric the
|
|
38
|
+
// node only clips when this is set; without it a fixed-height ScrollView lets its
|
|
39
|
+
// content bleed out over siblings (Android's native ViewGroup clips regardless, which
|
|
40
|
+
// is why the bug showed only on iOS). RN sets it on BOTH axes, so we do too.
|
|
41
|
+
// - `flexDirection: 'row'` (horizontal only): makes the single content child a MAIN-axis
|
|
42
|
+
// item, so Yoga sizes it to its content width and the view overflows and scrolls.
|
|
43
|
+
// Without it the content is a CROSS-axis item, stretched to the viewport, nothing to
|
|
44
|
+
// scroll. Vertical keeps the default `column`.
|
|
45
|
+
// Both axes match RN's baseHorizontal/baseVertical exactly. Composed UNDER the user style,
|
|
46
|
+
// so an explicit value still wins.
|
|
47
|
+
export const SCROLL_VIEW_BASE_HORIZONTAL = {
|
|
48
|
+
flexGrow: 1,
|
|
49
|
+
flexShrink: 1,
|
|
50
|
+
flexDirection: 'row',
|
|
51
|
+
overflow: 'scroll',
|
|
52
|
+
};
|
|
53
|
+
export const SCROLL_VIEW_BASE_VERTICAL = {
|
|
54
|
+
flexGrow: 1,
|
|
55
|
+
flexShrink: 1,
|
|
56
|
+
flexDirection: 'column',
|
|
57
|
+
overflow: 'scroll',
|
|
58
|
+
};
|
|
59
|
+
export function selectScrollIntrinsics(isHorizontal, contentContainerStyle) {
|
|
60
|
+
// Horizontal scroll resolves to a different native component on Android (its own
|
|
61
|
+
// ViewManager, not RCTScrollView+flag); on iOS both intrinsics map back to RCTScrollView.
|
|
62
|
+
// The name table does the per-platform mapping; here we only pick the intrinsic.
|
|
63
|
+
const scrollViewIntrinsic = isHorizontal
|
|
64
|
+
? 'symbiote-horizontal-scroll-view'
|
|
65
|
+
: 'symbiote-scroll-view';
|
|
66
|
+
const contentIntrinsic = isHorizontal
|
|
67
|
+
? 'symbiote-horizontal-scroll-content'
|
|
68
|
+
: 'symbiote-scroll-content';
|
|
69
|
+
const scrollViewBaseStyle = isHorizontal
|
|
70
|
+
? SCROLL_VIEW_BASE_HORIZONTAL
|
|
71
|
+
: SCROLL_VIEW_BASE_VERTICAL;
|
|
72
|
+
const contentStyle = isHorizontal
|
|
73
|
+
? [contentContainerStyle, { flexDirection: 'row' }]
|
|
74
|
+
: contentContainerStyle;
|
|
75
|
+
return { scrollViewIntrinsic, contentIntrinsic, scrollViewBaseStyle, contentStyle };
|
|
76
|
+
}
|
|
77
|
+
// Did the content view's measured size actually change since the last fire? RN synthesizes
|
|
78
|
+
// onContentSizeChange from the inner content view's onLayout, but that fires on every layout
|
|
79
|
+
// pass; RN dedupes so the user handler only sees real size changes (ScrollView.js). First
|
|
80
|
+
// measurement (last === null) always counts as a change.
|
|
81
|
+
export function didContentSizeChange(last, next) {
|
|
82
|
+
if (last === null)
|
|
83
|
+
return true;
|
|
84
|
+
return last.width !== next.width || last.height !== next.height;
|
|
85
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { IStyleProp, IViewStyle, ISymbioteEvent } from '@symbiote-native/engine';
|
|
2
|
+
import type { IDescriptor } from '../descriptor';
|
|
3
|
+
import type { IAccessibilityProps, IAriaProps } from '../accessibility-props';
|
|
4
|
+
export type ISwitchTrackColor = {
|
|
5
|
+
false?: string;
|
|
6
|
+
true?: string;
|
|
7
|
+
};
|
|
8
|
+
export interface ISwitchProps extends IAccessibilityProps, IAriaProps {
|
|
9
|
+
value?: boolean;
|
|
10
|
+
onValueChange?: (value: boolean, event: ISymbioteEvent) => void;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
trackColor?: ISwitchTrackColor;
|
|
13
|
+
thumbColor?: string;
|
|
14
|
+
ios_backgroundColor?: string;
|
|
15
|
+
style?: IStyleProp<IViewStyle>;
|
|
16
|
+
}
|
|
17
|
+
export type ISwitchPlatform = {
|
|
18
|
+
trackColorProps: (value: boolean, trackColor?: ISwitchTrackColor) => Record<string, unknown>;
|
|
19
|
+
};
|
|
20
|
+
export type ISwitchViewProps = {
|
|
21
|
+
value: boolean;
|
|
22
|
+
disabled?: boolean;
|
|
23
|
+
trackColor?: ISwitchTrackColor;
|
|
24
|
+
thumbColor?: string;
|
|
25
|
+
ios_backgroundColor?: string;
|
|
26
|
+
style?: IStyleProp<IViewStyle>;
|
|
27
|
+
passthrough: Record<string, unknown>;
|
|
28
|
+
};
|
|
29
|
+
export declare function renderSwitch(view: ISwitchViewProps, platform: ISwitchPlatform): IDescriptor;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Switch: the render half (framework-agnostic). Maps the resolved props onto the single
|
|
2
|
+
// `symbiote-switch` host node: the strict value fold lands as the `value` Fabric prop, the
|
|
3
|
+
// track colors take platform-specific prop NAMES (iOS onTintColor/tintColor vs Android
|
|
4
|
+
// trackColorFor*/trackTintColor, supplied via `platform`), thumbColor → thumbTintColor, and
|
|
5
|
+
// ios_backgroundColor folds into the style as the pill that shows through the shrunken track.
|
|
6
|
+
// Pure and prop-driven; no hooks, no events. The adapter owns those.
|
|
7
|
+
import { dlog } from '@symbiote-native/engine';
|
|
8
|
+
import { el } from '../descriptor';
|
|
9
|
+
// RN rounds the iOS background pill to this radius when ios_backgroundColor is set.
|
|
10
|
+
const IOS_BACKGROUND_BORDER_RADIUS = 16;
|
|
11
|
+
// Fold ios_backgroundColor into the style, matching RN's iOS branch: it paints the
|
|
12
|
+
// background that shows through the shrunken track. Untouched when unset, so a caller's own
|
|
13
|
+
// backgroundColor wins by simply not passing ios_backgroundColor.
|
|
14
|
+
function foldIosBackground(style, color) {
|
|
15
|
+
if (color === undefined)
|
|
16
|
+
return style;
|
|
17
|
+
return [style, { backgroundColor: color, borderRadius: IOS_BACKGROUND_BORDER_RADIUS }];
|
|
18
|
+
}
|
|
19
|
+
export function renderSwitch(view, platform) {
|
|
20
|
+
dlog(`Switch render value=${view.value} disabled=${String(view.disabled)}`);
|
|
21
|
+
// These color props reach Fabric as ordinary props: the shared ViewConfig declares
|
|
22
|
+
// Switch's only event as `change`, so routeProp sends the on*-prefixed color names through
|
|
23
|
+
// setProp rather than mistaking them for listeners.
|
|
24
|
+
const props = {
|
|
25
|
+
...view.passthrough,
|
|
26
|
+
value: view.value,
|
|
27
|
+
disabled: view.disabled,
|
|
28
|
+
...platform.trackColorProps(view.value, view.trackColor),
|
|
29
|
+
thumbTintColor: view.thumbColor,
|
|
30
|
+
style: foldIosBackground(view.style, view.ios_backgroundColor),
|
|
31
|
+
};
|
|
32
|
+
return el('symbiote-switch', props);
|
|
33
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { IDescriptor } from '../descriptor';
|
|
2
|
+
import type { IFoldedTextInputProps, ITextInputSelection } from '../state/text-input';
|
|
3
|
+
export type ITextInputViewProps = {
|
|
4
|
+
multiline: boolean;
|
|
5
|
+
text: string | undefined;
|
|
6
|
+
mostRecentEventCount: number;
|
|
7
|
+
selection?: ITextInputSelection;
|
|
8
|
+
folded: IFoldedTextInputProps;
|
|
9
|
+
passthrough: Record<string, unknown>;
|
|
10
|
+
};
|
|
11
|
+
export declare function renderTextInput(view: ITextInputViewProps): IDescriptor;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// TextInput: the render half (framework-agnostic). Picks the intrinsic (single- vs multiline)
|
|
2
|
+
// and maps the resolved props onto it: the controlled value rides as the private `text` prop
|
|
3
|
+
// plus `mostRecentEventCount`, the W3C/alias folds arrive pre-resolved in `folded`, and the
|
|
4
|
+
// ref + the change/focus/blur handlers + every pass-through prop (accessibility, testID, the
|
|
5
|
+
// remaining native events) arrive folded into `passthrough` and land on the host untouched.
|
|
6
|
+
// Pure and prop-driven; no hooks, no events. The adapter owns those.
|
|
7
|
+
import { dlog } from '@symbiote-native/engine';
|
|
8
|
+
import { el } from '../descriptor';
|
|
9
|
+
// One host element per native input class. Text carries the only non-trivial nesting elsewhere;
|
|
10
|
+
// here the choice is binary and runtime (the `multiline` prop), so the module stays flat.
|
|
11
|
+
const SINGLELINE_INTRINSIC = 'symbiote-text-input';
|
|
12
|
+
const MULTILINE_INTRINSIC = 'symbiote-text-input-multiline';
|
|
13
|
+
export function renderTextInput(view) {
|
|
14
|
+
const intrinsic = view.multiline ? MULTILINE_INTRINSIC : SINGLELINE_INTRINSIC;
|
|
15
|
+
dlog(`TextInput render multiline=${String(view.multiline)} ` +
|
|
16
|
+
`text=${JSON.stringify(view.text)} count=${view.mostRecentEventCount}`);
|
|
17
|
+
const props = {
|
|
18
|
+
...view.passthrough,
|
|
19
|
+
text: view.text,
|
|
20
|
+
mostRecentEventCount: view.mostRecentEventCount,
|
|
21
|
+
selection: view.selection,
|
|
22
|
+
keyboardType: view.folded.keyboardType,
|
|
23
|
+
returnKeyType: view.folded.returnKeyType,
|
|
24
|
+
editable: view.folded.editable,
|
|
25
|
+
submitBehavior: view.folded.submitBehavior,
|
|
26
|
+
selectionColor: view.folded.selectionColor,
|
|
27
|
+
cursorColor: view.folded.cursorColor,
|
|
28
|
+
selectionHandleColor: view.folded.selectionHandleColor,
|
|
29
|
+
underlineColorAndroid: view.folded.underlineColorAndroid,
|
|
30
|
+
autoComplete: view.folded.autoComplete,
|
|
31
|
+
textContentType: view.folded.textContentType,
|
|
32
|
+
showSoftInputOnFocus: view.folded.showSoftInputOnFocus,
|
|
33
|
+
};
|
|
34
|
+
return el(intrinsic, props);
|
|
35
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface IThemeAttrBackground {
|
|
2
|
+
type: 'ThemeAttrAndroid';
|
|
3
|
+
attribute: 'selectableItemBackground' | 'selectableItemBackgroundBorderless';
|
|
4
|
+
rippleRadius?: number;
|
|
5
|
+
}
|
|
6
|
+
export interface IRippleBackground {
|
|
7
|
+
type: 'RippleAndroid';
|
|
8
|
+
color: string | null;
|
|
9
|
+
borderless: boolean;
|
|
10
|
+
rippleRadius?: number;
|
|
11
|
+
}
|
|
12
|
+
export type INativeFeedbackBackground = IThemeAttrBackground | IRippleBackground;
|
|
13
|
+
export declare function canUseNativeForeground(): boolean;
|
|
14
|
+
export declare function selectableBackground(rippleRadius?: number): IThemeAttrBackground;
|
|
15
|
+
export declare function selectableBackgroundBorderless(rippleRadius?: number): IThemeAttrBackground;
|
|
16
|
+
export declare function rippleBackground(color: string, borderless: boolean, rippleRadius?: number): IRippleBackground;
|
|
17
|
+
export declare function backgroundProps(background: INativeFeedbackBackground, useForeground: boolean): Record<string, INativeFeedbackBackground>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// TouchableNativeFeedback: the shared render half (framework-agnostic). The static background
|
|
2
|
+
// factories (SelectableBackground / Ripple / …) are pure dict producers, and the mapping of a
|
|
3
|
+
// resolved background + useForeground onto the native prop Android reads is platform-invariant,
|
|
4
|
+
// both live here so every adapter inherits the exact same surface. The adapter only attaches these
|
|
5
|
+
// statics onto its component value and nests the feedback View under its Pressable.
|
|
6
|
+
import { Platform } from '@symbiote-native/engine';
|
|
7
|
+
// Native foreground ripple is Android-only (API 23+). RN gates this on Platform.OS === 'android'
|
|
8
|
+
// && Platform.Version >= 23. Version is a string on iOS (where the gate is irrelevant) and a
|
|
9
|
+
// number on Android, so guard the type at runtime before the numeric compare, no cast.
|
|
10
|
+
const ANDROID_FOREGROUND_MIN_VERSION = 23;
|
|
11
|
+
export function canUseNativeForeground() {
|
|
12
|
+
return (Platform.OS === 'android' &&
|
|
13
|
+
typeof Platform.Version === 'number' &&
|
|
14
|
+
Platform.Version >= ANDROID_FOREGROUND_MIN_VERSION);
|
|
15
|
+
}
|
|
16
|
+
export function selectableBackground(rippleRadius) {
|
|
17
|
+
return { type: 'ThemeAttrAndroid', attribute: 'selectableItemBackground', rippleRadius };
|
|
18
|
+
}
|
|
19
|
+
export function selectableBackgroundBorderless(rippleRadius) {
|
|
20
|
+
return {
|
|
21
|
+
type: 'ThemeAttrAndroid',
|
|
22
|
+
attribute: 'selectableItemBackgroundBorderless',
|
|
23
|
+
rippleRadius,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
// RN runs the color string through processColor (→ a native int); we have no native bridge here,
|
|
27
|
+
// so we keep the string and let Android resolve it. A null color is the documented "no tint".
|
|
28
|
+
export function rippleBackground(color, borderless, rippleRadius) {
|
|
29
|
+
return { type: 'RippleAndroid', color, borderless, rippleRadius };
|
|
30
|
+
}
|
|
31
|
+
// Maps the resolved background + useForeground onto the native prop Android reads. useForeground
|
|
32
|
+
// only paints the foreground when the platform supports it (canUseNativeForeground); otherwise it
|
|
33
|
+
// falls back to the background slot, matching RN. On iOS both props are inert.
|
|
34
|
+
export function backgroundProps(background, useForeground) {
|
|
35
|
+
if (useForeground && canUseNativeForeground()) {
|
|
36
|
+
return { nativeForegroundAndroid: background };
|
|
37
|
+
}
|
|
38
|
+
return { nativeBackgroundAndroid: background };
|
|
39
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@symbiote-native/components",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Framework-agnostic component logic (state machines + render functions) for SymbioteJS — written once, inherited by every adapter (React, Vue, Angular, ...).",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/OneEyed1366/symbiote-native.git",
|
|
8
|
+
"directory": "core/components"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/OneEyed1366/symbiote-native/tree/master/core/components#readme",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/OneEyed1366/symbiote-native/issues"
|
|
13
|
+
},
|
|
14
|
+
"author": "Andrey Prokopenko <psevdoproger@gmail.com>",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"main": "./build/index.js",
|
|
17
|
+
"module": "./build/index.js",
|
|
18
|
+
"types": "./build/index.d.ts",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./build/index.d.ts",
|
|
22
|
+
"default": "./build/index.js"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"build"
|
|
27
|
+
],
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@symbiote-native/engine": "0.1.0"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"typecheck": "tsc --build",
|
|
36
|
+
"format": "prettier --write \"src/**/*.{ts,tsx}\""
|
|
37
|
+
}
|
|
38
|
+
}
|