@symbiote-native/components 2.0.0 → 3.0.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 (79) hide show
  1. package/README.md +8 -9
  2. package/build/accessibility-props.d.ts +1 -1
  3. package/build/accessibility-props.js +2 -2
  4. package/build/behaviors/activity-indicator/shared.js +41 -70
  5. package/build/behaviors/button.d.ts +11 -0
  6. package/build/behaviors/button.js +101 -89
  7. package/build/behaviors/image-background.d.ts +1 -0
  8. package/build/behaviors/image-background.js +97 -81
  9. package/build/behaviors/image.d.ts +0 -1
  10. package/build/behaviors/image.js +24 -105
  11. package/build/behaviors/input-accessory-view.d.ts +0 -1
  12. package/build/behaviors/input-accessory-view.js +47 -53
  13. package/build/behaviors/pressable.d.ts +1 -1
  14. package/build/behaviors/pressable.js +68 -97
  15. package/build/behaviors/refresh-control.js +15 -2
  16. package/build/behaviors/scroll-view/index.android.js +25 -37
  17. package/build/behaviors/scroll-view/index.d.ts +1 -0
  18. package/build/behaviors/scroll-view/index.js +3 -0
  19. package/build/behaviors/scroll-view/responder.d.ts +4 -0
  20. package/build/behaviors/scroll-view/responder.js +202 -0
  21. package/build/behaviors/scroll-view/shared.d.ts +1 -3
  22. package/build/behaviors/scroll-view/shared.js +95 -95
  23. package/build/behaviors/scroll-view/sticky.d.ts +1 -0
  24. package/build/behaviors/scroll-view/sticky.js +62 -49
  25. package/build/behaviors/switch.js +43 -86
  26. package/build/behaviors/text-input.js +219 -107
  27. package/build/behaviors/touchable-highlight.js +74 -61
  28. package/build/behaviors/touchable-native-feedback.js +43 -122
  29. package/build/behaviors/touchable-opacity.js +71 -59
  30. package/build/behaviors/touchable-without-feedback.js +35 -100
  31. package/build/component-names/index.android.js +7 -11
  32. package/build/component-names/index.ios.js +0 -7
  33. package/build/component-names/shared.d.ts +1 -1
  34. package/build/index.d.ts +15 -21
  35. package/build/index.js +21 -28
  36. package/build/resolve-intrinsic.js +3 -9
  37. package/build/scroll-view-commands.d.ts +1 -9
  38. package/build/scroll-view-commands.js +13 -74
  39. package/build/state/flat-list.d.ts +2 -2
  40. package/build/state/flat-list.js +10 -2
  41. package/build/state/pressable.d.ts +6 -1
  42. package/build/state/pressable.js +63 -28
  43. package/build/state/section-list.d.ts +2 -0
  44. package/build/state/section-list.js +14 -7
  45. package/build/state/text-input.d.ts +7 -40
  46. package/build/state/text-input.js +17 -186
  47. package/build/state/touchable.d.ts +1 -0
  48. package/build/state/touchable.js +11 -8
  49. package/build/state/virtualized-list-reducer.d.ts +2 -2
  50. package/build/state/virtualized-list.d.ts +6 -6
  51. package/build/state/virtualized-list.js +71 -37
  52. package/build/text-props.d.ts +0 -8
  53. package/build/text-props.js +14 -25
  54. package/build/view/render-button.d.ts +1 -29
  55. package/build/view/render-button.js +44 -81
  56. package/build/view/render-image/index.d.ts +14 -1
  57. package/build/view/render-image/index.js +22 -147
  58. package/build/view/render-input-accessory-view.d.ts +1 -5
  59. package/build/view/render-input-accessory-view.js +26 -48
  60. package/build/view/render-keyboard-avoiding-view.d.ts +7 -1
  61. package/build/view/render-keyboard-avoiding-view.js +40 -1
  62. package/build/view/render-modal.d.ts +1 -1
  63. package/build/view/render-modal.js +15 -5
  64. package/build/view/render-pressable/index.d.ts +1 -0
  65. package/build/view/render-pressable/index.js +4 -0
  66. package/build/view/render-scroll-view.d.ts +0 -4
  67. package/build/view/render-scroll-view.js +3 -49
  68. package/build/view/render-switch.d.ts +0 -14
  69. package/build/view/render-switch.js +4 -42
  70. package/build/view/render-touchable-highlight.d.ts +1 -0
  71. package/build/view/render-touchable-native-feedback.d.ts +0 -1
  72. package/build/view/render-touchable-native-feedback.js +15 -9
  73. package/host-primitives.cjs +49 -203
  74. package/host-primitives.d.cts +0 -1
  75. package/package.json +3 -7
  76. package/build/fold-host-bag.d.ts +0 -15
  77. package/build/fold-host-bag.js +0 -99
  78. package/build/view/render-text-input.d.ts +0 -11
  79. package/build/view/render-text-input.js +0 -39
