@symbiote-native/react 0.2.7 → 0.3.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 (68) hide show
  1. package/build/bootstrap.js +4 -5
  2. package/build/components/activity-indicator/index.android.d.ts +2 -1
  3. package/build/components/activity-indicator/index.android.js +14 -10
  4. package/build/components/activity-indicator/index.ios.d.ts +2 -1
  5. package/build/components/activity-indicator/index.ios.js +9 -3
  6. package/build/components/activity-indicator/shared.d.ts +1 -2
  7. package/build/components/activity-indicator/shared.js +13 -11
  8. package/build/components/keyboard-avoiding-view/index.js +1 -1
  9. package/build/components/modal/index.d.ts +3 -2
  10. package/build/components/scroll-view/index.android.js +0 -2
  11. package/build/components/scroll-view/index.ios.js +10 -10
  12. package/build/components/scroll-view/shared.js +28 -32
  13. package/build/components/scroll-view/sticky-header.js +98 -74
  14. package/build/components/section-list/index.js +4 -6
  15. package/build/components/switch/index.android.d.ts +2 -1
  16. package/build/components/switch/index.android.js +9 -3
  17. package/build/components/switch/index.ios.d.ts +2 -1
  18. package/build/components/switch/index.ios.js +9 -3
  19. package/build/components/switch/shared.d.ts +2 -3
  20. package/build/components/switch/shared.js +44 -43
  21. package/build/components/text-input/index.js +1 -1
  22. package/build/components/touchable/index.js +22 -54
  23. package/build/components/virtualized-list/index.js +219 -305
  24. package/build/components/virtualized-section-list/index.js +2 -4
  25. package/build/create-tunnel/index.js +14 -20
  26. package/build/hooks/use-color-scheme.d.ts +1 -1
  27. package/build/hooks/use-color-scheme.js +1 -1
  28. package/build/hooks/use-window-dimensions.d.ts +1 -1
  29. package/build/hooks/use-window-dimensions.js +1 -1
  30. package/build/host-instance.js +5 -6
  31. package/build/index.d.ts +6 -31
  32. package/build/index.js +15 -25
  33. package/build/modules/animated/create-animated-component.js +24 -65
  34. package/build/render.js +28 -2
  35. package/metro-css-parser.cjs +7 -12
  36. package/package.json +4 -4
  37. package/build/modules/action-sheet-ios.d.ts +0 -2
  38. package/build/modules/action-sheet-ios.js +0 -4
  39. package/build/modules/alert.d.ts +0 -2
  40. package/build/modules/alert.js +0 -5
  41. package/build/modules/app-state.d.ts +0 -2
  42. package/build/modules/app-state.js +0 -3
  43. package/build/modules/appearance.d.ts +0 -2
  44. package/build/modules/appearance.js +0 -3
  45. package/build/modules/back-handler.d.ts +0 -2
  46. package/build/modules/back-handler.js +0 -3
  47. package/build/modules/dimensions.d.ts +0 -2
  48. package/build/modules/dimensions.js +0 -3
  49. package/build/modules/i18n-manager.d.ts +0 -2
  50. package/build/modules/i18n-manager.js +0 -4
  51. package/build/modules/keyboard.d.ts +0 -2
  52. package/build/modules/keyboard.js +0 -3
  53. package/build/modules/layout-animation.d.ts +0 -2
  54. package/build/modules/layout-animation.js +0 -3
  55. package/build/modules/linking.d.ts +0 -2
  56. package/build/modules/linking.js +0 -4
  57. package/build/modules/permissions-android.d.ts +0 -2
  58. package/build/modules/permissions-android.js +0 -3
  59. package/build/modules/settings.d.ts +0 -1
  60. package/build/modules/settings.js +0 -4
  61. package/build/modules/share.d.ts +0 -2
  62. package/build/modules/share.js +0 -4
  63. package/build/modules/text-input-state.d.ts +0 -1
  64. package/build/modules/text-input-state.js +0 -3
  65. package/build/modules/toast-android.d.ts +0 -1
  66. package/build/modules/toast-android.js +0 -4
  67. package/build/modules/vibration.d.ts +0 -1
  68. package/build/modules/vibration.js +0 -4
@@ -9,51 +9,52 @@
9
9
  // JS commands the old value back down (a plain prop re-push cannot cover the no-op case: the
