@symbiote-native/components 3.1.0 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -20,17 +20,19 @@ supplies only the third:
20
20
 
21
21
  1. **Logic — `src/state/*.ts`.** A pure reducer `(state, action) => state`, a
22
22
  `createInitial*State` factory, and pure predicates. Zero framework, zero render —
23
- `switchReducer` / `shouldSnapBack` / `valueFromChange` for `Switch`, `modalReducer` for `Modal`,
24
- the `virtualized-list` windowing math for the list family.
23
+ `modalReducer` for `Modal`, `shouldSnapBack` / `valueFromChange` for `Pressable`'s press
24
+ machine, the `virtualized-list` windowing math for the list family.
25
25
  2. **View — `src/view/render-*.ts`.** A pure function `render*(viewState, platform) => Descriptor`.
26
26
  State and visuals enter **only through arguments**; out comes a tree of `Descriptor` nodes
27
27
  (`{ type, props, children, key }`, built with `el()` / `txt()`) over the intrinsic primitives
28
- (`symbiote-view`, `symbiote-switch`, …). No framework, no state, no events.
28
+ (`symbiote-view`, `symbiote-scroll-view`, …). No framework, no state, no events.
29
29
  3. **Lifecycle — the adapter.** React wires the reducer through `useReducer`/`useLayoutEffect` and
30
30
  bridges the `Descriptor` to `React.createElement`; Vue wires it through `ref`/`watch` and
31
31
  bridges to `h()`. This is the ONLY part a new adapter has to write.
32
32
 
33
- `Switch` is the canonical reference for a full three-layer component.
33
+ `Modal` is the canonical reference for a full three-layer component. `Switch` used to be — its
34
+ `render*` function is gone now that the painting moved to the `switch` tag's own host behavior
35
+ (see "Host behaviors" below), so only its reducer and prop type still live at this layer.
34
36
 
35
37
  ### Install
36
38
 
@@ -45,28 +47,25 @@ npm install @symbiote-native/components
45
47
  ## Usage
46
48
 
47
49
  Nearly every consumer reaches this package **through an adapter**, not directly — an app imports
48
- `Switch` from `@symbiote-native/react` (or `@symbiote-native/vue`), and that adapter re-exports the prop
50
+ `Modal` from `@symbiote-native/react` (or `@symbiote-native/vue`), and that adapter re-exports the prop
49
51
  types and wires the reducer/render pair from here. Calling the render function directly is what an
50
52
  adapter itself does, to build its lifecycle wrapper:
51
53
 
52
54
  ```ts
53
55
  import {
54
- renderSwitch,
55
- createInitialSwitchState,
56
- switchReducer,
56
+ renderModal,
57
+ createInitialModalState,
58
+ modalReducer,
57
59
  } from '@symbiote-native/components';
58
60
 
59
61
  // inside an adapter's own hook/composable:
60
- const state = createInitialSwitchState();
61
- const next = switchReducer(state, { type: 'native-reported', value: true });
62
- const descriptor = renderSwitch(
63
- { value: true, disabled: false, passthrough: { onChange, ref } },
64
- {
65
- trackColorProps: (value, trackColor) => ({
66
- /* platform-specific prop names */
67
- }),
68
- },
69
- );
62
+ const state = createInitialModalState(visible);
63
+ const next = modalReducer(state, { type: visible ? 'show' : 'hide' });
64
+ const descriptor = renderModal({
65
+ visible,
66
+ transparent: false,
67
+ passthrough: { onDismiss, onRequestClose, ref },
68
+ });
70
69
  // descriptor is then handed to the adapter's own descriptorToReact / descriptorToVue bridge
71
70
  ```
72
71
 
@@ -111,7 +111,7 @@
111
111
  // since 2026-09-09, in the same commit that deleted the five wrappers, which is what makes it safe:
112
112
  // while a wrapper still built its own view and text under this tag, registering would have given
113
113
  // every Button a second copy of the subtree.
