@symbiote-native/components 1.0.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 +3 -4
- 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 +1 -1
- package/build/behaviors/image.js +2 -2
- package/build/behaviors/input-accessory-view.d.ts +1 -1
- package/build/behaviors/input-accessory-view.js +2 -2
- package/build/behaviors/pressable.d.ts +59 -1
- package/build/behaviors/pressable.js +93 -18
- 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 +1 -1
- package/build/behaviors/switch.js +9 -5
- package/build/behaviors/text-input.d.ts +2 -2
- package/build/behaviors/text-input.js +43 -15
- 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 +29 -19
- package/build/component-names/index.ios.js +31 -19
- 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/fold-host-bag.js +2 -2
- package/build/index.d.ts +17 -11
- package/build/index.js +44 -8
- package/build/register.d.ts +1 -0
- package/build/register.js +55 -0
- package/build/scroll-view-commands.d.ts +4 -0
- package/build/scroll-view-commands.js +30 -31
- package/build/state/text-input.d.ts +4 -1
- package/build/view/render-button.d.ts +36 -1
- package/build/view/render-button.js +101 -12
- package/build/view/render-image/index.js +2 -2
- package/build/view/render-input-accessory-view.js +1 -1
- 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 +2 -2
- package/build/view/render-text-input.js +2 -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 +202 -150
- package/host-primitives.d.cts +0 -2
- package/package.json +8 -17
- 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/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
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// ImageBackground's host behavior: the composition and the prop split the wrapper component did,
|
|
2
|
+
// moved below the framework so the primitive can be a bare tag.
|
|
3
|
+
//
|
|
4
|
+
// THE TWO-NODE SHAPE IS RN'S. `ImageBackground.js:74-103` opens a `<View>` carrying the app's
|
|
5
|
+
// `style`, puts an absolutely-filled `<Image>` inside it, and lays the app's `{children}` AFTER
|
|
6
|
+
// that image so they paint on top. The lowered form is the same two nodes — `image-background`
|
|
7
|
+
// (an RCTView, the tag an app writes) with an RCTImageView built under it.
|
|
8
|
+
//
|
|
9
|
+
// WHY THE SLOT TAKES NO CHILDREN, which is the one thing this primitive needed that ScrollView,
|
|
10
|
+
// ActivityIndicator and Button did not. `childHost` answers two questions at once — which node an
|
|
11
|
+
// owner prop redirects onto, and which node the app's children go under — and those had the same
|
|
12
|
+
// answer for every primitive until this one. Here they differ: the image takes `imageStyle` and the
|
|
13
|
+
// whole `...props` spread, while the children belong beside it. `slotTakesNoChildren` is what
|
|
14
|
+
// splits them (`IHostBehavior`, and it is not a JSX nicety upstream could have collapsed — an
|
|
15
|
+
// Android `<Image>` is an `ImageView`, not a `ViewGroup`).
|
|
16
|
+
//
|
|
17
|
+
// WHERE THE APP'S PROPS GO. RN destructures `children, style, imageStyle, imageRef,
|
|
18
|
+
// importantForAccessibility, ...props` and spreads `...props` onto the Image
|
|
19
|
+
// (`ImageBackground.js:62-81`), so the set that moves is OPEN — every event, every accessibility
|
|
20
|
+
// prop, `testID`, `id`, whatever an app writes next — and only a complement can express it.
|
|
21
|
+
// `IMAGE_BACKGROUND_HOST_PROPS` is the short list that stays behind, and it is shorter than RN's:
|
|
22
|
+
// `importantForAccessibility` rides to the image alone, which is what all five wrappers did, so
|
|
23
|
+
// the tag and the component it replaces commit the same payload. The divergence from RN predates
|
|
24
|
+
// lowering and is unchanged by it.
|
|
25
|
+
//
|
|
26
|
+
// WHAT THE IMAGE'S FOLD OWES. Everything on the image arrives as a real prop write, so its payload
|
|
27
|
+
// is built by the shared `foldImagePayload` like any other `image`. Two things cannot arrive that
|
|
28
|
+
// way and are folded here: the style, which is DERIVED from the owner's own `style` (RN proxies the
|
|
29
|
+
// wrapper's width/height onto the image so it fills the box rather than collapsing to the source's
|
|
30
|
+
// intrinsic size), and `id`, whose rename to `nativeID` is applied per adapter on the tag THEY
|
|
31
|
+
// create and so never reaches a node a behavior built.
|
|
32
|
+
import { appendChild, createElement, flattenStyle, registerHostBehavior, } from '@symbiote-native/engine';
|
|
33
|
+
import { descriptorFor } from '../component-names';
|
|
34
|
+
import { foldImagePayload, IMAGE_TAG } from './image.js';
|
|
35
|
+
export const IMAGE_BACKGROUND_TAG = 'image-background';
|
|
36
|
+
// The inner Image's positioning: absolute-fill behind the box's children.
|
|
37
|
+
const ABSOLUTE_FILL = {
|
|
38
|
+
position: 'absolute',
|
|
39
|
+
left: 0,
|
|
40
|
+
right: 0,
|
|
41
|
+
top: 0,
|
|
42
|
+
bottom: 0,
|
|
43
|
+
};
|
|
44
|
+
// The props RN keeps on the wrapper View (`ImageBackground.js:74-78`), plus the two spellings of a
|
|
45
|
+
// class name — `routeProp`'s slot redirect runs above its own class branch, so an unlisted `class`
|
|
46
|
+
// would style the image instead of the box.
|
|
47
|
+
const IMAGE_BACKGROUND_HOST_PROPS = ['style', 'class', 'className'];
|
|
48
|
+
// The owner props the image's payload is derived from. Only `style`, because a class name is
|
|
49
|
+
// published INTO `node.props.style` by `pushClassStyle` — so a `class` write arrives here spelled
|
|
50
|
+
// `style` and the proxied width/height follow a class-declared box as well as an inline one.
|
|
51
|
+
const IMAGE_BACKGROUND_SLOT_DERIVED = ['style'];
|
|
52
|
+
// `imageStyle` is the wrapper's own name for the image's `style`. A bare class NAME is a legal
|
|
53
|
+
// value for it — every adapter's wrapper resolved one — and `routeProp` routes a string landing on
|
|
54
|
+
// `style` as `class` instead, so the registry resolves it on the image with nothing needed here.
|
|
55
|
+
const IMAGE_BACKGROUND_SLOT_PROPS = { imageStyle: 'style' };
|
|
56
|
+
// A StyleProp is an object, an array of them, or a registered class array — all of which
|
|
57
|
+
// `flattenStyle` already handles one layer down. The only thing to exclude is a scalar.
|
|
58
|
+
function styleOf(value) {
|
|
59
|
+
if (typeof value !== 'object' || value === null)
|
|
60
|
+
return undefined;
|
|
61
|
+
if (Array.isArray(value))
|
|
62
|
+
return value;
|
|
63
|
+
return { ...value };
|
|
64
|
+
}
|
|
65
|
+
// RN opts the wrapper out of iOS's Smart Invert for its whole subtree (`ImageBackground.js:75`) —
|
|
66
|
+
// a photograph inverted by accessibility settings is the case that prop exists for. None of the
|
|
67
|
+
// five wrappers ever wrote it, so this closes a standing gap rather than reproducing one.
|
|
68
|
+
const hostFold = props => ({
|
|
69
|
+
...props,
|
|
70
|
+
accessibilityIgnoresInvertColors: true,
|
|
71
|
+
});
|
|
72
|
+
// Read one explicit dimension off the (already-flattened) box style. A dp number or a percentage
|
|
73
|
+
// string is a valid IDimensionValue; anything else (auto / undefined) yields undefined.
|
|
74
|
+
function readDimension(style, key) {
|
|
75
|
+
const value = Object.hasOwn(style, key) ? Reflect.get(style, key) : undefined;
|
|
76
|
+
if (typeof value === 'number' || typeof value === 'string')
|
|
77
|
+
return value;
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
function imageFold(owner) {
|
|
81
|
+
return props => {
|
|
82
|
+
// RN's own workaround, and its comment is worth reading before "simplifying" this
|
|
83
|
+
// (`ImageBackground.js:86-96`): an RN Image overwrites its own width/height from the source's
|
|
84
|
+
// intrinsic size, which fights the box's explicit dimensions, so they are proxied back on.
|
|
85
|
+
// Reads the OWNER's live style — a class name lands there too, published by `pushClassStyle`.
|
|
86
|
+
const box = flattenStyle(styleOf(owner.props.style));
|
|
87
|
+
const next = {
|
|
88
|
+
...props,
|
|
89
|
+
// `imageStyle` last, so a caller still wins over the fill and the proxy.
|
|
90
|
+
style: [
|
|
91
|
+
ABSOLUTE_FILL,
|
|
92
|
+
{
|
|
93
|
+
width: readDimension(box, 'width'),
|
|
94
|
+
height: readDimension(box, 'height'),
|
|
95
|
+
},
|
|
96
|
+
styleOf(props.style),
|
|
97
|
+
],
|
|
98
|
+
};
|
|
99
|
+
// Unconditional priority when both are set, matching RN (`View.js:77-79`) and `foldHostBag`.
|
|
100
|
+
// A raw `id` is a key no ViewConfig declares, so Fabric drops it and the nativeID is lost.
|
|
101
|
+
if (Object.hasOwn(next, 'id')) {
|
|
102
|
+
next.nativeID = next.id;
|
|
103
|
+
delete next.id;
|
|
104
|
+
}
|
|
105
|
+
return foldImagePayload(next);
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
// Returns the image, so `slotProps` / `slotPropsExcept` / `slotDerived` all point at it — and the
|
|
109
|
+
// app's children stay on the owner because of `slotTakesNoChildren`, not because of what this
|
|
110
|
+
// returns. The image is appended FIRST and nothing else is ever placed in front of it, which is
|
|
111
|
+
// what makes the children paint over it.
|
|
112
|
+
//
|
|
113
|
+
// Built WITHOUT handing `IMAGE_TAG` to `createElement`, deliberately: that would attach Image's own
|
|
114
|
+
// behavior and set `payloadFold` to `foldImagePayload` alone, and this node's style has to be
|
|
115
|
+
// derived from the owner. `payloadFold` is a single slot, so the composition is spelled here — and
|
|
116
|
+
// it still calls the one shared mapping rather than restating it.
|
|
117
|
+
function buildBackgroundImage(node) {
|
|
118
|
+
const descriptor = descriptorFor(IMAGE_TAG);
|
|
119
|
+
const image = createElement(descriptor.component, descriptor.isText);
|
|
120
|
+
image.payloadFold = imageFold(node);
|
|
121
|
+
appendChild(node, image);
|
|
122
|
+
return image;
|
|
123
|
+
}
|
|
124
|
+
const imageBackgroundBehavior = {
|
|
125
|
+
slotProps: IMAGE_BACKGROUND_SLOT_PROPS,
|
|
126
|
+
slotPropsExcept: IMAGE_BACKGROUND_HOST_PROPS,
|
|
127
|
+
slotDerived: IMAGE_BACKGROUND_SLOT_DERIVED,
|
|
128
|
+
slotTakesNoChildren: true,
|
|
129
|
+
buildStructure: buildBackgroundImage,
|
|
130
|
+
foldPayload: hostFold,
|
|
131
|
+
// Required by the interface and deliberately empty: this primitive owns no timer, no listener
|
|
132
|
+
// and no native handshake. Written out rather than shared with a `noop` so the emptiness reads
|
|
133
|
+
// as a decision.
|
|
134
|
+
attach() { },
|
|
135
|
+
detach() { },
|
|
136
|
+
};
|
|
137
|
+
export function registerImageBackgroundBehavior() {
|
|
138
|
+
registerHostBehavior(IMAGE_BACKGROUND_TAG, imageBackgroundBehavior);
|
|
139
|
+
}
|
package/build/behaviors/image.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// hand and says so in its own header, because nothing was exported to call.
|
|
11
11
|
//
|
|
12
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 `
|
|
13
|
+
// on the tag, and `renderImage` emits `image` too — so on a wrapper-built node this fold
|
|
14
14
|
// runs on ALREADY-FOLDED props. That is safe here and only here, because the mapping is idempotent:
|
|
15
15
|
// every alias it consumes (`src`, `srcSet`, `alt`, `width`, `height`, …) is absent from its own
|
|
16
16
|
// output, `source` comes back in the array shape `normalizeSource` guarantees, and
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
// so the property has to be pinned or it is an accident waiting to be broken.
|
|
21
21
|
import { registerHostBehavior, } from '@symbiote-native/engine';
|
|
22
22
|
import { IMAGE_VIEW_PROP_NAMES, mapImageProps, } from '../view/render-image/index.js';
|
|
23
|
-
export const IMAGE_TAG = '
|
|
23
|
+
export const IMAGE_TAG = 'image';
|
|
24
24
|
const RESIZE_MODES = new Set([
|
|
25
25
|
'cover',
|
|
26
26
|
'contain',
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export declare const INPUT_ACCESSORY_VIEW_TAG = "
|
|
1
|
+
export declare const INPUT_ACCESSORY_VIEW_TAG = "input-accessory-view";
|
|
2
2
|
export declare function foldInputAccessoryViewPayload(props: Readonly<Record<string, unknown>>): Record<string, unknown>;
|
|
3
3
|
export declare function registerInputAccessoryViewBehavior(): void;
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// nothing can end up with two owners on one node. Idempotence is asserted rather than reasoned.
|
|
16
16
|
//
|
|
17
17
|
// PLATFORM. This is the only primitive in its group that is not platform-invariant in what it
|
|
18
|
-
// COMMITS TO: `
|
|
18
|
+
// COMMITS TO: `input-accessory-view` resolves to `RCTInputAccessoryView` on iOS and to a
|
|
19
19
|
// plain `RCTView` on Android. The fold itself is platform-invariant on purpose — it reproduces the
|
|
20
20
|
// wrapper's mapping exactly, on both platforms, so the lowered and wrapped paths cannot diverge
|
|
21
21
|
// per-platform. What it does NOT do is fix the pre-existing Android divergence underneath it:
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
// make the two paths disagree.
|
|
27
27
|
import { registerHostBehavior, } from '@symbiote-native/engine';
|
|
28
28
|
import { INPUT_ACCESSORY_VIEW_PROP_NAMES, mapInputAccessoryViewProps, } from '../view/render-input-accessory-view.js';
|
|
29
|
-
export const INPUT_ACCESSORY_VIEW_TAG = '
|
|
29
|
+
export const INPUT_ACCESSORY_VIEW_TAG = 'input-accessory-view';
|
|
30
30
|
const CONSUMED = new Set(INPUT_ACCESSORY_VIEW_PROP_NAMES);
|
|
31
31
|
function stringOf(value) {
|
|
32
32
|
return typeof value === 'string' ? value : undefined;
|
|
@@ -1,2 +1,60 @@
|
|
|
1
|
-
|
|
1
|
+
import { type IHostBehavior, type ISymbioteNode } from '@symbiote-native/engine';
|
|
2
|
+
import { type IPressMachineConfig } from '../state/pressable';
|
|
3
|
+
import type { IAccessibilityStateValue } from '../accessibility-props';
|
|
4
|
+
export declare const PRESSABLE_TAG = "pressable";
|
|
5
|
+
/**
|
|
6
|
+
* A last look at the machine's config before its handlers are built, for a tag that IS a pressable
|
|
7
|
+
* plus something — TouchableOpacity, whose fade has to run between the machine and the app's own
|
|
8
|
+
* `onPressIn`.
|
|
9
|
+
*
|
|
10
|
+
* Called from `rebuild`, so once per gesture rather than once per mount: it sees the config the
|
|
11
|
+
* props actually hold by the time a finger lands, and anything it captures is discarded with the
|
|
12
|
+
* gesture. Per-node state that must OUTLIVE a gesture belongs on the caller's own WeakMap.
|
|
13
|
+
*/
|
|
14
|
+
export type IPressConfigRefinement = (node: ISymbioteNode, config: IPressMachineConfig) => IPressMachineConfig;
|
|
15
|
+
/**
|
|
16
|
+
* What the machine reads as `disabled`, for a tag whose spelling of it is not the raw prop.
|
|
17
|
+
*
|
|
18
|
+
* There is no resolver by default because RN's Pressable hands Pressability the RAW prop
|
|
19
|
+
* (`Pressable.js:266`) — `aria-disabled` there changes only what is ANNOUNCED. Button is the one
|
|
20
|
+
* primitive that differs: it resolves `disabled ?? aria-disabled ?? accessibilityState.disabled` in
|
|
21
|
+
* the component and passes the ANSWER down as the touchable's own prop (`Button.js:337` -> `:386`),
|
|
22
|
+
* which a single tag has no second node to pass to.
|
|
23
|
+
*
|
|
24
|
+
* Reads the bag and returns the answer; it must never write one back. `resolveButtonDisabled`
|
|
25
|
+
* short-circuits on an authored `disabled`, so a resolved value stored in `node.props.disabled`
|
|
26
|
+
* would answer the NEXT resolution as if the app had written it and the tag could never re-enable.
|
|
27
|
+
*/
|
|
28
|
+
export type IDisabledResolver = (props: Readonly<Record<string, unknown>>) => boolean | undefined;
|
|
29
|
+
export declare function booleanOr(value: unknown): boolean | undefined;
|
|
30
|
+
export declare function asAccessibilityState(value: unknown): IAccessibilityStateValue | undefined;
|
|
31
|
+
export declare function accessibleUnlessOptedOut(props: Readonly<Record<string, unknown>>): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* The machine on `node`, reading its props and the app's callbacks off `options.source` when that
|
|
34
|
+
* is a different node.
|
|
35
|
+
*
|
|
36
|
+
* Exported for a behavior whose responder is not its own node — `./touchable-native-feedback`,
|
|
37
|
+
* whose tag commits nothing and adopts the app's single child as the responder. Every other caller
|
|
38
|
+
* goes through `createPressBehavior`, where source and node are the same.
|
|
39
|
+
*
|
|
40
|
+
* Re-callable on the same node: a second call replaces the state and the dispatchers, which is what
|
|
41
|
+
* a re-arm after `detachPressMachine` needs.
|
|
42
|
+
*/
|
|
43
|
+
export declare function attachPressMachine(node: ISymbioteNode, options?: {
|
|
44
|
+
readonly refine?: IPressConfigRefinement;
|
|
45
|
+
readonly disabledOf?: IDisabledResolver;
|
|
46
|
+
readonly source?: ISymbioteNode;
|
|
47
|
+
}): void;
|
|
48
|
+
/** See `attachPressMachine`: the same teardown `createPressBehavior` registers as its `detach`. */
|
|
49
|
+
export declare function detachPressMachine(node: ISymbioteNode): void;
|
|
50
|
+
/**
|
|
51
|
+
* The press machine as behavior parts, so a tag that is a pressable PLUS something can compose it
|
|
52
|
+
* instead of re-implementing it.
|
|
53
|
+
*
|
|
54
|
+
* Spread into the caller's own behavior and wrap `attach`/`detach` around these — the touchable
|
|
55
|
+
* family needs a per-node Animated value opened before the machine and closed after it. The
|
|
56
|
+
* WeakMap holding the machine's own state is keyed by node, so one node may hold exactly one of
|
|
57
|
+
* these; a tag composing it therefore must not also register the plain `pressable` behavior.
|
|
58
|
+
*/
|
|
59
|
+
export declare function createPressBehavior(refine?: IPressConfigRefinement, disabledOf?: IDisabledResolver): Pick<IHostBehavior, 'attach' | 'detach' | 'foldPayload' | 'ownedListeners'>;
|
|
2
60
|
export declare function registerPressableBehavior(): void;
|
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
// does NOT re-export. A bare `import './register';` sitting NEXT TO such a re-export does not work
|
|
16
16
|
// either: Babel merges the two imports of one specifier and the merged dependency stays lazy.
|
|
17
17
|
import { appListenerFor, dlog, registerHostBehavior, requestCommitFor, setBehaviorListener, setNodePressed, } from '@symbiote-native/engine';
|
|
18
|
-
import { createPressHandlers, createPressRuntime, disposePressRuntime, DEFAULT_DELAY_LONG_PRESS_MS, rippleProps, } from '../state/pressable.js';
|
|
19
|
-
import { buildPressableListeners, resolveDisabledAccessibilityState, } from '../view/render-pressable/index.js';
|
|
20
|
-
export const PRESSABLE_TAG = '
|
|
18
|
+
import { createPressHandlers, createPressRuntime, disposePressRuntime, DEFAULT_DELAY_LONG_PRESS_MS, DEFAULT_MIN_PRESS_DURATION_MS, rippleProps, } from '../state/pressable.js';
|
|
19
|
+
import { buildPressableListeners, resolveDisabledAccessibilityState, resolvePressableFocusable, } from '../view/render-pressable/index.js';
|
|
20
|
+
export const PRESSABLE_TAG = 'pressable';
|
|
21
21
|
const states = new WeakMap();
|
|
22
22
|
function isRecord(value) {
|
|
23
23
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
@@ -25,6 +25,12 @@ function isRecord(value) {
|
|
|
25
25
|
function numberOr(value, fallback) {
|
|
26
26
|
return typeof value === 'number' ? value : fallback;
|
|
27
27
|
}
|
|
28
|
+
// The bag arrives as `unknown` off `node.props`; every RN default below is written against
|
|
29
|
+
// `boolean | undefined`. Exported to the sibling behaviors folding the same bag, and deliberately
|
|
30
|
+
// NOT to the shared barrel — same reasoning as `asAccessibilityState`.
|
|
31
|
+
export function booleanOr(value) {
|
|
32
|
+
return typeof value === 'boolean' ? value : undefined;
|
|
33
|
+
}
|
|
28
34
|
// A scalar offset or the per-edge object; anything else reads as "no offset", which the machine
|
|
29
35
|
// turns into RN's defaults. Same narrowing the component path does — kept here rather than shared
|
|
30
36
|
// because the component's version narrows Vue attrs, and this one narrows engine props.
|
|
@@ -61,16 +67,17 @@ const MACHINE_ONLY_KEYS = [
|
|
|
61
67
|
'disabled',
|
|
62
68
|
'cancelable',
|
|
63
69
|
'delayLongPress',
|
|
70
|
+
'minPressDuration',
|
|
64
71
|
'unstable_pressDelay',
|
|
65
72
|
'pressRetentionOffset',
|
|
66
73
|
'delayHoverIn',
|
|
67
74
|
'delayHoverOut',
|
|
68
75
|
];
|
|
69
76
|
// Narrowed field by field rather than cast: the bag arrives as `unknown` off `node.props`. A local
|
|
70
|
-
// twin of the guard each adapter keeps for its own attrs (Vue's `asAccessibilityState`) —
|
|
71
|
-
//
|
|
72
|
-
// narrowing helper is not API anyone should be able to import.
|
|
73
|
-
function asAccessibilityState(value) {
|
|
77
|
+
// twin of the guard each adapter keeps for its own attrs (Vue's `asAccessibilityState`) — exported
|
|
78
|
+
// to the sibling behaviors that fold the same bag, and deliberately NOT to the shared barrel, which
|
|
79
|
+
// every adapter re-exports wholesale: a narrowing helper is not API anyone should be able to import.
|
|
80
|
+
export function asAccessibilityState(value) {
|
|
74
81
|
if (!isRecord(value))
|
|
75
82
|
return undefined;
|
|
76
83
|
const state = {};
|
|
@@ -135,8 +142,26 @@ function foldPayload(props) {
|
|
|
135
142
|
// skipping undefined is a coincidence to lean on, not a contract to rely on here.
|
|
136
143
|
if (resolved !== undefined)
|
|
137
144
|
out.accessibilityState = resolved;
|
|
145
|
+
out.accessible = accessibleUnlessOptedOut(props);
|
|
146
|
+
// Pressable.js:258 — the plain form, with no press-handler or disabled leg. A Touchable composing
|
|
147
|
+
// this tag has already resolved its own three-leg formula and passed the answer in as `focusable`,
|
|
148
|
+
// which `!== false` leaves alone.
|
|
149
|
+
out.focusable = resolvePressableFocusable(booleanOr(props.focusable));
|
|
138
150
|
return out;
|
|
139
151
|
}
|
|
152
|
+
// RN makes every pressable accessible unless the app opts OUT — `Pressable.js:252`
|
|
153
|
+
// (`accessible: accessible !== false`), and the whole Touchable family repeats it verbatim
|
|
154
|
+
// (`TouchableOpacity.js:303`, `TouchableHighlight.js:337`). `!== false` rather than `?? true`, so
|
|
155
|
+
// only a literal `false` opts out and an explicit `undefined` still reads as accessible.
|
|
156
|
+
//
|
|
157
|
+
// Nothing in this repo did it until 2026-09-09, on either path, so a Pressable reached a screen
|
|
158
|
+
// reader as a plain view unless the app wrote the prop. Landing it in the fold above alone reddens
|
|
159
|
+
// four equivalence arms — correctly, since those compare the wrapper against the lowered path — so
|
|
160
|
+
// the behavior and every adapter's wrapper have to move in ONE change. Exported for the wrappers
|
|
161
|
+
// that need to say it themselves, and for the tags whose behavior is their only path.
|
|
162
|
+
export function accessibleUnlessOptedOut(props) {
|
|
163
|
+
return props.accessible !== false;
|
|
164
|
+
}
|
|
140
165
|
// From the STASH, not from `node.props`. Every name below is in `ownedListeners`, so `routeProp`
|
|
141
166
|
// diverts the app's `onPress` away from `node.listeners` (where it would evict the behavior's own
|
|
142
167
|
// dispatcher) and into the stash — which makes the stash the only place it exists. Reading
|
|
@@ -158,6 +183,11 @@ function configFor(node) {
|
|
|
158
183
|
onLongPress: callbackAt(node, 'longPress'),
|
|
159
184
|
delayLongPress: numberOr(node.props.delayLongPress, DEFAULT_DELAY_LONG_PRESS_MS),
|
|
160
185
|
unstable_pressDelay: numberOr(node.props.unstable_pressDelay, 0),
|
|
186
|
+
// RN's Touchables own the deactivation floor in their OWN machine and hand Pressability
|
|
187
|
+
// `minPressDuration: 0` (TouchableOpacity.js:195). While they were wrappers they passed it as
|
|
188
|
+
// an internal input; on the tag there is nowhere else to say it, so the floor has to be a
|
|
189
|
+
// readable prop or every Touchable holds its fade for the machine's 130 ms default.
|
|
190
|
+
minPressDuration: numberOr(node.props.minPressDuration, DEFAULT_MIN_PRESS_DURATION_MS),
|
|
161
191
|
hitSlop: asRectOffset(node.props.hitSlop),
|
|
162
192
|
pressRetentionOffset: asRectOffset(node.props.pressRetentionOffset),
|
|
163
193
|
};
|
|
@@ -170,12 +200,21 @@ function configFor(node) {
|
|
|
170
200
|
// once the props exist. A gesture is one interaction, so a handful of closures per press is
|
|
171
201
|
// invisible — unlike doing it per prop write, which is the cost this whole tier exists to remove.
|
|
172
202
|
function rebuild(node, state) {
|
|
173
|
-
|
|
203
|
+
// `state.source` for everything READ, `node` for the refinement, which acts on the responder
|
|
204
|
+
// (dispatching a view command needs the committed node, not the one holding the props).
|
|
205
|
+
const source = state.source;
|
|
206
|
+
const base = configFor(source);
|
|
207
|
+
const handlers = createPressHandlers(state.refine === undefined ? base : state.refine(node, base), state.runtime, state.host);
|
|
174
208
|
state.isBuilt = true;
|
|
209
|
+
// Re-read every gesture, so a tag whose resolver looks past `disabled` — `./button`, at
|
|
210
|
+
// `aria-disabled` — re-enables on the next touch instead of latching at its first answer.
|
|
211
|
+
const disabled = state.disabledOf === undefined
|
|
212
|
+
? source.props.disabled
|
|
213
|
+
: state.disabledOf(source.props);
|
|
175
214
|
state.listeners = buildPressableListeners(handlers, {
|
|
176
|
-
disabled:
|
|
177
|
-
cancelable: typeof
|
|
178
|
-
?
|
|
215
|
+
disabled: disabled === true ? true : undefined,
|
|
216
|
+
cancelable: typeof source.props.cancelable === 'boolean'
|
|
217
|
+
? source.props.cancelable
|
|
179
218
|
: undefined,
|
|
180
219
|
});
|
|
181
220
|
}
|
|
@@ -228,7 +267,24 @@ function installListeners(node, state) {
|
|
|
228
267
|
setBehaviorListener(node, event, symbioteEvent => dispatch(node, state, key, [symbioteEvent]));
|
|
229
268
|
}
|
|
230
269
|
}
|
|
231
|
-
function
|
|
270
|
+
function attachWith(refine, disabledOf) {
|
|
271
|
+
return node => attach(node, { refine, disabledOf });
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* The machine on `node`, reading its props and the app's callbacks off `options.source` when that
|
|
275
|
+
* is a different node.
|
|
276
|
+
*
|
|
277
|
+
* Exported for a behavior whose responder is not its own node — `./touchable-native-feedback`,
|
|
278
|
+
* whose tag commits nothing and adopts the app's single child as the responder. Every other caller
|
|
279
|
+
* goes through `createPressBehavior`, where source and node are the same.
|
|
280
|
+
*
|
|
281
|
+
* Re-callable on the same node: a second call replaces the state and the dispatchers, which is what
|
|
282
|
+
* a re-arm after `detachPressMachine` needs.
|
|
283
|
+
*/
|
|
284
|
+
export function attachPressMachine(node, options = {}) {
|
|
285
|
+
attach(node, options);
|
|
286
|
+
}
|
|
287
|
+
function attach(node, options) {
|
|
232
288
|
const timers = new Set();
|
|
233
289
|
const runtime = createPressRuntime();
|
|
234
290
|
const host = {
|
|
@@ -264,6 +320,9 @@ function attach(node) {
|
|
|
264
320
|
const state = {
|
|
265
321
|
runtime,
|
|
266
322
|
host,
|
|
323
|
+
refine: options.refine,
|
|
324
|
+
disabledOf: options.disabledOf,
|
|
325
|
+
source: options.source ?? node,
|
|
267
326
|
timers,
|
|
268
327
|
listeners: {},
|
|
269
328
|
isBuilt: false,
|
|
@@ -271,6 +330,10 @@ function attach(node) {
|
|
|
271
330
|
states.set(node, state);
|
|
272
331
|
installListeners(node, state);
|
|
273
332
|
}
|
|
333
|
+
/** See `attachPressMachine`: the same teardown `createPressBehavior` registers as its `detach`. */
|
|
334
|
+
export function detachPressMachine(node) {
|
|
335
|
+
detach(node);
|
|
336
|
+
}
|
|
274
337
|
function detach(node) {
|
|
275
338
|
const state = states.get(node);
|
|
276
339
|
if (state === undefined)
|
|
@@ -286,11 +349,18 @@ function detach(node) {
|
|
|
286
349
|
states.delete(node);
|
|
287
350
|
dlog('pressable behavior detached');
|
|
288
351
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
352
|
+
/**
|
|
353
|
+
* The press machine as behavior parts, so a tag that is a pressable PLUS something can compose it
|
|
354
|
+
* instead of re-implementing it.
|
|
355
|
+
*
|
|
356
|
+
* Spread into the caller's own behavior and wrap `attach`/`detach` around these — the touchable
|
|
357
|
+
* family needs a per-node Animated value opened before the machine and closed after it. The
|
|
358
|
+
* WeakMap holding the machine's own state is keyed by node, so one node may hold exactly one of
|
|
359
|
+
* these; a tag composing it therefore must not also register the plain `pressable` behavior.
|
|
360
|
+
*/
|
|
361
|
+
export function createPressBehavior(refine, disabledOf) {
|
|
362
|
+
return {
|
|
363
|
+
attach: attachWith(refine, disabledOf),
|
|
294
364
|
detach,
|
|
295
365
|
foldPayload,
|
|
296
366
|
// Every name the machine needs as an INPUT. The responder pair is not optional — it is how a
|
|
@@ -306,5 +376,10 @@ export function registerPressableBehavior() {
|
|
|
306
376
|
'responderMove',
|
|
307
377
|
'responderTerminationRequest',
|
|
308
378
|
],
|
|
309
|
-
}
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
// Idempotent: an adapter entry may be imported more than once in a bundle, and re-registering the
|
|
382
|
+
// same tag with an equivalent behavior must not double-install anything.
|
|
383
|
+
export function registerPressableBehavior() {
|
|
384
|
+
registerHostBehavior(PRESSABLE_TAG, createPressBehavior());
|
|
310
385
|
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// RefreshControl's machine, on the engine node instead of inside a framework component.
|
|
2
|
+
//
|
|
3
|
+
// WHAT THE FIVE WRAPPERS ACTUALLY DO, counted before writing this — the audit rule's instruction to
|
|
4
|
+
// grep the fold's OUTPUT rather than trust one wrapper. Four of the five (react, vue, solid,
|
|
5
|
+
// svelte) fold exactly `resolveAccessibilityProps` and forward, and that fold ALREADY runs in the
|
|
6
|
+
// engine at `fabricProps` on every path (the aria fold `SafeAreaView`'s spec entry cites). So there
|
|
7
|
+
// is nothing left for a `foldPayload` here to do, and this behavior deliberately declares none.
|
|
8
|
+
//
|
|
9
|
+
// The fifth is Angular, and it is the whole reason this file exists: it alone reproduces RN's
|
|
10
|
+
// CONTROLLED handshake (`RefreshControl.js:145-166`) — mirror what native last reported, and when
|
|
11
|
+
// the app's `refreshing` disagrees, command native back with `setNativeRefreshing`. React, Vue,
|
|
12
|
+
// Solid and Svelte have never had it, so a pull whose handler leaves `refreshing` false spins
|
|
13
|
+
// forever on four of five adapters. Moving it here closes that as a P0 parity gap rather than
|
|
14
|
+
// porting it four more times.
|
|
15
|
+
//
|
|
16
|
+
// ONE COMMAND NAME ON BOTH PLATFORMS — no `Platform.OS` branch, unlike Switch's snap-back
|
|
17
|
+
// (`setValue` / `setNativeValue`). RN sends `setNativeRefreshing` through both
|
|
18
|
+
// `PullToRefreshCommands` and `AndroidSwipeRefreshLayoutCommands` (`RefreshControl.js:152,157`).
|
|
19
|
+
//
|
|
20
|
+
// PLACEMENT IS NOT THIS FILE'S PROBLEM, though the tier audit once filed the primitive as
|
|
21
|
+
// impossible over it. iOS puts the control BESIDE the scroll view's content and Android makes it
|
|
22
|
+
// the scroll view's PARENT, and that decision belongs to the ScrollView, which now states it as
|
|
23
|
+
// data — `claimedChildren: { [REFRESH_CONTROL]: platform.claimMode }` in
|
|
24
|
+
// `behaviors/scroll-view/shared.ts`, honoured by the engine's `appendChild`. A claim is keyed on
|
|
25
|
+
// the child's FABRIC name and needs nothing from the child's own behavior, so the two are
|
|
26
|
+
// independent; `refresh-control-placement.test.ts` pins that both ways round.
|
|
27
|
+
//
|
|
28
|
+
// WHY THE DIVERGENCE CHECK IS DEFERRED A MICROTASK, and why `afterCommit` alone is not enough:
|
|
29
|
+
// `behaviors/switch.ts`'s module header, verbatim. Same shape, same two triggers, same reasons —
|
|
30
|
+
// an ACCEPTING app's state reaches `node.props` only after its own reconciliation, and a REJECTING
|
|
31
|
+
// app writes no prop at all, so the commit that `afterCommit` waits for never comes.
|
|
32
|
+
import { appListenerFor, dispatchViewCommand, dlog, registerHostBehavior, setBehaviorListener, } from '@symbiote-native/engine';
|
|
33
|
+
export const REFRESH_CONTROL_TAG = 'refresh-control';
|
|
34
|
+
// RN's own name for the command, sent to whichever of the two native views the platform resolved.
|
|
35
|
+
const SET_NATIVE_REFRESHING = 'setNativeRefreshing';
|
|
36
|
+
// What native LAST reported, absent until it has reported at all. Absent is not `false`: native is
|
|
37
|
+
// optimistic — it spins on the gesture before JS approves — so only a report can make the mirror
|
|
38
|
+
// authoritative, and an app that drives `refreshing` on its own initiative must never be corrected
|
|
39
|
+
// against a value native never claimed.
|
|
40
|
+
const reported = new WeakMap();
|
|
41
|
+
// Shared by both triggers — see the module header for why there are two.
|
|
42
|
+
function evaluateSnapBack(node) {
|
|
43
|
+
const lastNativeReport = reported.get(node);
|
|
44
|
+
if (lastNativeReport === undefined)
|
|
45
|
+
return; // no report yet, nothing to disagree with
|
|
46
|
+
const refreshing = node.props.refreshing === true;
|
|
47
|
+
if (lastNativeReport === refreshing) {
|
|
48
|
+
dlog(`RefreshControl behavior snap-back no-op refreshing=${refreshing}`);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
dlog(`RefreshControl behavior ${SET_NATIVE_REFRESHING} reported=${lastNativeReport} refreshing=${refreshing}`);
|
|
52
|
+
dispatchViewCommand(node, SET_NATIVE_REFRESHING, [refreshing]);
|
|
53
|
+
reported.set(node, refreshing);
|
|
54
|
+
}
|
|
55
|
+
function onRefresh(node, event) {
|
|
56
|
+
// Native has already started spinning by the time this arrives (RefreshControl.js:180), so the
|
|
57
|
+
// mirror moves BEFORE the app's handler runs — a handler that flips `refreshing` to true then
|
|
58
|
+
// agrees with it, and one that does nothing is what the deferred check corrects.
|
|
59
|
+
reported.set(node, true);
|
|
60
|
+
const listener = appListenerFor(node, 'refresh');
|
|
61
|
+
if (typeof listener === 'function')
|
|
62
|
+
listener(event);
|
|
63
|
+
queueMicrotask(() => evaluateSnapBack(node));
|
|
64
|
+
}
|
|
65
|
+
function attach(node) {
|
|
66
|
+
setBehaviorListener(node, 'refresh', event => onRefresh(node, event));
|
|
67
|
+
}
|
|
68
|
+
function detach(node) {
|
|
69
|
+
reported.delete(node);
|
|
70
|
+
}
|
|
71
|
+
// Idempotent: an adapter entry may be imported more than once in a bundle, and re-registering the
|
|
72
|
+
// same tag with an equivalent behavior must not double-install anything.
|
|
73
|
+
export function registerRefreshControlBehavior() {
|
|
74
|
+
registerHostBehavior(REFRESH_CONTROL_TAG, {
|
|
75
|
+
attach,
|
|
76
|
+
// Closes the case a microtask scheduled from `onRefresh` cannot: the app moves `refreshing` on
|
|
77
|
+
// its own while a past report is still unresolved. Costs nothing extra — it fires only on a
|
|
78
|
+
// commit that already changed something.
|
|
79
|
+
afterCommit: evaluateSnapBack,
|
|
80
|
+
detach,
|
|
81
|
+
ownedListeners: ['refresh'],
|
|
82
|
+
});
|
|
83
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function registerScrollViewBehavior(): void;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// ScrollView's behavior on Android, where a RefreshControl is not a child at all.
|
|
2
|
+
//
|
|
3
|
+
// An Android ScrollView holds exactly ONE child, so a sibling refresh control is an `addViewAt`
|
|
4
|
+
// crash rather than a layout mistake. RN inverts the tree instead: `AndroidSwipeRefreshLayout`
|
|
5
|
+
// WRAPS the scroll view, and the scroll view's style is split across the two boxes — layout on the
|
|
6
|
+
// wrapper's frame, visual on the scroller (`ScrollView.js:1856`). `nestedScrollEnabled` goes on the
|
|
7
|
+
// inner view so it consumes the gesture before the refresh parent sees it.
|
|
8
|
+
//
|
|
9
|
+
// The two folds below are what neither node can work out alone: the wrapper is the APP's node, so
|
|
10
|
+
// it carries whatever the app wrote on `<RefreshControl>` and knows nothing about the scroll view's
|
|
11
|
+
// style. RN reaches the same place through `cloneElement`, which likewise OVERRIDES the refresh
|
|
12
|
+
// control's own `style` — so replacing it here is parity, not a liberty.
|
|
13
|
+
import {} from '@symbiote-native/engine';
|
|
14
|
+
import { splitScrollViewStyle } from '../../scroll-view-commands.js';
|
|
15
|
+
import { ownerFold, registerScrollViewBehaviors, } from './shared.js';
|
|
16
|
+
// The owner under a wrap: the ordinary fold with the VISUAL half of its own style in place of the
|
|
17
|
+
// composed one. Delegating rather than restating is what keeps `decelerationRate`, `horizontal` and
|
|
18
|
+
// `nestedScrollEnabled` in ONE place — none of the three has anything to do with the wrap, and the
|
|
19
|
+
// hand-written copy this replaced had already lost the first of them.
|
|
20
|
+
function wrappedOwnerFold(base, horizontal) {
|
|
21
|
+
const plain = ownerFold(base, horizontal);
|
|
22
|
+
return props => ({
|
|
23
|
+
...plain(props),
|
|
24
|
+
style: splitScrollViewStyle(base, props.style).inner,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
// The wrapper: the LAYOUT half of the OWNER's style, read off the owner because that is where the
|
|
28
|
+
// app wrote it. Kept in step by `slotDerived` naming `style`, which marks the wrapper dirty on an
|
|
29
|
+
// owner style write.
|
|
30
|
+
function wrapperFold(owner, base) {
|
|
31
|
+
return props => ({
|
|
32
|
+
...props,
|
|
33
|
+
style: splitScrollViewStyle(base, owner.props.style).outer,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
const android = {
|
|
37
|
+
claimMode: 'wrap',
|
|
38
|
+
slotDerived: ['style'],
|
|
39
|
+
onWrapChange: (base, horizontal) => (owner, wrapper) => {
|
|
40
|
+
// Back to the ordinary composition, not to `undefined` — the plain fold carries the axis and
|
|
41
|
+
// the gesture props, which have nothing to do with the wrap.
|
|
42
|
+
owner.payloadFold =
|
|
43
|
+
wrapper === undefined
|
|
44
|
+
? ownerFold(base, horizontal)
|
|
45
|
+
: wrappedOwnerFold(base, horizontal);
|
|
46
|
+
if (wrapper !== undefined)
|
|
47
|
+
wrapper.payloadFold = wrapperFold(owner, base);
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
export function registerScrollViewBehavior() {
|
|
51
|
+
registerScrollViewBehaviors(android);
|
|
52
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function registerScrollViewBehavior(): void;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// ScrollView's behavior on iOS: a RefreshControl is a SIBLING of the content view, rendered before
|
|
2
|
+
// it (`ScrollView.js:1844`). That is the whole platform half — a claim in `beside` mode, and the
|
|
3
|
+
// engine's placement rule already puts a claimed child before the slot.
|
|
4
|
+
//
|
|
5
|
+
// This file is also the base the folder's `index.ts` re-exports for headless, matching
|
|
6
|
+
// `render-scroll-view`'s own choice to make iOS the default for anything platform-branched.
|
|
7
|
+
import { registerScrollViewBehaviors } from './shared.js';
|
|
8
|
+
export function registerScrollViewBehavior() {
|
|
9
|
+
registerScrollViewBehaviors({ claimMode: 'beside' });
|
|
10
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// The base of the folder-as-module group: Metro picks `index.ios` / `index.android` per platform,
|
|
2
|
+
// and everything else (tsx, vitest, headless) lands here. iOS is the default for the same reason
|
|
3
|
+
// `render-scroll-view`'s own `Platform.select` defaults to it.
|
|
4
|
+
export { registerScrollViewBehavior } from './index.ios.js';
|
|
5
|
+
export { HORIZONTAL_SCROLL_VIEW_TAG, REFRESH_CONTROL, SCROLL_VIEW_TAG, } from './shared.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type IClaimMode, type IHostBehavior, type IPayloadFold, type IViewStyle } from '@symbiote-native/engine';
|
|
2
|
+
export declare const SCROLL_VIEW_TAG = "scroll-view";
|
|
3
|
+
export declare const HORIZONTAL_SCROLL_VIEW_TAG = "horizontal-scroll-view";
|
|
4
|
+
export declare const REFRESH_CONTROL: string;
|
|
5
|
+
export declare function ownerFold(base: IViewStyle, horizontal: boolean): IPayloadFold;
|
|
6
|
+
export interface IScrollPlatform {
|
|
7
|
+
claimMode: IClaimMode;
|
|
8
|
+
onWrapChange?: (base: IViewStyle, horizontal: boolean) => IHostBehavior['onWrapChange'];
|
|
9
|
+
slotDerived?: readonly string[];
|
|
10
|
+
}
|
|
11
|
+
export declare function registerScrollViewBehaviors(platform: IScrollPlatform): void;
|