@symbiote-native/components 0.4.0 → 0.5.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 (39) hide show
  1. package/README.md +10 -2
  2. package/build/accessibility-props.js +2 -1
  3. package/build/bootstrap/index.js +1 -1
  4. package/build/component-names/index.android.js +1 -1
  5. package/build/component-names/index.ios.js +1 -1
  6. package/build/component-names/shared.js +4 -1
  7. package/build/descriptor.d.ts +8 -0
  8. package/build/descriptor.js +27 -0
  9. package/build/index.d.ts +19 -11
  10. package/build/index.js +12 -5
  11. package/build/state/pressable.js +13 -3
  12. package/build/state/sticky-header-reducer.js +28 -5
  13. package/build/state/switch.js +1 -1
  14. package/build/state/text-input.d.ts +2 -1
  15. package/build/state/text-input.js +35 -8
  16. package/build/state/touchable.d.ts +30 -1
  17. package/build/state/touchable.js +94 -5
  18. package/build/state/virtualized-list-diagnostics.d.ts +31 -0
  19. package/build/state/virtualized-list-diagnostics.js +33 -0
  20. package/build/state/virtualized-list-reducer.d.ts +14 -0
  21. package/build/state/virtualized-list-reducer.js +179 -45
  22. package/build/state/virtualized-list.d.ts +5 -2
  23. package/build/state/virtualized-list.js +143 -34
  24. package/build/text-props.d.ts +9 -0
  25. package/build/text-props.js +24 -0
  26. package/build/view/render-activity-indicator.js +11 -3
  27. package/build/view/render-image/index.js +13 -3
  28. package/build/view/render-input-accessory-view.js +5 -2
  29. package/build/view/render-keyboard-avoiding-view.d.ts +14 -2
  30. package/build/view/render-keyboard-avoiding-view.js +52 -6
  31. package/build/view/render-modal.js +4 -2
  32. package/build/view/render-pressable/index.js +3 -1
  33. package/build/view/render-scroll-sticky.js +1 -1
  34. package/build/view/render-scroll-view.js +9 -3
  35. package/build/view/render-switch.js +4 -1
  36. package/build/view/render-touchable-highlight.d.ts +11 -1
  37. package/build/view/render-touchable-highlight.js +11 -10
  38. package/build/view/render-touchable-native-feedback.js +5 -1
  39. package/package.json +2 -2
package/README.md CHANGED
@@ -51,14 +51,22 @@ types and wires the reducer/render pair from here. Calling the render function d
51
51
  adapter itself does, to build its lifecycle wrapper:
52
52
 
53
53
  ```ts
54
- import { renderSwitch, createInitialSwitchState, switchReducer } from '@symbiote-native/components';
54
+ import {
55
+ renderSwitch,
56
+ createInitialSwitchState,
57
+ switchReducer,
58
+ } from '@symbiote-native/components';
55
59
 
56
60
  // inside an adapter's own hook/composable:
57
61
  const state = createInitialSwitchState();
58
62
  const next = switchReducer(state, { type: 'native-reported', value: true });
59
63
  const descriptor = renderSwitch(
60
64
  { value: true, disabled: false, passthrough: { onChange, ref } },
61
- { trackColorProps: (value, trackColor) => ({ /* platform-specific prop names */ }) },
65
+ {
66
+ trackColorProps: (value, trackColor) => ({
67
+ /* platform-specific prop names */
68
+ }),
69
+ },
62
70
  );
63
71
  // descriptor is then handed to the adapter's own descriptorToReact / descriptorToVue bridge
64
72
  ```
@@ -95,7 +95,8 @@ export function resolveAccessibilityProps(props) {
95
95
  'aria-valuenow': undefined,
96
96
  'aria-valuetext': undefined,
97
97
  };