114
- import { addDerivedNode, appendChild, createElement, createRawText, markPropsDirty, Platform, registerHostBehavior, requestCommitFor, setProp, } from '@symbiote-native/engine';
114
+ import { addDerivedNode, appendChild, createElement, createRawText, markPropsDirty, Platform, registerHostBehavior, requestCommitFor, } from '@symbiote-native/engine';
115
115
  import { descriptorFor } from '../component-names';
116
116
  import { resolveButtonDisabled } from '../view/render-button.js';
117
117
  import { booleanOr, createPressBehavior, } from './pressable.js';
@@ -81,7 +81,7 @@
81
81
  // The slot's fold is assigned to the node inside `buildStructure`, not declared on the behavior:
82
82
  // `IHostBehavior.foldPayload` is the OWNER's, wired by `attachHostBehavior`, and a behavior that
83
83
  // builds a node owns what that node carries.
84
- import { appendChild, appListenerFor, createElement, dlog, registerHostBehavior, setBehaviorListener, setEventListener, setProp, } from '@symbiote-native/engine';
84
+ import { appendChild, appListenerFor, createElement, dlog, registerHostBehavior, setBehaviorListener, setEventListener, } from '@symbiote-native/engine';
85
85
  import { descriptorFor } from '../../component-names';
86
86
  import { didContentSizeChange, readLayoutDimension, } from '../../view/render-scroll-view.js';
87
87
  import { installResponderPredicates, RESPONDER_OWNED_LISTENERS, } from './responder.js';
@@ -151,8 +151,7 @@ export const REFRESH_CONTROL = descriptorFor('refresh-control').component;
151
151
  // `core/engine/cpp/tests/js/scroll-content-payload.itest.ts`.
152
152
  // `rowStyle` USED TO BE A PARAMETER HERE and is not one any more: the content node's row direction
153
153
  // is decided in the engine from that node's OWN tag (`horizontal-scroll-content`), so this builder
