@symbiote-native/components 1.0.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -13
- package/build/accessibility-props.d.ts +1 -1
- package/build/accessibility-props.js +2 -2
- 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 +120 -0
- package/build/behaviors/button.d.ts +13 -0
- package/build/behaviors/button.js +340 -0
- package/build/behaviors/image-background.d.ts +3 -0
- package/build/behaviors/image-background.js +155 -0
- package/build/behaviors/image.d.ts +1 -2
- package/build/behaviors/image.js +25 -106
- package/build/behaviors/input-accessory-view.d.ts +1 -2
- package/build/behaviors/input-accessory-view.js +49 -55
- package/build/behaviors/pressable.d.ts +59 -1
- package/build/behaviors/pressable.js +142 -96
- package/build/behaviors/refresh-control.d.ts +2 -0
- package/build/behaviors/refresh-control.js +96 -0
- package/build/behaviors/scroll-view/index.android.d.ts +1 -0
- package/build/behaviors/scroll-view/index.android.js +40 -0
- package/build/behaviors/scroll-view/index.d.ts +3 -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 +8 -0
- package/build/behaviors/scroll-view/responder.d.ts +4 -0
- package/build/behaviors/scroll-view/responder.js +202 -0
- package/build/behaviors/scroll-view/shared.d.ts +9 -0
- package/build/behaviors/scroll-view/shared.js +291 -0
- package/build/behaviors/scroll-view/sticky.d.ts +18 -0
- package/build/behaviors/scroll-view/sticky.js +581 -0
- package/build/behaviors/switch.d.ts +1 -1
- package/build/behaviors/switch.js +49 -88
- package/build/behaviors/text-input.d.ts +2 -2
- package/build/behaviors/text-input.js +244 -104
- package/build/behaviors/touchable-highlight.d.ts +9 -0
- package/build/behaviors/touchable-highlight.js +205 -0
- package/build/behaviors/touchable-native-feedback.d.ts +20 -0
- package/build/behaviors/touchable-native-feedback.js +254 -0
- package/build/behaviors/touchable-opacity.d.ts +12 -0
- package/build/behaviors/touchable-opacity.js +239 -0
- package/build/behaviors/touchable-without-feedback.d.ts +2 -0
- package/build/behaviors/touchable-without-feedback.js +231 -0
- package/build/component-names/index.android.js +31 -25
- package/build/component-names/index.ios.js +28 -23
- package/build/component-names/shared.d.ts +2 -1
- package/build/component-names/shared.js +16 -6
- package/build/descriptor.js +4 -4
- package/build/index.d.ts +26 -26
- package/build/index.js +56 -27
- package/build/register.d.ts +1 -0
- package/build/register.js +55 -0
- package/build/resolve-intrinsic.js +3 -9
- package/build/scroll-view-commands.d.ts +1 -5
- package/build/scroll-view-commands.js +23 -85
- package/build/state/flat-list.d.ts +2 -2
- package/build/state/flat-list.js +10 -2
- package/build/state/pressable.d.ts +6 -1
- package/build/state/pressable.js +63 -28
- package/build/state/section-list.d.ts +2 -0
- package/build/state/section-list.js +14 -7
- package/build/state/text-input.d.ts +10 -40
- package/build/state/text-input.js +17 -186
- package/build/state/touchable.d.ts +1 -0
- package/build/state/touchable.js +11 -8
- package/build/state/virtualized-list-reducer.d.ts +2 -2
- package/build/state/virtualized-list.d.ts +6 -6
- package/build/state/virtualized-list.js +71 -37
- package/build/text-props.d.ts +0 -8
- package/build/text-props.js +14 -25
- package/build/view/render-button.d.ts +11 -4
- package/build/view/render-button.js +74 -22
- package/build/view/render-image/index.d.ts +14 -1
- package/build/view/render-image/index.js +22 -147
- package/build/view/render-input-accessory-view.d.ts +1 -5
- package/build/view/render-input-accessory-view.js +26 -48
- package/build/view/render-keyboard-avoiding-view.d.ts +7 -1
- package/build/view/render-keyboard-avoiding-view.js +40 -1
- package/build/view/render-modal.d.ts +1 -1
- package/build/view/render-modal.js +18 -8
- package/build/view/render-pressable/index.d.ts +3 -0
- package/build/view/render-pressable/index.js +28 -0
- package/build/view/render-scroll-view.d.ts +1 -4
- package/build/view/render-scroll-view.js +17 -54
- package/build/view/render-switch.d.ts +4 -15
- package/build/view/render-switch.js +4 -42
- package/build/view/render-touchable-highlight.d.ts +1 -0
- package/build/view/render-touchable-native-feedback.d.ts +19 -1
- package/build/view/render-touchable-native-feedback.js +34 -9
- package/host-primitives.cjs +178 -280
- package/host-primitives.d.cts +0 -3
- package/package.json +8 -21
- package/build/fold-host-bag.d.ts +0 -15
- package/build/fold-host-bag.js +0 -99
- package/build/state-style.d.ts +0 -15
- package/build/state-style.js +0 -47
- package/build/view/render-activity-indicator.d.ts +0 -25
- package/build/view/render-activity-indicator.js +0 -88
- package/build/view/render-image-background.d.ts +0 -9
- package/build/view/render-image-background.js +0 -48
- package/build/view/render-text-input.d.ts +0 -11
- package/build/view/render-text-input.js +0 -39
- package/lowering-fixtures.cjs +0 -259
- package/lowering-fixtures.d.cts +0 -17
- package/specialize-state-style.cjs +0 -219
- package/specialize-state-style.d.cts +0 -15
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@symbiote-native/components",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.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": {
|
|
@@ -40,43 +40,30 @@
|
|
|
40
40
|
"types": "./host-primitives.d.cts",
|
|
41
41
|
"default": "./host-primitives.cjs"
|
|
42
42
|
},
|
|
43
|
-
"./specialize-state-style": "./specialize-state-style.cjs",
|
|
44
|
-
"./lowering-fixtures": {
|
|
45
|
-
"types": "./lowering-fixtures.d.cts",
|
|
46
|
-
"default": "./lowering-fixtures.cjs"
|
|
47
|
-
},
|
|
48
|
-
"./state-style": {
|
|
49
|
-
"types": "./build/state-style.d.ts",
|
|
50
|
-
"default": "./build/state-style.js"
|
|
51
|
-
},
|
|
52
|
-
"./fold-host-bag": {
|
|
53
|
-
"types": "./build/fold-host-bag.d.ts",
|
|
54
|
-
"default": "./build/fold-host-bag.js"
|
|
55
|
-
},
|
|
56
43
|
"./resolve-intrinsic": {
|
|
57
44
|
"types": "./build/resolve-intrinsic.d.ts",
|
|
58
45
|
"default": "./build/resolve-intrinsic.js"
|
|
46
|
+
},
|
|
47
|
+
"./register": {
|
|
48
|
+
"types": "./build/register.d.ts",
|
|
49
|
+
"default": "./build/register.js"
|
|
59
50
|
}
|
|
60
51
|
},
|
|
61
52
|
"files": [
|
|
62
53
|
"build",
|
|
63
54
|
"host-primitives.cjs",
|
|
64
|
-
"host-primitives.d.cts"
|
|
65
|
-
"specialize-state-style.cjs",
|
|
66
|
-
"specialize-state-style.d.cts",
|
|
67
|
-
"lowering-fixtures.cjs",
|
|
68
|
-
"lowering-fixtures.d.cts"
|
|
55
|
+
"host-primitives.d.cts"
|
|
69
56
|
],
|
|
70
57
|
"publishConfig": {
|
|
71
58
|
"access": "public"
|
|
72
59
|
},
|
|
73
60
|
"peerDependencies": {
|
|
74
61
|
"react-native": ">=0.86",
|
|
75
|
-
"@symbiote-native/engine": "^0.
|
|
62
|
+
"@symbiote-native/engine": "^1.0.0"
|
|
76
63
|
},
|
|
77
64
|
"devDependencies": {
|
|
78
65
|
"@types/node": "^26.0.0",
|
|
79
|
-
"@symbiote-native/engine": "0.
|
|
66
|
+
"@symbiote-native/engine": "1.0.0"
|
|
80
67
|
},
|
|
81
68
|
"scripts": {
|
|
82
69
|
"typecheck": "tsc --build",
|
package/build/fold-host-bag.d.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { type IFoldOp } from '../host-primitives.cjs';
|
|
2
|
-
export type IHostBag = Record<string, unknown>;
|
|
3
|
-
interface IFoldPlan {
|
|
4
|
-
readonly aliases: ReadonlyArray<readonly [string, string]>;
|
|
5
|
-
readonly defaults: ReadonlyArray<readonly [string, IFoldOp]>;
|
|
6
|
-
}
|
|
7
|
-
export declare const FOLD_PLAN_BY_TAG: ReadonlyMap<string, IFoldPlan>;
|
|
8
|
-
/**
|
|
9
|
-
* Apply a primitive's aliases and defaults to a bag, copy-on-write.
|
|
10
|
-
*
|
|
11
|
-
* Never mutates the input: the bag belongs to whoever built it, and a framework may hand the same
|
|
12
|
-
* object back on a re-render, so writing into it would leak a fold into the author's own state.
|
|
13
|
-
*/
|
|
14
|
-
export declare function foldHostBag(tagName: string, bag: IHostBag): IHostBag;
|
|
15
|
-
export {};
|
package/build/fold-host-bag.js
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
// The RUNTIME half of `HOST_PRIMITIVES` — RN's prop folds applied to a props bag on its way into
|
|
2
|
-
// the engine, keyed by intrinsic tag.
|
|
3
|
-
//
|
|
4
|
-
// Why it exists at all. A primitive's host node is produced by THREE paths, and the compile-time
|
|
5
|
-
// fold in a lowering transform covers exactly one of them:
|
|
6
|
-
//
|
|
7
|
-
// lowered `<View>` the transform folded it covered by the transform
|
|
8
|
-
// `<View>` it refused the wrapper folds it covered by the wrapper
|
|
9
|
-
// a hand-authored tag nobody folds it covered by NOTHING
|
|
10
|
-
//
|
|
11
|
-
// The third path is not hypothetical: Svelte's `components/button.svelte` writes `<symbiote-text>`
|
|
12
|
-
// directly rather than composing `Text.svelte`, so its title reached Fabric with no
|
|
13
|
-
// `ellipsizeMode` and clipped mid-word where every other adapter ellipsised (2026-08-31). Angular
|
|
14
|
-
// shipped the identical defect from the identical cause, and both fixed it the same way — a seed
|
|
15
|
-
// in the layer every path crosses.
|
|
16
|
-
//
|
|
17
|
-
// Why it is SHARED rather than one copy per adapter: an adapter that lowers has a transform doing
|
|
18
|
-
// this at compile time, an adapter that does not has only this, and a primitive with no wrapper at
|
|
19
|
-
// all (a bare intrinsic tag) has only this everywhere. Three producers of one answer is the shape
|
|
20
|
-
// `<adapters_stay_thin>` exists to stop. Both halves read the same spec, so they cannot drift into
|
|
21
|
-
// two different answers — only into one answer applied twice, which every operation here is
|
|
22
|
-
// idempotent under (`(x ?? 'tail') ?? 'tail'`, `(x !== false) !== false`).
|
|
23
|
-
import { HOST_PRIMITIVES, } from '../host-primitives.cjs';
|
|
24
|
-
const planFor = (primitive) => ({
|
|
25
|
-
aliases: Object.entries(primitive.aliases),
|
|
26
|
-
defaults: Object.entries(primitive.defaults),
|
|
27
|
-
});
|
|
28
|
-
// The wrapper's spelling of a tag whose behavior must attach to the LOWERED path only
|
|
29
|
-
// (`component-names/shared.ts`). A suffix rather than a spec field because it cannot go stale: the
|
|
30
|
-
// next primitive to grow a `-managed` twin is covered the day it is named, with no edit here.
|
|
31
|
-
// `managed-tags-fold.test.ts` pins the convention against the platform tables, so a twin named some
|
|
32
|
-
// other way fails rather than silently losing its folds.
|
|
33
|
-
const managedSpellingOf = (tag) => `${tag}-managed`;
|
|
34
|
-
// EVERY spelling a primitive commits under, and the two axes are independent.
|
|
35
|
-
//
|
|
36
|
-
// `intrinsicWhen` lets one primitive commit two different tags (`TextInput` ->
|
|
37
|
-
// `symbiote-text-input` / `…-multiline`). `-managed` is the wrapper's twin of each of those. A map
|
|
38
|
-
// keyed on the lowered spellings alone folds the lowered path and silently skips the component one
|
|
39
|
-
// — which is exactly what shipped: TextInput and Switch committed a raw `id`, a key no ViewConfig
|
|
40
|
-
// declares, so Fabric dropped it and the nativeID was lost on device with nothing red. Found by
|
|
41
|
-
// Svelte's equivalence arm, 2026-09-01, and it is the two paths of ONE adapter disagreeing.
|
|
42
|
-
//
|
|
43
|
-
// Folding a bag the wrapper already folded is a no-op — an alias deletes its source key, so the
|
|
44
|
-
// second pass finds nothing to rename — which is what makes covering both paths from one plan safe.
|
|
45
|
-
function tagsOf(primitive) {
|
|
46
|
-
const alternate = primitive.intrinsicWhen?.intrinsic;
|
|
47
|
-
const lowered = alternate === undefined
|
|
48
|
-
? [primitive.intrinsic]
|
|
49
|
-
: [primitive.intrinsic, alternate];
|
|
50
|
-
return [...lowered, ...lowered.map(managedSpellingOf)];
|
|
51
|
-
}
|
|
52
|
-
export const FOLD_PLAN_BY_TAG = new Map(Object.values(HOST_PRIMITIVES).flatMap(primitive => {
|
|
53
|
-
const plan = planFor(primitive);
|
|
54
|
-
return tagsOf(primitive).map((tag) => [tag, plan]);
|
|
55
|
-
}));
|
|
56
|
-
// The runtime twin of a transform's `foldExpression`, which emits these same two operations as
|
|
57
|
-
// SOURCE. `notFalse` is RN's own encoding (`allowFontScaling !== false`): an explicit `undefined`
|
|
58
|
-
// reads as "not set" and only a literal `false` opts out, which is why it is not a `??`.
|
|
59
|
-
function fold(op, authored) {
|
|
60
|
-
return op.op === 'notFalse' ? authored !== false : (authored ?? op.value);
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Apply a primitive's aliases and defaults to a bag, copy-on-write.
|
|
64
|
-
*
|
|
65
|
-
* Never mutates the input: the bag belongs to whoever built it, and a framework may hand the same
|
|
66
|
-
* object back on a re-render, so writing into it would leak a fold into the author's own state.
|
|
67
|
-
*/
|
|
68
|
-
export function foldHostBag(tagName, bag) {
|
|
69
|
-
const plan = FOLD_PLAN_BY_TAG.get(tagName);
|
|
70
|
-
if (plan === undefined)
|
|
71
|
-
return bag;
|
|
72
|
-
let next = bag;
|
|
73
|
-
for (const [from, to] of plan.aliases) {
|
|
74
|
-
if (!(from in next))
|
|
75
|
-
continue;
|
|
76
|
-
if (next === bag)
|
|
77
|
-
next = { ...bag };
|
|
78
|
-
// RN gives the alias unconditional priority when both are set (View.js:77-79,
|
|
79
|
-
// `processedProps.nativeID = id`), and the raw key must not survive — no ViewConfig declares
|
|
80
|
-
// `id`, so Fabric would drop it silently.
|
|
81
|
-
next[to] = next[from];
|
|
82
|
-
delete next[from];
|
|
83
|
-
}
|
|
84
|
-
// Seeded whether or not the key was authored: a default that only applies to a key already
|
|
85
|
-
// present is not a default.
|
|
86
|
-
//
|
|
87
|
-
// A bag a transform already folded takes the `continue` on every key and never reaches the copy —
|
|
88
|
-
// which is what keeps the compile-time fold worth having once this one exists. It is no longer
|
|
89
|
-
// the mechanism, but it does keep the hot path allocation-free.
|
|
90
|
-
for (const [key, op] of plan.defaults) {
|
|
91
|
-
const folded = fold(op, next[key]);
|
|
92
|
-
if (key in next && Object.is(next[key], folded))
|
|
93
|
-
continue;
|
|
94
|
-
if (next === bag)
|
|
95
|
-
next = { ...bag };
|
|
96
|
-
next[key] = folded;
|
|
97
|
-
}
|
|
98
|
-
return next;
|
|
99
|
-
}
|
package/build/state-style.d.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
export interface IPressStateArgument {
|
|
2
|
-
pressed: boolean;
|
|
3
|
-
}
|
|
4
|
-
export interface IResolvedStateStyle {
|
|
5
|
-
style: unknown;
|
|
6
|
-
activeStyle: unknown;
|
|
7
|
-
}
|
|
8
|
-
/**
|
|
9
|
-
* Splits an authored `style` into its resting and pressed halves, reading the value once.
|
|
10
|
-
*
|
|
11
|
-
* A non-callback passes through untouched with no active variant, which the engine reads as "leave
|
|
12
|
-
* slot 1 alone" — that is what makes one emission safe for an expression whose value cannot be
|
|
13
|
-
* known at compile time.
|
|
14
|
-
*/
|
|
15
|
-
export declare function resolveStateStyle(value: unknown): IResolvedStateStyle;
|
package/build/state-style.js
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
// The runtime half of state-style lowering: splits an authored `style` into its resting and pressed
|
|
2
|
-
// halves. Referenced by the code every lowering transform EMITS, never by app code.
|
|
3
|
-
//
|
|
4
|
-
// SHARED for the same reason `HOST_PRIMITIVES` and `specializeStateStyle` are — two adapters had
|
|
5
|
-
// written byte-identical copies within an hour of each other, which is the duplication
|
|
6
|
-
// `<adapters_stay_thin>` exists to stop. Each adapter re-exports it from its own `./state-style`
|
|
7
|
-
// subpath so the emitted import specifier stays inside the package the app already depends on.
|
|
8
|
-
//
|
|
9
|
-
// WHY A HELPER EXISTS BESIDE THE INLINE GUARD, since a transform may emit either. Building the pair
|
|
10
|
-
// needs the authored `style` expression, and the two emissions are NOT equivalent:
|
|
11
|
-
//
|
|
12
|
-
// inline typeof e === 'function' ? e({pressed:false}) : e prints `e` on both props, so a
|
|
13
|
-
// `getStyle()` runs twice and a `flag ? a : b` can take DIFFERENT branches
|
|
14
|
-
// helper resolveStateStyle(e) prints `e` once and calls its
|
|
15
|
-
// RESULT twice
|
|
16
|
-
//
|
|
17
|
-
// The rule the two must satisfy is `REFUSAL_CATEGORIES.emitStyleExpressionOnce`, and it is about
|
|
18
|
-
// the output rather than the input: **an expression capable of DOING WORK is printed once.** A bare
|
|
19
|
-
// name or a non-computed dotted path is a read, not work, so the inline form is legitimate there —
|
|
20
|
-
// and it matters, because a helper returns one object, which a framework must spread, and a spread
|
|
21
|
-
// costs the element its patch flag (Vue measured `12 /* STYLE, PROPS */` becoming
|
|
22
|
-
// `16 /* FULL_PROPS */` plus a `mergeProps` per render on the hottest element in the tree).
|
|
23
|
-
//
|
|
24
|
-
// So the VERDICT is identical across adapters — every shape lowers — and only the cost differs,
|
|
25
|
-
// exactly as compile-time substitution differs between a JSX path and an SFC one. The verdicts are
|
|
26
|
-
// what `@symbiote-native/components/lowering-fixtures` pins.
|
|
27
|
-
//
|
|
28
|
-
// THE CONTRACT THIS IMPOSES ON APP CODE: a style callback must be PURE in `pressed`. Its result is
|
|
29
|
-
// invoked twice under every emission, so a side-effecting body is observable now.
|
|
30
|
-
function isStyleCallback(value) {
|
|
31
|
-
return typeof value === 'function';
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Splits an authored `style` into its resting and pressed halves, reading the value once.
|
|
35
|
-
*
|
|
36
|
-
* A non-callback passes through untouched with no active variant, which the engine reads as "leave
|
|
37
|
-
* slot 1 alone" — that is what makes one emission safe for an expression whose value cannot be
|
|
38
|
-
* known at compile time.
|
|
39
|
-
*/
|
|
40
|
-
export function resolveStateStyle(value) {
|
|
41
|
-
if (!isStyleCallback(value))
|
|
42
|
-
return { style: value, activeStyle: undefined };
|
|
43
|
-
return {
|
|
44
|
-
style: value({ pressed: false }),
|
|
45
|
-
activeStyle: value({ pressed: true }),
|
|
46
|
-
};
|
|
47
|
-
}
|
|
@@ -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,88 +0,0 @@
|
|
|
1
|
-
// ACTIVITYINDICATOR IS NOT A LOWERABLE PRIMITIVE, AND WILL NOT BECOME ONE. Decided 2026-09-01.
|
|
2
|
-
// It stays a component in every adapter; do not add it to `HOST_PRIMITIVES`.
|
|
3
|
-
//
|
|
4
|
-
// The reason is the `el('symbiote-view', wrapperProps, [el('symbiote-activity-indicator', …)])` at
|
|
5
|
-
// the bottom of this file: the render SYNTHESIZES a node that is not the primitive itself. A lowered
|
|
6
|
-
// tag is ONE engine node, and a host behavior's `foldPayload` maps props to props on that node — it
|
|
7
|
-
// cannot create a child. Lowering would therefore drop the centering container and change layout,
|
|
8
|
-
// which is an optimisation moving the observable surface, the one thing this design may not do.
|
|
9
|
-
//
|
|
10
|
-
// The wrapper is not over-building: RN's own `ActivityIndicator.js:112` renders a `<View>` around
|
|
11
|
-
// the native spinner too, so the two nodes are inherent. And the container cannot be folded INTO the
|
|
12
|
-
// spinner — it carries `alignItems`/`justifyContent`, which centre the spinner inside the space it
|
|
13
|
-
// was given; moved onto the spinner they would centre its children, of which it has none.
|
|
14
|
-
//
|
|
15
|
-
// The two alternatives were priced and both cost more than the primitive is worth. A behavior that
|
|
16
|
-
// CREATES a node needs a commit hook, i.e. a machine, i.e. a `-managed` tag split — a new category,
|
|
17
|
-
// not a fold. Synthesising the container inside the engine's commit walk is NOT the `RCTVirtualText`
|
|
18
|
-
// precedent it resembles: `viewNameFor` changes what one node IS and never ADDS one, so adding one
|
|
19
|
-
// would make the retained and committed trees disagree on node count — which every counter, census
|
|
20
|
-
// probe and benchmark in this repo assumes. Spinners are rare on a screen, so the per-instance cost
|
|
21
|
-
// lowering removes is not measurably paid here; the objection to leaving it is uniformity, not speed.
|
|
22
|
-
//
|
|
23
|
-
// Under half A this costs an app NOTHING: `View` and `ActivityIndicator` are both ordinary imports
|
|
24
|
-
// from the adapter barrel, so which one is internally a tag and which a component is invisible at
|
|
25
|
-
// the call site. The general rule is in `.claude/rules/host-primitive-tier.md`.
|
|
26
|
-
//
|
|
27
|
-
// ActivityIndicator: the render half (framework-agnostic). RN wraps the native spinner
|
|
28
|
-
// in a centering View and translates `size` in JS: 'small'/'large' map to a native size
|
|
29
|
-
// enum AND a fixed box style; a numeric size never reaches native (it sizes the spinner
|
|
30
|
-
// via style only). That translation is platform-invariant and lives here.
|
|
31
|
-
//
|
|
32
|
-
// What IS platform-specific: Android's AndroidProgressBar needs
|
|
33
|
-
// `styleAttr` (which triggers its setStyle(), without it the view throws "setStyle() not
|
|
34
|
-
// called") plus `indeterminate: true`, and its default color is the theme (null), whereas
|
|
35
|
-
// iOS's ActivityIndicatorView takes neither and defaults to GRAY. The adapter's per-host
|
|
36
|
-
// file supplies those bits via `platform`.
|
|
37
|
-
import { dlog } from '@symbiote-native/engine';
|
|
38
|
-
import { el } from '../descriptor.js';
|
|
39
|
-
// Fixed pixel boxes RN gives the two named sizes (styles.sizeSmall/sizeLarge).
|
|
40
|
-
const SIZE_SMALL_PX = 20;
|
|
41
|
-
const SIZE_LARGE_PX = 36;
|
|
42
|
-
// Centering wrapper RN puts around the spinner (styles.container).
|
|
43
|
-
const CONTAINER_STYLE = {
|
|
44
|
-
alignItems: 'center',
|
|
45
|
-
justifyContent: 'center',
|
|
46
|
-
};
|
|
47
|
-
function resolveSize(size) {
|
|
48
|
-
if (size === 'small') {
|
|
49
|
-
return {
|
|
50
|
-
sizeStyle: { width: SIZE_SMALL_PX, height: SIZE_SMALL_PX },
|
|
51
|
-
sizeProp: 'small',
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
if (size === 'large') {
|
|
55
|
-
return {
|
|
56
|
-
sizeStyle: { width: SIZE_LARGE_PX, height: SIZE_LARGE_PX },
|
|
57
|
-
sizeProp: 'large',
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
return { sizeStyle: { width: size, height: size } };
|
|
61
|
-
}
|
|
62
|
-
export function renderActivityIndicator(view, platform) {
|
|
63
|
-
const { sizeStyle, sizeProp } = resolveSize(view.size);
|
|
64
|
-
dlog(sizeProp !== undefined
|
|
65
|
-
? `ActivityIndicator size '${sizeProp}' -> native size enum '${sizeProp}'`
|
|
66
|
-
: `ActivityIndicator size ${String(view.size)} -> style only, native size not set`);
|
|
67
|
-
const nativeProps = {
|
|
68
|
-
animating: view.animating,
|
|
69
|
-
hidesWhenStopped: view.hidesWhenStopped,
|
|
70
|
-
style: sizeStyle,
|
|
71
|
-
...platform.nativeExtras,
|
|
72
|
-
};
|
|
73
|
-
// Omit color entirely when neither given nor defaulted (Android's theme default is
|
|
74
|
-
// null); a null color prop would be rejected by Fabric's color parser.
|
|
75
|
-
const resolvedColor = view.color ?? platform.defaultColor;
|
|
76
|
-
if (resolvedColor !== null)
|
|
77
|
-
nativeProps.color = resolvedColor;
|
|
78
|
-
if (sizeProp !== undefined)
|
|
79
|
-
nativeProps.size = sizeProp;
|
|
80
|
-
dlog('ActivityIndicator -> RCTView(spinner)');
|
|
81
|
-
const wrapperProps = {
|
|
82
|
-
...view.passthrough,
|
|
83
|
-
style: [CONTAINER_STYLE, view.style],
|
|
84
|
-
};
|
|
85
|
-
return el('symbiote-view', wrapperProps, [
|
|
86
|
-
el('symbiote-activity-indicator', nativeProps),
|
|
87
|
-
]);
|
|
88
|
-
}
|
|
@@ -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;
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
// ImageBackground: the render half (framework-agnostic). Pure JS composition, no native
|
|
2
|
-
// component of its own (mirrors react-native/Libraries/Image/ImageBackground.js): an outer
|
|
3
|
-
// View receives the wrapper `style`; an absolutely-filled Image sits behind it; the user's
|
|
4
|
-
// `children` paint on top (as siblings AFTER the image in the wrapper's child order, injected
|
|
5
|
-
// by the adapter). Shared verbatim across adapters: React and Vue both bridge this Descriptor.
|
|
6
|
-
//
|
|
7
|
-
// The inner Image is positioned absolute-fill and has the wrapper's width/height reapplied:
|
|
8
|
-
// RN's Image overwrites its own width/height from the source's intrinsic size, which would
|
|
9
|
-
// fight the wrapper's explicit dimensions, so we proxy them back onto the Image so it fills
|
|
10
|
-
// the box. `imageStyle` wins last.
|
|
11
|
-
import { dlog, flattenStyle, } from '@symbiote-native/engine';
|
|
12
|
-
import { el } from '../descriptor.js';
|
|
13
|
-
import { renderImage } from './render-image/index.js';
|
|
14
|
-
// The inner Image's positioning: absolute-fill behind the wrapper's children.
|
|
15
|
-
const IMAGE_BACKGROUND_ABSOLUTE_FILL = {
|
|
16
|
-
position: 'absolute',
|
|
17
|
-
left: 0,
|
|
18
|
-
right: 0,
|
|
19
|
-
top: 0,
|
|
20
|
-
bottom: 0,
|
|
21
|
-
};
|
|
22
|
-
// Read one explicit dimension off the (already-flattened) wrapper style. A dp number or a
|
|
23
|
-
// percentage string is a valid IDimensionValue; anything else (auto / undefined) yields undefined.
|
|
24
|
-
function readDimension(style, key) {
|
|
25
|
-
const value = Object.hasOwn(style, key) ? Reflect.get(style, key) : undefined;
|
|
26
|
-
if (typeof value === 'number' || typeof value === 'string')
|
|
27
|
-
return value;
|
|
28
|
-
return undefined;
|
|
29
|
-
}
|
|
30
|
-
export function renderImageBackground(view) {
|
|
31
|
-
// Flatten only to read the wrapper's explicit dimensions; RN copies these onto the Image so
|
|
32
|
-
// it fills the box rather than collapsing to the source's intrinsic size. `imageStyle` last.
|
|
33
|
-
const flattenedWrapper = flattenStyle(view.style);
|
|
34
|
-
const imageMergedStyle = [
|
|
35
|
-
IMAGE_BACKGROUND_ABSOLUTE_FILL,
|
|
36
|
-
{
|
|
37
|
-
width: readDimension(flattenedWrapper, 'width'),
|
|
38
|
-
height: readDimension(flattenedWrapper, 'height'),
|
|
39
|
-
},
|
|
40
|
-
view.imageStyle,
|
|
41
|
-
];
|
|
42
|
-
dlog('ImageBackground -> View(RCTView) > Image(RCTImageView absolute-fill) + children');
|
|
43
|
-
// The wrapper View holds the inner Image as its only structural child; the adapter appends
|
|
44
|
-
// the user children after it (so they paint on top).
|
|
45
|
-
return el('symbiote-view', { style: view.style }, [
|
|
46
|
-
renderImage({ ...view.image, style: imageMergedStyle }),
|
|
47
|
-
]);
|
|
48
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { IDescriptor } from '../descriptor';
|
|
2
|
-
import type { IFoldedTextInputProps, ITextInputSelection } from '../state/text-input';
|
|
3
|
-
export type ITextInputViewProps = {
|
|
4
|
-
multiline: boolean;
|
|
5
|
-
text: string | undefined;
|
|
6
|
-
mostRecentEventCount: number;
|
|
7
|
-
selection?: ITextInputSelection;
|
|
8
|
-
folded: IFoldedTextInputProps;
|
|
9
|
-
passthrough: Record<string, unknown>;
|
|
10
|
-
};
|
|
11
|
-
export declare function renderTextInput(view: ITextInputViewProps): IDescriptor;
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
// TextInput: the render half (framework-agnostic). Picks the intrinsic (single- vs multiline)
|
|
2
|
-
// and maps the resolved props onto it: the controlled value rides as the private `text` prop
|
|
3
|
-
// plus `mostRecentEventCount`, the W3C/alias folds arrive pre-resolved in `folded`, and the
|
|
4
|
-
// ref + the change/focus/blur handlers + every pass-through prop (accessibility, testID, the
|
|
5
|
-
// remaining native events) arrive folded into `passthrough` and land on the host untouched.
|
|
6
|
-
// Pure and prop-driven; no hooks, no events. The adapter owns those.
|
|
7
|
-
import { dlog } from '@symbiote-native/engine';
|
|
8
|
-
import { el } from '../descriptor.js';
|
|
9
|
-
// One host element per native input class. Text carries the only non-trivial nesting elsewhere;
|
|
10
|
-
// here the choice is binary and runtime (the `multiline` prop), so the module stays flat.
|
|
11
|
-
// The `-managed` spelling, NOT the plain one the lowering transforms emit. Both resolve to the
|
|
12
|
-
// same native view; the tag is what decides whether the engine's TextInput machine attaches, and
|
|
13
|
-
// on this path the adapter's own lifecycle already owns that state. Sharing the tag would run both
|
|
14
|
-
// copies at once. Full reasoning in `component-names/shared.ts`.
|
|
15
|
-
const SINGLELINE_INTRINSIC = 'symbiote-text-input-managed';
|
|
16
|
-
const MULTILINE_INTRINSIC = 'symbiote-text-input-multiline-managed';
|
|
17
|
-
export function renderTextInput(view) {
|
|
18
|
-
const intrinsic = view.multiline ? MULTILINE_INTRINSIC : SINGLELINE_INTRINSIC;
|
|
19
|
-
dlog(`TextInput render multiline=${String(view.multiline)} ` +
|
|
20
|
-
`text=${JSON.stringify(view.text)} count=${view.mostRecentEventCount}`);
|
|
21
|
-
const props = {
|
|
22
|
-
...view.passthrough,
|
|
23
|
-
text: view.text,
|
|
24
|
-
mostRecentEventCount: view.mostRecentEventCount,
|
|
25
|
-
selection: view.selection,
|
|
26
|
-
keyboardType: view.folded.keyboardType,
|
|
27
|
-
returnKeyType: view.folded.returnKeyType,
|
|
28
|
-
editable: view.folded.editable,
|
|
29
|
-
submitBehavior: view.folded.submitBehavior,
|
|
30
|
-
selectionColor: view.folded.selectionColor,
|
|
31
|
-
cursorColor: view.folded.cursorColor,
|
|
32
|
-
selectionHandleColor: view.folded.selectionHandleColor,
|
|
33
|
-
underlineColorAndroid: view.folded.underlineColorAndroid,
|
|
34
|
-
autoComplete: view.folded.autoComplete,
|
|
35
|
-
textContentType: view.folded.textContentType,
|
|
36
|
-
showSoftInputOnFocus: view.folded.showSoftInputOnFocus,
|
|
37
|
-
};
|
|
38
|
-
return el(intrinsic, props);
|
|
39
|
-
}
|