@symbiote-native/components 0.5.0 → 1.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 (44) hide show
  1. package/README.md +20 -6
  2. package/build/accessibility-props.d.ts +11 -0
  3. package/build/accessibility-props.js +30 -118
  4. package/build/behaviors/image.d.ts +3 -0
  5. package/build/behaviors/image.js +123 -0
  6. package/build/behaviors/input-accessory-view.d.ts +3 -0
  7. package/build/behaviors/input-accessory-view.js +70 -0
  8. package/build/behaviors/pressable.d.ts +2 -0
  9. package/build/behaviors/pressable.js +310 -0
  10. package/build/behaviors/switch.d.ts +2 -0
  11. package/build/behaviors/switch.js +182 -0
  12. package/build/behaviors/text-input.d.ts +14 -0
  13. package/build/behaviors/text-input.js +291 -0
  14. package/build/component-names/index.android.js +22 -0
  15. package/build/component-names/index.ios.js +8 -0
  16. package/build/component-names/shared.d.ts +1 -1
  17. package/build/component-names/shared.js +36 -0
  18. package/build/fold-host-bag.d.ts +15 -0
  19. package/build/fold-host-bag.js +99 -0
  20. package/build/index.d.ts +6 -1
  21. package/build/index.js +6 -1
  22. package/build/resolve-intrinsic.d.ts +7 -0
  23. package/build/resolve-intrinsic.js +49 -0
  24. package/build/state/pressable.d.ts +9 -0
  25. package/build/state/pressable.js +120 -34
  26. package/build/state/text-input.d.ts +6 -1
  27. package/build/state/text-input.js +11 -0
  28. package/build/state-style.d.ts +15 -0
  29. package/build/state-style.js +47 -0
  30. package/build/text-props.js +2 -1
  31. package/build/view/render-activity-indicator.js +26 -0
  32. package/build/view/render-image/index.d.ts +2 -0
  33. package/build/view/render-image/index.js +29 -2
  34. package/build/view/render-input-accessory-view.d.ts +2 -0
  35. package/build/view/render-input-accessory-view.js +37 -5
  36. package/build/view/render-switch.js +7 -1
  37. package/build/view/render-text-input.js +6 -2
  38. package/host-primitives.cjs +380 -0
  39. package/host-primitives.d.cts +35 -0
  40. package/lowering-fixtures.cjs +259 -0
  41. package/lowering-fixtures.d.cts +17 -0
  42. package/package.json +42 -5
  43. package/specialize-state-style.cjs +219 -0
  44. package/specialize-state-style.d.cts +15 -0
package/README.md CHANGED
@@ -88,17 +88,31 @@ host node untouched — the render function never names a framework type.
88
88
  keep-alive frame).
89
89
  - **Render-only components** (no state machine) — `ActivityIndicator`, `Image`,
90
90
  `ImageBackground`, `InputAccessoryView`.
91
- - **Pure logic/plumbing without a `Descriptor`** — `Pressable`'s press state machine
92
- (`createPressHandlers` / `createPressRuntime`), the `Touchable*` timing constants,
93
- `Button`'s shared text-style fold, `TextInput`'s controlled-value/event-count handshake
94
- (`resolveTextInputProps`, `foldText`, `eventCountFromChange`, …), `KeyboardAvoidingView`'s inset
95
- math, `ScrollView`'s intrinsics/sticky-header math (no full 3-layer split — the adapter owns the
96
- element assembly).
91
+ - **Pure logic/plumbing without a full `Descriptor`** — `Pressable`'s press state machine
92
+ (`createPressHandlers` / `createPressRuntime` in `state/pressable`) plus its render-decision
93
+ helpers (`buildPressableListeners`, `resolveDisabledAccessibilityState`, `shouldClaimResponder`,
94
+ … in `view/render-pressable`), the `Touchable*` timing constants, `Button`'s shared text-style
95
+ fold, `TextInput`'s controlled-value/event-count handshake (`resolveTextInputProps`, `foldText`,
96
+ `eventCountFromChange`, …), `KeyboardAvoidingView`'s inset math, `ScrollView`'s
97
+ intrinsics/sticky-header math (no full 3-layer split — the adapter owns the element assembly).
98
+ - **Two more shared reducers, driven by every adapter's reactive glue**: the sticky-header effect
99
+ machine (`reduceSticky` / `createInitialStickyState`, the zero-swallow gate + debounce pick every
100
+ sticky-header consumer needs) and the list orchestration reducer (`reduceList` /
101
+ `createInitialListState`, the after-commit effect skeleton — window recompute → edge →
102
+ viewability → initial-scroll → MVCP — every list adapter runs).
97
103
  - **The `VirtualizedList` family's windowing engine** — `computeWindow`, `buildListPlan`,
