@symbiote-native/components 0.5.0 → 2.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.
- package/README.md +23 -10
- package/build/accessibility-props.d.ts +11 -0
- package/build/accessibility-props.js +30 -118
- package/build/behaviors/activity-indicator/index.android.d.ts +1 -0
- package/build/behaviors/activity-indicator/index.android.js +16 -0
- package/build/behaviors/activity-indicator/index.d.ts +3 -0
- package/build/behaviors/activity-indicator/index.ios.d.ts +1 -0
- package/build/behaviors/activity-indicator/index.ios.js +14 -0
- package/build/behaviors/activity-indicator/index.js +5 -0
- package/build/behaviors/activity-indicator/shared.d.ts +18 -0
- package/build/behaviors/activity-indicator/shared.js +149 -0
- package/build/behaviors/button.d.ts +2 -0
- package/build/behaviors/button.js +328 -0
- package/build/behaviors/image-background.d.ts +2 -0
- package/build/behaviors/image-background.js +139 -0
- package/build/behaviors/image.d.ts +3 -0
- package/build/behaviors/image.js +123 -0
- package/build/behaviors/input-accessory-view.d.ts +3 -0
- package/build/behaviors/input-accessory-view.js +70 -0
- package/build/behaviors/pressable.d.ts +60 -0
- package/build/behaviors/pressable.js +385 -0
- package/build/behaviors/refresh-control.d.ts +2 -0
- package/build/behaviors/refresh-control.js +83 -0
- package/build/behaviors/scroll-view/index.android.d.ts +1 -0
- package/build/behaviors/scroll-view/index.android.js +52 -0
- package/build/behaviors/scroll-view/index.d.ts +2 -0
- package/build/behaviors/scroll-view/index.ios.d.ts +1 -0
- package/build/behaviors/scroll-view/index.ios.js +10 -0
- package/build/behaviors/scroll-view/index.js +5 -0
- package/build/behaviors/scroll-view/shared.d.ts +11 -0
- package/build/behaviors/scroll-view/shared.js +291 -0
- package/build/behaviors/scroll-view/sticky.d.ts +17 -0
- package/build/behaviors/scroll-view/sticky.js +568 -0
- package/build/behaviors/switch.d.ts +2 -0
- package/build/behaviors/switch.js +186 -0
- package/build/behaviors/text-input.d.ts +14 -0
- package/build/behaviors/text-input.js +319 -0
- package/build/behaviors/touchable-highlight.d.ts +9 -0
- package/build/behaviors/touchable-highlight.js +192 -0
- package/build/behaviors/touchable-native-feedback.d.ts +20 -0
- package/build/behaviors/touchable-native-feedback.js +333 -0
- package/build/behaviors/touchable-opacity.d.ts +12 -0
- package/build/behaviors/touchable-opacity.js +227 -0
- package/build/behaviors/touchable-without-feedback.d.ts +2 -0
- package/build/behaviors/touchable-without-feedback.js +296 -0
- package/build/component-names/index.android.js +47 -15
- package/build/component-names/index.ios.js +35 -15
- package/build/component-names/shared.d.ts +2 -1
- package/build/component-names/shared.js +49 -3
- package/build/descriptor.js +4 -4
- package/build/fold-host-bag.d.ts +15 -0
- package/build/fold-host-bag.js +99 -0
- package/build/index.d.ts +23 -12
- package/build/index.js +50 -9
- package/build/register.d.ts +1 -0
- package/build/register.js +55 -0
- package/build/resolve-intrinsic.d.ts +7 -0
- package/build/resolve-intrinsic.js +49 -0
- package/build/scroll-view-commands.d.ts +4 -0
- package/build/scroll-view-commands.js +30 -31
- package/build/state/pressable.d.ts +9 -0
- package/build/state/pressable.js +120 -34
- package/build/state/text-input.d.ts +10 -2
- package/build/state/text-input.js +11 -0
- package/build/text-props.js +2 -1
- package/build/view/render-button.d.ts +36 -1
- package/build/view/render-button.js +101 -12
- package/build/view/render-image/index.d.ts +2 -0
- package/build/view/render-image/index.js +29 -2
- package/build/view/render-input-accessory-view.d.ts +2 -0
- package/build/view/render-input-accessory-view.js +37 -5
- package/build/view/render-modal.js +3 -3
- package/build/view/render-pressable/index.d.ts +2 -0
- package/build/view/render-pressable/index.js +24 -0
- package/build/view/render-scroll-view.d.ts +1 -0
- package/build/view/render-scroll-view.js +18 -9
- package/build/view/render-switch.d.ts +4 -1
- package/build/view/render-switch.js +8 -2
- package/build/view/render-text-input.js +6 -2
- package/build/view/render-touchable-native-feedback.d.ts +19 -0
- package/build/view/render-touchable-native-feedback.js +19 -0
- package/host-primitives.cjs +432 -0
- package/host-primitives.d.cts +33 -0
- package/package.json +33 -5
- package/build/view/render-activity-indicator.d.ts +0 -25
- package/build/view/render-activity-indicator.js +0 -62
- package/build/view/render-image-background.d.ts +0 -9
- package/build/view/render-image-background.js +0 -48
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
// The PRIMITIVE SPEC — one description of which intrinsic tag each primitive is, what it folds,
|
|
2
|
+
// and which of its two Fabric views a prop selects. Data only: no AST, no framework, no code.
|
|
3
|
+
//
|
|
4
|
+
// WHY IT IS A `.cjs` AND NOT PART OF `src/`. Its consumers include build-tool files that run
|
|
5
|
+
// before any TS exists and cannot import from `src/` — `adapters/vue/intrinsic-tags.cjs`, which
|
|
6
|
+
// both Vue compilers read to answer element-vs-component. That constraint is why the map was
|
|
7
|
+
// copied per adapter in the first place.
|
|
8
|
+
//
|
|
9
|
+
// WHAT IT DOES NOT UNIFY, stated here so nobody hunts for a contradiction that is not one: this
|
|
10
|
+
// says WHAT a fold is, never WHICH LAYER applies it. Svelte folds Text's defaults in its DOM shim;
|
|
11
|
+
// Vue and Solid apply the same fold in their renderers. Both are correct.
|
|
12
|
+
//
|
|
13
|
+
// Four transforms carried their own copy of this before it existed, and it had already produced a
|
|
14
|
+
// real behaviour split — see `aliases` below.
|
|
15
|
+
|
|
16
|
+
// `intrinsicWhen` DECLARED AHEAD OF ITS FIRST ENTRY.
|
|
17
|
+
//
|
|
18
|
+
// WHAT IT IS FOR. `TextInput` is the first primitive whose TAG depends on a prop: `multiline`
|
|
19
|
+
// selects between two different Fabric views, `text-input` and
|
|
20
|
+
// `text-input-multiline` (`src/view/render-text-input.ts:33`), not between two values of
|
|
21
|
+
// one view. `src/resolve-intrinsic.ts` reads it at element creation, where the value is known.
|
|
22
|
+
//
|
|
23
|
+
// ONE selector and ONE alternative, deliberately — not a map and not a list. There is exactly one
|
|
24
|
+
// such prop in the whole surface, and a wider field would be invented rather than needed. Absent
|
|
25
|
+
// `intrinsicWhen` means "one tag", so no existing entry changes.
|
|
26
|
+
//
|
|
27
|
+
// THE BOUNDARY IS IDENTITY, not truthiness: only `true` picks the alternative, and a truthy
|
|
28
|
+
// non-boolean like `multiline={1}` does not. The spec types the selector as a boolean; guessing
|
|
29
|
+
// past that commits the wrong native view, and no later prop write moves a node between views.
|
|
30
|
+
//
|
|
31
|
+
// THE TYPEDEF BELOW IS NOT WHAT TYPESCRIPT READS. `host-primitives.d.cts` is a hand-written
|
|
32
|
+
// declaration file, and a field added here and not there compiles fine in every `.cjs` transform
|
|
33
|
+
// while failing `tsc` in the one consumer written in TypeScript — measured 2026-08-31, when
|
|
34
|
+
// `intrinsicWhen` landed here alone and reddened Svelte's preprocessor with the whole suite green
|
|
35
|
+
// (vitest does not typecheck). The reverse is worse and silent: a field in the `.d.cts` and not
|
|
36
|
+
// here typechecks everywhere and arrives `undefined` in all five transforms. Change both, together.
|
|
37
|
+
/**
|
|
38
|
+
* @typedef {{ op: 'nullish', value: unknown } | { op: 'notFalse' }} IFoldOp
|
|
39
|
+
* @typedef {{ prop: string, intrinsic: string }} IIntrinsicWhen
|
|
40
|
+
* @typedef {{ intrinsic: string, aliases: Record<string, string>, defaults: Record<string, IFoldOp>, intrinsicWhen?: IIntrinsicWhen }} IHostPrimitive
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
// `id` is RN's W3C-named alias for `nativeID` and it WINS when both are set. Verified against RN
|
|
44
|
+
// 0.86 rather than against our own adapters, because the three that implemented it disagreed:
|
|
45
|
+
//
|
|
46
|
+
// View.js:77-79 if (id !== undefined) processedProps.nativeID = id;
|
|
47
|
+
// Text.js:222 const _nativeID = id ?? nativeID;
|
|
48
|
+
//
|
|
49
|
+
// Same outcome on both tags: `nativeID = id ?? nativeID`, and the raw `id` key must NOT reach
|
|
50
|
+
// Fabric (no ViewConfig declares it, so it is silently dropped). What the adapters actually did:
|
|
51
|
+
// Solid folded it on both tags, Svelte on View only (its `else` branch skips Text), and Vue on
|
|
52
|
+
// neither — not in either transform, not in its renderer, not in `routeProp`. Vue's gap is OLDER
|
|
53
|
+
// than lowering (its `View` wrapper was always a bare pass-through), so it is a standing
|
|
54
|
+
// <adapters_reach_full_feature_parity> miss, not a lowering regression.
|
|
55
|
+
//
|
|
56
|
+
// WHICH LAYER APPLIES IT IS THE ADAPTER'S CHOICE, exactly as for `defaults` below. Solid and
|
|
57
|
+
// Svelte rename at COMPILE time inside the lowering transform; Vue applies it at RUNTIME
|
|
58
|
+
// (`PROP_ALIASES` in `adapters/vue/src/renderer/index.ts`, in `patchProp`) because Vue has FOUR
|
|
59
|
+
// paths to a node — lowered SFC, lowered TSX, the component wrapper, and a hand-written
|
|
60
|
+
// `h('view', {id})` — and compile time only covers two of them. A transform reading this
|
|
61
|
+
// spec must therefore not assume it owns the fold. Applying it at both layers happens to be
|
|
62
|
+
// harmless here (the rename deletes `id`, so the second pass sees nothing), but that is a property
|
|
63
|
+
// of THIS alias, not a licence.
|
|
64
|
+
//
|
|
65
|
+
// KNOWN DIVERGENCE FROM UPSTREAM, present in two adapters and not introduced by this file: when an
|
|
66
|
+
// element carries BOTH `id` and `nativeID`, RN gives `id` unconditional priority, while a per-key
|
|
67
|
+
// rename (Vue's runtime patchProp, Solid's compile-time rename producing two `nativeID`
|
|
68
|
+
// attributes) lets the LAST one win. Honest parity needs per-node state; no example and no test
|
|
69
|
+
// sets both today.
|
|
70
|
+
const ID_ALIAS = { id: 'nativeID' };
|
|
71
|
+
|
|
72
|
+
/** @type {Record<string, IHostPrimitive>} */
|
|
73
|
+
const HOST_PRIMITIVES = {
|
|
74
|
+
View: {
|
|
75
|
+
intrinsic: 'view',
|
|
76
|
+
aliases: ID_ALIAS,
|
|
77
|
+
defaults: {},
|
|
78
|
+
},
|
|
79
|
+
// The five-way switch, thrown 2026-08-23 once all three transforms carried the refusals
|
|
80
|
+
// (`observesState` below). `pressable` resolves to the SAME `RCTView` a plain view does
|
|
81
|
+
// — the tag exists only so the host-behavior registry, which is keyed by TAG and never by
|
|
82
|
+
// resolved name, can find the press machine.
|
|
83
|
+
//
|
|
84
|
+
// No defaults, and only the `id` -> `nativeID` alias every primitive carries: a lowered Pressable
|
|
85
|
+
// forwards its props otherwise untouched, and the machine reads them off `node.props` at event
|
|
86
|
+
// time. (This read "No aliases and no defaults" while the line below already said `ID_ALIAS`, and
|
|
87
|
+
// a Solid test injected a `Pressable` entry with `aliases: {}` on the strength of it.)
|
|
88
|
+
Pressable: {
|
|
89
|
+
intrinsic: 'pressable',
|
|
90
|
+
aliases: ID_ALIAS,
|
|
91
|
+
defaults: {},
|
|
92
|
+
// Turns on `stateInTemplate` and `renderPropChild`. Without them a render-prop button becomes
|
|
93
|
+
// a tag with no machine — the whole reason this entry landed last.
|
|
94
|
+
observesState: true,
|
|
95
|
+
},
|
|
96
|
+
// The three that were WITHHELD until their wrappers collapsed, landed 2026-09-11 now that none
|
|
97
|
+
// exists on any adapter. The condition the old note stated — "it lands when the wrappers collapse
|
|
98
|
+
// to a forwarder over the tag" — was met by deleting them outright.
|
|
99
|
+
//
|
|
100
|
+
// What the entry buys, since the tag and its behavior already worked without one: a hand-written
|
|
101
|
+
// `<scroll-view>` was resolving as a COMPONENT on Vue, because `adapters/vue/intrinsic-tags.cjs`
|
|
102
|
+
// derives its element set from this table and both Vue compilers read it. Missing here, the tag
|
|
103
|
+
// cost a dev-mode resolve warning per element plus the component codegen path — a slot closure
|
|
104
|
+
// instead of `_createElementBlock`.
|
|
105
|
+
//
|
|
106
|
+
// `aliases: ID_ALIAS` even though each behavior's own `foldPayload` already renames `id`. The
|
|
107
|
+
// two COMPOSE: `foldHostBag` deletes the source key, so the behavior's fold finds no `id` and is
|
|
108
|
+
// a no-op. Measured on the committed payload rather than reasoned, both arms identical — the
|
|
109
|
+
// check `adapter-parity-audit.md` demands before declining a majority value, run in the
|
|
110
|
+
// direction of accepting one.
|
|
111
|
+
TouchableOpacity: {
|
|
112
|
+
intrinsic: 'touchable-opacity',
|
|
113
|
+
aliases: ID_ALIAS,
|
|
114
|
+
defaults: {},
|
|
115
|
+
},
|
|
116
|
+
TouchableHighlight: {
|
|
117
|
+
intrinsic: 'touchable-highlight',
|
|
118
|
+
aliases: ID_ALIAS,
|
|
119
|
+
defaults: {},
|
|
120
|
+
},
|
|
121
|
+
// The second primitive whose TAG depends on a prop, and the first where the prop is one RN's own
|
|
122
|
+
// API takes (`<ScrollView horizontal>`): the axis is a SEPARATE native ViewManager, not a flag on
|
|
123
|
+
// one view (`behaviors/scroll-view/shared.ts:131`). So an app may write either spelling and
|
|
124
|
+
// `resolveIntrinsicTag` picks the view, which is also what puts `horizontal-scroll-view` into
|
|
125
|
+
// Vue's element set — a tag apps write directly and which would otherwise resolve as a component.
|
|
126
|
+
ScrollView: {
|
|
127
|
+
intrinsic: 'scroll-view',
|
|
128
|
+
aliases: ID_ALIAS,
|
|
129
|
+
defaults: {},
|
|
130
|
+
intrinsicWhen: {
|
|
131
|
+
prop: 'horizontal',
|
|
132
|
+
intrinsic: 'horizontal-scroll-view',
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
// Landed 2026-08-31, on the second attempt. The first threw the switch with the runtime half
|
|
136
|
+
// unwired and was reverted the same hour; both gaps it exposed are closed here, and the record is
|
|
137
|
+
// kept because the SEQUENCE is the reusable part — an entry here is a switch for four transforms
|
|
138
|
+
// at once, so it goes in after every side is ready, never to prove the transforms work.
|
|
139
|
+
//
|
|
140
|
+
// 1. `registerTextInputBehavior()` is now called by `adapters/{vue,svelte,solid}/src/register.ts`,
|
|
141
|
+
// the three that lower. React and Angular have no lowering transform, so no lowered node ever
|
|
142
|
+
// exists there and neither carries a `register.ts` — the same reason they skip Pressable's.
|
|
143
|
+
//
|
|
144
|
+
// 2. The component path no longer shares these tags. It renders `text-input-managed`
|
|
145
|
+
// (`component-names/shared.ts`), because the registry is keyed by TAG and the wrappers run the
|
|
146
|
+
// same machine in their own lifecycle — one shared tag would have installed both copies on a
|
|
147
|
+
// wrapper-built node and fired `setInputFocused` twice per focus.
|
|
148
|
+
TextInput: {
|
|
149
|
+
intrinsic: 'text-input',
|
|
150
|
+
aliases: ID_ALIAS,
|
|
151
|
+
defaults: {},
|
|
152
|
+
// `multiline` picks between two SEPARATE native views, not one view with a flag, so the tag is
|
|
153
|
+
// decided at compile time and a runtime selector must refuse — a wrong view here is
|
|
154
|
+
// uncorrectable by any later prop write.
|
|
155
|
+
intrinsicWhen: {
|
|
156
|
+
prop: 'multiline',
|
|
157
|
+
intrinsic: 'text-input-multiline',
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
// Landed 2026-09-01, same order as TextInput and Image: runtime half built
|
|
161
|
+
// (`core/components/src/behaviors/switch.ts`), registered by the four lowering adapters, proven
|
|
162
|
+
// against the wrapper's payload (positive + negative controls, a break-tested async-timing case)
|
|
163
|
+
// before this key existed.
|
|
164
|
+
//
|
|
165
|
+
// `-managed` twin, same reason as TextInput: the behavior carries a machine (mirrors the last
|
|
166
|
+
// value native reported, sends a platform snap-back command on disagreement), so a wrapper-built
|
|
167
|
+
// node — which already runs that same machine in its own lifecycle — must not also get the
|
|
168
|
+
// engine's copy. `render-switch.ts` emits `switch-managed`; this key's `intrinsic` is
|
|
169
|
+
// the bare tag the behavior registry attaches to.
|
|
170
|
+
//
|
|
171
|
+
// IDEMPOTENCE OF THE FOLD IS MOOT HERE FOR A DIFFERENT REASON THAN IMAGE'S. Image's entry has no
|
|
172
|
+
// `-managed` twin, so its fold genuinely CAN run twice (component then lowered, same tag), and
|
|
173
|
+
// idempotence is what makes that safe — asserted, not assumed. Switch's fold is NOT trivial (it
|
|
174
|
+
// maps `trackColor`/`thumbColor`/`ios_backgroundColor` to native prop names, keyed on
|
|
175
|
+
// `Platform.OS`) and running it twice would NOT be a no-op — but the question never arises: the
|
|
176
|
+
// `-managed` split means only the bare `switch` tag ever carries this behavior, and the
|
|
177
|
+
// wrapper never emits that tag, so no node's payload ever passes through this fold more than
|
|
178
|
+
// once. Unreachable by construction, not idempotent by property — the same distinction
|
|
179
|
+
// TextInput's own entry draws for its fold.
|
|
180
|
+
//
|
|
181
|
+
// No `observesState`: nothing in Switch's public surface is a function-valued style or a
|
|
182
|
+
// render-prop child (`style?: IStyleProp<IViewStyle>`, never a callback), so neither
|
|
183
|
+
// `stateInTemplate` nor `renderPropChild` applies — unlike Pressable, whose machine is what
|
|
184
|
+
// forced that flag.
|
|
185
|
+
Switch: {
|
|
186
|
+
intrinsic: 'switch',
|
|
187
|
+
aliases: ID_ALIAS,
|
|
188
|
+
defaults: {},
|
|
189
|
+
},
|
|
190
|
+
// Filed as NOT LOWERABLE for a week under `.claude/rules/host-primitive-tier.md`'s "SECOND
|
|
191
|
+
// disqualifier" — its own node is a single element, but its POSITION is decided by the ScrollView
|
|
192
|
+
// and differs per platform (iOS a sibling before the content view, Android the scroll view's
|
|
193
|
+
// PARENT), and a per-node behavior cannot own a decision another component makes.
|
|
194
|
+
//
|
|
195
|
+
// That is settled and it was settled elsewhere: the ScrollView states the placement as DATA
|
|
196
|
+
// (`claimedChildren: { [REFRESH_CONTROL]: platform.claimMode }`, `behaviors/scroll-view/shared.ts`)
|
|
197
|
+
// and the ENGINE moves the node in `appendChild`. So the claim needs nothing from this primitive's
|
|
198
|
+
// own behavior — verified on a bare node with no wrapper anywhere, both platforms, in
|
|
199
|
+
// `behaviors/refresh-control.test.ts`.
|
|
200
|
+
//
|
|
201
|
+
// What the behavior owes is therefore only the CONTROLLED HANDSHAKE, and no fold at all: four of
|
|
202
|
+
// the five wrappers folded exactly `resolveAccessibilityProps`, which the engine already runs at
|
|
203
|
+
// `fabricProps` on every path — the same reason SafeAreaView has no behavior file.
|
|
204
|
+
//
|
|
205
|
+
// ID_ALIAS, and it is the SafeAreaView resolution rather than the SafeAreaView position: none of
|
|
206
|
+
// the five wrappers declared `id`, and upstream's RefreshControl spreads `...ViewProps`
|
|
207
|
+
// (RefreshControl.js:70), so that was a standing parity gap rather than a deliberate omission.
|
|
208
|
+
// The prop is declared on all five in the same change as this alias — half of it in either
|
|
209
|
+
// direction is broken (a fold for a key nobody can pass, or a raw `id` reaching a view whose
|
|
210
|
+
// ViewConfig declares none).
|
|
211
|
+
//
|
|
212
|
+
// No `-managed` twin: the behavior carries a machine, so it needs one owner per node, and it has
|
|
213
|
+
// one — the wrappers forward to this tag and none of them runs a mirror any more.
|
|
214
|
+
RefreshControl: {
|
|
215
|
+
intrinsic: 'refresh-control',
|
|
216
|
+
aliases: ID_ALIAS,
|
|
217
|
+
// None. RN seeds nothing: `refreshing` is required, and every other prop is per-platform
|
|
218
|
+
// styling the native view defaults itself.
|
|
219
|
+
defaults: {},
|
|
220
|
+
},
|
|
221
|
+
Text: {
|
|
222
|
+
intrinsic: 'text',
|
|
223
|
+
aliases: ID_ALIAS,
|
|
224
|
+
// RN's Text.js applies both unconditionally on the non-virtual path. Each key below cites
|
|
225
|
+
// the upstream line verbatim, because THIS DATA is now the thing that must not drift from RN.
|
|
226
|
+
// The
|
|
227
|
+
// authority on what they MEAN is `src/text-props.ts`'s resolveTextProps, which every wrapper
|
|
228
|
+
// path already calls; this is the same fold expressed as data so a COMPILE-time transform can
|
|
229
|
+
// emit it too. `notFalse`, never `nullish` — RN treats an explicit `undefined` like a missing
|
|
230
|
+
// prop and only a literal `false` opts out. Emit both keys unconditionally: a fold whose two
|
|
231
|
+
// branches emit different key sets is the hazard `.claude/rules/solid-descriptor-bridge.md` §1
|
|
232
|
+
// exists for.
|
|
233
|
+
defaults: {
|
|
234
|
+
// Text.js:291 processedProps.ellipsizeMode = ellipsizeMode ?? 'tail';
|
|
235
|
+
ellipsizeMode: { op: 'nullish', value: 'tail' },
|
|
236
|
+
// Text.js:289 processedProps.allowFontScaling = allowFontScaling !== false;
|
|
237
|
+
allowFontScaling: { op: 'notFalse' },
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
// FOLD-ONLY: the behavior registered for this tag carries a prop fold and nothing else — no
|
|
241
|
+
// listeners, no commit hook, no per-node runtime (`core/components/src/behaviors/image.ts`). The
|
|
242
|
+
// whole of the wrapper's body was prop mapping, so the lowered form owes exactly that.
|
|
243
|
+
//
|
|
244
|
+
// No `-managed` twin, unlike TextInput, and the reason is a measured PROPERTY rather than a
|
|
245
|
+
// precedent: `mapImageProps` is idempotent, so registering the fold on the tag `renderImage`
|
|
246
|
+
// already emits means a wrapper-built node simply folds a second time to no effect. Asserted in
|
|
247
|
+
// `behaviors/image.test.ts`; break-tested. TextInput's split is NOT about idempotence (its fold
|
|
248
|
+
// is idempotent too) — it is about one owner per node, because that behavior carries a machine.
|
|
249
|
+
//
|
|
250
|
+
// Entered LAST, after the runtime half was built, registered by all four lowering adapters and
|
|
251
|
+
// proven against the wrapper's payload. Adding this key is what makes every transform start
|
|
252
|
+
// lowering `Image` at once, so a fold that had not landed would surface as a raw `src` reaching
|
|
253
|
+
// Fabric — a key no ViewConfig declares, which throws nothing and paints nothing.
|
|
254
|
+
// THE ENTRY IS NOT OPTIONAL HERE, and the reason has nothing to do with folds: this table is what
|
|
255
|
+
// `adapters/vue/intrinsic-tags.cjs` derives element-vs-component from, and a hyphenated tag it
|
|
256
|
+
// does not name compiles to `resolveComponent("image-background")` — children become a slot the
|
|
257
|
+
// element path never reads, so the subtree renders BLANK with no error. `image`/`view`/`text` are
|
|
258
|
+
// real SVG element names and survive that gap; this one is not.
|
|
259
|
+
//
|
|
260
|
+
// `aliases: ID_ALIAS` was MEASURED against the arm without it rather than reasoned about, because
|
|
261
|
+
// `behaviors/image-background.ts` folds `id` itself on the built image. Both arms commit
|
|
262
|
+
// `nativeID` on the image and no `id` anywhere, and the two compose because an alias DELETES its
|
|
263
|
+
// source key — so the second pass finds nothing. Kept for the property `foldHostBag` provides and
|
|
264
|
+
// the behavior cannot: the rename happens on the OWNER bag, before the redirect, so any adapter
|
|
265
|
+
// path that folds bags gets it whether or not the behavior ever runs.
|
|
266
|
+
ImageBackground: {
|
|
267
|
+
intrinsic: 'image-background',
|
|
268
|
+
aliases: ID_ALIAS,
|
|
269
|
+
// None. The absolute-fill style, the box-dimension proxy and the Image mapping are all derived
|
|
270
|
+
// from live props at commit, which a compile-time seed cannot express.
|
|
271
|
+
defaults: {},
|
|
272
|
+
},
|
|
273
|
+
Image: {
|
|
274
|
+
intrinsic: 'image',
|
|
275
|
+
aliases: ID_ALIAS,
|
|
276
|
+
// None. Every default RN's Image applies is already inside the shared mapping (the source
|
|
277
|
+
// array shape, the width/height style fold, `alt` -> accessibilityLabel), which the behavior
|
|
278
|
+
// runs at commit — so there is nothing left for a compile-time seed to do.
|
|
279
|
+
defaults: {},
|
|
280
|
+
},
|
|
281
|
+
// Entered LAST, same order Image used: runtime half built, registered by the four lowering
|
|
282
|
+
// adapters, and proven against the wrapper's payload before this key existed.
|
|
283
|
+
//
|
|
284
|
+
// The ONLY primitive so far whose intrinsic resolves to a different Fabric component per
|
|
285
|
+
// platform — `RCTInputAccessoryView` on iOS, a plain `RCTView` on Android. The fold is
|
|
286
|
+
// platform-invariant on purpose (it reproduces the wrapper's mapping on both, so the lowered and
|
|
287
|
+
// wrapped paths cannot diverge per platform); what it does NOT do is repair what sits underneath,
|
|
288
|
+
// where upstream RN renders nothing at all off iOS. That divergence predates the lowering, is
|
|
289
|
+
// identical on both paths, and is with the owner as its own decision.
|
|
290
|
+
InputAccessoryView: {
|
|
291
|
+
intrinsic: 'input-accessory-view',
|
|
292
|
+
aliases: ID_ALIAS,
|
|
293
|
+
// None. The mapping has no aliasing and no derived value — every consumed name leaves under the
|
|
294
|
+
// same name — so there is nothing for a compile-time seed to do.
|
|
295
|
+
defaults: {},
|
|
296
|
+
},
|
|
297
|
+
// The emptiest entry here, and deliberately so — the withholding protocol has nothing to protect
|
|
298
|
+
// for this one. Every other primitive was held back until its runtime half existed and was proven
|
|
299
|
+
// against the wrapper's payload; SafeAreaView has no runtime half to build. All five adapters fold
|
|
300
|
+
// exactly one thing, `resolveAccessibilityProps`, and that fold already runs in the engine at
|
|
301
|
+
// `fabricProps` on both commit paths (the `aria-bag-fold` row). So there is no
|
|
302
|
+
// `behaviors/safe-area-view.ts`, and a reader who assumes one exists will go looking for a file
|
|
303
|
+
// that was never needed.
|
|
304
|
+
//
|
|
305
|
+
// Counted before writing, which is the only thing standing behind that claim: five
|
|
306
|
+
// implementations, zero shared, none synthesizing a node — each renders ONE
|
|
307
|
+
// `safe-area-view` with children on its framework's own channel (React's third argument,
|
|
308
|
+
// a Vue slot, a Solid JSX child, Angular's `<ng-content>`, a Svelte snippet). That clears the
|
|
309
|
+
// disqualifier in `.claude/rules/host-primitive-tier.md`.
|
|
310
|
+
//
|
|
311
|
+
// NO `ID_ALIAS`, and this is the one place SafeAreaView departs from every entry above it. The
|
|
312
|
+
// alias exists to REPRODUCE a fold the wrapper performs; not one of the five SafeAreaView wrappers
|
|
313
|
+
// folds `id`, and none declares it. Adding the alias here would make the lowered element fold a
|
|
314
|
+
// prop its component spelling passes through untouched — a lowering that ADDS a capability, which
|
|
315
|
+
// `.claude/rules/adapter-parity-audit.md` records as a bug in the same way as one that drops it.
|
|
316
|
+
// That the five entries above all share `ID_ALIAS` is a property of those five primitives, not a
|
|
317
|
+
// house style to copy: the sixth is where "every case so far did X" stops being a rule.
|
|
318
|
+
//
|
|
319
|
+
// The `id` surface gap itself is real and PRE-EXISTING — upstream's SafeAreaView takes `ViewProps`,
|
|
320
|
+
// so RN accepts `id` where our wrappers do not. It predates lowering, is identical on both paths,
|
|
321
|
+
// and closing it means adding `id` to five wrappers AND this alias together, never one of the two.
|
|
322
|
+
SafeAreaView: {
|
|
323
|
+
intrinsic: 'safe-area-view',
|
|
324
|
+
// ID_ALIAS was deliberately ABSENT here until 2026-09-01, because none of the five wrappers
|
|
325
|
+
// declared `id` and aliasing on the lowered path alone would have made lowering ADD a fold the
|
|
326
|
+
// component spelling does not perform. That exposed a real divergence — Solid's renderer folds
|
|
327
|
+
// `id` from two string constants on the write path, so it aliased for a primitive whose spec
|
|
328
|
+
// said not to (`adapters/solid/src/renderer-alias-fold.test.ts`, whose header predicted exactly
|
|
329
|
+
// this the day a primitive stopped sharing the pair).
|
|
330
|
+
//
|
|
331
|
+
// Resolved by closing the gap rather than routing around it: `id` is now declared on all five
|
|
332
|
+
// wrappers and folded here. That keeps Solid's constant-pair fast path (32 001 writes on a
|
|
333
|
+
// benchmark create) and removes a real parity deficit — upstream's SafeAreaView takes the full
|
|
334
|
+
// ViewProps surface, so RN accepts `id` where none of ours did. Half of this is not an option
|
|
335
|
+
// in either direction: the alias without the prop folds a key nobody can pass, and the prop
|
|
336
|
+
// without the alias sends a raw `id` to a view whose ViewConfig declares no such key.
|
|
337
|
+
aliases: ID_ALIAS,
|
|
338
|
+
defaults: {},
|
|
339
|
+
},
|
|
340
|
+
// The one primitive that commits NO NODE: its intrinsic resolves to the engine's anchor, and the
|
|
341
|
+
// behavior (`src/behaviors/touchable-native-feedback.ts`) clones the owner's props onto the single
|
|
342
|
+
// child instead — RN's own shape (TouchableNativeFeedback.js:289,339). Entered in the same commit
|
|
343
|
+
// that deletes the five wrappers, because the registry is keyed by TAG: a wrapper still emitting
|
|
344
|
+
// its own `pressable` while the behavior is registered would put two press machines on one tree.
|
|
345
|
+
//
|
|
346
|
+
// ID_ALIAS, and it was proposed WITHOUT one on the reasoning that the behavior already reads
|
|
347
|
+
// `id ?? nativeID` itself (:373) so the shared alias would double-fold. Measured instead of
|
|
348
|
+
// reasoned (`behaviors/touchable-native-feedback.test.ts`, "folds `id` the same whichever layer
|
|
349
|
+
// renamed it"): the two compose idempotently — the alias renames on the OWNER, whose props never
|
|
350
|
+
// reach Fabric, and the behavior's `??` then reads the renamed key to the same answer. Declining
|
|
351
|
+
// the pair would have bought nothing and broken Solid's constant-pair fast path, whose guard
|
|
352
|
+
// (`adapters/solid/src/renderer-alias-fold.test.ts`) is what makes one string compare legal on
|
|
353
|
+
// 32 001 prop writes.
|
|
354
|
+
//
|
|
355
|
+
// No `defaults`: RN's TNF seeds nothing at all — every value it derives (`accessible`,
|
|
356
|
+
// `focusable`, `accessibilityState`, the ripple background) depends on ANOTHER prop or on a
|
|
357
|
+
// listener, which is a fold and not a default.
|
|
358
|
+
TouchableNativeFeedback: {
|
|
359
|
+
intrinsic: 'touchable-native-feedback',
|
|
360
|
+
aliases: ID_ALIAS,
|
|
361
|
+
defaults: {},
|
|
362
|
+
},
|
|
363
|
+
// The SECOND primitive that commits no node, same anchor shape and same reason
|
|
364
|
+
// (TouchableWithoutFeedback.js:229,286). Its clone list is not TNF's: the passthrough half is
|
|
365
|
+
// copied only when SET (:281), there is no ripple, and `onBlur`/`onFocus` are cloned where TNF
|
|
366
|
+
// drops them — read `src/behaviors/touchable-without-feedback.ts`'s header rather than inheriting
|
|
367
|
+
// the neighbour's fold.
|
|
368
|
+
//
|
|
369
|
+
// ID_ALIAS for the reason measured on TNF: the alias renames on the OWNER, whose props never reach
|
|
370
|
+
// Fabric, and the behavior's own `id ?? nativeID` then reads the renamed key to the same answer.
|
|
371
|
+
// Upstream's passthrough loop lets an explicit `nativeID` win over `id` here (:280-284, unlike
|
|
372
|
+
// TNF's :373); NOT reproduced, because with the alias in place that quirk would depend on which
|
|
373
|
+
// adapter folds where. No `defaults` — every value TWF derives depends on another prop or on a
|
|
374
|
+
// listener, which is a fold and not a default.
|
|
375
|
+
TouchableWithoutFeedback: {
|
|
376
|
+
intrinsic: 'touchable-without-feedback',
|
|
377
|
+
aliases: ID_ALIAS,
|
|
378
|
+
defaults: {},
|
|
379
|
+
},
|
|
380
|
+
// RN's Button is a touchable wrapping a View wrapping a Text and takes NO children — `title` is a
|
|
381
|
+
// string prop (Button.js:363-388) — so the behavior owns the whole subtree and the tag is the
|
|
382
|
+
// only spelling. Entered in the same commit that deletes the five wrappers: the registry is keyed
|
|
383
|
+
// by TAG, and a wrapper still building its own View/Text under a registered `button` would give
|
|
384
|
+
// every existing Button a second copy of the subtree.
|
|
385
|
+
//
|
|
386
|
+
// ID_ALIAS, and here it is REQUIRED rather than inherited — the one entry so far where declining
|
|
387
|
+
// it would have shipped a PLATFORM-DEPENDENT bug. Button's touchable is swapped by platform
|
|
388
|
+
// (Button.js:281-284), and only one of the two arms renames `id` itself: `touchable-opacity`'s
|
|
389
|
+
// own `foldPayload` does (it has no spec entry to do it for it), the bare press behavior does not
|
|
390
|
+
// (`Pressable`'s entry does it instead). Measured on the committed payload, no entry here:
|
|
391
|
+
//
|
|
392
|
+
// iOS nativeID: 'from-id' id: absent the touchable-opacity fold
|
|
393
|
+
// Android nativeID: undefined id: 'from-id' a key no ViewConfig declares -> dropped
|
|
394
|
+
//
|
|
395
|
+
// So the alias is what makes the two platforms agree, and it composes idempotently with the
|
|
396
|
+
// iOS-side fold exactly as TNF's does: the rename happens on the bag, so `Object.hasOwn(next,
|
|
397
|
+
// 'id')` one layer down finds nothing left to do.
|
|
398
|
+
//
|
|
399
|
+
// No `defaults`: every value RN's Button seeds is derived from another prop or from a listener
|
|
400
|
+
// (`accessible`, `focusable`, the greyed label, the uppercased title), which is a fold, not a
|
|
401
|
+
// default.
|
|
402
|
+
Button: {
|
|
403
|
+
intrinsic: 'button',
|
|
404
|
+
aliases: ID_ALIAS,
|
|
405
|
+
defaults: {},
|
|
406
|
+
},
|
|
407
|
+
// RN wraps the native spinner in a centering `<View>` (ActivityIndicator.js:112), so this tag is
|
|
408
|
+
// that View and the behavior builds `activity-indicator-spinner` under it. Entered in the same
|
|
409
|
+
// commit that deletes the five wrappers: the registry is keyed by TAG, and a wrapper still
|
|
410
|
+
// painting its own spinner while the behavior is registered would give every indicator two.
|
|
411
|
+
//
|
|
412
|
+
// ID_ALIAS, and unlike Button's it is not platform-dependent — measured on the committed payload,
|
|
413
|
+
// both platform arms, with the entry absent:
|
|
414
|
+
//
|
|
415
|
+
// iOS spinner: id 'probe' nativeID absent ActivityIndicatorView declares no `id`
|
|
416
|
+
// Android spinner: id 'probe' nativeID absent AndroidProgressBar declares no `id`
|
|
417
|
+
//
|
|
418
|
+
// i.e. identically broken on both, because the platform half of this primitive is the spinner's
|
|
419
|
+
// COLOUR and native extras, and nothing about it touches the name fold. The alias renames on the
|
|
420
|
+
// OWNER's bag, before `slotPropsExcept` routes the survivor down — so the key that reaches the
|
|
421
|
+
// spinner is `nativeID`, which is where RN's `...restProps` puts it too (ActivityIndicator.js:99).
|
|
422
|
+
//
|
|
423
|
+
// No `defaults`: `animating` and `hidesWhenStopped` ARE `notFalse` folds, but they belong to the
|
|
424
|
+
// SPINNER, and this table's ops are applied to the tag's own bag before any slot routing. They
|
|
425
|
+
// live in the behavior's spinner fold instead, which is the only layer that can see that node.
|
|
426
|
+
ActivityIndicator: {
|
|
427
|
+
intrinsic: 'activity-indicator',
|
|
428
|
+
aliases: ID_ALIAS,
|
|
429
|
+
defaults: {},
|
|
430
|
+
},
|
|
431
|
+
};
|
|
432
|
+
module.exports = { HOST_PRIMITIVES };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Types for the lowering spec. Hand-written because the spec itself must stay `.cjs` — its
|
|
2
|
+
// consumers are Babel/Metro transforms that run before any TS exists (see the module's header).
|
|
3
|
+
// Svelte's preprocessor is TS and reads the same file through these.
|
|
4
|
+
|
|
5
|
+
export type IFoldOp = { op: 'nullish'; value: unknown } | { op: 'notFalse' };
|
|
6
|
+
|
|
7
|
+
export interface IHostPrimitive {
|
|
8
|
+
intrinsic: string;
|
|
9
|
+
aliases: Readonly<Record<string, string>>;
|
|
10
|
+
defaults: Readonly<Record<string, IFoldOp>>;
|
|
11
|
+
/**
|
|
12
|
+
* The primitive's own state is observable from the TEMPLATE, so a call site that reads it
|
|
13
|
+
* cannot be lowered — the state resolves below the framework (`:active` in the style registry)
|
|
14
|
+
* and never travels back up. Turns on the `stateInTemplate` and `renderPropChild` refusals.
|
|
15
|
+
* Absent means false; only a stateful primitive ever sets it.
|
|
16
|
+
*/
|
|
17
|
+
observesState?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* The primitive's TAG depends on a prop. `TextInput` is the only one: `multiline` selects between
|
|
20
|
+
* two different Fabric views (`symbiote-text-input` / `symbiote-text-input-multiline`), not
|
|
21
|
+
* between two values of one view. A transform prints a static tag, so it resolves the choice only
|
|
22
|
+
* for a literal — and the boundary is IDENTITY, not truthiness: a bare attribute is `true`, an
|
|
23
|
+
* explicit boolean literal is itself, absence is `false`, and everything else refuses (including a
|
|
24
|
+
* truthy non-boolean like `multiline={1}`, which a type-shaped check waves through).
|
|
25
|
+
*
|
|
26
|
+
* ONE selector and ONE alternative, deliberately. Absent means "one tag", so no existing entry
|
|
27
|
+
* changes. Full rationale in `host-primitives.cjs`, which this file must be kept in step with —
|
|
28
|
+
* see the note there.
|
|
29
|
+
*/
|
|
30
|
+
intrinsicWhen?: { prop: string; intrinsic: string };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export declare const HOST_PRIMITIVES: Readonly<Record<string, IHostPrimitive>>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@symbiote-native/components",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "2.0.0",
|
|
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": {
|
|
@@ -17,6 +17,16 @@
|
|
|
17
17
|
"main": "./build/index.js",
|
|
18
18
|
"module": "./build/index.js",
|
|
19
19
|
"types": "./build/index.d.ts",
|
|
20
|
+
"keywords": [
|
|
21
|
+
"react-native",
|
|
22
|
+
"symbiote-native",
|
|
23
|
+
"cross-platform-components",
|
|
24
|
+
"state-machine",
|
|
25
|
+
"ui-components",
|
|
26
|
+
"descriptor",
|
|
27
|
+
"accessibility",
|
|
28
|
+
"cross-platform"
|
|
29
|
+
],
|
|
20
30
|
"exports": {
|
|
21
31
|
".": {
|
|
22
32
|
"types": "./build/index.d.ts",
|
|
@@ -25,21 +35,39 @@
|
|
|
25
35
|
"./bootstrap": {
|
|
26
36
|
"types": "./build/bootstrap/index.d.ts",
|
|
27
37
|
"default": "./build/bootstrap/index.js"
|
|
38
|
+
},
|
|
39
|
+
"./host-primitives": {
|
|
40
|
+
"types": "./host-primitives.d.cts",
|
|
41
|
+
"default": "./host-primitives.cjs"
|
|
42
|
+
},
|
|
43
|
+
"./fold-host-bag": {
|
|
44
|
+
"types": "./build/fold-host-bag.d.ts",
|
|
45
|
+
"default": "./build/fold-host-bag.js"
|
|
46
|
+
},
|
|
47
|
+
"./resolve-intrinsic": {
|
|
48
|
+
"types": "./build/resolve-intrinsic.d.ts",
|
|
49
|
+
"default": "./build/resolve-intrinsic.js"
|
|
50
|
+
},
|
|
51
|
+
"./register": {
|
|
52
|
+
"types": "./build/register.d.ts",
|
|
53
|
+
"default": "./build/register.js"
|
|
28
54
|
}
|
|
29
55
|
},
|
|
30
56
|
"files": [
|
|
31
|
-
"build"
|
|
57
|
+
"build",
|
|
58
|
+
"host-primitives.cjs",
|
|
59
|
+
"host-primitives.d.cts"
|
|
32
60
|
],
|
|
33
61
|
"publishConfig": {
|
|
34
62
|
"access": "public"
|
|
35
63
|
},
|
|
36
64
|
"peerDependencies": {
|
|
37
|
-
"
|
|
38
|
-
"
|
|
65
|
+
"react-native": ">=0.86",
|
|
66
|
+
"@symbiote-native/engine": "^0.5.0"
|
|
39
67
|
},
|
|
40
68
|
"devDependencies": {
|
|
41
69
|
"@types/node": "^26.0.0",
|
|
42
|
-
"@symbiote-native/engine": "0.
|
|
70
|
+
"@symbiote-native/engine": "0.5.0"
|
|
43
71
|
},
|
|
44
72
|
"scripts": {
|
|
45
73
|
"typecheck": "tsc --build",
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import type { IStyleProp, IViewStyle, ISymbioteEvent } from '@symbiote-native/engine';
|
|
2
|
-
import type { IDescriptor } from '../descriptor';
|
|
3
|
-
import type { IAccessibilityProps, IAriaProps } from '../accessibility-props';
|
|
4
|
-
export type IActivityIndicatorSize = 'small' | 'large' | number;
|
|
5
|
-
export interface IActivityIndicatorProps extends IAccessibilityProps, IAriaProps {
|
|
6
|
-
animating?: boolean;
|
|
7
|
-
color?: string;
|
|
8
|
-
size?: IActivityIndicatorSize;
|
|
9
|
-
hidesWhenStopped?: boolean;
|
|
10
|
-
style?: IStyleProp<IViewStyle>;
|
|
11
|
-
onLayout?: (event: ISymbioteEvent) => void;
|
|
12
|
-
}
|
|
13
|
-
export type IActivityIndicatorViewProps = {
|
|
14
|
-
animating: boolean;
|
|
15
|
-
hidesWhenStopped: boolean;
|
|
16
|
-
size: IActivityIndicatorSize;
|
|
17
|
-
color?: string;
|
|
18
|
-
style?: IStyleProp<IViewStyle>;
|
|
19
|
-
passthrough: Record<string, unknown>;
|
|
20
|
-
};
|
|
21
|
-
export type IActivityIndicatorPlatform = {
|
|
22
|
-
defaultColor: string | null;
|
|
23
|
-
nativeExtras: Readonly<Record<string, unknown>>;
|
|
24
|
-
};
|
|
25
|
-
export declare function renderActivityIndicator(view: IActivityIndicatorViewProps, platform: IActivityIndicatorPlatform): IDescriptor;
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
// ActivityIndicator: the render half (framework-agnostic). RN wraps the native spinner
|
|
2
|
-
// in a centering View and translates `size` in JS: 'small'/'large' map to a native size
|
|
3
|
-
// enum AND a fixed box style; a numeric size never reaches native (it sizes the spinner
|
|
4
|
-
// via style only). That translation is platform-invariant and lives here.
|
|
5
|
-
//
|
|
6
|
-
// What IS platform-specific: Android's AndroidProgressBar needs
|
|
7
|
-
// `styleAttr` (which triggers its setStyle(), without it the view throws "setStyle() not
|
|
8
|
-
// called") plus `indeterminate: true`, and its default color is the theme (null), whereas
|
|
9
|
-
// iOS's ActivityIndicatorView takes neither and defaults to GRAY. The adapter's per-host
|
|
10
|
-
// file supplies those bits via `platform`.
|
|
11
|
-
import { dlog } from '@symbiote-native/engine';
|
|
12
|
-
import { el } from '../descriptor.js';
|
|
13
|
-
// Fixed pixel boxes RN gives the two named sizes (styles.sizeSmall/sizeLarge).
|
|
14
|
-
const SIZE_SMALL_PX = 20;
|
|
15
|
-
const SIZE_LARGE_PX = 36;
|
|
16
|
-
// Centering wrapper RN puts around the spinner (styles.container).
|
|
17
|
-
const CONTAINER_STYLE = {
|
|
18
|
-
alignItems: 'center',
|
|
19
|
-
justifyContent: 'center',
|
|
20
|
-
};
|
|
21
|
-
function resolveSize(size) {
|
|
22
|
-
if (size === 'small') {
|
|
23
|
-
return {
|
|
24
|
-
sizeStyle: { width: SIZE_SMALL_PX, height: SIZE_SMALL_PX },
|
|
25
|
-
sizeProp: 'small',
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
if (size === 'large') {
|
|
29
|
-
return {
|
|
30
|
-
sizeStyle: { width: SIZE_LARGE_PX, height: SIZE_LARGE_PX },
|
|
31
|
-
sizeProp: 'large',
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
return { sizeStyle: { width: size, height: size } };
|
|
35
|
-
}
|
|
36
|
-
export function renderActivityIndicator(view, platform) {
|
|
37
|
-
const { sizeStyle, sizeProp } = resolveSize(view.size);
|
|
38
|
-
dlog(sizeProp !== undefined
|
|
39
|
-
? `ActivityIndicator size '${sizeProp}' -> native size enum '${sizeProp}'`
|
|
40
|
-
: `ActivityIndicator size ${String(view.size)} -> style only, native size not set`);
|
|
41
|
-
const nativeProps = {
|
|
42
|
-
animating: view.animating,
|
|
43
|
-
hidesWhenStopped: view.hidesWhenStopped,
|
|
44
|
-
style: sizeStyle,
|
|
45
|
-
...platform.nativeExtras,
|
|
46
|
-
};
|
|
47
|
-
// Omit color entirely when neither given nor defaulted (Android's theme default is
|
|
48
|
-
// null); a null color prop would be rejected by Fabric's color parser.
|
|
49
|
-
const resolvedColor = view.color ?? platform.defaultColor;
|
|
50
|
-
if (resolvedColor !== null)
|
|
51
|
-
nativeProps.color = resolvedColor;
|
|
52
|
-
if (sizeProp !== undefined)
|
|
53
|
-
nativeProps.size = sizeProp;
|
|
54
|
-
dlog('ActivityIndicator -> RCTView(spinner)');
|
|
55
|
-
const wrapperProps = {
|
|
56
|
-
...view.passthrough,
|
|
57
|
-
style: [CONTAINER_STYLE, view.style],
|
|
58
|
-
};
|
|
59
|
-
return el('symbiote-view', wrapperProps, [
|
|
60
|
-
el('symbiote-activity-indicator', nativeProps),
|
|
61
|
-
]);
|
|
62
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { type IStyleProp, type IViewStyle } from '@symbiote-native/engine';
|
|
2
|
-
import { type IDescriptor } from '../descriptor';
|
|
3
|
-
import { type IImageViewProps } from './render-image';
|
|
4
|
-
export type IImageBackgroundViewProps = {
|
|
5
|
-
style?: IStyleProp<IViewStyle>;
|
|
6
|
-
imageStyle?: IStyleProp<IViewStyle>;
|
|
7
|
-
image: IImageViewProps;
|
|
8
|
-
};
|
|
9
|
-
export declare function renderImageBackground(view: IImageBackgroundViewProps): IDescriptor;
|