10
10
  // prop never changes, so the retained tree never diverges). The command name is platform-
11
11
  // specific (iOS setValue / Android setNativeValue) and arrives via `platform`.
12
+ //
13
+ // useSwitchLogic is a plain hook, not a component factory returning a closure: React Compiler's
14
+ // component/hook detection only walks top-level declarations, so index.ios.ts / index.android.ts
15
+ // each declare their OWN top-level `Switch` function that calls this hook, rather than exporting
16
+ // whatever a factory here returns.
12
17
  import { useCallback, useLayoutEffect, useReducer, useRef } from 'react';
13
18
  import { renderSwitch, switchReducer, createInitialSwitchState, shouldSnapBack, valueFromChange, } from '@symbiote-native/components';
14
19
  import { dispatchViewCommand, dlog, } from '@symbiote-native/engine';
15
20
  import { resolveAccessibilityProps } from '@symbiote-native/components';
16
- import { descriptorToReact } from '../../descriptor-to-react/index.js';
17
- export function createSwitch(platform) {
18
- return rawProps => {
19
- // Switch owns its host element rather than rendering through a symbiote View, so it folds
20
- // aria/role into accessibility* here; the resolved accessibility* fields (plus testID etc.)
21
- // ride down to the host node via `passthrough`.
22
- const props = resolveAccessibilityProps(rawProps);
23
- const { value, onValueChange, disabled, trackColor, thumbColor, ios_backgroundColor, style, ...passthrough } = props;
24
- const ref = useRef(null);
25
- const [state, dispatch] = useReducer(switchReducer, undefined, createInitialSwitchState);
26
- const handleChange = useCallback((event) => {
27
- const next = valueFromChange(event);
28
- dlog(`Switch onChange value=${String(next)} eventCount=${String(event.nativeEvent.eventCount)}`);
29
- if (next === undefined)
30
- return;
31
- onValueChange?.(next, event);
32
- dispatch({ type: 'native-reported', value: next });
33
- }, [onValueChange]);
34
- // value is a real Fabric prop, folded to a strict boolean: RN sends `value === true`, so
35
- // an undefined prop reads as "off" rather than absent.
36
- const fabricValue = value === true;
37
- useLayoutEffect(() => {
38
- const node = ref.current;
39
- if (node === null)
40
- return;
41
- if (!shouldSnapBack(state, fabricValue)) {
42
- dlog(`Switch snap-back no-op reported=${String(state.lastNativeReport)} value=${fabricValue}`);
43
- return;
44
- }
45
- dlog(`Switch ${platform.snapBackCommand} snap-back reported=${String(state.lastNativeReport)} value=${fabricValue}`);
46
- dispatchViewCommand(node, platform.snapBackCommand, [fabricValue]);
47
- }, [fabricValue, state]);
48
- const descriptor = renderSwitch({
49
- value: fabricValue,
50
- disabled,
51
- trackColor,
52
- thumbColor,
53
- ios_backgroundColor,
54
- style,
55
- passthrough: { ...passthrough, ref, onChange: handleChange },
56
- }, platform);
57
- return descriptorToReact(descriptor);
58
- };
21
+ export function useSwitchLogic(rawProps, platform) {
22
+ // Switch owns its host element rather than rendering through a symbiote View, so it folds
23
+ // aria/role into accessibility* here; the resolved accessibility* fields (plus testID etc.)
24
+ // ride down to the host node via `passthrough`.
25
+ const props = resolveAccessibilityProps(rawProps);
26
+ const { value, onValueChange, disabled, trackColor, thumbColor, ios_backgroundColor, style, ...passthrough } = props;
27
+ const ref = useRef(null);
28
+ const [state, dispatch] = useReducer(switchReducer, undefined, createInitialSwitchState);
29
+ const handleChange = useCallback((event) => {
30
+ const next = valueFromChange(event);
31
+ dlog(`Switch onChange value=${String(next)} eventCount=${String(event.nativeEvent.eventCount)}`);
32
+ if (next === undefined)
33
+ return;
34
+ onValueChange?.(next, event);
35
+ dispatch({ type: 'native-reported', value: next });
36
+ }, [onValueChange]);
37
+ // value is a real Fabric prop, folded to a strict boolean: RN sends `value === true`, so
38
+ // an undefined prop reads as "off" rather than absent.
39
+ const fabricValue = value === true;
40
+ useLayoutEffect(() => {
41
+ const node = ref.current;
42
+ if (node === null)
43
+ return;
44
+ if (!shouldSnapBack(state, fabricValue)) {
45
+ dlog(`Switch snap-back no-op reported=${String(state.lastNativeReport)} value=${fabricValue}`);
46
+ return;
47
+ }
48
+ dlog(`Switch ${platform.snapBackCommand} snap-back reported=${String(state.lastNativeReport)} value=${fabricValue}`);
49
+ dispatchViewCommand(node, platform.snapBackCommand, [fabricValue]);
50
+ }, [fabricValue, state]);
51
+ return renderSwitch({
52
+ value: fabricValue,
53
+ disabled,
54
+ trackColor,
55
+ thumbColor,
56
+ ios_backgroundColor,
57
+ style,
58
+ passthrough: { ...passthrough, ref, onChange: handleChange },
59
+ }, platform);
59
60
  }
@@ -10,7 +10,7 @@
10
10
  // (setTextAndSelection), never a plain prop re-push. See @symbiote-native/components/state/text-input.
11
11
  import { forwardRef, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useRef, useState, } from 'react';
12
12
  import { dispatchViewCommand, dlog, } from '@symbiote-native/engine';
13
- import { blurTextInput, setInputBlurred, setInputFocused } from '../../modules/text-input-state.js';
13
+ import { blurTextInput, setInputBlurred, setInputFocused } from '@symbiote-native/engine';
14
14
  import { resolveAccessibilityProps, resolveTextInputProps, renderTextInput, foldText, textFromChange, eventCountFromChange, shouldCommandText, INITIAL_EVENT_COUNT, SELECTION_NONE, } from '@symbiote-native/components';
15
15
  import { descriptorToReact } from '../../descriptor-to-react/index.js';
16
16
  export const TextInput = forwardRef((rawProps, forwardedRef) => {
@@ -8,10 +8,15 @@
8
8
  // pressed-state style (RN drives this with setState, not Animated, faithfully).
9
9
  // TouchableWithoutFeedback: no visual change, just the press wiring.
10
10
  import { createElement, useRef } from 'react';
11
- import { dlog } from '@symbiote-native/engine';
12
- import { computePressOutWait, DEFAULT_ACTIVE_OPACITY, DEFAULT_HIGHLIGHT_CHILD_OPACITY, DEFAULT_MIN_PRESS_DURATION_MS, DEFAULT_UNDERLAY_COLOR, OPACITY_ACTIVE_DURATION_MS, OPACITY_INACTIVE_DURATION_MS, RESTING_OPACITY, } from '@symbiote-native/components';
11
+ import { createTouchableFeedbackHandlers, createTouchableFeedbackRuntime, highlightPressedStyle, DEFAULT_ACTIVE_OPACITY, DEFAULT_HIGHLIGHT_CHILD_OPACITY, DEFAULT_MIN_PRESS_DURATION_MS, DEFAULT_UNDERLAY_COLOR, OPACITY_ACTIVE_DURATION_MS, OPACITY_INACTIVE_DURATION_MS, RESTING_OPACITY, } from '@symbiote-native/components';
13
12
  import { Pressable } from '../pressable/index.js';
14
13
  import { Animated } from '../../modules/animated/index.js';
14
+ // The real setTimeout the shared feedback machine schedules its deferred activation/deactivation on
15
+ // (core/components has no timer globals). Returns a canceller so an early release flushes the timer.
16
+ function scheduleTimeout(callback, ms) {
17
+ const id = setTimeout(callback, ms);
18
+ return () => clearTimeout(id);
19
+ }
15
20
  export const TouchableOpacity = props => {
16
21
  const { activeOpacity = DEFAULT_ACTIVE_OPACITY, style,
17
22
  // className is pulled out here, like style, and applied to the inner Animated.View below —
@@ -21,10 +26,8 @@ export const TouchableOpacity = props => {
21
26
  // One Animated.Value per mount, resting at full opacity. The Animated.View leaf commits its
22
27
  // current value every frame, so timing it animates the real view.
23
28
  const opacity = useRef(new Animated.Value(RESTING_OPACITY)).current;
24
- // The pending delayPressIn timer, so a release before it fires can flush it.
25
- const pressInTimer = useRef(undefined);
26
- // When the active visual actually started, to floor onPressOut by minPressDuration.
27
- const activatedAt = useRef(undefined);
29
+ // The shared press-scheduling cell (delayPressIn timer + activation clock), held across renders.
30
+ const runtime = useRef(createTouchableFeedbackRuntime()).current;
28
31
  function setOpacityTo(toValue, duration) {
29
32
  Animated.timing(opacity, {
30
33
  toValue,
@@ -33,59 +36,24 @@ export const TouchableOpacity = props => {
33
36
  useNativeDriver: false,
34
37
  }).start();
35
38
  }
36
- function clearPressInTimer() {
37
- if (pressInTimer.current !== undefined) {
38
- clearTimeout(pressInTimer.current);
39
- pressInTimer.current = undefined;
40
- }
41
- }
42
- function activate(event) {
43
- activatedAt.current = Date.now();
44
- setOpacityTo(activeOpacity, OPACITY_ACTIVE_DURATION_MS);
45
- onPressIn?.(event);
46
- }
47
- function deactivate(event) {
48
- activatedAt.current = undefined;
49
- setOpacityTo(RESTING_OPACITY, OPACITY_INACTIVE_DURATION_MS);
50
- onPressOut?.(event);
51
- }
52
- // RN's _createPressabilityConfig forwards delayPressIn: defer the active visual and onPressIn
53
- // behind the delay (a release before it elapses flushes it synchronously).
54
- function handlePressIn(event) {
55
- if (delayPressIn > 0) {
56
- dlog(`TouchableOpacity pressIn deferred ${delayPressIn}ms`);
57
- pressInTimer.current = setTimeout(() => {
58
- pressInTimer.current = undefined;
59
- activate(event);
60
- }, delayPressIn);
61
- return;
62
- }
63
- activate(event);
64
- }
65
- // delayPressOut + minPressDuration (RN _deactivate): the press-out waits at least minPress-
66
- // Duration past activation (so a fast tap holds the active visual) and at least delayPressOut.
67
- function handlePressOut(event) {
68
- if (pressInTimer.current !== undefined) {
69
- clearPressInTimer();
70
- activate(event);
71
- }
72
- const heldFor = activatedAt.current === undefined ? 0 : Date.now() - activatedAt.current;
73
- const wait = computePressOutWait(heldFor, minPressDuration, delayPressOut);
74
- if (wait > 0) {
75
- dlog(`TouchableOpacity pressOut deferred ${wait}ms`);
76
- setTimeout(() => deactivate(event), wait);
77
- return;
78
- }
79
- deactivate(event);
80
- }
39
+ // The shared machine owns the delayPressIn/minPressDuration scheduling; the adapter supplies only
40
+ // the native seam — the Animated opacity fade + the framework emit — as activate/deactivate.
41
+ const { handlePressIn, handlePressOut } = createTouchableFeedbackHandlers({ delayPressIn, delayPressOut, minPressDuration, schedule: scheduleTimeout, now: Date.now }, runtime, {
42
+ activate(event) {
43
+ setOpacityTo(activeOpacity, OPACITY_ACTIVE_DURATION_MS);
44
+ onPressIn?.(event);
45
+ },
46
+ deactivate(event) {
47
+ setOpacityTo(RESTING_OPACITY, OPACITY_INACTIVE_DURATION_MS);
48
+ onPressOut?.(event);
49
+ },
50
+ });
81
51
  return createElement(Pressable, { ...rest, onPressIn: handlePressIn, onPressOut: handlePressOut }, createElement(Animated.View, { style: [style, { opacity }], className }, children));
82
52
  };
83
53
  export const TouchableHighlight = props => {
84
54
  const { activeOpacity = DEFAULT_HIGHLIGHT_CHILD_OPACITY, underlayColor = DEFAULT_UNDERLAY_COLOR, style, children, ...rest } = props;
85
55
  function pressedStyle({ pressed }) {
86
- if (!pressed)
87
- return style;
88
- return [style, { backgroundColor: underlayColor, opacity: activeOpacity }];
56
+ return highlightPressedStyle(pressed, style, underlayColor, activeOpacity);
89
57
  }
90
58
  return createElement(Pressable, { ...rest, style: pressedStyle }, children);
91
59
  };