@@ -11,7 +11,7 @@
11
11
  // WHY THE DIVERGENCE CHECK IS DEFERRED A MICROTASK, NOT RUN SYNCHRONOUSLY INSIDE `onChange`. The
12
12
  // obvious place to compare "what native just reported" against "what the app currently authors" is
13
13
  // right where the report arrives. It is wrong: an ACCEPTED toggle updates the app's own state, and
14
- // that state reaches `node.props.value` only once the app's OWN reconciliation runs — which, for
14
+ // that state reaches the node only once the app's OWN reconciliation runs — which, for
15
15
  // every adapter here, happens strictly after `onChange` returns, never during it. Checking
16
16
  // synchronously would read the STALE pre-accept value and send a spurious snap-back on every
17
17
  // accepted toggle. The wrapper avoids this by running its own check from an effect that fires AFTER
@@ -36,97 +36,40 @@
36
36
  // unresolved. `afterCommit` costs nothing extra (it fires only on a commit that already changed
37
37
  // something) and closes that one case; the microtask path is what the no-op-handler case actually
38
38
  // needs.
39
- import { appListenerFor, dispatchViewCommand, dlog, Platform, registerHostBehavior, setBehaviorListener, } from '@symbiote-native/engine';
39
+ import { appListenerFor, dispatchViewCommand, dlog, Platform, propOf, registerHostBehavior, setBehaviorListener, } from '@symbiote-native/engine';
40
40
  import { createInitialSwitchState, shouldSnapBack, switchReducer, valueFromChange, } from '../state/switch.js';
41
- // The LOWERED tag — NOT the wrapper's `switch-managed` (`render-switch.ts`). One owner
42
- // per node: the wrapper already runs this same machine in its own lifecycle, so registering here
43
- // under the tag it emits would attach a second, redundant copy.
44
41
  export const SWITCH_TAG = 'switch';
45
42
  const states = new WeakMap();
46
43
  function stateOf(node) {
47
44
  return states.get(node);
48
45
  }
49
- function stringOf(value) {
50
- return typeof value === 'string' ? value : undefined;
51
- }
52
- function booleanOf(value) {
53
- return typeof value === 'boolean' ? value : undefined;
54
- }
55
- // `{ false?, true? }` narrowed at runtime — an authored object arriving through an untyped bag
56
- // cannot be trusted to the type system without reading it field by field, the same idiom
57
- // `text-input.ts`'s `selectionOf` uses for `{ start, end? }`.
58
- function trackColorOf(value) {
59
- if (typeof value !== 'object' || value === null)
60
- return undefined;
61
- const bag = { ...value };
62
- const falseColor = stringOf(bag.false);
63
- const trueColor = stringOf(bag.true);
64
- if (falseColor === undefined && trueColor === undefined)
65
- return undefined;
66
- return { false: falseColor, true: trueColor };
67
- }
68
- // The wrapper-body prop fold this primitive owes its lowered form: `trackColor` / `thumbColor` /
69
- // `ios_backgroundColor` are AUTHORED names, none of them a real Fabric prop — RN's Switch view
46
+ // The three narrowing helpers that stood here — `stringOf`, `booleanOf` and `trackColorOf` — went
47
+ // with the fold. They existed to read an untyped bag safely, and the bag is read on the other side
48
+ // of the wire now, where a `folly::dynamic` is checked the same way and with no allocation.
49
+ // THE PROP FOLD MOVED TO THE ENGINE — `foldSwitchProps` in `SymbioteFabricProps.cpp`, with
50
+ // `core/engine/cpp/tests/js/switch-payload.itest.ts` as its contract.
51
+ //
52
+ // It is the clearest case of the three ports so far: `trackColor` / `thumbColor` /
53
+ // `ios_backgroundColor` are AUTHORED names and none of them is a real Fabric prop. RN's Switch view
70
54
  // declares `onTintColor`/`tintColor` (iOS) or `trackColorFor*`/`trackTintColor` (Android), plus
