@symbiote-native/engine 0.2.0 → 0.4.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 (75) hide show
  1. package/README.md +24 -11
  2. package/build/accessibility-props.d.ts +19 -0
  3. package/build/accessibility-props.js +209 -0
  4. package/build/action-sheet-ios/index.js +4 -1
  5. package/build/animated/animations/composition.js +3 -1
  6. package/build/animated/animations/spring-config.js +4 -1
  7. package/build/animated/animations/spring.js +23 -9
  8. package/build/animated/animations/timing.js +3 -2
  9. package/build/animated/bezier.js +2 -1
  10. package/build/animated/color.js +4 -2
  11. package/build/animated/event.js +7 -3
  12. package/build/animated/graph.js +8 -3
  13. package/build/animated/index.d.ts +2 -2
  14. package/build/animated/index.js +2 -2
  15. package/build/animated/interpolation.js +7 -2
  16. package/build/animated/mock.js +1 -1
  17. package/build/animated/native/native-animated.js +1 -1
  18. package/build/animated/operators.js +27 -6
  19. package/build/animated/props.js +1 -1
  20. package/build/animated/rgba.js +18 -3
  21. package/build/animated/style.js +13 -3
  22. package/build/animated/value-xy.d.ts +8 -5
  23. package/build/animated/value-xy.js +4 -1
  24. package/build/app-registry/index.d.ts +1 -0
  25. package/build/app-registry/index.js +15 -5
  26. package/build/appearance/index.js +4 -1
  27. package/build/commit.d.ts +35 -0
  28. package/build/commit.js +613 -74
  29. package/build/debug.js +10 -4
  30. package/build/events/index.js +142 -88
  31. package/build/fabric-props.js +171 -11
  32. package/build/fabric.d.ts +10 -3
  33. package/build/fabric.js +11 -2
  34. package/build/host-behavior.d.ts +47 -0
  35. package/build/host-behavior.js +262 -0
  36. package/build/host-instance/index.d.ts +2 -10
  37. package/build/host-instance/index.js +13 -46
  38. package/build/image-loader.js +3 -2
  39. package/build/index.d.ts +24 -17
  40. package/build/index.js +27 -13
  41. package/build/interaction-manager/index.js +2 -1
  42. package/build/layout-animation/index.js +12 -4
  43. package/build/native-events.js +2 -1
  44. package/build/native-modules/index.js +3 -1
  45. package/build/node.d.ts +121 -2
  46. package/build/node.js +623 -41
  47. package/build/pan-responder/index.js +20 -9
  48. package/build/permissions-android/index.js +3 -1
  49. package/build/platform/index.android.d.ts +1 -1
  50. package/build/platform/index.android.js +1 -1
  51. package/build/platform/index.ios.d.ts +1 -1
  52. package/build/platform-color/index.js +3 -1
  53. package/build/post-commit.d.ts +1 -0
  54. package/build/post-commit.js +6 -0
  55. package/build/process-background-image/index.js +30 -9
  56. package/build/process-filter.js +11 -4
  57. package/build/registry.js +10 -2
  58. package/build/report-error.js +4 -1
  59. package/build/share/index.android.js +1 -1
  60. package/build/share/index.ios.js +1 -1
  61. package/build/status-bar/index.android.js +3 -4
  62. package/build/status-bar/index.ios.js +1 -1
  63. package/build/style-registry/index.d.ts +11 -2
  64. package/build/style-registry/index.js +223 -169
  65. package/build/style-registry/scope.d.ts +15 -1
  66. package/build/style-registry/scope.js +29 -27
  67. package/build/styles.d.ts +11 -1
  68. package/build/surface.d.ts +1 -1
  69. package/build/surface.js +8 -0
  70. package/build/tags.d.ts +1 -0
  71. package/build/tags.js +31 -1
  72. package/build/touch-history.js +9 -2
  73. package/build/vibration/index.ios.js +1 -1
  74. package/build/view-config.js +20 -2
  75. package/package.json +12 -2
package/README.md CHANGED
@@ -9,7 +9,7 @@ current one, clones only what changed, and calls `completeRoot` — the persiste
9
9
  dance Fabric requires, done **once**, for every framework.
10
10
 
11
11
  > New to SymbioteNative? The [root README](../../README.md) has the architecture and the one fact it
12
- > rests on — React is just *one client* of `nativeFabricUIManager`. This package is what sits
12
+ > rests on — React is just _one client_ of `nativeFabricUIManager`. This package is what sits
13
13
  > between every adapter and that native slot.
14
14
 
15
15
  ---
@@ -48,17 +48,24 @@ The entire surface a renderer seam drives:
48
48
 