98
- if (ariaLabelledBy !== undefined && next.accessibilityLabelledBy === undefined) {
98
+ if (ariaLabelledBy !== undefined &&
99
+ next.accessibilityLabelledBy === undefined) {
99
100
  next.accessibilityLabelledBy = ariaLabelledBy.split(/\s*,\s*/g);
100
101
  }
101
102
  if (ariaLabel !== undefined && next.accessibilityLabel === undefined) {
@@ -4,7 +4,7 @@
4
4
  // startup. Lives OUTSIDE the package's main barrel (see package.json's separate "./bootstrap"
5
5
  // export): react-native's own source is Flow syntax Vitest's transform can't parse, so anything
6
6
  // importing it directly must stay unreachable from the tested main index.ts.
7
- import { processColor, DeviceEventEmitter, Image } from 'react-native';
7
+ import { processColor, DeviceEventEmitter, Image, } from 'react-native';
8
8
  import { setColorProcessor, setDeviceEventSource, setImageSourceResolver, setNativeViewConfigSource, } from '@symbiote-native/engine';
9
9
  // @ts-expect-error react-native ships no types for this internal path (plain .js) - the
10
10
  // try/catch below is what actually proves the shape, not TS.
@@ -2,7 +2,7 @@
2
2
  // Each name is the ViewManager's REACT_CLASS in react-native/ReactAndroid/.../views/**.
3
3
  // device-verify-pending: source-confirmed from RN's Android ViewManagers, proven on a
4
4
  // real host by the absence of a "Can't find ViewManager '<name>'" red box.
5
- import { buildDescriptors, makeDescriptorFor } from './shared.js';
5
+ import { buildDescriptors, makeDescriptorFor, } from './shared.js';
6
6
  const ANDROID_NAMES = {
7
7
  'symbiote-view': 'RCTView',
8
8
  'symbiote-text': 'RCTText',
@@ -2,7 +2,7 @@
2
2
  // (component-names.ts re-exports it) for headless tsx / tsc / web fallback.
3
3
  // Fabric names are the codegen spec's registered name (the new-arch name), not the legacy
4
4
  // paperComponentName (RCTSwitch, …).
5
- import { buildDescriptors, makeDescriptorFor } from './shared.js';
5
+ import { buildDescriptors, makeDescriptorFor, } from './shared.js';
6
6
  const IOS_NAMES = {
7
7
  'symbiote-view': 'RCTView',
8
8
  'symbiote-text': 'RCTText',
@@ -13,7 +13,10 @@ const TEXT_INTRINSICS = new Set(['symbiote-text']);
13
13
  export function buildDescriptors(names) {
14
14
  const descriptors = {};
15
15
  for (const [intrinsic, component] of Object.entries(names)) {
16
- descriptors[intrinsic] = { component, isText: TEXT_INTRINSICS.has(intrinsic) };
16
+ descriptors[intrinsic] = {
17
+ component,
18
+ isText: TEXT_INTRINSICS.has(intrinsic),
19
+ };
17
20
  }
18
21
  return descriptors;
19
22
  }
@@ -9,3 +9,11 @@ export type IDescriptor = {
9
9
  };
10
10
  export declare function el(type: IDescriptorType, props?: IDescriptorProps, children?: IDescriptorChild[], key?: string): IDescriptor;
11
11
  export declare function txt(props?: IDescriptorProps, children?: IDescriptorChild[]): IDescriptor;
12
+ export type IDescriptorShapeGuard = {
13
+ error(detail: string): Error;
14
+ assertType(expected: IDescriptorType, actual: IDescriptorType): void;
15
+ assertChildCount(expected: number, actual: number): void;
16
+ asText(child: IDescriptorChild): string;
17
+ asElement(child: IDescriptorChild): IDescriptor;
18
+ };
19
+ export declare function createDescriptorShapeGuard(bridge: string): IDescriptorShapeGuard;
@@ -13,3 +13,30 @@ export function el(type, props = {}, children = [], key) {
13
13
  export function txt(props = {}, children = []) {
14
14
  return { type: 'symbiote-text', props, children };
15
15
  }
16
+ // `bridge` names the caller (`descriptorToSolid`, `descriptorToSvelte`) so a device log says which
17
+ // one tripped without a stack to read.
18
+ export function createDescriptorShapeGuard(bridge) {
19
+ const error = (detail) => new Error(`${bridge}: Descriptor shape changed between renders (${detail}) — a render-*.ts fn must ` +
20
+ `produce a CONSTANT tree shape; only prop values may vary between calls.`);
21
+ return {
22
+ error,
23
+ assertType(expected, actual) {
24
+ if (expected !== actual)
25
+ throw error(`${expected} -> ${actual}`);
26
+ },
27
+ assertChildCount(expected, actual) {
28
+ if (expected !== actual)
29
+ throw error(`child count ${expected} -> ${actual}`);
30
+ },
31
+ asText(child) {
32
+ if (typeof child !== 'string')
33
+ throw error(`text -> ${child.type}`);
34
+ return child;
35
+ },
36
+ asElement(child) {
37
+ if (typeof child === 'string')
38
+ throw error('element -> text');
39
+ return child;
40
+ },
41
+ };
42
+ }
package/build/index.d.ts CHANGED
@@ -1,10 +1,14 @@
1
1
  export { el, txt } from './descriptor';
2
2
  export type { IDescriptor, IDescriptorType, IDescriptorProps, IDescriptorChild, } from './descriptor';
3
+ export { createDescriptorShapeGuard } from './descriptor';
4
+ export type { IDescriptorShapeGuard } from './descriptor';
3
5
  export { resolveAccessibilityProps } from './accessibility-props';
6
+ export { resolveTextProps } from './text-props';
7
+ export type { IEllipsizeMode, ITextDefaultableProps } from './text-props';
4
8
  export type { IAccessibilityProps, IAriaProps, IAccessibilityRole, IRole, IAccessibilityStateValue, IAccessibilityValue, IAccessibilityActionInfo, } from './accessibility-props';
5
9
  export { descriptorFor, COMPONENT_DESCRIPTORS } from './component-names';
6
10
  export { buildDescriptors, makeDescriptorFor } from './component-names/shared';
7
- export type { ISymbioteIntrinsic, IComponentDescriptor } from './component-names/shared';
11
+ export type { ISymbioteIntrinsic, IComponentDescriptor, } from './component-names/shared';
8
12
  export { renderActivityIndicator } from './view/render-activity-indicator';
9
13
  export type { IActivityIndicatorProps, IActivityIndicatorViewProps, IActivityIndicatorPlatform, IActivityIndicatorSize, } from './view/render-activity-indicator';
10
14
  export { renderSwitch } from './view/render-switch';
@@ -17,12 +21,12 @@ export type { IScrollIntrinsics, IContentSize, IScrollForwarding, IScrollForward
17
21
  export { buildScrollViewHandle, splitLayoutProps, attachStickyScroll, isSymbioteEvent, forwardScrollEvent, } from './scroll-view-commands';
18
22
  export type { IScrollViewHandle } from './scroll-view-commands';
19
23
  export { computeStickyInterpolation, nextStickyHeaderY, stickyDebounceMs, readLayoutNumber, STICKY_HEADER_Z_INDEX, } from './view/render-scroll-sticky';
20
- export type { IStickyHeaderProps, IStickyInterpolationParams } from './view/render-scroll-sticky';
24
+ export type { IStickyHeaderProps, IStickyInterpolationParams, } from './view/render-scroll-sticky';
21
25
  export { reduceSticky, createInitialStickyState, stickyEffectSignature, } from './state/sticky-header-reducer';
22
26
  export type { IStickyHeaderState, IStickyReducerInputs, IStickyAction, IStickyEffect, IStickyReduceResult, } from './state/sticky-header-reducer';
23
27
  export type { IImageSource, IImageSourceProp, IResizeMode, IImageProps, IImageViewProps, } from './view/render-image';
24
28
  export { renderImage } from './view/render-image';
25
- export type { IImageStatics, IImageSize, IImageCacheStatus } from '@symbiote-native/engine';
29
+ export type { IImageStatics, IImageSize, IImageCacheStatus, } from '@symbiote-native/engine';
26
30
  export { imageStatics, setImageSourceResolver } from '@symbiote-native/engine';
27
31
  export { renderImageBackground } from './view/render-image-background';
28
32
  export type { IImageBackgroundViewProps } from './view/render-image-background';
@@ -30,29 +34,33 @@ export { renderInputAccessoryView } from './view/render-input-accessory-view';
30
34
  export type { IInputAccessoryViewViewProps } from './view/render-input-accessory-view';
31
35
  export { renderModal } from './view/render-modal';
32
36
  export type { IModalViewProps, IModalAnimationType, IModalPresentationStyle, IModalOrientation, IModalOrientationChangeEvent, } from './view/render-modal';
33
- export { modalReducer, createInitialModalState, shouldRenderModal } from './state/modal';
37
+ export { modalReducer, createInitialModalState, shouldRenderModal, } from './state/modal';
34
38
  export type { IModalState, IModalAction } from './state/modal';
35
- export { computeInset, readKeyboardFrame, readLayoutFrame, resolveKeyboardAvoidingLayout, DEFAULT_VERTICAL_OFFSET, } from './view/render-keyboard-avoiding-view';
36
- export type { IKeyboardAvoidingBehavior, IMeasuredFrame, IKeyboardFrame, IKeyboardAvoidingLayout, IResolveKeyboardAvoidingLayoutParams, } from './view/render-keyboard-avoiding-view';
39
+ export { computeInset, keyboardAvoidingEventNamesFor, readPrefersCrossFadeTransitions, readKeyboardFrame, readLayoutFrame, resolveKeyboardAvoidingLayout, DEFAULT_VERTICAL_OFFSET, } from './view/render-keyboard-avoiding-view';
40
+ export type { IComputeInsetOptions, IKeyboardAvoidingBehavior, IKeyboardAvoidingEventNames, IMeasuredFrame, IKeyboardFrame, IKeyboardAvoidingLayout, IResolveKeyboardAvoidingLayoutParams, } from './view/render-keyboard-avoiding-view';
37
41
  export { createPressHandlers, createPressRuntime, normalizeRect, maxEdge, isTouchWithinRegion, readPoint, computeRegion, rippleProps, DEFAULT_DELAY_LONG_PRESS_MS, DEFAULT_PRESS_RECT_OFFSETS, } from './state/pressable';
38
42
  export type { IPressState, IPressHandler, IPressHandlers, IPressHost, IPressRuntime, IPressMachineConfig, IFrameCallback, IRectOffset, IEdgeInsets, IResponderRegion, IPressableAndroidRippleConfig, IRippleBackground as IPressableRippleBackground, } from './state/pressable';
39
43
  export { buildPressableListeners, resolveDisabledAccessibilityState, noteHoverNoop, shouldSuppressPress, shouldClaimResponder, isTerminationAllowed, } from './view/render-pressable';
40
- export { computePressOutWait, createTouchableFeedbackRuntime, createTouchableFeedbackHandlers, 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';
41
- export type { IPressTimingProps, ITouchableHandler, ITouchableFeedbackRuntime, ITouchableFeedbackConfig, ITouchableFeedbackCallbacks, ITouchableFeedbackHandlers, } from './state/touchable';
42
- export { highlightPressedStyle } from './view/render-touchable-highlight';
44
+ export { computePressOutWait, createTouchableFeedbackRuntime, createTouchableFeedbackHandlers, DEFAULT_ACTIVE_OPACITY, OPACITY_ACTIVE_GRANT_DURATION_MS, OPACITY_ACTIVE_DURATION_MS, OPACITY_INACTIVE_DURATION_MS, RESTING_OPACITY, DEFAULT_HIGHLIGHT_CHILD_OPACITY, DEFAULT_UNDERLAY_COLOR, TOUCHABLE_MIN_PRESS_DURATION_MS, restingOpacityFromStyle, hasTouchablePressHandler, createHighlightUnderlayRuntime, createHighlightUnderlayHandlers, } from './state/touchable';
45
+ export type { IPressTimingProps, ITouchableHandler, ITouchableFeedbackRuntime, ITouchableFeedbackConfig, ITouchableFeedbackCallbacks, ITouchableFeedbackHandlers, ITouchablePressHandlerProps, IHighlightUnderlayRuntime, IHighlightUnderlayConfig, IHighlightUnderlayCallbacks, IHighlightUnderlayHandlers, } from './state/touchable';
46
+ export { resolveHighlightExtraStyles } from './view/render-touchable-highlight';
47
+ export type { ITouchableHighlightExtraStyles, ITouchableHighlightUnderlayView, } from './view/render-touchable-highlight';
43
48
  export { backgroundProps, canUseNativeForeground, selectableBackground, selectableBackgroundBorderless, rippleBackground, } from './view/render-touchable-native-feedback';
44
49
  export type { INativeFeedbackBackground, IThemeAttrBackground, IRippleBackground, } from './view/render-touchable-native-feedback';
45
50
  export { BUTTON_ACCESSIBILITY_ROLE, buttonTextStyle, resolveButtonTextStyle, } from './view/render-button';
46
51
  export type { IButtonProps } from './view/render-button';
47
52
  export { resolveTextInputProps, foldText, foldAutoComplete, foldSubmitBehavior, mapAutoComplete, textFromChange, eventCountFromChange, shouldCommandText, INITIAL_EVENT_COUNT, SELECTION_NONE, } from './state/text-input';
48
53
  export type { ITextInputProps, ITextInputHandle, ITextInputSelection, ITextInputEventHandler, ITextInputFoldInput, IFoldedTextInputProps, IInputMode, IEnterKeyHint, ISubmitBehavior, } from './state/text-input';
54
+ export { keyboardTypeForInputMode } from './state/text-input';
49
55
  export { renderTextInput } from './view/render-text-input';
50
56
  export type { ITextInputViewProps } from './view/render-text-input';
51
- export { DEFAULT_WINDOW_SIZE, DEFAULT_INITIAL_NUM_TO_RENDER, DEFAULT_END_REACHED_THRESHOLD, DEFAULT_MAX_TO_RENDER_PER_BATCH, DEFAULT_UPDATE_CELLS_BATCHING_PERIOD, DEFAULT_VIEW_AREA_COVERAGE_PERCENT_THRESHOLD, DEFAULT_START_REACHED_THRESHOLD, FIRST_INDEX, EMPTY_OFFSET, NO_INDEX, NO_CONTENT_LENGTH_SENT, INVERTED_Y_STYLE, INVERTED_X_STYLE, readScrollOffset, readLayoutLength, buildOffsets, computeWindow, throttleWindow, visiblePercent, isCellViewable, offsetForIndex, averageMeasuredLength, highestMeasuredIndex, computeEndReached, computeStartReached, buildViewabilityPairs, computeViewableSet, diffViewable, maxMinimumViewTime, buildListPlan, computeMvcpAdjustment, resolveItemKey, indexOfItem, offsetForEnd, isSeparatorGapInRange, decideEdgeReached, resolveStickySectionHeaders, wrapFixedLayout, resolveAverageLength, } from './state/virtualized-list';
57
+ export { DEFAULT_WINDOW_SIZE, DEFAULT_INITIAL_NUM_TO_RENDER, DEFAULT_END_REACHED_THRESHOLD, DEFAULT_MAX_TO_RENDER_PER_BATCH, DEFAULT_UPDATE_CELLS_BATCHING_PERIOD, DEFAULT_VIEW_AREA_COVERAGE_PERCENT_THRESHOLD, DEFAULT_START_REACHED_THRESHOLD, FIRST_INDEX, EMPTY_OFFSET, NO_INDEX, NO_CONTENT_LENGTH_SENT, INVERTED_Y_STYLE, INVERTED_X_STYLE, readScrollOffset, readLayoutLength, readLayoutOffset, buildOffsets, computeWindow, throttleWindow, visiblePercent, isCellViewable, offsetForIndex, averageMeasuredLength, averageMeasuredStride, highestMeasuredIndex, computeEndReached, computeStartReached, buildViewabilityPairs, computeViewableSet, diffViewable, maxMinimumViewTime, buildListPlan, computeMvcpAdjustment, resolveItemKey, indexOfItem, offsetForEnd, isSeparatorGapInRange, decideEdgeReached, resolveStickySectionHeaders, wrapFixedLayout, resolveAverageLength, } from './state/virtualized-list';
52
58
  export type { IMvcpAction, IMvcpAdjustmentParams, IMvcpAdjustmentResult, ICellLayout, ISeparators, ISeparatorProps, IViewToken, IViewableItemsChangedInfo, IViewabilityConfig, IViewabilityConfigCallbackPair, IVirtualizedListHandle, IViewableSetParams, IListCellPlan, IListPlan, IListPlanParams, } from './state/virtualized-list';
53
59
  export { reduceList, createInitialListState, listEffectSignature, } from './state/virtualized-list-reducer';
54
60
  export type { IListState, IListMetrics, IListReducerInputs, IListAction, IListEffect, IListReduceResult, } from './state/virtualized-list-reducer';
61
+ export { subscribeListDiagnostics } from './state/virtualized-list-diagnostics';
62
+ export type { IListDiagnosticFrame, IListDiagnosticMove, } from './state/virtualized-list-diagnostics';
55
63
  export { SINGLE_COLUMN, chunkIntoRows, rowKeyExtractor, expandRowToken, expandRowViewability, lastItemOfRow, firstItemOfRow, } from './state/flat-list';
56
64
  export type { IRow } from './state/flat-list';
57
65
  export { flattenSections, unwrapEntryItem, sectionEntryKey, scrollLocationToFlatIndex, } from './state/section-list';
58
- export type { ISection, ISectionEntry, IVirtualizedSectionListHandle } from './state/section-list';
66
+ export type { ISection, ISectionEntry, IVirtualizedSectionListHandle, } from './state/section-list';
package/build/index.js CHANGED
@@ -3,10 +3,15 @@
3
3
  // wires state→render with ITS lifecycle (React hooks / Vue reactivity) and maps the
4
4
  // Descriptor onto its own element.
5
5
  export { el, txt } from './descriptor.js';
6
+ // The shape-stability guard a fine-grained adapter's Descriptor bridge builds on. Internal to
7
+ // adapter bridges (Svelte's and Solid's), NOT part of an app's surface — so it stays out of the
8
+ // adapter barrels, unlike the passthrough names in tests/adapter-barrel-parity.test.ts.
9
+ export { createDescriptorShapeGuard } from './descriptor.js';
6
10
  // Accessibility folding: the web-alias (aria-*/role) → canonical accessibility* transform
7
11
  // and its types. Framework-agnostic, so React, Vue, and the next adapter all fold
8
12
  // identically; moved here from @symbiote-native/react. RefreshControl/SafeAreaView/ScrollView consume it.
9
13
  export { resolveAccessibilityProps } from './accessibility-props.js';
14
+ export { resolveTextProps } from './text-props.js';
10
15
  // Intrinsic (`symbiote-*`) → Fabric view-name resolution. Shared by every adapter so the
11
16
  // names CANNOT drift between them (one engine, one Fabric). The name tables are
12
17
  // Metro-split (.ios/.android, filename selects, no Platform.OS read); the base
@@ -37,9 +42,9 @@ export { renderImageBackground } from './view/render-image-background.js';
37
42
  export { renderInputAccessoryView } from './view/render-input-accessory-view.js';
38
43
  // Modal: full 3-layer split (state machine gates the iOS keep-alive frame).
39
44
  export { renderModal } from './view/render-modal.js';
40
- export { modalReducer, createInitialModalState, shouldRenderModal } from './state/modal.js';
45
+ export { modalReducer, createInitialModalState, shouldRenderModal, } from './state/modal.js';
41
46
  // KeyboardAvoidingView: pure inset/behavior math; the adapter owns the Keyboard subscription.
42
- export { computeInset, readKeyboardFrame, readLayoutFrame, resolveKeyboardAvoidingLayout, DEFAULT_VERTICAL_OFFSET, } from './view/render-keyboard-avoiding-view.js';
47
+ export { computeInset, keyboardAvoidingEventNamesFor, readPrefersCrossFadeTransitions, readKeyboardFrame, readLayoutFrame, resolveKeyboardAvoidingLayout, DEFAULT_VERTICAL_OFFSET, } from './view/render-keyboard-avoiding-view.js';
43
48
  // Pressable: the press state machine (logic) + render decisions. The pure lifecycle (timers,
44
49
  // geometry, drift test, suppression flags) lives in state/pressable; the responder-listener +
45
50
  // accessibilityState + ripple render decisions in view/render-pressable. Every adapter supplies
@@ -48,8 +53,8 @@ export { createPressHandlers, createPressRuntime, normalizeRect, maxEdge, isTouc
48
53
  export { buildPressableListeners, resolveDisabledAccessibilityState, noteHoverNoop, shouldSuppressPress, shouldClaimResponder, isTerminationAllowed, } from './view/render-pressable/index.js';
49
54
  // Touchable*: shared press-timing constants + the deactivation-floor math (the Animated feedback
50
55
  // itself stays per-adapter), and TouchableNativeFeedback's pure static factories + background map.
51
- export { computePressOutWait, createTouchableFeedbackRuntime, createTouchableFeedbackHandlers, 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';
52
- export { highlightPressedStyle } from './view/render-touchable-highlight.js';
56
+ export { computePressOutWait, createTouchableFeedbackRuntime, createTouchableFeedbackHandlers, DEFAULT_ACTIVE_OPACITY, OPACITY_ACTIVE_GRANT_DURATION_MS, OPACITY_ACTIVE_DURATION_MS, OPACITY_INACTIVE_DURATION_MS, RESTING_OPACITY, DEFAULT_HIGHLIGHT_CHILD_OPACITY, DEFAULT_UNDERLAY_COLOR, TOUCHABLE_MIN_PRESS_DURATION_MS, restingOpacityFromStyle, hasTouchablePressHandler, createHighlightUnderlayRuntime, createHighlightUnderlayHandlers, } from './state/touchable.js';
57
+ export { resolveHighlightExtraStyles } from './view/render-touchable-highlight.js';
53
58
  export { backgroundProps, canUseNativeForeground, selectableBackground, selectableBackgroundBorderless, rippleBackground, } from './view/render-touchable-native-feedback.js';
54
59
  // Button: shared base text style, role constant, and color fold (the adapter composes its own
55
60
  // TouchableOpacity + Text).
@@ -60,15 +65,17 @@ export { BUTTON_ACCESSIBILITY_ROLE, buttonTextStyle, resolveButtonTextStyle, } f
60
65
  // resolved native props. Both shared verbatim across React and Vue; the adapter owns only the
61
66
  // hooks/reactivity + the imperative handle.
62
67
  export { resolveTextInputProps, foldText, foldAutoComplete, foldSubmitBehavior, mapAutoComplete, textFromChange, eventCountFromChange, shouldCommandText, INITIAL_EVENT_COUNT, SELECTION_NONE, } from './state/text-input.js';
68
+ export { keyboardTypeForInputMode } from './state/text-input.js';
63
69
  export { renderTextInput } from './view/render-text-input.js';
64
70
  // VirtualizedList family: the framework-agnostic windowing engine + data shapes. Lists
65
71
  // have NO view/render-*.ts (the cell content is the framework's own children, so there is
66
72
  // no Descriptor render fn) — the shared layer for lists is this STATE/logic, reused
67
73
  // verbatim by React and Vue.
68
- export { DEFAULT_WINDOW_SIZE, DEFAULT_INITIAL_NUM_TO_RENDER, DEFAULT_END_REACHED_THRESHOLD, DEFAULT_MAX_TO_RENDER_PER_BATCH, DEFAULT_UPDATE_CELLS_BATCHING_PERIOD, DEFAULT_VIEW_AREA_COVERAGE_PERCENT_THRESHOLD, DEFAULT_START_REACHED_THRESHOLD, FIRST_INDEX, EMPTY_OFFSET, NO_INDEX, NO_CONTENT_LENGTH_SENT, INVERTED_Y_STYLE, INVERTED_X_STYLE, readScrollOffset, readLayoutLength, buildOffsets, computeWindow, throttleWindow, visiblePercent, isCellViewable, offsetForIndex, averageMeasuredLength, highestMeasuredIndex, computeEndReached, computeStartReached, buildViewabilityPairs, computeViewableSet, diffViewable, maxMinimumViewTime, buildListPlan, computeMvcpAdjustment, resolveItemKey, indexOfItem, offsetForEnd, isSeparatorGapInRange, decideEdgeReached, resolveStickySectionHeaders, wrapFixedLayout, resolveAverageLength, } from './state/virtualized-list.js';
74
+ export { DEFAULT_WINDOW_SIZE, DEFAULT_INITIAL_NUM_TO_RENDER, DEFAULT_END_REACHED_THRESHOLD, DEFAULT_MAX_TO_RENDER_PER_BATCH, DEFAULT_UPDATE_CELLS_BATCHING_PERIOD, DEFAULT_VIEW_AREA_COVERAGE_PERCENT_THRESHOLD, DEFAULT_START_REACHED_THRESHOLD, FIRST_INDEX, EMPTY_OFFSET, NO_INDEX, NO_CONTENT_LENGTH_SENT, INVERTED_Y_STYLE, INVERTED_X_STYLE, readScrollOffset, readLayoutLength, readLayoutOffset, buildOffsets, computeWindow, throttleWindow, visiblePercent, isCellViewable, offsetForIndex, averageMeasuredLength, averageMeasuredStride, highestMeasuredIndex, computeEndReached, computeStartReached, buildViewabilityPairs, computeViewableSet, diffViewable, maxMinimumViewTime, buildListPlan, computeMvcpAdjustment, resolveItemKey, indexOfItem, offsetForEnd, isSeparatorGapInRange, decideEdgeReached, resolveStickySectionHeaders, wrapFixedLayout, resolveAverageLength, } from './state/virtualized-list.js';
69
75
  // The orchestration reducer: the one pure state machine every list adapter drives, folding the
70
76
  // after-commit effect skeleton (window recompute -> edge -> viewability -> initial-scroll -> MVCP)
71
77
  // out of each adapter's reactive glue. The adapter maps events to actions and runs the effects.
72
78
  export { reduceList, createInitialListState, listEffectSignature, } from './state/virtualized-list-reducer.js';
79
+ export { subscribeListDiagnostics } from './state/virtualized-list-diagnostics.js';
73
80
  export { SINGLE_COLUMN, chunkIntoRows, rowKeyExtractor, expandRowToken, expandRowViewability, lastItemOfRow, firstItemOfRow, } from './state/flat-list.js';
74
81
  export { flattenSections, unwrapEntryItem, sectionEntryKey, scrollLocationToFlatIndex, } from './state/section-list.js';
@@ -13,7 +13,12 @@ import { dlog, Platform } from '@symbiote-native/engine';
13
13
  export const DEFAULT_DELAY_LONG_PRESS_MS = 500;
14
14
  // RN's default extra slop kept around a press once it is active, before a drift fires pressOut.
15
15
  // The PressRect extension (Pressability.js DEFAULT_PRESS_RECT_OFFSETS). Per-edge, deeper bottom.
16
- export const DEFAULT_PRESS_RECT_OFFSETS = { top: 20, left: 20, bottom: 30, right: 20 };
16
+ export const DEFAULT_PRESS_RECT_OFFSETS = {
17
+ top: 20,
18
+ left: 20,
19
+ bottom: 30,
20
+ right: 20,
21
+ };
17
22
  // ---- geometry (pure) --------------------------------------------------------------------------
18
23
  // A rect offset is a per-edge object (not the scalar shorthand). Reads the asymmetric form
19
24
  // without a cast.
@@ -58,7 +63,12 @@ export function readPoint(event) {
58
63
  export function computeRegion(width, height, pageX, pageY) {
59
64
  if (!width && !height && !pageX && !pageY)
60
65
  return undefined;
61
- return { left: pageX, top: pageY, right: pageX + width, bottom: pageY + height };
66
+ return {
67
+ left: pageX,
68
+ top: pageY,
69
+ right: pageX + width,
70
+ bottom: pageY + height,
71
+ };
62
72
  }
63
73
  // Build the Android native-feedback prop the inner View carries from the ripple config. RN runs
64
74
  // the color through processColor → a native int; we have no native bridge in JS, so we keep the
@@ -132,7 +142,7 @@ export function createPressHandlers(config, runtime, host) {
132
142
  const origin = runtime.pressOrigin;
133
143
  if (origin === undefined)
134
144
  return true;
135
- return Math.hypot(point.x - origin.x, point.y - origin.y) <= fallbackThreshold;
145
+ return (Math.hypot(point.x - origin.x, point.y - origin.y) <= fallbackThreshold);
136
146
  }
137
147
  function clearLongPress() {
138
148
  if (runtime.longPressCancel !== undefined) {
@@ -15,7 +15,7 @@
15
15
  // ./view/render-scroll-sticky; this module composes them into the ordered transition every adapter
16
16
  // shares. Ported from ScrollViewStickyHeader.js's effect.
17
17
  import { dlog } from '@symbiote-native/engine';
18
- import { computeStickyInterpolation, stickyDebounceMs } from '../view/render-scroll-sticky.js';
18
+ import { computeStickyInterpolation, stickyDebounceMs, } from '../view/render-scroll-sticky.js';
19
19
  // The un-measured identity interpolation (RN: a fresh AnimatedInterpolation before the header has
20
20
  // measured its own y/height). Kept as the reset the initial state and every rebuild start from.
21
21
  const IDENTITY_INPUT_RANGE = [-1, 0];
@@ -68,7 +68,9 @@ function headerTag(state) {
68
68
  }
69
69
  export function reduceSticky(state, action, inputs) {
70
70
  dlog(`STICKY[reducer ${headerTag(state)}] action=${action.kind}` +
71
- (action.kind === 'layout' ? ` y=${action.y} height=${action.height}` : '') +
71
+ (action.kind === 'layout'
72
+ ? ` y=${action.y} height=${action.height}`
73
+ : '') +
72
74
  (action.kind === 'animated-tick' || action.kind === 'debounce-fired'
73
75
  ? ` value=${action.value}`
74
76
  : '') +
@@ -88,13 +90,19 @@ export function reduceSticky(state, action, inputs) {
88
90
  // native-driven rebuild (fresh AnimatedProps/AnimatedStyle graph, fresh native connect) commit a
89
91
  // fresh prop identity on every redundant layout, which can itself provoke another relayout pass —
90
92
  // an unbounded same-tick rebuild ping-pong that trips Svelte's effect_update_depth_exceeded guard.
91
- const alreadyAtThisGeometry = state.measured && state.layoutY === action.y && state.layoutHeight === action.height;
93
+ const alreadyAtThisGeometry = state.measured &&
94
+ state.layoutY === action.y &&
95
+ state.layoutHeight === action.height;
92
96
  state.layoutY = action.y;
93
97
  state.layoutHeight = action.height;
94
98
  state.measured = true;
95
99
  const effects = [];
96
100
  if (inputs.index !== undefined) {
97
- effects.push({ kind: 'record-header-y', index: inputs.index, y: action.y });
101
+ effects.push({
102
+ kind: 'record-header-y',
103
+ index: inputs.index,
104
+ y: action.y,
105
+ });
98
106
  }
99
107
  if (alreadyAtThisGeometry && state.rangesEmitted) {
100
108
  dlog(`STICKY[reducer ${headerTag(state)}] layout: redundant geometry, skipped rebuild`);
@@ -161,12 +169,27 @@ export function reduceSticky(state, action, inputs) {
161
169
  return {
162
170
  state,
163
171
  effects: [
164
- { kind: 'schedule-debounce', delay: stickyDebounceMs(inputs.os), value: action.value },
172
+ {
173
+ kind: 'schedule-debounce',
174
+ delay: stickyDebounceMs(inputs.os),
175
+ value: action.value,
176
+ },
165
177
  ],
166
178
  changed: false,
167
179
  };
168
180
  }
169
181
  case 'debounce-fired': {
182
+ // Already sitting at this value: emit nothing. Same bail-out the 'layout' case above spells
183
+ // out, for the same reason - React's own header gets it free from setTranslateY (an unchanged
184
+ // primitive bails out of the re-render), a reducer has to say it. Device-confirmed 2026-08-18:
185
+ // without it a re-arriving settled value emits apply-passthrough, the adapter force-renders,
186
+ // the passthrough prop gets a fresh identity, the animated graph reconnects and re-emits into
187
+ // another tick -> another debounce -> another passthrough. One header survives it; a screen of
188
+ // 200 trips React's "Maximum update depth exceeded" and takes the app down.
189
+ if (state.translateY === action.value) {
190
+ dlog(`STICKY[reducer ${headerTag(state)}] debounce-fired: already at translateY=${action.value}, no-op`);
191
+ return { state, effects: [], changed: false };
192
+ }
170
193
  // The debounce completed: commit the settled translateY. Once a NON-zero value commits, re-arm
171
194
  // the swallow gate so the next interpolation rebuild's spurious 0 is dropped (RN).
172
195
  dlog(`STICKY[reducer ${headerTag(state)}] debounce-fired: committing translateY=${action.value}`);
@@ -27,5 +27,5 @@ export function valueFromChange(event) {
27
27
  // retained tree never diverges and nothing re-commits. The imperative command is the only
28
28
  // correction path. No report yet (null) or agreement → no command.
29
29
  export function shouldSnapBack(state, fabricValue) {
30
- return state.lastNativeReport !== null && state.lastNativeReport !== fabricValue;
30
+ return (state.lastNativeReport !== null && state.lastNativeReport !== fabricValue);
31
31
  }
@@ -1,4 +1,4 @@
1
- import type { ISymbioteEvent, ITextStyle } from '@symbiote-native/engine';
1
+ import type { IPlatformOSType, ISymbioteEvent, ITextStyle } from '@symbiote-native/engine';
2
2
  import type { IAccessibilityProps, IAriaProps } from '../accessibility-props';
3
3
  export type IInputMode = 'none' | 'text' | 'decimal' | 'numeric' | 'tel' | 'search' | 'email' | 'url';
4
4
  export type IEnterKeyHint = 'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send';
@@ -10,6 +10,7 @@ export type ITextInputSelection = {
10
10
  export type ITextInputEventHandler = (event: ISymbioteEvent) => void;
11
11
  export declare const INITIAL_EVENT_COUNT = 0;
12
12
  export declare const SELECTION_NONE = -1;
13
+ export declare function keyboardTypeForInputMode(inputMode: string, os: IPlatformOSType): string | undefined;
13
14
  export declare function mapAutoComplete(map: Record<string, string>, token: string): string | undefined;
14
15
  export declare function foldAutoComplete(token: string | undefined): {
15
16
  autoComplete: string | undefined;
@@ -11,6 +11,7 @@
11
11
  // latest count, while `lastNativeText` is bookkeeping the controlled-write effect mutates
12
12
  // without a render. So the logic layer is the pure folds/maps + the controlled-write predicate;
13
13
  // each adapter holds the two pieces in ITS own primitives (React useState/useRef, Vue ref/let).
14
+ import { Platform } from '@symbiote-native/engine';
14
15
  // The acknowledged count starts at 0, native has reported nothing yet, so the first
15
16
  // controlled write echoes 0 and lands on eventLag 0.
16
17
  export const INITIAL_EVENT_COUNT = 0;
@@ -29,11 +30,25 @@ const inputModeToKeyboardType = {
29
30
  email: 'email-address',
30
31
  none: 'default',
31
32
  numeric: 'number-pad',
32
- search: 'default',
33
33
  tel: 'phone-pad',
34
34
  text: 'default',
35
35
  url: 'url',
36
36
  };
37
+ // `search` is the ONE inputMode token RN resolves per platform (TextInput.js:815-825):
38
+ // search: Platform.OS === 'ios' ? 'web-search' : 'default'
39
+ // iOS has a dedicated search keyboard whose return key is a magnifier; every other host falls back
40
+ // to the default one. It is deliberately absent from the map above, so there is one source for it
41
+ // rather than a map entry and an override that can drift apart.
42
+ //
43
+ // The host is an ARGUMENT rather than a `Platform.OS` read inside the body, because the headless
44
+ // Platform module always resolves to iOS (platform/index.ts re-exports index.ios) — a direct read
45
+ // would leave the android branch permanently unprovable. resolveTextInputProps supplies the real
46
+ // value. This costs the callers nothing: the exported fold signature is unchanged.
47
+ export function keyboardTypeForInputMode(inputMode, os) {
48
+ if (inputMode === 'search')
49
+ return os === 'ios' ? 'web-search' : 'default';
50
+ return mapAutoComplete(inputModeToKeyboardType, inputMode);
51
+ }
37
52
  // RN's enterKeyHint -> returnKeyType map (TextInput.js:805). Note `enter` -> 'default'.
38
53
  const enterKeyHintToReturnKeyType = {
39
54
  done: 'done',
@@ -122,7 +137,9 @@ const autoCompleteWebToTextContentType = {
122
137
  // if the token has no native equivalent. The caller decides the per-platform fallback. Named
123
138
  // for its first user (autoComplete), but it is the one generic safe lookup every map shares.
124
139
  export function mapAutoComplete(map, token) {
125
- return Object.prototype.hasOwnProperty.call(map, token) ? map[token] : undefined;
140
+ return Object.prototype.hasOwnProperty.call(map, token)
141
+ ? map[token]
142
+ : undefined;
126
143
  }
127
144
  // RN folds W3C `autoComplete` per platform (TextInput.js:938): Android takes the mapped
128
145
  // `autoComplete` token (falling back to the raw token), iOS takes the mapped `textContentType`
@@ -187,7 +204,7 @@ export function resolveTextInputProps(input) {
187
204
  const folded = foldAutoComplete(input.autoComplete);
188
205
  return {
189
206
  keyboardType: input.inputMode !== undefined
190
- ? mapAutoComplete(inputModeToKeyboardType, input.inputMode)
207
+ ? keyboardTypeForInputMode(input.inputMode, Platform.OS)
191
208
  : input.keyboardType,
192
209
  returnKeyType: input.enterKeyHint !== undefined
193
210
  ? mapAutoComplete(enterKeyHintToReturnKeyType, input.enterKeyHint)
@@ -195,11 +212,21 @@ export function resolveTextInputProps(input) {
195
212
  editable: input.readOnly !== undefined ? !input.readOnly : input.editable,
196
213
  submitBehavior: foldSubmitBehavior(input.submitBehavior, input.blurOnSubmit, input.multiline),
197
214
  selectionColor: input.selectionColor,
198
- cursorColor: input.cursorColor !== undefined ? input.cursorColor : input.selectionColor,
199
- selectionHandleColor: input.selectionHandleColor !== undefined ? input.selectionHandleColor : input.selectionColor,
200
- underlineColorAndroid: input.underlineColorAndroid !== undefined ? input.underlineColorAndroid : 'transparent',
215
+ cursorColor: input.cursorColor !== undefined
216
+ ? input.cursorColor
217
+ : input.selectionColor,
218
+ selectionHandleColor: input.selectionHandleColor !== undefined
219
+ ? input.selectionHandleColor
220
+ : input.selectionColor,
221
+ underlineColorAndroid: input.underlineColorAndroid !== undefined
222
+ ? input.underlineColorAndroid
223
+ : 'transparent',
201
224
  autoComplete: folded.autoComplete,
202
- textContentType: input.textContentType !== undefined ? input.textContentType : folded.textContentType,
203
- showSoftInputOnFocus: input.inputMode !== undefined ? input.inputMode !== 'none' : input.showSoftInputOnFocus,
225
+ textContentType: input.textContentType !== undefined
226
+ ? input.textContentType
227
+ : folded.textContentType,
228
+ showSoftInputOnFocus: input.inputMode !== undefined
229
+ ? input.inputMode !== 'none'
230
+ : input.showSoftInputOnFocus,
204
231
  };
205
232
  }
@@ -1,11 +1,11 @@
1
1
  import { type ISymbioteEvent } from '@symbiote-native/engine';
2
2
  export declare const DEFAULT_ACTIVE_OPACITY = 0.2;
3
+ export declare const OPACITY_ACTIVE_GRANT_DURATION_MS = 0;
3
4
  export declare const OPACITY_ACTIVE_DURATION_MS = 150;
4
5
  export declare const OPACITY_INACTIVE_DURATION_MS = 250;
5
6
  export declare const RESTING_OPACITY = 1;
6
7
  export declare const DEFAULT_HIGHLIGHT_CHILD_OPACITY = 0.85;
7
8
  export declare const DEFAULT_UNDERLAY_COLOR = "black";
8
- export declare const DEFAULT_MIN_PRESS_DURATION_MS = 130;
9
9
  export type ITouchableHandler = (event: ISymbioteEvent) => void;
10
10
  export interface IPressTimingProps {
11
11
  delayPressIn?: number;
@@ -34,3 +34,32 @@ export interface ITouchableFeedbackHandlers {
34
34
  handlePressOut: ITouchableHandler;
35
35
  }
36
36
  export declare function createTouchableFeedbackHandlers(config: ITouchableFeedbackConfig, runtime: ITouchableFeedbackRuntime, callbacks: ITouchableFeedbackCallbacks): ITouchableFeedbackHandlers;
37
+ export declare const TOUCHABLE_MIN_PRESS_DURATION_MS = 0;
38
+ export declare function restingOpacityFromStyle(style: unknown): number;
39
+ export interface ITouchablePressHandlerProps {
40
+ onPress?: unknown;
41
+ onPressIn?: unknown;
42
+ onPressOut?: unknown;
43
+ onLongPress?: unknown;
44
+ }
45
+ export declare function hasTouchablePressHandler(props: ITouchablePressHandlerProps): boolean;
46
+ export interface IHighlightUnderlayRuntime {
47
+ hideTimerCancel: (() => void) | undefined;
48
+ }
49
+ export declare function createHighlightUnderlayRuntime(): IHighlightUnderlayRuntime;
50
+ export interface IHighlightUnderlayConfig {
51
+ delayPressOut: number;
52
+ hasPressHandler: boolean;
53
+ schedule: (callback: () => void, ms: number) => () => void;
54
+ }
55
+ export interface IHighlightUnderlayCallbacks {
56
+ setShown: (shown: boolean) => void;
57
+ onShowUnderlay?: () => void;
58
+ onHideUnderlay?: () => void;
59
+ }
60
+ export interface IHighlightUnderlayHandlers {
61
+ handlePressIn: ITouchableHandler;
62
+ handlePress: ITouchableHandler;
63
+ handlePressOut: ITouchableHandler;
64
+ }
65
+ export declare function createHighlightUnderlayHandlers(config: IHighlightUnderlayConfig, runtime: IHighlightUnderlayRuntime, callbacks: IHighlightUnderlayCallbacks): IHighlightUnderlayHandlers;