71
- // `thumbTintColor`. `render-switch.ts` folds them for the wrapper path via an adapter-supplied
72
- // `ISwitchPlatform`; a lowered node has no adapter to supply one, so this reads `Platform.OS`
73
- // directly — the same fact every adapter's own index.ios.ts/index.android.ts already encodes as a
74
- // literal, just read once here instead of five times.
75
- function trackColorPropsFor(value, trackColor) {
76
- if (Platform.OS === 'android') {
77
- return {
78
- trackColorForFalse: trackColor?.false,
79
- trackColorForTrue: trackColor?.true,
80
- trackTintColor: value ? trackColor?.true : trackColor?.false,
81
- };
82
- }
83
- return {
84
- onTintColor: trackColor?.true,
85
- tintColor: trackColor?.false,
86
- };
87
- }
88
- // RN rounds the iOS background pill to this radius when `ios_backgroundColor` is set — the same
89
- // constant `render-switch.ts` uses, kept independent rather than exported+imported for one
90
- // primitive-local literal (see that file for the upstream fact it encodes).
91
- const IOS_BACKGROUND_BORDER_RADIUS = 16;
55
+ // `thumbTintColor`, and `ios_backgroundColor` is a STYLE rather than a prop. The wrappers took those
56
+ // per-platform NAMES from an adapter-supplied table; a tag has no adapter to ask, so the branch
57
+ // belongs beside the tree — once, instead of the five copies it had.
58
+ //
59
+ // What is still here is the MACHINE: the snap-back handshake below, which reads app state a
60
+ // microtask after native reports a toggle and corrects a disagreement with an imperative command.
92
61
  // The platform-specific imperative command RN's own Switch sends to correct a rejected toggle
93
- // (Switch.js:221-225) — read off Platform.OS for the same reason `trackColorPropsFor` is.
62
+ // (Switch.js:221-225). It reads `Platform.OS` because it is an IMPERATIVE call decided at gesture
63
+ // time, not a prop — the payload half of the same platform split went to the engine with the fold.
94
64
  function snapBackCommand() {
95
65
  return Platform.OS === 'android' ? 'setNativeValue' : 'setValue';
96
66
  }
97
- function foldPayload(props) {
98
- const value = props.value === true;
99
- const trackColor = trackColorOf(props.trackColor);
100
- const iosBackground = stringOf(props.ios_backgroundColor);
101
- const out = {
102
- ...props,
103
- value,
104
- disabled: booleanOf(props.disabled),
105
- ...trackColorPropsFor(value, trackColor),
106
- thumbTintColor: stringOf(props.thumbColor),
107
- style: iosBackground === undefined
108
- ? props.style
109
- : [
110
- props.style,
111
- {
112
- backgroundColor: iosBackground,
113
- borderRadius: IOS_BACKGROUND_BORDER_RADIUS,
114
- },
115
- ],
116
- };
117
- // The authored names themselves must NOT ride along: none is a Fabric prop, and leaving them in
118
- // the payload is how a reader concludes the fold ran when it did not.
119
- delete out.trackColor;
120
- delete out.thumbColor;
121
- delete out.ios_backgroundColor;
122
- return out;
123
- }
124
67
  // Shared by both triggers — see the module header for why there are two.