98
104
  viewability tracking (`computeViewableSet`, `diffViewable`), and the `FlatList`/`SectionList`
99
105
  row/section folding helpers. Lists have no `view/render-*.ts` (a cell's content is the
100
106
  framework's own children) — the shared layer here is pure state/logic, reused verbatim by every
101
107
  adapter.
108
+ - **Host behaviors (`src/behaviors/*.ts`)** — tier-2 host-primitive lowering: a primitive's state
109
+ machine and prop folds registered directly on the engine node (`registerPressableBehavior`,
110
+ `registerSwitchBehavior`, `registerImageBehavior`, `registerTextInputBehavior`,
111
+ `registerInputAccessoryViewBehavior`, plus the folds a lowered element still needs —
112
+ `foldImagePayload`, `foldInputAccessoryViewPayload`, `buildTextInputHandle`) so a
113
+ `Pressable`/`Switch`/`TextInput`/`Image`/`InputAccessoryView` can compile to a bare intrinsic tag
114
+ instead of a framework component. Built on `@symbiote-native/engine`'s `registerHostBehavior`
115
+ seam; see `.claude/rules/host-primitive-tier.md`.
102
116
 
103
117
  ## What it does NOT do
104
118
 
@@ -61,4 +61,15 @@ export interface IAriaProps {
61
61
  'aria-valuenow'?: number;
62
62
  'aria-valuetext'?: string;
63
63
  }
64
+ /**
65
+ * The typed entry point adapters import. The FOLD itself now lives in `@symbiote-native/engine`
66
+ * (`core/engine/src/accessibility-props.ts`) so it runs at the layer every path goes through —
67
+ * including a LOWERED element, which has no component wrapper to run it. This function stays here
68
+ * because the public types do, and because it keeps the typed gate: `hasAnyAriaKey` is checked
69
+ * before anything is allocated, so the ~99% of nodes carrying no alias cost the same as before.
70
+ *
71
+ * Idempotent by construction, and that is load-bearing now that the engine folds too: pass 1 blanks
72
+ * every alias, so a wrapper still calling this after the engine has run finds nothing and returns
73
+ * by identity.
74
+ */
64
75
  export declare function resolveAccessibilityProps<T extends IAccessibilityProps & IAriaProps>(props: T): T;
@@ -8,43 +8,11 @@
8
8
  //
9
9
  // Framework-agnostic (imports only @symbiote-native/engine), so every adapter (React,
10
10
  // Vue, and the next) folds aria/role into accessibility* identically.
11
- import { dlog } from '@symbiote-native/engine';
12
- // RN's web `role` → native `accessibilityRole`. Where the web role has no native
13
- // counterpart it is forwarded unchanged (the AccessibilityRole union stays open),
14
- // so the map only lists the values that actually differ.
15
- const ROLE_TO_ACCESSIBILITY_ROLE = {
16
- alert: 'alert',
17
- button: 'button',
18
- checkbox: 'checkbox',
19
- combobox: 'combobox',
20
- grid: 'grid',
21
- heading: 'header',
22
- img: 'image',
23
- link: 'link',
24
- list: 'list',
25
- listitem: 'list',
26
- menu: 'menu',
27
- menubar: 'menubar',
28
- menuitem: 'menuitem',
29
- none: 'none',
30
- presentation: 'none',
31
- progressbar: 'progressbar',
32
- radio: 'radio',
33
- radiogroup: 'radiogroup',
34
- scrollbar: 'scrollbar',
35
- searchbox: 'search',
36
- slider: 'adjustable',
37
- spinbutton: 'spinbutton',
38
- summary: 'summary',
39
- switch: 'switch',
40
- tab: 'tab',
41
- tablist: 'tablist',
42
- timer: 'timer',
43
- toolbar: 'toolbar',
44
- };
45
- function accessibilityRoleFromRole(role) {
46
- return ROLE_TO_ACCESSIBILITY_ROLE[role] ?? role;
47
- }
11
+ import { foldAriaProps } from '@symbiote-native/engine';
12
+ // The gate's key list, typed against `IAriaProps` so a new alias added to that interface and
13
+ // forgotten here is a type error. The engine carries its own untyped copy for the lowered path;
14
+ // `core/engine/src/accessibility-props.ts` is the single implementation of the FOLD, this is only
15
+ // the cheap probe that decides whether to call it.
48
16
  const ARIA_KEYS = [
49
17
  'role',
50
18
  'aria-label',
@@ -62,90 +30,34 @@ const ARIA_KEYS = [
62
30
  'aria-valuenow',
63
31
  'aria-valuetext',
64
32
  ];
33
+ // An indexed loop rather than `.some(key => …)`: the callback captures `props`, so the closure is
34
+ // allocated on every call, and this runs once per accessibility-bearing component instance — 8 000
35
+ // of them on one benchmark create. The fifteen property reads it replaces the closure with are
36
+ // cheaper than the allocation.
65
37
  function hasAnyAriaKey(props) {
66
- return ARIA_KEYS.some(key => props[key] !== undefined);
38
+ for (let index = 0; index < ARIA_KEYS.length; index += 1) {
39
+ if (props[ARIA_KEYS[index]] !== undefined)
40
+ return true;
41
+ }
42
+ return false;
67
43
  }
68
- // Fold the web-alias `aria-*` / `role` props into the canonical `accessibility*`
69
- // props, mirroring RN's View.js transform. Canonical props take precedence per
70
- // View.js: each aria value fills in via `??` only where the canonical field is
71
- // still empty. The alias keys are blanked to `undefined` in the result so they
72
- // never reach native (the commit layer drops undefined props); the returned
73
- // object keeps type `T`, spreadable straight into createElement. When no alias
74
- // is present the input passes through untouched (cheap fast path).
44
+ /**
45
+ * The typed entry point adapters import. The FOLD itself now lives in `@symbiote-native/engine`
46
+ * (`core/engine/src/accessibility-props.ts`) so it runs at the layer every path goes through —
47
+ * including a LOWERED element, which has no component wrapper to run it. This function stays here
48
+ * because the public types do, and because it keeps the typed gate: `hasAnyAriaKey` is checked
49
+ * before anything is allocated, so the ~99% of nodes carrying no alias cost the same as before.
50
+ *
51
+ * Idempotent by construction, and that is load-bearing now that the engine folds too: pass 1 blanks
52
+ * every alias, so a wrapper still calling this after the engine has run finds nothing and returns
53
+ * by identity.
54
+ */
75
55
  export function resolveAccessibilityProps(props) {
76
56
  if (!hasAnyAriaKey(props))
77
57
  return props;
78
- dlog('resolveAccessibilityProps: folding aria/role aliases into accessibility* props');
79
- const { role, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, 'aria-live': ariaLive, 'aria-hidden': ariaHidden, 'aria-busy': ariaBusy, 'aria-checked': ariaChecked, 'aria-disabled': ariaDisabled, 'aria-expanded': ariaExpanded, 'aria-selected': ariaSelected, 'aria-modal': ariaModal, 'aria-valuemax': ariaValueMax, 'aria-valuemin': ariaValueMin, 'aria-valuenow': ariaValueNow, 'aria-valuetext': ariaValueText, } = props;
80
- const next = {
81
- ...props,
82
- role: undefined,
83
- 'aria-label': undefined,
84
- 'aria-labelledby': undefined,
85
- 'aria-live': undefined,
86
- 'aria-hidden': undefined,
87
- 'aria-busy': undefined,
88
- 'aria-checked': undefined,
89
- 'aria-disabled': undefined,
90
- 'aria-expanded': undefined,
91
- 'aria-selected': undefined,
92
- 'aria-modal': undefined,
93
- 'aria-valuemax': undefined,
94
- 'aria-valuemin': undefined,
95
- 'aria-valuenow': undefined,
96
- 'aria-valuetext': undefined,
97
- };
98
- if (ariaLabelledBy !== undefined &&
99
- next.accessibilityLabelledBy === undefined) {
100
- next.accessibilityLabelledBy = ariaLabelledBy.split(/\s*,\s*/g);
101
- }
102
- if (ariaLabel !== undefined && next.accessibilityLabel === undefined) {
103
- next.accessibilityLabel = ariaLabel;
104
- }
105
- if (ariaLive !== undefined && next.accessibilityLiveRegion === undefined) {
106
- next.accessibilityLiveRegion = ariaLive === 'off' ? 'none' : ariaLive;
107
- }
108
- if (ariaHidden !== undefined) {
109
- if (next.accessibilityElementsHidden === undefined) {
110
- next.accessibilityElementsHidden = ariaHidden;
111
- }
112
- if (ariaHidden === true && next.importantForAccessibility === undefined) {
113
- next.importantForAccessibility = 'no-hide-descendants';
114
- }
115
- }
116
- if (ariaModal !== undefined && next.accessibilityViewIsModal === undefined) {
117
- next.accessibilityViewIsModal = ariaModal;
118
- }
119
- if (role !== undefined && next.accessibilityRole === undefined) {
120
- next.accessibilityRole = accessibilityRoleFromRole(role);
121
- }
122
- const existingState = props.accessibilityState;
123
- if (existingState !== undefined ||
124
- ariaBusy !== undefined ||
125
- ariaChecked !== undefined ||
126
- ariaDisabled !== undefined ||
127
- ariaExpanded !== undefined ||
128
- ariaSelected !== undefined) {
129
- next.accessibilityState = {
130
- busy: ariaBusy ?? existingState?.busy,
131
- checked: ariaChecked ?? existingState?.checked,
132
- disabled: ariaDisabled ?? existingState?.disabled,
133
- expanded: ariaExpanded ?? existingState?.expanded,
134
- selected: ariaSelected ?? existingState?.selected,
135
- };
136
- }
137
- const existingValue = props.accessibilityValue;
138
- if (existingValue !== undefined ||
139
- ariaValueMax !== undefined ||
140
- ariaValueMin !== undefined ||
141
- ariaValueNow !== undefined ||
142
- ariaValueText !== undefined) {
143
- next.accessibilityValue = {
144
- max: ariaValueMax ?? existingValue?.max,
145
- min: ariaValueMin ?? existingValue?.min,
146
- now: ariaValueNow ?? existingValue?.now,
147
- text: ariaValueText ?? existingValue?.text,
148
- };
149
- }
150
- return next;
58
+ // `Object.entries` rather than a spread: an interface has no index signature, so a spread of `T`
59
+ // is not assignable to `Record<string, unknown>` and the alternative would be a cast. Paid only
60
+ // on the folding path, never on the gate.
61
+ const folded = foldAriaProps(Object.fromEntries(Object.entries(props)));
62
+ return Object.assign({}, props, folded);
151
63
  }
@@ -0,0 +1,3 @@
1
+ export declare const IMAGE_TAG = "symbiote-image";
2
+ export declare function foldImagePayload(props: Readonly<Record<string, unknown>>): Record<string, unknown>;
3
+ export declare function registerImageBehavior(): void;
@@ -0,0 +1,123 @@
1
+ // Image's host behavior, and it carries NOTHING but a prop fold — no listeners, no timers, no
2
+ // commit hook. That is what a "fold-only" primitive means: the wrapper's whole body was prop
3
+ // mapping, so its lowered form owes exactly that and nothing else.
4
+ //
5
+ // The fold itself is not written here. `mapImageProps` in `../view/render-image` is the one
6
+ // implementation, and this file only narrows a flat prop bag into the typed view it takes — the
7
+ // same shape `behaviors/text-input.ts` uses (`stringOf` / `booleanOf` guards, then hand the shared
8
+ // resolver a typed object). A second copy of the mapping is the exact drift this primitive has
9
+ // already paid for once: `adapters/svelte/src/components/image/image-logic.ts` reproduces it by
10
+ // hand and says so in its own header, because nothing was exported to call.
11
+ //
12
+ // WHY THIS MAY SHARE THE WRAPPER'S TAG, where TextInput needed `-managed`. A behavior fold is keyed
13
+ // on the tag, and `renderImage` emits `symbiote-image` too — so on a wrapper-built node this fold
14
+ // runs on ALREADY-FOLDED props. That is safe here and only here, because the mapping is idempotent:
15
+ // every alias it consumes (`src`, `srcSet`, `alt`, `width`, `height`, …) is absent from its own
16
+ // output, `source` comes back in the array shape `normalizeSource` guarantees, and
17
+ // `loadingIndicatorSource` leaves under a DIFFERENT name (`loadingIndicatorSrc`), so a second pass
18
+ // finds nothing left to fold. Idempotence is asserted in `image.test.ts` rather than assumed — the
19
+ // audit rule's point is that a double fold is invisible precisely when it happens to be harmless,
20
+ // so the property has to be pinned or it is an accident waiting to be broken.
21
+ import { registerHostBehavior, } from '@symbiote-native/engine';
22
+ import { IMAGE_VIEW_PROP_NAMES, mapImageProps, } from '../view/render-image/index.js';
23
+ export const IMAGE_TAG = 'symbiote-image';
24
+ const RESIZE_MODES = new Set([
25
+ 'cover',
26
+ 'contain',
27
+ 'stretch',
28
+ 'repeat',
29
+ 'center',
30
+ ]);
31
+ const CROSS_ORIGINS = new Set([
32
+ 'anonymous',
33
+ 'use-credentials',
34
+ ]);
35
+ const CONSUMED = new Set(IMAGE_VIEW_PROP_NAMES);
36
+ function stringOf(value) {
37
+ return typeof value === 'string' ? value : undefined;
38
+ }
39
+ function numberOf(value) {
40
+ return typeof value === 'number' ? value : undefined;
41
+ }
42
+ function resizeModeOf(value) {
43
+ if (typeof value !== 'string' || !RESIZE_MODES.has(value))
44
+ return undefined;
45
+ // Narrowed by the set membership above, then re-stated as the union through a switch rather than
46
+ // a cast: the set and the type are two declarations of one list, and only this makes them agree.
47
+ switch (value) {
48
+ case 'cover':
49
+ case 'contain':
50
+ case 'stretch':
51
+ case 'repeat':
52
+ case 'center':
53
+ return value;
54
+ default:
55
+ return undefined;
56
+ }
57
+ }
58
+ function crossOriginOf(value) {
59
+ if (typeof value !== 'string' || !CROSS_ORIGINS.has(value))
60
+ return undefined;
61
+ return value === 'anonymous' ? 'anonymous' : 'use-credentials';
62
+ }
63
+ // A source is an asset id (number), a `{uri}` object, or an array of those. Anything else cannot be
64
+ // resolved and is dropped rather than forwarded — a malformed source reaching Fabric paints nothing
65
+ // and reports nothing, which is worse than an image that is simply absent.
66
+ function sourceOf(value) {
67
+ if (typeof value === 'number')
68
+ return value;
69
+ if (typeof value !== 'object' || value === null)
70
+ return undefined;
71
+ if (Array.isArray(value))
72
+ return value;
73
+ if (typeof Reflect.get(value, 'uri') === 'string')
74
+ return { ...value };
75
+ return undefined;
76
+ }
77
+ // A StyleProp is an object, an array of them, or a registered class array — all of which
78
+ // `flattenStyle` already handles. The only thing to exclude is a scalar.
79
+ function styleOf(value) {
80
+ if (typeof value !== 'object' || value === null)
81
+ return undefined;
82
+ if (Array.isArray(value))
83
+ return value;
84
+ return { ...value };
85
+ }
86
+ export function foldImagePayload(props) {
87
+ const passthrough = {};
88
+ for (const key of Object.keys(props)) {
89
+ if (!CONSUMED.has(key))
90
+ passthrough[key] = props[key];
91
+ }
92
+ return mapImageProps({
93
+ source: sourceOf(props.source),
94
+ defaultSource: sourceOf(props.defaultSource),
95
+ loadingIndicatorSource: sourceOf(props.loadingIndicatorSource),
96
+ style: styleOf(props.style),
97
+ resizeMode: resizeModeOf(props.resizeMode),
98
+ tintColor: stringOf(props.tintColor),
99
+ src: stringOf(props.src),
100
+ srcSet: stringOf(props.srcSet),
101
+ alt: stringOf(props.alt),
102
+ width: numberOf(props.width),
103
+ height: numberOf(props.height),
104
+ crossOrigin: crossOriginOf(props.crossOrigin),
105
+ referrerPolicy: stringOf(props.referrerPolicy),
106
+ passthrough,
107
+ });
108
+ }
109
+ // Required by IHostBehavior and deliberately empty: Image owns no per-node runtime. Written out
110
+ // rather than shared with a `noop` helper so the emptiness reads as a decision.
111
+ function attach(_node) {
112
+ // nothing to set up
113
+ }
114
+ function detach(_node) {
115
+ // nothing to release
116
+ }
117
+ export function registerImageBehavior() {
118
+ registerHostBehavior(IMAGE_TAG, {
119
+ attach,
120
+ detach,
121
+ foldPayload: foldImagePayload,
122
+ });
123
+ }
@@ -0,0 +1,3 @@
1
+ export declare const INPUT_ACCESSORY_VIEW_TAG = "symbiote-input-accessory-view";
2
+ export declare function foldInputAccessoryViewPayload(props: Readonly<Record<string, unknown>>): Record<string, unknown>;
3
+ export declare function registerInputAccessoryViewBehavior(): void;
@@ -0,0 +1,70 @@
1
+ // InputAccessoryView's host behavior — a prop fold and nothing else. No listeners, no timers, no
2
+ // commit hook: the wrapper's whole body was prop mapping, so its lowered form owes exactly that.
3
+ //
4
+ // The fold is NOT written here. `mapInputAccessoryViewProps` in
5
+ // `../view/render-input-accessory-view` is the one implementation and this file only narrows a flat
6
+ // bag into the typed view it takes, the `stringOf` / `styleOf` guard idiom `behaviors/image.ts`
7
+ // uses. Counted before starting, which is step one of the fold-only recipe: unlike Image, which had
8
+ // THREE implementations, all five adapters already call the shared render fn and Svelte's
9
+ // `input-accessory-view-props.ts` is a type declaration rather than a second mapping. So there was
10
+ // one implementation to extract, not three to collapse.
11
+ //
12
+ // SHARES THE WRAPPER'S TAG, no `-managed` twin. The two questions the recipe separates both answer
13
+ // no: the fold is idempotent (there is no aliasing at all — every consumed name leaves under the
14
+ // same name, so a second pass rewrites the same values), and the behavior carries no machine, so
15
+ // nothing can end up with two owners on one node. Idempotence is asserted rather than reasoned.
16
+ //
17
+ // PLATFORM. This is the only primitive in its group that is not platform-invariant in what it
18
+ // COMMITS TO: `symbiote-input-accessory-view` resolves to `RCTInputAccessoryView` on iOS and to a
19
+ // plain `RCTView` on Android. The fold itself is platform-invariant on purpose — it reproduces the
20
+ // wrapper's mapping exactly, on both platforms, so the lowered and wrapped paths cannot diverge
21
+ // per-platform. What it does NOT do is fix the pre-existing Android divergence underneath it:
22
+ // upstream RN renders NOTHING there (`InputAccessoryView.js` — `console.warn('<InputAccessoryView>
23
+ // is only supported on iOS.'); return null`), while we commit an RCTView, and `backgroundColor` is
24
+ // a declared prop of the iOS view but a style key on RCTView. Both predate this behavior and are
25
+ // identical on both paths; neither is in scope here, and a fold that "fixed" one silently would
26
+ // make the two paths disagree.
27
+ import { registerHostBehavior, } from '@symbiote-native/engine';
28
+ import { INPUT_ACCESSORY_VIEW_PROP_NAMES, mapInputAccessoryViewProps, } from '../view/render-input-accessory-view.js';
29
+ export const INPUT_ACCESSORY_VIEW_TAG = 'symbiote-input-accessory-view';
30
+ const CONSUMED = new Set(INPUT_ACCESSORY_VIEW_PROP_NAMES);
31
+ function stringOf(value) {
32
+ return typeof value === 'string' ? value : undefined;
33
+ }
34
+ // A StyleProp is an object, an array of them, or a registered class array — all shapes
35
+ // `flattenStyle` already handles. Only a scalar has to be excluded.
36
+ function styleOf(value) {
37
+ if (typeof value !== 'object' || value === null)
38
+ return undefined;
39
+ if (Array.isArray(value))
40
+ return value;
41
+ return { ...value };
42
+ }
43
+ export function foldInputAccessoryViewPayload(props) {
44
+ const passthrough = {};
45
+ for (const key of Object.keys(props)) {
46
+ if (!CONSUMED.has(key))
47
+ passthrough[key] = props[key];
48
+ }
49
+ return mapInputAccessoryViewProps({
50
+ nativeID: stringOf(props.nativeID),
51
+ backgroundColor: stringOf(props.backgroundColor),
52
+ style: styleOf(props.style),
53
+ passthrough,
54
+ });
55
+ }
56
+ // Required by IHostBehavior and deliberately empty: this primitive owns no per-node runtime.
57
+ // Written out rather than shared with a `noop` so the emptiness reads as a decision.
58
+ function attach(_node) {
59
+ // nothing to set up
60
+ }
61
+ function detach(_node) {
62
+ // nothing to release
63
+ }
64
+ export function registerInputAccessoryViewBehavior() {
65
+ registerHostBehavior(INPUT_ACCESSORY_VIEW_TAG, {
66
+ attach,
67
+ detach,
68
+ foldPayload: foldInputAccessoryViewPayload,
69
+ });
70
+ }
@@ -0,0 +1,2 @@
1
+ export declare const PRESSABLE_TAG = "symbiote-pressable";
2
+ export declare function registerPressableBehavior(): void;