49
49
  ```ts
50
50
  import {
51
- createElement, createRawText, createAnchor,
52
- appendChild, insertBefore, removeChild,
53
- routeProp, setEventListener, setProp, setText,
51
+ createElement,
52
+ createRawText,
53
+ createAnchor,
54
+ appendChild,
55
+ insertBefore,
56
+ removeChild,
57
+ routeProp,
58
+ setEventListener,
59
+ setProp,
60
+ setText,
54
61
  } from '@symbiote-native/engine';
55
62
 
56
- const node = createElement('RCTView'); // component IS the Fabric view name
63
+ const node = createElement('RCTView'); // component IS the Fabric view name
57
64
  const text = createRawText('Hello');
58
65
  appendChild(node, text);
59
- routeProp(node, 'onPress', () => {}); // ← the flat-bag entry point (React/Vue/Solid):
60
- // decides event-vs-prop via the ViewConfig,
61
- // NOT by the "onX" naming convention
66
+ routeProp(node, 'onPress', () => {}); // ← the flat-bag entry point (React/Vue/Solid):
67
+ // decides event-vs-prop via the ViewConfig,
68
+ // NOT by the "onX" naming convention
62
69
  ```
63
70
 
64
71
  `routeProp` is the one call a flat-bag adapter should route every prop through — a **structural**
@@ -72,7 +79,7 @@ import { createSurface } from '@symbiote-native/engine';
72
79
 
73
80
  const surface = createSurface(rootTag);
74
81
  surface.appendChild(root, node);
75
- surface.commit(); // synchronous — for a framework that already batches (React)
82
+ surface.commit(); // synchronous — for a framework that already batches (React)
76
83
  // surface.requestCommit(); // microtask-coalesced — for reactive frameworks (Vue/Svelte/Angular)