125
68
  function evaluateSnapBack(node) {
126
69
  const state = stateOf(node);
127
70
  if (state === undefined)
128
71
  return; // detached before this ran
129
- const fabricValue = node.props.value === true;
72
+ const fabricValue = propOf(node, 'value') === true;
130
73
  if (!shouldSnapBack(state, fabricValue)) {
131
74
  dlog(`Switch behavior snap-back no-op reported=${String(state.lastNativeReport)} value=${fabricValue}`);
132
75
  return;
@@ -143,30 +86,45 @@ function onChange(node, event) {
143
86
  return;
144
87
  dlog(`Switch behavior onChange value=${String(value)} eventCount=${String(event.nativeEvent.eventCount)}`);
145
88
  states.set(node, switchReducer(state, { type: 'native-reported', value }));
89
+ // Switch.js:201-207's `handleChange` — `onChange` first, THEN `onValueChange`, always. An app
90
+ // with side effects observable across both (a shared counter, a log) sees that exact order on a
91
+ // real device.
92
+ //
93
+ // `onChange` is a raw `change` listener authored directly on the bare tag — not part of any
94
+ // adapter's public surface today, but `change` is the name this behavior's own dispatcher owns
95
+ // (`ownedListeners` below), so one is stashed rather than silently evicting the machine.
96
+ const rawListener = appListenerFor(node, 'change');
97
+ if (typeof rawListener === 'function')
98
+ rawListener(event);
146
99
  // `onValueChange` is not a Fabric event — it is a fold the component wrapper does over the raw
147
100
  // `change` payload (same class as TextInput's `onValueChange`, `text-input.ts`'s
148
- // `callValueChange`), so it lands in `node.props` as a plain function key rather than through
101
+ // `callValueChange`), so it lands on the node as a plain prop key rather than through
149
102
  // `ownedListeners`. ONE argument, `value` carried on the event (`ISwitchChangeEvent`) — same
150
103
  // reason as TextInput: Svelte's compiler forces an individual `on*` attribute through a native
151
104
  // listener wrapper that calls with exactly one argument, always a real object.
152
- const listener = node.props.onValueChange;
105
+ const listener = propOf(node, 'onValueChange');
153
106
  if (typeof listener === 'function') {
154
107
  const changeEvent = Object.assign(event, { value });
155
108
  listener(changeEvent);
156
109
  }
157
- // A raw `change` listener authored directly on the bare tag — not part of any adapter's public
158
- // surface today, but `change` is the name this behavior's own dispatcher owns
159
- // (`ownedListeners` below), so one is stashed rather than silently evicting the machine.
160
- const rawListener = appListenerFor(node, 'change');
161
- if (typeof rawListener === 'function')
162
- rawListener(event);
163
110
  // See the module header: deferred so an ACCEPTING app's own state update has a turn of the
164
- // microtask queue to reach `node.props.value` first.
111
+ // microtask queue to write the node first.
165
112
  queueMicrotask(() => evaluateSnapBack(node));
166
113
  }
114
+ // Switch.js:238-239,288-289 — unconditional on both platforms, never a function of any prop: a
115
+ // switch always claims the responder and never yields it, so its own native drag-to-toggle cannot
116
+ // be stolen mid-gesture by a parent ScrollView's own responder negotiation.
117
+ function alwaysClaimsResponder() {
118
+ return true;
119
+ }
120
+ function neverYieldsResponder() {
121
+ return false;
122
+ }
167
123
  function attach(node) {
168
124
  states.set(node, createInitialSwitchState());
169
125
  setBehaviorListener(node, 'change', event => onChange(node, event));
126
+ setBehaviorListener(node, 'startShouldSetResponder', alwaysClaimsResponder);
127
+ setBehaviorListener(node, 'responderTerminationRequest', neverYieldsResponder);
170
128
  }
171
129
  function detach(node) {
172
130
  states.delete(node);
@@ -180,7 +138,6 @@ export function registerSwitchBehavior() {
180
138
  // cannot — a prop change with no preceding native event.
181
139
  afterCommit: evaluateSnapBack,
182
140
  detach,
183
- foldPayload,
184
141
  ownedListeners: ['change'],
185
142
  });
186
143
  }