154
- // no longer needs to know the axis to build it. `scrollBehavior` still derives `horizontal` from the
155
- // row style for the things that DO still need it in JS.
154
+ // no longer needs to know the axis to build it.
156
155
  function buildContent(contentIntrinsic) {
157
156
  return (node) => {
158
157
  const descriptor = descriptorFor(contentIntrinsic);
@@ -233,9 +232,6 @@ function syncOwnedListener(owner, name, wired) {
233
232
  syncOwnerLayout(owner);
234
233
  }
235
234
  function scrollBehavior(contentIntrinsic, rowStyle, platform) {
236
- // The row style is the horizontal tag's constant and nothing else carries it, so it IS the axis —
237
- // deriving keeps the two from ever disagreeing about which behavior this is.
238
- const horizontal = rowStyle !== undefined;
239
235
  return {
240
236
  // `scroll` and `layout` are owned for the collision reason rather than because the behavior
241
237
  // consumes them: RN's ScrollView installs `_handleScroll` and `_handleLayout` on the native
@@ -2,6 +2,7 @@ import { type IColorValue, type IDeviceEventSource, type INativeViewConfigSource
2
2
  export type IBootstrapHostOptions = {
3
3
  colorProcessor?: (value: IColorValue) => unknown;
4
4
  imageSourceResolver?: (source: unknown) => unknown;
5
+ assetSourceResolver?: (source: unknown) => unknown;
5
6
  deviceEventSource?: IDeviceEventSource;
6
7
  nativeViewConfigSource?: INativeViewConfigSource;
7
8
  debug?: boolean;
@@ -5,7 +5,7 @@
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
7
  import { processColor, DeviceEventEmitter, Image, } from 'react-native';
8
- import { installBackHandler, setColorProcessor, setDeviceEventSource, setImageSourceResolver, setNativeViewConfigSource, } from '@symbiote-native/engine';
8
+ import { setAssetSourceResolver, installBackHandler, 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.
11
11
  import * as ReactNativeViewConfigRegistry from 'react-native/Libraries/Renderer/shims/ReactNativeViewConfigRegistry';
@@ -37,6 +37,7 @@ export function bootstrapHost(options = {}) {
37
37
  globalThis.__SYMBIOTE_DEBUG__ = options.debug ?? process.env.DEBUG === '1';
38
38
  setColorProcessor(options.colorProcessor ?? defaultColorProcessor);
39
39
  setImageSourceResolver(options.imageSourceResolver ?? defaultImageSourceResolver);
40
+ setAssetSourceResolver(options.assetSourceResolver ?? defaultImageSourceResolver);
40
41
  setDeviceEventSource(options.deviceEventSource ?? DeviceEventEmitter);
41
42
  // After the event source: the back button subscribes through it (see installBackHandler).
42
43
  installBackHandler();
@@ -1,9 +1,9 @@
1
- // ScrollView: the imperative + style-routing module (framework-agnostic, no 3-layer split:
2
- // ScrollView has no state machine). The imperative handle, the layout/visual style split for
3
- // the Android RefreshControl wrap, the scroll-event guard/forwarder, and the native sticky
4
- // scroll-attach are all platform- and framework-invariant, so they live here. The adapter
5
- // supplies the lifecycle (the node getter, the effect) and re-exports these.
6
- import { attachNativeEvent, dlog, flattenStyle, isSymbioteEvent, } from '@symbiote-native/engine';
1
+ // ScrollView: the imperative + event-routing module (framework-agnostic, no 3-layer split:
2
+ // ScrollView has no state machine). The imperative handle, the scroll-event guard/forwarder, and
3
+ // the native sticky scroll-attach are platform- and framework-invariant, so they live here. The
4
+ // Android RefreshControl wrap's style split moved out — see the note below. The adapter supplies
5
+ // the lifecycle (the node getter, the effect) and re-exports these.
6
+ import { attachNativeEvent, dlog, isSymbioteEvent, } from '@symbiote-native/engine';
7
7
  // THE ANDROID WRAP SPLIT LEFT THIS FILE ON 2026-09-18, and with it the last copy of RN's
8
8
  // `splitLayoutProps` key partition. Both halves are `SymbioteFabricProps.cpp` now
9
9
  // (`splitScrollViewStyle`, `foldRefreshWrapperProps`), reached off the tag and the tree rather than
@@ -11,15 +11,6 @@
11
11
  // What is NOT here, because a layer below already does it: the `aria-*` -> `accessibilityState`
12
12
  // fold RN performs in `Button.js:326-331`. The engine folds those for every node
13
13
  // (`core/engine/src/accessibility-props.ts`), so repeating it here would fold twice.
14
- import { Platform } from '@symbiote-native/engine';
15
- // Button.js:394-437, one constant per literal so a value cannot drift silently. The LABEL's seven
16
- // went with `resolveButtonTextStyle` on 2026-09-18 — they live in `SymbioteFabricProps.cpp` beside
17
- // the rule that reads them and are deliberately not duplicated here.
18
- const ANDROID_BUTTON_BLUE = '#2196F3';
19
- const ANDROID_DISABLED_BACKGROUND = '#dfdfdf';
20
- const ANDROID_ELEVATION = 4;
21
- const ANDROID_DISABLED_ELEVATION = 0;
22
- const ANDROID_BORDER_RADIUS = 2;
23
14
  // `BUTTON_ACCESSIBILITY_ROLE` and `resolveButtonImportantForAccessibility` WERE HERE and are gone
24
15
  // (2026-09-18). Both are `foldButtonProps` in `SymbioteFabricProps.cpp` now, and neither had a
25
16
  // caller left afterwards — only its own unit test, which is the shape this project calls a mirror:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symbiote-native/components",
3
- "version": "3.1.0",
3
+ "version": "3.1.1",
4
4
  "description": "Framework-agnostic component logic (state machines + render functions) for SymbioteNative — written once, inherited by every adapter (React, Vue, Angular, ...).",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -59,11 +59,11 @@
59
59
  },
60
60
  "peerDependencies": {
61
61
  "react-native": ">=0.86",
62
- "@symbiote-native/engine": "^1.2.0"
62
+ "@symbiote-native/engine": "^1.3.0"
63
63
  },
64
64
  "devDependencies": {
65
65
  "@types/node": "^26.0.0",
66
- "@symbiote-native/engine": "1.2.0"
66
+ "@symbiote-native/engine": "1.3.0"
67
67
  },
68
68
  "scripts": {
69
69
  "typecheck": "tsc --build",