@symbiote-native/components 0.2.4 → 0.2.5

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/build/index.js CHANGED
@@ -40,7 +40,7 @@ export { computeInset, readKeyboardFrame, readLayoutFrame, resolveKeyboardAvoidi
40
40
  // accessibilityState + ripple render decisions in view/render-pressable. Every adapter supplies
41
41
  // only its reactive `pressed` cell + the View's measure handle (see the 3-layer split).
42
42
  export { createPressHandlers, createPressRuntime, normalizeRect, maxEdge, isTouchWithinRegion, readPoint, computeRegion, rippleProps, DEFAULT_DELAY_LONG_PRESS_MS, DEFAULT_PRESS_RECT_OFFSETS, } from './state/pressable.js';
43
- export { buildPressableListeners, resolveDisabledAccessibilityState, noteHoverNoop, shouldSuppressPress, shouldClaimResponder, isTerminationAllowed, } from './view/render-pressable.js';
43
+ export { buildPressableListeners, resolveDisabledAccessibilityState, noteHoverNoop, shouldSuppressPress, shouldClaimResponder, isTerminationAllowed, } from './view/render-pressable/index.js';
44
44
  // Touchable*: shared press-timing constants + the deactivation-floor math (the Animated feedback
45
45
  // itself stays per-adapter), and TouchableNativeFeedback's pure static factories + background map.
46
46
  export { computePressOutWait, DEFAULT_ACTIVE_OPACITY, OPACITY_ACTIVE_DURATION_MS, OPACITY_INACTIVE_DURATION_MS, RESTING_OPACITY, DEFAULT_HIGHLIGHT_CHILD_OPACITY, DEFAULT_UNDERLAY_COLOR, DEFAULT_MIN_PRESS_DURATION_MS, } from './state/touchable.js';
@@ -0,0 +1,10 @@
1
+ import type { ISymbioteNode } from '@symbiote-native/engine';
2
+ import type { IScrollViewHandle } from '../../scroll-view-commands';
3
+ export interface IScrollRoutingHandle {
4
+ flashScrollIndicators(): void;
5
+ getNativeScrollRef(): IScrollViewHandle | null;
6
+ getScrollableNode(): IScrollViewHandle | null;
7
+ getScrollResponder(): IScrollViewHandle | null;
8
+ getScrollNode(): ISymbioteNode | null;
9
+ recordInteraction(): void;
10
+ }
@@ -0,0 +1,6 @@
1
+ // The inner-scroll routing tail every windowed-list imperative handle exposes
2
+ // (VirtualizedList, VirtualizedSectionList, and via those FlatList/SectionList). Each
3
+ // method forwards straight to the underlying ScrollView's own handle/node; neither list
4
+ // adds behavior of its own, so the 6 members live here once and both list handle types
5
+ // extend this instead of hand-duplicating (and risking drift in) the signatures.
6
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { ISymbioteEvent } from '@symbiote-native/engine';
2
+ export declare function readLayoutField(event: ISymbioteEvent, key: 'width' | 'height' | 'y'): number | undefined;
@@ -0,0 +1,12 @@
1
+ // Shared across render-scroll-view's content/frame dimension read (width/height) and
2
+ // render-scroll-sticky's header position read (y/height): both pull a single numeric field out of
3
+ // an onLayout event's nativeEvent.layout. SymbioteEvent.nativeEvent is Record<string, unknown>, so
4
+ // the layout box and the requested field are narrowed at runtime, no cast. A malformed event
5
+ // yields undefined (no-op).
6
+ export function readLayoutField(event, key) {
7
+ const layout = event.nativeEvent.layout;
8
+ if (typeof layout !== 'object' || layout === null)
9
+ return undefined;
10
+ const value = Reflect.get(layout, key);
11
+ return typeof value === 'number' ? value : undefined;
12
+ }
@@ -0,0 +1,56 @@
1
+ import { type IImageSource, type IImageSourceProp, type IStyleProp, type ISymbioteEvent, type IViewStyle } from '@symbiote-native/engine';
2
+ import type { IAccessibilityProps, IAriaProps } from '../../accessibility-props';
3
+ import { type IDescriptor } from '../../descriptor';
4
+ export type { IImageSource, IImageSourceProp };
5
+ type IImageEventHandler = (event: ISymbioteEvent) => void;
6
+ export type IResizeMode = 'cover' | 'contain' | 'stretch' | 'repeat' | 'center';
7
+ export type IImageCapInsets = {
8
+ top: number;
9
+ left: number;
10
+ bottom: number;
11
+ right: number;
12
+ };
13
+ export type IResizeMethod = 'auto' | 'resize' | 'scale' | 'none';
14
+ export type IImageProps = IAccessibilityProps & IAriaProps & {
15
+ source?: IImageSourceProp;
16
+ defaultSource?: IImageSourceProp;
17
+ loadingIndicatorSource?: IImageSourceProp;
18
+ style?: IStyleProp<IViewStyle>;
19
+ resizeMode?: IResizeMode;
20
+ resizeMethod?: IResizeMethod;
21
+ tintColor?: string;
22
+ blurRadius?: number;
23
+ capInsets?: IImageCapInsets;
24
+ fadeDuration?: number;
25
+ progressiveRenderingEnabled?: boolean;
26
+ src?: string;
27
+ srcSet?: string;
28
+ alt?: string;
29
+ width?: number;
30
+ height?: number;
31
+ crossOrigin?: 'anonymous' | 'use-credentials';
32
+ referrerPolicy?: string;
33
+ onLoadStart?: IImageEventHandler;
34
+ onLoad?: IImageEventHandler;
35
+ onLoadEnd?: IImageEventHandler;
36
+ onError?: IImageEventHandler;
37
+ onProgress?: IImageEventHandler;
38
+ onPartialLoad?: IImageEventHandler;
39
+ };
40
+ export type IImageViewProps = {
41
+ source?: IImageSourceProp;
42
+ defaultSource?: IImageSourceProp;
43
+ loadingIndicatorSource?: IImageSourceProp;
44
+ style?: IStyleProp<IViewStyle>;
45
+ resizeMode?: IResizeMode;
46
+ tintColor?: string;
47
+ src?: string;
48
+ srcSet?: string;
49
+ alt?: string;
50
+ width?: number;
51
+ height?: number;
52
+ crossOrigin?: 'anonymous' | 'use-credentials';
53
+ referrerPolicy?: string;
54
+ passthrough: Record<string, unknown>;
55
+ };
56
+ export declare function renderImage(view: IImageViewProps): IDescriptor;
@@ -0,0 +1,133 @@
1
+ import { dlog, flattenStyle, resolveImageSource, } from '@symbiote-native/engine';
2
+ import { el } from '../../descriptor.js';
3
+ // Resolve the source, then normalize to the array shape native expects. A single
4
+ // object/number becomes a one-element array; an already-array source passes through.
5
+ function normalizeSource(source) {
6
+ const resolved = resolveImageSource(source);
7
+ const sources = Array.isArray(resolved) ? resolved : [resolved];
8
+ dlog(`Image source resolved to ${JSON.stringify(sources)}`);
9
+ return sources;
10
+ }
11
+ // The HTTP headers the W3C aliases (crossOrigin / referrerPolicy) contribute to
12
+ // every folded source, mirroring ImageSourceUtils.js getImageSourcesFromImageProps:
13
+ // 'use-credentials' adds the credentials header; referrerPolicy adds Referrer-Policy.
14
+ function headersFromAliases(view) {
15
+ const headers = {};
16
+ if (view.crossOrigin === 'use-credentials') {
17
+ headers['Access-Control-Allow-Credentials'] = 'true';
18
+ }
19
+ if (view.referrerPolicy !== undefined) {
20
+ headers['Referrer-Policy'] = view.referrerPolicy;
21
+ }
22
+ return headers;
23
+ }
24
+ // Expand a `srcSet` descriptor list into scaled sources, falling back to `src` for
25
+ // the 1x slot when srcSet omits it. Direct port of getImageSourcesFromImageProps'
26
+ // srcSet branch (ImageSourceUtils.js:48). Invalid scale tokens are skipped, matching
27
+ // RN's parse-and-warn behavior.
28
+ function expandSrcSet(srcSet, view, headers) {
29
+ const sources = [];
30
+ let useSrcForDefaultScale = true;
31
+ for (const entry of srcSet.split(', ')) {
32
+ const [uri, xScale = '1x'] = entry.split(' ');
33
+ if (!xScale.endsWith('x')) {
34
+ dlog(`Image srcSet: unsupported scale token "${xScale}", skipping`);
35
+ continue;
36
+ }
37
+ const scale = parseInt(xScale.slice(0, -1), 10);
38
+ if (Number.isNaN(scale))
39
+ continue;
40
+ if (scale === 1)
41
+ useSrcForDefaultScale = false;
42
+ sources.push({ uri, scale, width: view.width, height: view.height, ...{ headers } });
43
+ }
44
+ if (useSrcForDefaultScale && view.src !== undefined) {
45
+ sources.push({
46
+ uri: view.src,
47
+ scale: 1,
48
+ width: view.width,
49
+ height: view.height,
50
+ ...{ headers },
51
+ });
52
+ }
53
+ if (sources.length === 0)
54
+ dlog('Image srcSet: produced no valid sources');
55
+ return sources;
56
+ }
57
+ // Resolve the native `source` array from whichever of source / src / srcSet the
58
+ // caller provided. Mirrors ImageSourceUtils.js getImageSourcesFromImageProps:
59
+ // srcSet wins, then src, then a header-decorated source, then the plain source.
60
+ // Always returns the array shape native expects (the same contract normalizeSource
61
+ // guarantees), so the component never sends a bare object.
62
+ function resolveSourceArray(view) {
63
+ const headers = headersFromAliases(view);
64
+ if (view.srcSet !== undefined) {
65
+ return expandSrcSet(view.srcSet, view, headers);
66
+ }
67
+ if (view.src !== undefined) {
68
+ return [{ uri: view.src, width: view.width, height: view.height, ...{ headers } }];
69
+ }
70
+ if (view.source === undefined) {
71
+ dlog('Image: no source / src / srcSet provided');
72
+ return [];
73
+ }
74
+ const sources = normalizeSource(view.source);
75
+ // A header-decorated single object source gets the headers merged in, per RN's
76
+ // `source.uri && headers` branch; the array/number shapes pass through untouched.
77
+ if (Object.keys(headers).length > 0 && sources.length === 1) {
78
+ const [only] = sources;
79
+ if (typeof only === 'object' && only !== null && typeof Reflect.get(only, 'uri') === 'string') {
80
+ return [{ ...only, headers }];
81
+ }
82
+ }
83
+ return sources;
84
+ }
85
+ function readStyleString(style, key) {
86
+ if (style === undefined)
87
+ return undefined;
88
+ // style is a StyleProp (possibly a nested array), so flatten before reading a key.
89
+ const flat = flattenStyle(style);
90
+ const value = Object.hasOwn(flat, key) ? flat[key] : undefined;
91
+ return typeof value === 'string' ? value : undefined;
92
+ }
93
+ // Resolve an asset source and read its single uri. RN forwards the Android
94
+ // loading indicator as a bare uri string (`loadingIndicatorSrc`), not the
95
+ // array shape the main source uses, so we resolve and pluck the uri.
96
+ function readSourceUri(source) {
97
+ const [resolved] = normalizeSource(source);
98
+ if (typeof resolved === 'object' && resolved !== null) {
99
+ const uri = Reflect.get(resolved, 'uri');
100
+ if (typeof uri === 'string')
101
+ return uri;
102
+ }
103
+ return undefined;
104
+ }
105
+ export function renderImage(view) {
106
+ // `width` / `height` aliases fold into style (ImageProps.js:195,202); explicit
107
+ // style keys win, matching RN's `{width, height}, ...style` ordering.
108
+ const foldedStyle = view.width === undefined && view.height === undefined
109
+ ? view.style
110
+ : [{ width: view.width, height: view.height }, view.style];
111
+ const mapped = {
112
+ ...view.passthrough,
113
+ style: foldedStyle,
114
+ source: resolveSourceArray(view),
115
+ resizeMode: view.resizeMode ?? readStyleString(view.style, 'resizeMode'),
116
+ tintColor: view.tintColor ?? readStyleString(view.style, 'tintColor'),
117
+ };
118
+ // `alt` is the accessibility text: it sets accessibilityLabel and marks the image
119
+ // accessible (Image.ios.js / Image.android.js: alt -> accessibilityLabel + accessible).
120
+ // An explicit accessibilityLabel (already folded into passthrough) still wins.
121
+ if (view.alt !== undefined) {
122
+ if (mapped.accessibilityLabel === undefined)
123
+ mapped.accessibilityLabel = view.alt;
124
+ mapped.accessible = true;
125
+ }
126
+ if (view.defaultSource !== undefined)
127
+ mapped.defaultSource = normalizeSource(view.defaultSource);
128
+ if (view.loadingIndicatorSource !== undefined) {
129
+ mapped.loadingIndicatorSrc = readSourceUri(view.loadingIndicatorSource);
130
+ }
131
+ dlog('Image -> RCTImageView');
132
+ return el('symbiote-image', mapped);
133
+ }
@@ -1,5 +1,5 @@
1
- import type { IAccessibilityStateValue } from '../accessibility-props';
2
- import type { IPressHandlers } from '../state/pressable';
1
+ import type { IAccessibilityStateValue } from '../../accessibility-props';
2
+ import type { IPressHandlers } from '../../state/pressable';
3
3
  export declare function resolveDisabledAccessibilityState(accessibilityState: IAccessibilityStateValue | undefined, disabled: boolean | undefined): IAccessibilityStateValue | undefined;
4
4
  export declare function shouldSuppressPress(disabled: boolean | undefined): boolean;
5
5
  export declare function shouldClaimResponder(disabled: boolean | undefined): boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symbiote-native/components",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "Framework-agnostic component logic (state machines + render functions) for SymbioteNative — written once, inherited by every adapter (React, Vue, Angular, ...).",
5
5
  "repository": {
6
6
  "type": "git",
@@ -22,8 +22,8 @@
22
22
  "default": "./build/index.js"
23
23
  },
24
24
  "./bootstrap": {
25
- "types": "./build/bootstrap.d.ts",
26
- "default": "./build/bootstrap.js"
25
+ "types": "./build/bootstrap/index.d.ts",
26
+ "default": "./build/bootstrap/index.js"
27
27
  }
28
28
  },
29
29
  "files": [
@@ -32,14 +32,13 @@
32
32
  "publishConfig": {
33
33
  "access": "public"
34
34
  },
35
- "dependencies": {
36
- "@symbiote-native/engine": "0.1.5"
37
- },
38
35
  "peerDependencies": {
36
+ "@symbiote-native/engine": ">=0.1.6",
39
37
  "react-native": ">=0.86"
40
38
  },
41
39
  "devDependencies": {
42
- "@types/node": "^26.0.0"
40
+ "@types/node": "^26.0.0",
41
+ "@symbiote-native/engine": "0.1.6"
43
42
  },
44
43
  "scripts": {
45
44
  "typecheck": "tsc --build",
File without changes
File without changes