77
84
  ```
78
85
 
@@ -90,15 +97,21 @@ before a tag is guaranteed to exist.
90
97
  - **Runtime modules**, framework-agnostic, re-exported by every adapter: `Platform`,
91
98
  `StyleSheet` (+ `computeHairlineWidth`), `Dimensions`, `PixelRatio`, `Appearance`, `AppState`,
92
99
  `Keyboard`, `AccessibilityInfo`, `BackHandler`, `PermissionsAndroid`, `LayoutAnimation`,
93
- `InteractionManager`, `PanResponder`, and the imperative modules `Alert`, `Share`,
100
+ `InteractionManager`, `PanResponder`, `StatusBar`, and the imperative modules `Alert`, `Share`,
94
101
  `ActionSheetIOS`, `Linking`, `Vibration`, `ToastAndroid`, `Settings`, `I18nManager`.
102
+ - **Host behaviors** (`registerHostBehavior` / `IHostBehavior` / `hasHostBehaviors` /
103
+ `clearHostBehaviors` / `appListenerFor` / `setBehaviorListener` / `requestCommitFor`) — the
104
+ registry that lets a primitive's state machine live directly on the engine node instead of
105
+ inside a framework component, so a `Pressable`/`Switch`/`TextInput`/`Image` can compile to a
106
+ bare intrinsic tag ("host-primitive lowering"). `@symbiote-native/components` registers its
107
+ behaviors against this seam; the engine never imports them back.
95
108
  - **`Animated`** — both the JS and native driver (`timing` / `spring` / `decay` / `loop` /
96
109
  `ValueXY` / tracking / `diffClamp` / `Easing`), including the native-event attachment path
97
110
  (`attachNativeEvent`, `AnimatedEvent`).
98
111
  - **The style pipeline** — `flattenStyle`, the CSS-style processors RN itself runs in JS
99
112
  (`processBoxShadow`, `processFilter`, `processTransform`, `processTransformOrigin`,
100
113
  `processAspectRatio`, `processFontVariant`, `processBackgroundImage`), and the runtime
101
- **class-name registry** (`registerStyles` / `resolveClassName` / `scopeClassName`) that
114
+ **class-name registry** (`registerRules` / `resolveClassName` / `renameClassTokens`) that
102
115
  `@symbiote-native/css-parser`'s build-time output resolves against — shared by every adapter's
103
116
  `class` / `className` / `addClass` prop path.
104
117
  - **`AppRegistry` core** (`createAppRegistry`) — registry bookkeeping + headless-task plumbing;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Whether one key is an alias this fold consumes. `startsWith` rather than a Set lookup: this runs
3
+ * on `setProp`, the hottest write path in the engine (32 001 writes on one benchmark create), and
4
+ * it is guarded by the node's sticky flag so it is reached at most once per node per key. The
5
+ * `role` comparison comes first because it is the one alias with no prefix.
6
+ */
7
+ export declare function isAriaAliasKey(key: string): boolean;
8
+ /**
9
+ * Fold the web-alias `aria-*` / `role` props into RN's canonical `accessibility*` props.
10
+ *
11
+ * Returns the input BY IDENTITY when no alias is present — the fast path that keeps this off the
12
+ * hot path for the ~99% of nodes carrying none, and the property idempotence rests on: pass 1
13
+ * blanks every alias, so a second pass finds nothing and returns by identity again.
14
+ *
15
+ * The alias keys are blanked to `undefined` rather than deleted. That is not laziness: `setProp`
16
+ * treats an `undefined` write as a delete and `fabricProps` skips undefined, so a blanked alias
17
+ * cannot reach Fabric, while a `delete` would deoptimise the object's shape on every folded node.
18
+ */
19
+ export declare function foldAriaProps(props: Record<string, unknown>): Record<string, unknown>;
@@ -0,0 +1,209 @@
1
+ // RN's `aria-*` / `role` -> `accessibility*` fold, at the layer every path goes through.
2
+ //
3
+ // WHY IT IS HERE AND NOT IN A WRAPPER. It used to run inside each primitive's COMPONENT, which is
4
+ // exactly the layer host-primitive lowering removes. A per-attribute element path cannot do it —
5
+ // `aria-checked` has to be folded against a sibling `accessibilityState` — so the four lowering
6
+ // transforms REFUSED any element carrying `role` or an `aria-*` attribute
7
+ // (`REFUSAL_CATEGORIES.bagFold`). Accessibility props are ordinary in real code, so that refusal
8
+ // cost lowering coverage on every primitive, including the three already lowered. Moving the fold
9
+ // down deletes the refusal instead of teaching four transforms a bag operation they cannot express.
10
+ //
11
+ // IT IS A MOVE, NOT A REWRITE, AND THAT IS DELIBERATE. The function carries TWO CONTRADICTORY
12
+ // PRECEDENCE RULES: for the scalars an explicit `accessibility*` WINS and the alias only fills a
13
+ // hole (`if (next.X === undefined)`), while INSIDE the `accessibilityState` / `accessibilityValue`
14
+ // composites the ALIAS wins per field (`ariaBusy ?? existing?.busy`). Both mirror RN's View.js.
15
+ // Anyone "cleaning this up" collapses them into one rule, and every component test stays green
16
+ // while real accessibility silently changes. `core/components/src/accessibility-props.test.ts`
17
+ // pins both directions; read it before touching the branches below.
18
+ //
19
+ // Record-level rather than typed, because the engine's caller has a raw `node.props` bag and an
20
+ // interface is not assignable to `Record<string, unknown>` (no index signature). The typed
21
+ // `resolveAccessibilityProps<T>` in `core/components` stays where adapters already import it and
22
+ // delegates here, keeping its own typed gate so the fast path allocates nothing.
23
+ import { dlog } from './debug.js';
24
+ // Copied line for line from the wrapper this replaces. The first copy silently dropped five
25
+ // entries (`button`, `grid`, `link`, `list`, `listitem`) — a role that falls through simply passes
26
+ // unmapped, so `role="listitem"` would have reached Fabric as `listitem` instead of `list` with
27
+ // nothing red anywhere. Diff this against RN's View.js rather than reading it for plausibility.
28
+ const ROLE_TO_ACCESSIBILITY_ROLE = {
29
+ alert: 'alert',
30
+ button: 'button',
31
+ checkbox: 'checkbox',
32
+ combobox: 'combobox',
33
+ grid: 'grid',
34
+ heading: 'header',
35
+ img: 'image',
36
+ link: 'link',
37
+ list: 'list',
38
+ listitem: 'list',
39
+ menu: 'menu',
40
+ menubar: 'menubar',
41
+ menuitem: 'menuitem',
42
+ none: 'none',
43
+ presentation: 'none',
44
+ progressbar: 'progressbar',
45
+ radio: 'radio',
46
+ radiogroup: 'radiogroup',
47
+ scrollbar: 'scrollbar',
48
+ searchbox: 'search',
49
+ slider: 'adjustable',
50
+ spinbutton: 'spinbutton',
51
+ summary: 'summary',
52
+ switch: 'switch',
53
+ tab: 'tab',
54
+ tablist: 'tablist',
55
+ timer: 'timer',
56
+ toolbar: 'toolbar',
57
+ };
58
+ const ARIA_KEYS = [
59
+ 'role',
60
+ 'aria-label',
61
+ 'aria-labelledby',
62
+ 'aria-live',
63
+ 'aria-hidden',
64
+ 'aria-busy',
65
+ 'aria-checked',
66
+ 'aria-disabled',
67
+ 'aria-expanded',
68
+ 'aria-selected',
69
+ 'aria-modal',
70
+ 'aria-valuemax',
71
+ 'aria-valuemin',
72
+ 'aria-valuenow',
73
+ 'aria-valuetext',
74
+ ];
75
+ // An indexed loop rather than `.some(key => …)`: the callback captures `props`, so a closure is
76
+ // allocated per call, and this is the gate on a path that runs once per node.
77
+ function hasAnyAriaKey(props) {
78
+ for (let index = 0; index < ARIA_KEYS.length; index += 1) {
79
+ if (props[ARIA_KEYS[index]] !== undefined)
80
+ return true;
81
+ }
82
+ return false;
83
+ }
84
+ /**
85
+ * Whether one key is an alias this fold consumes. `startsWith` rather than a Set lookup: this runs
86
+ * on `setProp`, the hottest write path in the engine (32 001 writes on one benchmark create), and
87
+ * it is guarded by the node's sticky flag so it is reached at most once per node per key. The
88
+ * `role` comparison comes first because it is the one alias with no prefix.
89
+ */
90
+ export function isAriaAliasKey(key) {
91
+ return key === 'role' || key.startsWith('aria-');
92
+ }
93
+ function isRecord(value) {
94
+ return typeof value === 'object' && value !== null;
95
+ }
96
+ function fieldOf(source, field) {
97
+ return isRecord(source) ? source[field] : undefined;
98
+ }
99
+ /**
100
+ * Fold the web-alias `aria-*` / `role` props into RN's canonical `accessibility*` props.
101
+ *
102
+ * Returns the input BY IDENTITY when no alias is present — the fast path that keeps this off the
103
+ * hot path for the ~99% of nodes carrying none, and the property idempotence rests on: pass 1
104
+ * blanks every alias, so a second pass finds nothing and returns by identity again.
105
+ *
106
+ * The alias keys are blanked to `undefined` rather than deleted. That is not laziness: `setProp`
107
+ * treats an `undefined` write as a delete and `fabricProps` skips undefined, so a blanked alias
108
+ * cannot reach Fabric, while a `delete` would deoptimise the object's shape on every folded node.
109
+ */
110
+ export function foldAriaProps(props) {
111
+ if (!hasAnyAriaKey(props))
112
+ return props;
113
+ const bag = { ...props };
114
+ dlog('foldAriaProps: folding aria/role aliases into accessibility* props');
115
+ const role = bag.role;
116
+ const ariaLabel = bag['aria-label'];
117
+ const ariaLabelledBy = bag['aria-labelledby'];
118
+ const ariaLive = bag['aria-live'];
119
+ const ariaHidden = bag['aria-hidden'];
120
+ const ariaBusy = bag['aria-busy'];
121
+ const ariaChecked = bag['aria-checked'];
122
+ const ariaDisabled = bag['aria-disabled'];
123
+ const ariaExpanded = bag['aria-expanded'];
124
+ const ariaSelected = bag['aria-selected'];
125
+ const ariaModal = bag['aria-modal'];
126
+ const ariaValueMax = bag['aria-valuemax'];
127
+ const ariaValueMin = bag['aria-valuemin'];
128
+ const ariaValueNow = bag['aria-valuenow'];
129
+ const ariaValueText = bag['aria-valuetext'];
130
+ for (let index = 0; index < ARIA_KEYS.length; index += 1) {
131
+ bag[ARIA_KEYS[index]] = undefined;
132
+ }
133
+ // RULE ONE, for every scalar: the explicit prop WINS, the alias only fills a hole.
134
+ if (typeof ariaLabelledBy === 'string' &&
135
+ bag.accessibilityLabelledBy === undefined) {
136
+ bag.accessibilityLabelledBy = ariaLabelledBy.split(/\s*,\s*/g);
137
+ }
138
+ if (ariaLabel !== undefined && bag.accessibilityLabel === undefined) {
139
+ bag.accessibilityLabel = ariaLabel;
140
+ }
141
+ if (ariaLive !== undefined && bag.accessibilityLiveRegion === undefined) {
142
+ bag.accessibilityLiveRegion = ariaLive === 'off' ? 'none' : ariaLive;
143
+ }
144
+ // One input, TWO outputs, and the second is conditional on the VALUE rather than on presence.
145
+ if (ariaHidden !== undefined) {
146
+ if (bag.accessibilityElementsHidden === undefined) {
147
+ bag.accessibilityElementsHidden = ariaHidden;
148
+ }
149
+ if (ariaHidden === true && bag.importantForAccessibility === undefined) {
150
+ bag.importantForAccessibility = 'no-hide-descendants';
151
+ }
152
+ }
153
+ if (ariaModal !== undefined && bag.accessibilityViewIsModal === undefined) {
154
+ bag.accessibilityViewIsModal = ariaModal;
155
+ }
156
+ if (typeof role === 'string' && bag.accessibilityRole === undefined) {
157
+ bag.accessibilityRole = ROLE_TO_ACCESSIBILITY_ROLE[role] ?? role;
158
+ }
159
+ // RULE TWO, INSIDE the composites: the polarity INVERTS and the ALIAS wins per field. Read from
160
+ // the ORIGINAL props, not from `bag` — the loop above has already blanked the aliases there.
161
+ //
162
+ // UPSTREAM-BUG(react-native): View.js:96 is `checked: ariaChecked ?? accessibilityState?.checked`
163
+ // — NO type coercion. A template writes `aria-checked="true"` as a STRING in every framework we
164
+ // support, so `accessibilityState.checked` reaches native as `'true'` where the native side
165
+ // declares `boolean | 'mixed'`. Ported verbatim for parity; do NOT add a cast without recording
166
+ // a deliberate divergence. Found by the Svelte session 2026-08-31 and pinned as an ASSERTION in
167
+ // `adapters/svelte/src/aria-fold-parity.test.ts`, so a future decision to coerce shows up as a
168
+ // failing test at the point the decision was made rather than as a silent behaviour change.
169
+ //
170
+ // A SECOND, SMALLER DIVERGENCE, and this one is ours rather than upstream's — it predates the
171
+ // move down and is kept only because changing it here would be an undeclared behaviour change:
172
+ // upstream gates the composite on `!= null` (View.js:87-92) and this gates on `!== undefined`.
173
+ // So an explicit `aria-busy={null}` builds an all-undefined `accessibilityState` here and builds
174
+ // nothing upstream. The VALUES agree either way — `??` treats null and undefined alike — so only
175
+ // the composite's existence differs.
176
+ //
177
+ // The composite is REPLACED by a fresh literal listing exactly the known fields, so an unknown
178
+ // field riding on the incoming object is dropped. Faithful to RN and pinned by a test; it is the
179
+ // shape of bug that only shows for whoever passes a field RN adds later.
180
+ const existingState = fieldOf(props, 'accessibilityState');
181
+ if (existingState !== undefined ||
182
+ ariaBusy !== undefined ||
183
+ ariaChecked !== undefined ||
184
+ ariaDisabled !== undefined ||
185
+ ariaExpanded !== undefined ||
186
+ ariaSelected !== undefined) {
187
+ bag.accessibilityState = {
188
+ busy: ariaBusy ?? fieldOf(existingState, 'busy'),
189
+ checked: ariaChecked ?? fieldOf(existingState, 'checked'),
190
+ disabled: ariaDisabled ?? fieldOf(existingState, 'disabled'),
191
+ expanded: ariaExpanded ?? fieldOf(existingState, 'expanded'),
192
+ selected: ariaSelected ?? fieldOf(existingState, 'selected'),
193
+ };
194
+ }
195
+ const existingValue = fieldOf(props, 'accessibilityValue');
196
+ if (existingValue !== undefined ||
197
+ ariaValueMax !== undefined ||
198
+ ariaValueMin !== undefined ||
199
+ ariaValueNow !== undefined ||
200
+ ariaValueText !== undefined) {
201
+ bag.accessibilityValue = {
202
+ max: ariaValueMax ?? fieldOf(existingValue, 'max'),
203
+ min: ariaValueMin ?? fieldOf(existingValue, 'min'),
204
+ now: ariaValueNow ?? fieldOf(existingValue, 'now'),
205
+ text: ariaValueText ?? fieldOf(existingValue, 'text'),
206
+ };
207
+ }
208
+ return bag;
209
+ }
@@ -41,7 +41,10 @@ export const ActionSheetIOS = {
41
41
  else if (typeof destructiveButtonIndex === 'number') {
42
42
  destructiveButtonIndices = [destructiveButtonIndex];
43
43
  }
44
- const nativeOptions = { ...remainingOptions, destructiveButtonIndices };
44
+ const nativeOptions = {
45
+ ...remainingOptions,
46
+ destructiveButtonIndices,
47
+ };
45
48
  manager.showActionSheetWithOptions(nativeOptions, buttonIndex => {
46
49
  dlog(`ActionSheetIOS callback buttonIndex=${buttonIndex}`);
47
50
  callback(buttonIndex);
@@ -211,7 +211,9 @@ export function loop(animation, config = {}) {
211
211
  return;
212
212
  }
213
213
  const restart = (result = { finished: true }) => {
214
- if (isFinished || iterationsSoFar === iterations || result.finished === false) {
214
+ if (isFinished ||
215
+ iterationsSoFar === iterations ||
216
+ result.finished === false) {
215
217
  callback?.(result);
216
218
  }
217
219
  else {
@@ -34,7 +34,10 @@ export function fromBouncinessAndSpeed(bounciness, speed) {
34
34
  return 0.000044 * Math.pow(x, 3) - 0.006 * Math.pow(x, 2) + 0.36 * x + 2;
35
35
  }
36
36
  function b3Friction3(x) {
37
- return 0.00000045 * Math.pow(x, 3) - 0.000332 * Math.pow(x, 2) + 0.1078 * x + 5.84;
37
+ return (0.00000045 * Math.pow(x, 3) -
38
+ 0.000332 * Math.pow(x, 2) +
39
+ 0.1078 * x +
40
+ 5.84);
38
41
  }
39
42
  function b3Nobounce(tension) {
40
43
  if (tension <= 18)
@@ -6,10 +6,12 @@
6
6
  // continuous. The native-config export is dropped.
7
7
  import { dlog } from '../../debug.js';
8
8
  import { BaseAnimation } from './base.js';
9
- import { cancelFrame, clearTimer, requestFrame, setTimer } from './raf.js';
10
- import { fromBouncinessAndSpeed, fromOrigamiTensionAndFriction } from './spring-config.js';
9
+ import { cancelFrame, clearTimer, requestFrame, setTimer, } from './raf.js';
10
+ import { fromBouncinessAndSpeed, fromOrigamiTensionAndFriction, } from './spring-config.js';
11
11
  function resolveStiffnessDampingMass(config) {
12
- if (config.stiffness !== undefined || config.damping !== undefined || config.mass !== undefined) {
12
+ if (config.stiffness !== undefined ||
13
+ config.damping !== undefined ||
14
+ config.mass !== undefined) {
13
15
  return {
14
16
  stiffness: config.stiffness ?? 100,
15
17
  damping: config.damping ?? 10,
@@ -18,10 +20,18 @@ function resolveStiffnessDampingMass(config) {
18
20
  }
19
21
  if (config.bounciness !== undefined || config.speed !== undefined) {
20
22
  const springConfig = fromBouncinessAndSpeed(config.bounciness ?? 8, config.speed ?? 12);
21
- return { stiffness: springConfig.stiffness, damping: springConfig.damping, mass: 1 };
23
+ return {
24
+ stiffness: springConfig.stiffness,
25
+ damping: springConfig.damping,
26
+ mass: 1,
27
+ };
22
28
  }
23
29
  const springConfig = fromOrigamiTensionAndFriction(config.tension ?? 40, config.friction ?? 7);
24
- return { stiffness: springConfig.stiffness, damping: springConfig.damping, mass: 1 };
30
+ return {
31
+ stiffness: springConfig.stiffness,
32
+ damping: springConfig.damping,
33
+ mass: 1,
34
+ };
25
35
  }
26
36
  export class SpringAnimation extends BaseAnimation {
27
37
  overshootClamping;
@@ -141,7 +151,8 @@ export class SpringAnimation extends BaseAnimation {
141
151
  position =
142
152
  this.toValue -
143
153
  envelope *
144
- (((v0 + zeta * omega0 * x0) / omega1) * Math.sin(omega1 * t) + x0 * Math.cos(omega1 * t));
154
+ (((v0 + zeta * omega0 * x0) / omega1) * Math.sin(omega1 * t) +
155
+ x0 * Math.cos(omega1 * t));
145
156
  velocity =
146
157
  zeta *
147
158
  omega0 *
@@ -149,13 +160,15 @@ export class SpringAnimation extends BaseAnimation {
149
160
  ((Math.sin(omega1 * t) * (v0 + zeta * omega0 * x0)) / omega1 +
150
161
  x0 * Math.cos(omega1 * t)) -
151
162
  envelope *
152
- (Math.cos(omega1 * t) * (v0 + zeta * omega0 * x0) - omega1 * x0 * Math.sin(omega1 * t));
163
+ (Math.cos(omega1 * t) * (v0 + zeta * omega0 * x0) -
164
+ omega1 * x0 * Math.sin(omega1 * t));
153
165
  }
154
166
  else {
155
167
  // Critically damped.
156
168
  const envelope = Math.exp(-omega0 * t);
157
169
  position = this.toValue - envelope * (x0 + (v0 + omega0 * x0) * t);
158
- velocity = envelope * (v0 * (t * omega0 - 1) + t * x0 * (omega0 * omega0));
170
+ velocity =
171
+ envelope * (v0 * (t * omega0 - 1) + t * x0 * (omega0 * omega0));
159
172
  }
160
173
  this.lastTime = now;
161
174
  this.lastPosition = position;
@@ -177,7 +190,8 @@ export class SpringAnimation extends BaseAnimation {
177
190
  const isVelocity = Math.abs(velocity) <= this.restSpeedThreshold;
178
191
  let isDisplacement = true;
179
192
  if (this.stiffness !== 0) {
180
- isDisplacement = Math.abs(this.toValue - position) <= this.restDisplacementThreshold;
193
+ isDisplacement =
194
+ Math.abs(this.toValue - position) <= this.restDisplacementThreshold;
181
195
  }
182
196
  if (isOvershooting || (isVelocity && isDisplacement)) {
183
197
  if (this.stiffness !== 0) {
@@ -5,7 +5,7 @@
5
5
  import { Easing } from '../easing.js';
6
6
  import { dlog } from '../../debug.js';
7
7
  import { BaseAnimation } from './base.js';
8
- import { cancelFrame, clearTimer, requestFrame, setTimer } from './raf.js';
8
+ import { cancelFrame, clearTimer, requestFrame, setTimer, } from './raf.js';
9
9
  let cachedEaseInOut;
10
10
  function easeInOut() {
11
11
  if (cachedEaseInOut === undefined) {
@@ -80,7 +80,8 @@ export class TimingAnimation extends BaseAnimation {
80
80
  return;
81
81
  }
82
82
  this.onUpdate(this.fromValue +
83
- this.easing((now - this.startTime) / this.duration) * (this.toValue - this.fromValue));
83
+ this.easing((now - this.startTime) / this.duration) *
84
+ (this.toValue - this.fromValue));
84
85
  if (this.__active) {
85
86
  this.animationFrame = requestFrame(() => this.onFrame());
86
87
  }
@@ -40,7 +40,8 @@ function binarySubdivide(x, lower, upper, mX1, mX2) {
40
40
  else {
41
41
  lo = currentT;
42
42
  }
43
- } while (Math.abs(currentX) > SUBDIVISION_PRECISION && ++i < SUBDIVISION_MAX_ITERATIONS);
43
+ } while (Math.abs(currentX) > SUBDIVISION_PRECISION &&
44
+ ++i < SUBDIVISION_MAX_ITERATIONS);
44
45
  return currentT;
45
46
  }
46
47
  function newtonRaphsonIterate(x, guess, mX1, mX2) {
@@ -11,7 +11,7 @@ import { DEFAULT_COLOR, normalizeColor } from './rgba.js';
11
11
  // live in the dependency-free ./rgba leaf (see its header for the cycle it breaks).
12
12
  export { normalizeColor };
13
13
  function isRgbaInput(value) {
14
- return typeof value === 'object' && value !== null && 'r' in value && 'g' in value;
14
+ return (typeof value === 'object' && value !== null && 'r' in value && 'g' in value);
15
15
  }
16
16
  function toChannel(value) {
17
17
  return value instanceof AnimatedValue ? value : new AnimatedValue(value);
@@ -59,7 +59,9 @@ export class AnimatedColor extends AnimatedWithChildren {
59
59
  // pattern). flushValue dedupes by leaf identity, so a single flush rebuilds
60
60
  // every bound prop once even though four channels changed.
61
61
  setValue(value) {
62
- const rgba = typeof value === 'object' ? value : (normalizeColor(value) ?? DEFAULT_COLOR);
62
+ const rgba = typeof value === 'object'
63
+ ? value
64
+ : (normalizeColor(value) ?? DEFAULT_COLOR);
63
65
  this.withSuspendedCallbacks(() => {
64
66
  this.r.setValue(rgba.r);
65
67
  this.g.setValue(rgba.g);
@@ -17,14 +17,16 @@ import { dlog } from '../debug.js';
17
17
  import { getNativeTag, whenCommitted } from '../commit.js';
18
18
  import { isSymbioteNode } from '../node.js';
19
19
  import { AnimatedNode, flushValue } from './graph.js';
20
- import { isNativeAnimatedAvailable, nativeAnimated } from './native/native-animated.js';
20
+ import { isNativeAnimatedAvailable, nativeAnimated, } from './native/native-animated.js';
21
21
  import { isRecord } from '../type-guards.js';
22
22
  // A value node settable from an event field. AnimatedValue carries setValue; we
23
23
  // duck-type it (rather than importing AnimatedValue) to keep this leaf-agnostic
24
24
  // and avoid a value<->event import cycle.
25
25
  function settableValue(node) {
26
26
  const candidate = Reflect.get(node, 'setValue');
27
- return typeof candidate === 'function' ? value => candidate.call(node, value) : undefined;
27
+ return typeof candidate === 'function'
28
+ ? value => candidate.call(node, value)
29
+ : undefined;
28
30
  }
29
31
  // Walk the mapping to every leaf AnimatedNode, recording its key path. Shared by
30
32
  // the JS handler (to set values) and the native attach (to register paths).
@@ -128,7 +130,9 @@ export class AnimatedEvent {
128
130
  if (!this.attachedNatively) {
129
131
  // Paths are stored relative to `nativeEvent` (the native module excludes
130
132
  // that prefix); the JS event arg still carries it, so re-add it here.
131
- const nativeEvent = isRecord(args[0]) ? Reflect.get(args[0], 'nativeEvent') : undefined;
133
+ const nativeEvent = isRecord(args[0])
134
+ ? Reflect.get(args[0], 'nativeEvent')
135
+ : undefined;
132
136
  for (const mapped of this.mappedValues) {
133
137
  const extracted = extractAtPath(nativeEvent, mapped.path);
134
138
  if (extracted === undefined)
@@ -10,7 +10,7 @@
10
10
  // B) bottom-up: each flagged leaf re-pulls its whole subtree via __getValue()
11
11
  // to rebuild composite props (e.g. transform from many parents).
12
12
  import { generateNativeNodeTag, nativeAnimated, } from './native/native-animated.js';
13
- import { checkValidRanges, createInterpolation } from './interpolation.js';
13
+ import { checkValidRanges, createInterpolation, } from './interpolation.js';
14
14
  import { dlog } from '../debug.js';
15
15
  let nextListenerId = 1;
16
16
  // Depth of the currently-active withSuspendedCallbacks blocks. While > 0, a
@@ -81,7 +81,10 @@ export class AnimatedNode {
81
81
  // (AnimatedNode.js:146-148) rather than in each node's config method.
82
82
  const config = this.platformConfig === undefined
83
83
  ? this.__getNativeConfig()
84
- : { ...this.__getNativeConfig(), platformConfig: this.platformConfig };
84
+ : {
85
+ ...this.__getNativeConfig(),
86
+ platformConfig: this.platformConfig,
87
+ };
85
88
  nativeAnimated.createAnimatedNode(this.nativeTag, config);
86
89
  }
87
90
  return this.nativeTag;
@@ -221,7 +224,9 @@ function numericValueOf(node) {
221
224
  // under useDefineForClassFields. So leaves are detected structurally instead.
222
225
  function leafUpdate(node) {
223
226
  const candidate = Reflect.get(node, 'update');
224
- return typeof candidate === 'function' ? () => candidate.call(node) : undefined;
227
+ return typeof candidate === 'function'
228
+ ? () => candidate.call(node)
229
+ : undefined;
225
230
  }
226
231
  // Top-down walk to the leaves, then re-pull each leaf (deduped by node identity,
227
232
  // so a diamond in the graph still updates a leaf once). Suppressed inside a
@@ -14,7 +14,7 @@ export { AnimatedTracking } from './animations/tracking';
14
14
  export { timing, spring, decay, parallel, sequence, stagger, loop, delay, type ICompositeAnimation, type ITimingConfig, type ISpringConfig, type IDecayConfig, type IParallelConfig, type ILoopAnimationConfig, } from './animations/composition';
15
15
  export { nativeAnimated, isNativeAnimatedAvailable, type INativeNodeConfig, type INativeAnimationConfig, type INativeEventMapping, type IPlatformConfig, } from './native/native-animated';
16
16
  export { AnimatedProps } from './props';
17
- export { createAnimatedLeafLifecycle, type IAnimatedLeafLifecycle } from './leaf-lifecycle';
17
+ export { createAnimatedLeafLifecycle, type IAnimatedLeafLifecycle, } from './leaf-lifecycle';
18
18
  export { AnimatedStyle, AnimatedTransform } from './style';
19
19
  export { AnimatedMock } from './mock';
20
- export { reduceProps, isAnimatedNode, readPassthroughStyle, resolveHostNode } from './shared';
20
+ export { reduceProps, isAnimatedNode, readPassthroughStyle, resolveHostNode, } from './shared';
@@ -21,8 +21,8 @@ export { nativeAnimated, isNativeAnimatedAvailable, } from './native/native-anim
21
21
  // no React/Vue). They live here with the rest of the graph; every adapter's
22
22
  // createAnimatedComponent + Animated namespace re-exports them.
23
23
  export { AnimatedProps } from './props.js';
24
- export { createAnimatedLeafLifecycle } from './leaf-lifecycle.js';
24
+ export { createAnimatedLeafLifecycle, } from './leaf-lifecycle.js';
25
25
  export { AnimatedStyle, AnimatedTransform } from './style.js';
26
26
  export { AnimatedMock } from './mock.js';
27
27
  // Framework-agnostic createAnimatedComponent helpers. Both adapters import them.
28
- export { reduceProps, isAnimatedNode, readPassthroughStyle, resolveHostNode } from './shared.js';
28
+ export { reduceProps, isAnimatedNode, readPassthroughStyle, resolveHostNode, } from './shared.js';
@@ -171,7 +171,9 @@ function createStringInterpolation(config, outputRange) {
171
171
  return input => {
172
172
  const values = interpolations.map(interpolation => interpolation(input));
173
173
  let i = 0;
174
- return template.map(c => (typeof c === 'number' ? values[i++] : c)).join('');
174
+ return template
175
+ .map(c => (typeof c === 'number' ? values[i++] : c))
176
+ .join('');
175
177
  };
176
178
  }
177
179
  // Colors: r,g,b must be integers, so round them; alpha stays continuous but is
@@ -195,5 +197,8 @@ export function createInterpolation(config) {
195
197
  if (isStringOutputRange(config.outputRange)) {
196
198
  return createStringInterpolation(config, config.outputRange);
197
199
  }
198
- return createNumericInterpolation({ ...config, outputRange: config.outputRange });
200
+ return createNumericInterpolation({
201
+ ...config,
202
+ outputRange: config.outputRange,
203
+ });
199
204
  }
@@ -11,7 +11,7 @@ import { AnimatedValueXY } from './value-xy.js';
11
11
  import { AnimatedColor } from './color.js';
12
12
  import { AnimatedNode } from './graph.js';
13
13
  import { Easing } from './easing.js';
14
- import { add, subtract, multiply, divide, modulo, diffClamp } from './operators.js';
14
+ import { add, subtract, multiply, divide, modulo, diffClamp, } from './operators.js';
15
15
  import { event, forkEvent, unforkEvent } from './event.js';
16
16
  import { dlog } from '../debug.js';
17
17
  // Prevent a callback invocation from recursively triggering another callback,
@@ -11,7 +11,7 @@
11
11
  // native host) is inert until a native-driven animation actually starts.
12
12
  import { dlog } from '../../debug.js';
13
13
  import { getNativeModule } from '../../native-modules/index.js';
14
- import { NativeEventEmitter } from '../../native-events.js';
14
+ import { NativeEventEmitter, } from '../../native-events.js';
15
15
  import { isRecord } from '../../type-guards.js';
16
16
  // iOS bridgeless registers the Turbo variant; the legacy name is the fallback.
17
17
  const TURBO_MODULE_NAME = 'NativeAnimatedTurboModule';