@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.
Files changed (67) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +110 -0
  3. package/build/accessibility-props.d.ts +64 -0
  4. package/build/accessibility-props.js +150 -0
  5. package/build/component-names/index.android.d.ts +3 -0
  6. package/build/component-names/index.android.js +32 -0
  7. package/build/component-names/index.d.ts +1 -0
  8. package/build/component-names/index.ios.d.ts +3 -0
  9. package/build/component-names/index.ios.js +26 -0
  10. package/build/component-names/index.js +5 -0
  11. package/build/component-names/shared.d.ts +7 -0
  12. package/build/component-names/shared.js +36 -0
  13. package/build/descriptor.d.ts +11 -0
  14. package/build/descriptor.js +17 -0
  15. package/build/index.d.ts +51 -0
  16. package/build/index.js +63 -0
  17. package/build/responder-props.d.ts +18 -0
  18. package/build/responder-props.js +9 -0
  19. package/build/scroll-view-commands.d.ts +23 -0
  20. package/build/scroll-view-commands.js +123 -0
  21. package/build/state/drawer-layout-android.d.ts +22 -0
  22. package/build/state/drawer-layout-android.js +53 -0
  23. package/build/state/flat-list.d.ts +12 -0
  24. package/build/state/flat-list.js +40 -0
  25. package/build/state/modal.d.ts +11 -0
  26. package/build/state/modal.js +28 -0
  27. package/build/state/pressable.d.ts +90 -0
  28. package/build/state/pressable.js +236 -0
  29. package/build/state/section-list.d.ts +46 -0
  30. package/build/state/section-list.js +51 -0
  31. package/build/state/switch.d.ts +12 -0
  32. package/build/state/switch.js +31 -0
  33. package/build/state/text-input.d.ts +103 -0
  34. package/build/state/text-input.js +205 -0
  35. package/build/state/touchable.d.ts +15 -0
  36. package/build/state/touchable.js +22 -0
  37. package/build/state/virtualized-list.d.ts +161 -0
  38. package/build/state/virtualized-list.js +306 -0
  39. package/build/view/render-activity-indicator.d.ts +25 -0
  40. package/build/view/render-activity-indicator.js +54 -0
  41. package/build/view/render-button.d.ts +19 -0
  42. package/build/view/render-button.js +24 -0
  43. package/build/view/render-drawer-layout-android.d.ts +23 -0
  44. package/build/view/render-drawer-layout-android.js +56 -0
  45. package/build/view/render-image-background.d.ts +9 -0
  46. package/build/view/render-image-background.js +48 -0
  47. package/build/view/render-image.d.ts +86 -0
  48. package/build/view/render-image.js +298 -0
  49. package/build/view/render-input-accessory-view.d.ts +9 -0
  50. package/build/view/render-input-accessory-view.js +18 -0
  51. package/build/view/render-keyboard-avoiding-view.d.ts +30 -0
  52. package/build/view/render-keyboard-avoiding-view.js +75 -0
  53. package/build/view/render-modal.d.ts +23 -0
  54. package/build/view/render-modal.js +70 -0
  55. package/build/view/render-pressable.d.ts +8 -0
  56. package/build/view/render-pressable.js +42 -0
  57. package/build/view/render-scroll-sticky.d.ts +24 -0
  58. package/build/view/render-scroll-sticky.js +81 -0
  59. package/build/view/render-scroll-view.d.ts +18 -0
  60. package/build/view/render-scroll-view.js +85 -0
  61. package/build/view/render-switch.d.ts +29 -0
  62. package/build/view/render-switch.js +33 -0
  63. package/build/view/render-text-input.d.ts +11 -0
  64. package/build/view/render-text-input.js +35 -0
  65. package/build/view/render-touchable-native-feedback.d.ts +17 -0
  66. package/build/view/render-touchable-native-feedback.js +39 -0
  67. 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
+ }