@symbiote-native/svelte 0.2.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/bootstrap.js +1 -1
- package/build/class-value.js +1 -1
- package/build/components/RefreshControl.svelte +4 -2
- package/build/components/Text.svelte +11 -2
- package/build/components/View.svelte +3 -1
- package/build/components/activity-indicator/index.svelte +8 -2
- package/build/components/button.svelte +8 -3
- package/build/components/flat-list/index.svelte +46 -11
- package/build/components/image/image-logic.js +13 -3
- package/build/components/image-background/index.svelte +8 -2
- package/build/components/index.d.ts +2 -2
- package/build/components/input-accessory-view/index.svelte +4 -1
- package/build/components/keyboard-avoiding-view/index.svelte +34 -11
- package/build/components/modal/index.svelte +4 -1
- package/build/components/pressable/index.svelte +48 -23
- package/build/components/pressable/index.svelte.d.ts +5 -1
- package/build/components/pressable/pressable-props.d.ts +1 -1
- package/build/components/safe-area-view-props.d.ts +1 -0
- package/build/components/scroll-view/index.svelte +54 -21
- package/build/components/scroll-view/sticky-header.svelte +49 -16
- package/build/components/section-list/index.svelte +6 -1
- package/build/components/switch/index.svelte +16 -5
- package/build/components/text-input/index.svelte +80 -8
- package/build/components/text-input/index.svelte.d.ts +5 -0
- package/build/components/touchable-highlight/index.svelte +115 -15
- package/build/components/touchable-highlight/touchable-highlight-props.d.ts +2 -0
- package/build/components/touchable-native-feedback/touchable-native-feedback.svelte +8 -3
- package/build/components/touchable-opacity/index.svelte +113 -56
- package/build/components/touchable-without-feedback/index.svelte +60 -4
- package/build/components/virtualized-list/index.svelte +233 -70
- package/build/components/virtualized-list/virtualized-list-props.js +8 -4
- package/build/components/virtualized-section-list/index.svelte +56 -9
- package/build/components/virtualized-section-list/index.svelte.d.ts +1 -1
- package/build/components/virtualized-section-list/virtualized-section-list-props.d.ts +5 -0
- package/build/create-portal/index.d.ts +22 -0
- package/build/create-portal/index.js +160 -0
- package/build/descriptor-to-svelte.js +15 -21
- package/build/dom-shim/element.d.ts +9 -3
- package/build/dom-shim/element.js +77 -25
- package/build/dom-shim/fold-host-bag.d.ts +1 -0
- package/build/dom-shim/fold-host-bag.js +7 -0
- package/build/dom-shim/index.d.ts +1 -0
- package/build/dom-shim/index.js +3 -0
- package/build/dom-shim/patch-globals.js +4 -2
- package/build/dom-shim/shim-node.js +11 -0
- package/build/dom-shim/text.js +38 -1
- package/build/host-instance.js +9 -5
- package/build/index.d.ts +6 -3
- package/build/index.js +20 -11
- package/build/modules/animated/create-animated-component.d.ts +5 -0
- package/build/modules/animated/create-animated-component.js +131 -0
- package/build/modules/animated/index.d.ts +24 -14
- package/build/modules/animated/index.js +30 -30
- package/build/modules/status-bar/index.js +1 -1
- package/build/modules/status-bar/index.svelte +4 -1
- package/build/native-view-bridge.d.ts +1 -1
- package/build/native-view-bridge.js +1 -1
- package/build/preprocessor/collapse-text-whitespace.js +20 -7
- package/build/preprocessor/forbid-web-only-constructs.js +8 -2
- package/build/preprocessor/lower-host-primitives.d.ts +10 -0
- package/build/preprocessor/lower-host-primitives.js +455 -0
- package/build/preprocessor/scoped-styles.js +141 -104
- package/build/register.d.ts +1 -0
- package/build/register.js +33 -0
- package/build/render.js +48 -3
- package/build/scope-token.d.ts +3 -2
- package/build/scope-token.js +6 -17
- package/build/state-style.d.ts +1 -0
- package/build/state-style.js +8 -0
- package/build/style-scope.d.ts +2 -1
- package/build/style-scope.js +10 -8
- package/metro-svelte-transformer.cjs +48 -14
- package/package.json +28 -10
- package/build/modules/animated/AnimatedImage.svelte +0 -115
- package/build/modules/animated/AnimatedImage.svelte.d.ts +0 -5
- package/build/modules/animated/AnimatedScrollView.svelte +0 -92
- package/build/modules/animated/AnimatedScrollView.svelte.d.ts +0 -17
- package/build/modules/animated/AnimatedText.svelte +0 -58
- package/build/modules/animated/AnimatedText.svelte.d.ts +0 -5
- package/build/modules/animated/AnimatedView.svelte +0 -95
- package/build/modules/animated/AnimatedView.svelte.d.ts +0 -5
package/build/bootstrap.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// own source is Flow syntax Vitest's transform can't parse, so anything importing it directly
|
|
4
4
|
// must stay unreachable from the tested main index.ts.
|
|
5
5
|
import { AppRegistry as RNAppRegistry } from 'react-native';
|
|
6
|
-
import { bootstrapHost } from '@symbiote-native/components/bootstrap';
|
|
6
|
+
import { bootstrapHost, } from '@symbiote-native/components/bootstrap';
|
|
7
7
|
import { AppRegistry, setHostRegistrar } from './modules/app-registry/index.js';
|
|
8
8
|
// Mirrors the Vue/React adapters' createApp(App).mount(appName) two-step idiom.
|
|
9
9
|
export function createApp(RootComponent, options = {}) {
|
package/build/class-value.js
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
// compile error at the call site rather than a surprise at runtime. An empty object is
|
|
26
26
|
// deliberately NOT treated as a clsx map: both readings contribute no style, so the cheaper,
|
|
27
27
|
// non-allocating branch is the right one.
|
|
28
|
-
import { isClassNameValue, resolveClassName } from '@symbiote-native/engine';
|
|
28
|
+
import { isClassNameValue, resolveClassName, } from '@symbiote-native/engine';
|
|
29
29
|
// Returns a class STRING when the value is clsx-shaped, and the value UNTOUCHED otherwise, so
|
|
30
30
|
// everything the engine already resolved correctly keeps taking the exact path it took before.
|
|
31
31
|
export function normalizeSvelteClass(value) {
|
|
@@ -20,8 +20,10 @@
|
|
|
20
20
|
|
|
21
21
|
dlog('RefreshControl -> PullToRefreshView');
|
|
22
22
|
dlog(`RefreshControl refreshing=${String(rest.refreshing)}`);
|
|
23
|
-
if (rest.enabled !== undefined)
|
|
24
|
-
|
|
23
|
+
if (rest.enabled !== undefined)
|
|
24
|
+
dlog(`RefreshControl enabled=${String(rest.enabled)} (Android-only)`);
|
|
25
|
+
if (rest.onRefresh !== undefined)
|
|
26
|
+
dlog('RefreshControl onRefresh listener wired');
|
|
25
27
|
|
|
26
28
|
const bag = $derived(resolveAccessibilityProps(rest));
|
|
27
29
|
|
|
@@ -7,17 +7,26 @@
|
|
|
7
7
|
</script>
|
|
8
8
|
|
|
9
9
|
<script lang="ts">
|
|
10
|
+
import { resolveTextProps } from '@symbiote-native/components';
|
|
10
11
|
import { createAttachmentsSync } from '../runes/attachments';
|
|
11
12
|
import type { ShimElement } from '../dom-shim';
|
|
12
13
|
|
|
13
|
-
let { children, ...
|
|
14
|
+
let { children, ...rest }: ITextProps = $props();
|
|
14
15
|
|
|
15
16
|
// See View.svelte's note: `{@attach}` arrives as a symbol-keyed entry in the same rest object.
|
|
16
17
|
let hostShim = $state.raw<ShimElement | null>(null);
|
|
17
18
|
const syncAttachments = createAttachmentsSync();
|
|
18
19
|
$effect(() => {
|
|
19
|
-
syncAttachments(hostShim,
|
|
20
|
+
syncAttachments(hostShim, rest);
|
|
20
21
|
});
|
|
22
|
+
|
|
23
|
+
// A FRESH object per pass, exactly as View.svelte builds its own: ShimElement's `p` setter
|
|
24
|
+
// diffs the incoming bag against the one it stored last time, so handing it the SAME live rest
|
|
25
|
+
// proxy twice compares that object with itself and applies nothing — every prop update after
|
|
26
|
+
// mount was silently dropped.
|
|
27
|
+
// resolveTextProps carries RN's Text.js defaults (ellipsizeMode 'tail', allowFontScaling
|
|
28
|
+
// true); without them native falls back to `clip` and a clamped Text cuts mid-word.
|
|
29
|
+
const bag = $derived(resolveTextProps({ ...rest }));
|
|
21
30
|
</script>
|
|
22
31
|
|
|
23
32
|
<symbiote-text p={bag} bind:this={hostShim}>
|
|
@@ -33,7 +33,9 @@
|
|
|
33
33
|
// the raw `id` key so it never reaches Fabric (every non-function prop in the bag otherwise
|
|
34
34
|
// passes through to the slot as-is) — mirrors React's `resolveId` in
|
|
35
35
|
// `adapters/react/src/components.ts`, adapted to the object-bag idiom.
|
|
36
|
-
const bag = $derived(
|
|
36
|
+
const bag = $derived(
|
|
37
|
+
id === undefined ? { ...rest, nativeID } : { ...rest, nativeID: id },
|
|
38
|
+
);
|
|
37
39
|
</script>
|
|
38
40
|
|
|
39
41
|
<symbiote-view p={bag} bind:this={hostShim}>
|
|
@@ -12,7 +12,10 @@
|
|
|
12
12
|
</script>
|
|
13
13
|
|
|
14
14
|
<script lang="ts">
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
renderActivityIndicator,
|
|
17
|
+
resolveAccessibilityProps,
|
|
18
|
+
} from '@symbiote-native/components';
|
|
16
19
|
import { PLATFORM } from './activity-indicator-platform';
|
|
17
20
|
import { createDescriptorChildrenSync } from '../../descriptor-to-svelte';
|
|
18
21
|
import { createAttachmentsSync } from '../../runes/attachments';
|
|
@@ -36,7 +39,10 @@
|
|
|
36
39
|
size,
|
|
37
40
|
color,
|
|
38
41
|
style,
|
|
39
|
-
passthrough: {
|
|
42
|
+
passthrough: {
|
|
43
|
+
...resolveAccessibilityProps(passthrough),
|
|
44
|
+
class: className,
|
|
45
|
+
},
|
|
40
46
|
},
|
|
41
47
|
PLATFORM,
|
|
42
48
|
),
|
|
@@ -11,7 +11,10 @@
|
|
|
11
11
|
</script>
|
|
12
12
|
|
|
13
13
|
<script lang="ts">
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
BUTTON_ACCESSIBILITY_ROLE,
|
|
16
|
+
resolveButtonTextStyle,
|
|
17
|
+
} from '@symbiote-native/components';
|
|
15
18
|
import TouchableOpacity from './touchable-opacity/index.svelte';
|
|
16
19
|
|
|
17
20
|
let {
|
|
@@ -38,9 +41,11 @@
|
|
|
38
41
|
// excess-property typing does not reject the native-only keys. TV-focus is inert on a phone.
|
|
39
42
|
const nativeForward = $derived.by(() => {
|
|
40
43
|
const forward: Record<string, unknown> = { testID };
|
|
41
|
-
if (hasTVPreferredFocus !== undefined)
|
|
44
|
+
if (hasTVPreferredFocus !== undefined)
|
|
45
|
+
forward.hasTVPreferredFocus = hasTVPreferredFocus;
|
|
42
46
|
if (nextFocusDown !== undefined) forward.nextFocusDown = nextFocusDown;
|
|
43
|
-
if (nextFocusForward !== undefined)
|
|
47
|
+
if (nextFocusForward !== undefined)
|
|
48
|
+
forward.nextFocusForward = nextFocusForward;
|
|
44
49
|
if (nextFocusLeft !== undefined) forward.nextFocusLeft = nextFocusLeft;
|
|
45
50
|
if (nextFocusRight !== undefined) forward.nextFocusRight = nextFocusRight;
|
|
46
51
|
if (nextFocusUp !== undefined) forward.nextFocusUp = nextFocusUp;
|
|
@@ -23,10 +23,16 @@
|
|
|
23
23
|
type ISeparators,
|
|
24
24
|
type IViewableItemsChangedInfo,
|
|
25
25
|
} from '@symbiote-native/components';
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
resolveClassName,
|
|
28
|
+
type ISymbioteNode,
|
|
29
|
+
} from '@symbiote-native/engine';
|
|
27
30
|
import VirtualizedList from '../virtualized-list/index.svelte';
|
|
28
31
|
import { pickAccessibilityProps } from '../virtualized-list/virtualized-list-props';
|
|
29
|
-
import type {
|
|
32
|
+
import type {
|
|
33
|
+
IVirtualizedListHandle,
|
|
34
|
+
IScrollViewHandle,
|
|
35
|
+
} from '../virtualized-list/virtualized-list-props';
|
|
30
36
|
import type { IFlatListProps as IProps } from './flat-list-props';
|
|
31
37
|
import { pickAttachmentProps } from '../../runes/attachments';
|
|
32
38
|
|
|
@@ -37,7 +43,9 @@
|
|
|
37
43
|
// scrollHandle over its ShimElement).
|
|
38
44
|
let inner = $state.raw<IVirtualizedListHandle | null>(null);
|
|
39
45
|
|
|
40
|
-
const numColumns = $derived(
|
|
46
|
+
const numColumns = $derived(
|
|
47
|
+
typeof props.numColumns === 'number' ? props.numColumns : SINGLE_COLUMN,
|
|
48
|
+
);
|
|
41
49
|
const rows = $derived.by((): IRow<ItemT>[] =>
|
|
42
50
|
numColumns > SINGLE_COLUMN ? chunkIntoRows(props.data, numColumns) : [],
|
|
43
51
|
);
|
|
@@ -56,7 +64,9 @@
|
|
|
56
64
|
}
|
|
57
65
|
|
|
58
66
|
function isStyleLike(value: unknown): boolean {
|
|
59
|
-
return
|
|
67
|
+
return (
|
|
68
|
+
Array.isArray(value) || (typeof value === 'object' && value !== null)
|
|
69
|
+
);
|
|
60
70
|
}
|
|
61
71
|
|
|
62
72
|
const rowStyle = $derived.by(() => [
|
|
@@ -70,7 +80,9 @@
|
|
|
70
80
|
|
|
71
81
|
// The divider between rows shows real items (last of the row above, first of the row below), so
|
|
72
82
|
// the user's `separator` snippet, typed on ItemT, sees items rather than the IRow wrapper.
|
|
73
|
-
function rowSeparatorProps(
|
|
83
|
+
function rowSeparatorProps(
|
|
84
|
+
entryProps: ISeparatorProps<IRow<ItemT>>,
|
|
85
|
+
): ISeparatorProps<ItemT> {
|
|
74
86
|
return {
|
|
75
87
|
...entryProps,
|
|
76
88
|
leadingItem: lastItemOfRow(entryProps.leadingItem),
|
|
@@ -87,8 +99,12 @@
|
|
|
87
99
|
const rowViewabilityPairs = $derived(
|
|
88
100
|
props.viewabilityConfigCallbackPairs?.map(pair => ({
|
|
89
101
|
viewabilityConfig: pair.viewabilityConfig,
|
|
90
|
-
onViewableItemsChanged: (
|
|
91
|
-
|
|
102
|
+
onViewableItemsChanged: (
|
|
103
|
+
rowInfo: IViewableItemsChangedInfo<IRow<ItemT>>,
|
|
104
|
+
): void => {
|
|
105
|
+
pair.onViewableItemsChanged(
|
|
106
|
+
expandRowViewability(rowInfo, props.keyExtractor),
|
|
107
|
+
);
|
|
92
108
|
},
|
|
93
109
|
})),
|
|
94
110
|
);
|
|
@@ -100,7 +116,10 @@
|
|
|
100
116
|
const accessibilityProps = $derived(pickAccessibilityProps(props));
|
|
101
117
|
|
|
102
118
|
// ---- imperative handle: delegate straight through to the inner VirtualizedList instance. ----
|
|
103
|
-
export function scrollToOffset(params: {
|
|
119
|
+
export function scrollToOffset(params: {
|
|
120
|
+
offset: number;
|
|
121
|
+
animated?: boolean;
|
|
122
|
+
}): void {
|
|
104
123
|
inner?.scrollToOffset(params);
|
|
105
124
|
}
|
|
106
125
|
export function scrollToIndex(params: {
|
|
@@ -111,7 +130,11 @@
|
|
|
111
130
|
}): void {
|
|
112
131
|
inner?.scrollToIndex(params);
|
|
113
132
|
}
|
|
114
|
-
export function scrollToItem(params: {
|
|
133
|
+
export function scrollToItem(params: {
|
|
134
|
+
item: unknown;
|
|
135
|
+
animated?: boolean;
|
|
136
|
+
viewPosition?: number;
|
|
137
|
+
}): void {
|
|
115
138
|
inner?.scrollToItem(params);
|
|
116
139
|
}
|
|
117
140
|
export function scrollToEnd(params?: { animated?: boolean }): void {
|
|
@@ -141,11 +164,23 @@
|
|
|
141
164
|
const attachments = $derived(pickAttachmentProps(props));
|
|
142
165
|
</script>
|
|
143
166
|
|
|
144
|
-
{#snippet rowItem({
|
|
167
|
+
{#snippet rowItem({
|
|
168
|
+
item: row,
|
|
169
|
+
index,
|
|
170
|
+
separators,
|
|
171
|
+
}: {
|
|
172
|
+
item: IRow<ItemT>;
|
|
173
|
+
index: number;
|
|
174
|
+
separators: ISeparators;
|
|
175
|
+
})}
|
|
145
176
|
<symbiote-view p={{ style: rowStyle }}>
|
|
146
177
|
{#each row.items as rowItem, column (props.keyExtractor ? props.keyExtractor(rowItem, row.startIndex + column) : String(row.startIndex + column))}
|
|
147
178
|
<symbiote-view p={{ style: { flex: 1 } }}>
|
|
148
|
-
{@render props.item({
|
|
179
|
+
{@render props.item({
|
|
180
|
+
item: rowItem,
|
|
181
|
+
index: row.startIndex + column,
|
|
182
|
+
separators,
|
|
183
|
+
})}
|
|
149
184
|
</symbiote-view>
|
|
150
185
|
{/each}
|
|
151
186
|
</symbiote-view>
|
|
@@ -42,7 +42,13 @@ function expandSrcSet(srcSet, view, headers) {
|
|
|
42
42
|
continue;
|
|
43
43
|
if (scale === 1)
|
|
44
44
|
useSrcForDefaultScale = false;
|
|
45
|
-
sources.push({
|
|
45
|
+
sources.push({
|
|
46
|
+
uri,
|
|
47
|
+
scale,
|
|
48
|
+
width: view.width,
|
|
49
|
+
height: view.height,
|
|
50
|
+
...{ headers },
|
|
51
|
+
});
|
|
46
52
|
}
|
|
47
53
|
if (useSrcForDefaultScale && view.src !== undefined) {
|
|
48
54
|
sources.push({
|
|
@@ -63,7 +69,9 @@ function resolveSourceArray(view) {
|
|
|
63
69
|
return expandSrcSet(view.srcSet, view, headers);
|
|
64
70
|
}
|
|
65
71
|
if (view.src !== undefined) {
|
|
66
|
-
return [
|
|
72
|
+
return [
|
|
73
|
+
{ uri: view.src, width: view.width, height: view.height, ...{ headers } },
|
|
74
|
+
];
|
|
67
75
|
}
|
|
68
76
|
if (view.source === undefined) {
|
|
69
77
|
dlog('Image: no source / src / srcSet provided');
|
|
@@ -72,7 +80,9 @@ function resolveSourceArray(view) {
|
|
|
72
80
|
const sources = normalizeSource(view.source);
|
|
73
81
|
if (Object.keys(headers).length > 0 && sources.length === 1) {
|
|
74
82
|
const [only] = sources;
|
|
75
|
-
if (typeof only === 'object' &&
|
|
83
|
+
if (typeof only === 'object' &&
|
|
84
|
+
only !== null &&
|
|
85
|
+
typeof Reflect.get(only, 'uri') === 'string') {
|
|
76
86
|
return [{ ...only, headers }];
|
|
77
87
|
}
|
|
78
88
|
}
|
|
@@ -13,7 +13,10 @@
|
|
|
13
13
|
</script>
|
|
14
14
|
|
|
15
15
|
<script lang="ts">
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
renderImageBackground,
|
|
18
|
+
resolveAccessibilityProps,
|
|
19
|
+
} from '@symbiote-native/components';
|
|
17
20
|
import { resolveClassName } from '@symbiote-native/engine';
|
|
18
21
|
import { createAttachmentsSync } from '../../runes/attachments';
|
|
19
22
|
import type { ShimElement } from '../../dom-shim';
|
|
@@ -78,4 +81,7 @@
|
|
|
78
81
|
});
|
|
79
82
|
</script>
|
|
80
83
|
|
|
81
|
-
<symbiote-view p={wrapperBag} bind:this={hostShim}
|
|
84
|
+
<symbiote-view p={wrapperBag} bind:this={hostShim}>
|
|
85
|
+
<symbiote-image p={imageBag} />
|
|
86
|
+
{@render children?.()}
|
|
87
|
+
</symbiote-view>
|
|
@@ -43,8 +43,8 @@ export type { IStickyHeaderComponentProps } from './scroll-view/sticky-header-pr
|
|
|
43
43
|
export { default as VirtualizedList } from './virtualized-list/index.svelte';
|
|
44
44
|
export type { IVirtualizedListProps, IVirtualizedListHandle, } from './virtualized-list/virtualized-list-props';
|
|
45
45
|
export { default as FlatList } from './flat-list/index.svelte';
|
|
46
|
-
export type { IFlatListProps, IFlatListHandle } from './flat-list/flat-list-props';
|
|
46
|
+
export type { IFlatListProps, IFlatListHandle, } from './flat-list/flat-list-props';
|
|
47
47
|
export { default as VirtualizedSectionList } from './virtualized-section-list/index.svelte';
|
|
48
48
|
export type { IVirtualizedSectionListProps } from './virtualized-section-list/virtualized-section-list-props';
|
|
49
49
|
export { default as SectionList } from './section-list/index.svelte';
|
|
50
|
-
export type { ISectionListProps, ISection } from './section-list/section-list-props';
|
|
50
|
+
export type { ISectionListProps, ISection, } from './section-list/section-list-props';
|
|
@@ -14,7 +14,10 @@
|
|
|
14
14
|
</script>
|
|
15
15
|
|
|
16
16
|
<script lang="ts">
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
renderInputAccessoryView,
|
|
19
|
+
resolveAccessibilityProps,
|
|
20
|
+
} from '@symbiote-native/components';
|
|
18
21
|
import { createAttachmentsSync } from '../../runes/attachments';
|
|
19
22
|
import type { ShimElement } from '../../dom-shim';
|
|
20
23
|
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
// this adapter supplies only the lifecycle: $state for the inset, a mutable frame/initialHeight
|
|
6
6
|
// pair (NOT $state — changing them alone shouldn't re-render, same non-reactive-ref shape as
|
|
7
7
|
// React's frameRef/initialHeightRef and Vue's plain `let frame`), an $effect subscribing to the
|
|
8
|
-
// Keyboard module (mirrors React's useEffect /
|
|
9
|
-
// wrapper assembly around the Snippet children.
|
|
8
|
+
// Keyboard module and reading the cross-fade device setting once (mirrors React's useEffect /
|
|
9
|
+
// Vue's onMounted+onUnmounted pair), and the wrapper assembly around the Snippet children.
|
|
10
10
|
//
|
|
11
11
|
// render-keyboard-avoiding-view.ts does NOT return a Descriptor here (by its own design — it
|
|
12
12
|
// returns a layout DESCRIPTION instead, because KAV wraps arbitrary user children the Descriptor
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
<script lang="ts">
|
|
22
22
|
import {
|
|
23
23
|
Keyboard,
|
|
24
|
-
|
|
24
|
+
Platform,
|
|
25
25
|
dlog,
|
|
26
26
|
type IEventSubscription,
|
|
27
27
|
type ISymbioteEvent,
|
|
@@ -30,6 +30,8 @@
|
|
|
30
30
|
import type { ShimElement } from '../../dom-shim';
|
|
31
31
|
import {
|
|
32
32
|
computeInset,
|
|
33
|
+
keyboardAvoidingEventNamesFor,
|
|
34
|
+
readPrefersCrossFadeTransitions,
|
|
33
35
|
readKeyboardFrame,
|
|
34
36
|
readLayoutFrame,
|
|
35
37
|
resolveKeyboardAvoidingLayout,
|
|
@@ -55,10 +57,22 @@
|
|
|
55
57
|
// next keyboard event's inset math.
|
|
56
58
|
let frame: IMeasuredFrame | undefined;
|
|
57
59
|
let initialHeight: number | undefined;
|
|
60
|
+
// A device accessibility setting, not component state: it cannot change mid-session, and nothing
|
|
61
|
+
// in the markup reads it — it only feeds the next keyboard event's math — so a plain `let`, not
|
|
62
|
+
// $state. Resolves false on Android and when the native getter is absent.
|
|
63
|
+
let prefersCrossFadeTransitions = false;
|
|
58
64
|
|
|
59
65
|
function onShow(payload: unknown): void {
|
|
60
66
|
const keyboard = readKeyboardFrame(payload);
|
|
61
|
-
|
|
67
|
+
// `behavior` and `inset` are both read HERE, at event time, never captured when this handler
|
|
68
|
+
// was built: Svelte compiles a destructured prop into a live getter, so a `behavior` changed
|
|
69
|
+
// after mount reaches the math on the next event, and 'height' mode feeds back the inset
|
|
70
|
+
// CURRENTLY applied (RN's this.state.bottom) to cancel the shrink its own wrapper caused.
|
|
71
|
+
const next = computeInset(frame, keyboard, keyboardVerticalOffset, {
|
|
72
|
+
behavior,
|
|
73
|
+
previousInset: inset,
|
|
74
|
+
prefersCrossFadeTransitions,
|
|
75
|
+
});
|
|
62
76
|
dlog(`KeyboardAvoidingView show -> inset ${next}`);
|
|
63
77
|
inset = next;
|
|
64
78
|
}
|
|
@@ -68,15 +82,22 @@
|
|
|
68
82
|
}
|
|
69
83
|
|
|
70
84
|
// No reactive state is read synchronously in this callback body (only inside onShow/onHide,
|
|
71
|
-
// which run later at event-time via closures over the live `
|
|
72
|
-
//
|
|
73
|
-
//
|
|
85
|
+
// which run later at event-time via closures over the live props and `inset`), so this effect has
|
|
86
|
+
// zero tracked dependencies and runs exactly once per mount/unmount — the same subscribe-once/
|
|
87
|
+
// cleanup-once shape as Vue's onMounted+onUnmounted pair. TWO listeners, never three: RN's own
|
|
88
|
+
// comment warns that a change-frame notification arrives BEFORE the hide one on an undocked iOS
|
|
89
|
+
// keyboard, so subscribing to it applies a frame captured mid-dismissal.
|
|
74
90
|
$effect(() => {
|
|
91
|
+
const events = keyboardAvoidingEventNamesFor(Platform.OS);
|
|
75
92
|
const subscriptions: IEventSubscription[] = [
|
|
76
|
-
Keyboard.addListener(
|
|
77
|
-
Keyboard.addListener(
|
|
78
|
-
Keyboard.addListener(KEYBOARD_EVENT.didHide, onHide),
|
|
93
|
+
Keyboard.addListener(events.show, onShow),
|
|
94
|
+
Keyboard.addListener(events.hide, onHide),
|
|
79
95
|
];
|
|
96
|
+
// Through the core wrapper, not AccessibilityInfo directly: nobody awaits this, and the
|
|
97
|
+
// engine's iOS getters reject on a native error, so the fallback-to-false lives there once.
|
|
98
|
+
void readPrefersCrossFadeTransitions().then(prefers => {
|
|
99
|
+
prefersCrossFadeTransitions = prefers;
|
|
100
|
+
});
|
|
80
101
|
return () => {
|
|
81
102
|
for (const subscription of subscriptions) subscription.remove();
|
|
82
103
|
};
|
|
@@ -111,7 +132,9 @@
|
|
|
111
132
|
onLayout: handleLayout,
|
|
112
133
|
});
|
|
113
134
|
|
|
114
|
-
const innerBag = $derived(
|
|
135
|
+
const innerBag = $derived(
|
|
136
|
+
layout.kind === 'nested' ? { style: layout.innerStyle } : undefined,
|
|
137
|
+
);
|
|
115
138
|
|
|
116
139
|
// See View.svelte's note on `{@attach}` — bound to the OUTER wrapper, which is the node
|
|
117
140
|
// this component's own style lands on in both the nested and flat layouts.
|
|
@@ -53,7 +53,10 @@
|
|
|
53
53
|
// isVisible=false with isRendered still true before this effect flips it) but isn't observable
|
|
54
54
|
// as its own committed Fabric frame the way React's per-render synchronous commit makes it.
|
|
55
55
|
$effect(() => {
|
|
56
|
-
state = modalReducer(
|
|
56
|
+
state = modalReducer(
|
|
57
|
+
state,
|
|
58
|
+
isVisible ? { type: 'show' } : { type: 'hide' },
|
|
59
|
+
);
|
|
57
60
|
});
|
|
58
61
|
|
|
59
62
|
$effect(() => {
|
|
@@ -22,15 +22,18 @@
|
|
|
22
22
|
</script>
|
|
23
23
|
|
|
24
24
|
<script lang="ts">
|
|
25
|
+
import { onDestroy } from 'svelte';
|
|
25
26
|
import {
|
|
26
27
|
createPressHandlers,
|
|
27
28
|
createPressRuntime,
|
|
29
|
+
disposePressRuntime,
|
|
28
30
|
rippleProps,
|
|
29
31
|
buildPressableListeners,
|
|
30
32
|
resolveDisabledAccessibilityState,
|
|
31
33
|
resolveAccessibilityProps,
|
|
32
34
|
noteHoverNoop,
|
|
33
35
|
DEFAULT_DELAY_LONG_PRESS_MS,
|
|
36
|
+
DEFAULT_MIN_PRESS_DURATION_MS,
|
|
34
37
|
type IPressHost,
|
|
35
38
|
type IPressState,
|
|
36
39
|
} from '@symbiote-native/components';
|
|
@@ -50,6 +53,7 @@
|
|
|
50
53
|
hitSlop,
|
|
51
54
|
pressRetentionOffset,
|
|
52
55
|
unstable_pressDelay = 0,
|
|
56
|
+
__minPressDuration = DEFAULT_MIN_PRESS_DURATION_MS,
|
|
53
57
|
android_ripple,
|
|
54
58
|
android_disableSound,
|
|
55
59
|
onHoverIn,
|
|
@@ -61,7 +65,10 @@
|
|
|
61
65
|
class: className,
|
|
62
66
|
children,
|
|
63
67
|
...rest
|
|
64
|
-
}: IPressableProps
|
|
68
|
+
}: IPressableProps & {
|
|
69
|
+
/** @internal Touchable* mirrors RN's Pressability minPressDuration: 0 override. */
|
|
70
|
+
__minPressDuration?: number;
|
|
71
|
+
} = $props();
|
|
65
72
|
|
|
66
73
|
let pressed = $state(false);
|
|
67
74
|
// Plain setup-scope object, never `$state`: mutated by the machine on every event, never
|
|
@@ -85,8 +92,13 @@
|
|
|
85
92
|
const id = setTimeout(callback, ms);
|
|
86
93
|
return () => clearTimeout(id);
|
|
87
94
|
},
|
|
95
|
+
now: Date.now,
|
|
88
96
|
};
|
|
89
97
|
|
|
98
|
+
onDestroy(() => {
|
|
99
|
+
disposePressRuntime(runtime);
|
|
100
|
+
});
|
|
101
|
+
|
|
90
102
|
$effect(() => {
|
|
91
103
|
noteHoverNoop(onHoverIn, onHoverOut);
|
|
92
104
|
});
|
|
@@ -107,6 +119,7 @@
|
|
|
107
119
|
onLongPress,
|
|
108
120
|
delayLongPress,
|
|
109
121
|
unstable_pressDelay,
|
|
122
|
+
minPressDuration: __minPressDuration,
|
|
110
123
|
hitSlop,
|
|
111
124
|
pressRetentionOffset,
|
|
112
125
|
},
|
|
@@ -115,31 +128,43 @@
|
|
|
115
128
|
),
|
|
116
129
|
);
|
|
117
130
|
|
|
118
|
-
const resolvedAccessibilityState = $derived(
|
|
119
|
-
resolveDisabledAccessibilityState(accessibilityState, disabled),
|
|
120
|
-
);
|
|
121
|
-
|
|
122
|
-
// Folds aria-*/role into accessibility* — Pressable owns its host node directly (it does not
|
|
123
|
-
// compose View.svelte), so it must fold this itself, exactly like Vue's Pressable and every
|
|
124
|
-
// other Svelte component that hand-authors a raw host tag (RefreshControl.svelte, modal/
|
|
125
|
-
// index.svelte).
|
|
126
|
-
const resolved = $derived(
|
|
127
|
-
resolveAccessibilityProps({ ...rest, accessibilityState: resolvedAccessibilityState }),
|
|
128
|
-
);
|
|
129
|
-
|
|
130
|
-
const resolvedStyle = $derived(typeof style === 'function' ? style(state) : style);
|
|
131
|
-
|
|
132
131
|
// android_ripple rides a dedicated inner View; on iOS rippleProps() returns undefined, so the
|
|
133
132
|
// child renders unwrapped, no extra node — mirrors React's Pressable + touchable-native-feedback.
|
|
134
|
-
const ripple = $derived(
|
|
133
|
+
const ripple = $derived(
|
|
134
|
+
android_ripple !== undefined ? rippleProps(android_ripple) : undefined,
|
|
135
|
+
);
|
|
135
136
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
137
|
+
// ONE derived, not four. The accessibility fold and the style resolution used to be their own
|
|
138
|
+
// `$derived`s, each read by exactly this bag and by nothing else — a memo nobody can reuse, in
|
|
139
|
+
// exchange for a reaction-graph node per Pressable instance. A 1 000-row list mounts 2 000 of
|
|
140
|
+
// them, which made this component the largest single allocation site in a create profile.
|
|
141
|
+
// `handlers` and `ripple` stay separate BECAUSE they are read elsewhere: `handlers` must keep
|
|
142
|
+
// its identity across an unrelated bag change or every recompute re-registers native listeners,
|
|
143
|
+
// and `ripple` is read twice (the branch test and the inner view's own bag).
|
|
144
|
+
const bag = $derived.by(() => {
|
|
145
|
+
// Folds aria-*/role into accessibility* — Pressable owns its host node directly (it does not
|
|
146
|
+
// compose View.svelte), so it must fold this itself, exactly like Vue's Pressable and every
|
|
147
|
+
// other Svelte component that hand-authors a raw host tag (RefreshControl.svelte, modal/
|
|
148
|
+
// index.svelte).
|
|
149
|
+
const resolved = resolveAccessibilityProps({
|
|
150
|
+
...rest,
|
|
151
|
+
accessibilityState: resolveDisabledAccessibilityState(
|
|
152
|
+
accessibilityState,
|
|
153
|
+
disabled,
|
|
154
|
+
),
|
|
155
|
+
});
|
|
156
|
+
const next: Record<string, unknown> = {
|
|
157
|
+
...resolved,
|
|
158
|
+
style: typeof style === 'function' ? style(state) : style,
|
|
159
|
+
class: className,
|
|
160
|
+
hitSlop,
|
|
161
|
+
...buildPressableListeners(handlers, { disabled, cancelable }),
|
|
162
|
+
};
|
|
163
|
+
// Assigned rather than conditionally spread: `...(cond ? { x } : {})` allocated an empty
|
|
164
|
+
// object literal on every evaluation for the overwhelmingly common `undefined` case.
|
|
165
|
+
if (android_disableSound !== undefined)
|
|
166
|
+
next.android_disableSound = android_disableSound;
|
|
167
|
+
return next;
|
|
143
168
|
});
|
|
144
169
|
|
|
145
170
|
// See View.svelte's note on `{@attach}`.
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { IPressableProps } from './pressable-props';
|
|
2
2
|
export type { IPressableProps };
|
|
3
|
-
|
|
3
|
+
type $$ComponentProps = IPressableProps & {
|
|
4
|
+
/** @internal Touchable* mirrors RN's Pressability minPressDuration: 0 override. */
|
|
5
|
+
__minPressDuration?: number;
|
|
6
|
+
};
|
|
7
|
+
declare const Index: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
4
8
|
type Index = ReturnType<typeof Index>;
|
|
5
9
|
export default Index;
|
|
@@ -2,7 +2,7 @@ import type { Snippet } from 'svelte';
|
|
|
2
2
|
import type { IStyleProp, IViewStyle } from '@symbiote-native/engine';
|
|
3
3
|
import type { IAccessibilityProps, IAriaProps, IPressHandler, IPressState, IRectOffset, IPressableAndroidRippleConfig } from '@symbiote-native/components';
|
|
4
4
|
import type { ISvelteClassValue } from '../../class-value';
|
|
5
|
-
export type { IPressState, IPressableAndroidRippleConfig } from '@symbiote-native/components';
|
|
5
|
+
export type { IPressState, IPressableAndroidRippleConfig, } from '@symbiote-native/components';
|
|
6
6
|
export interface IPressableProps extends IAccessibilityProps, IAriaProps {
|
|
7
7
|
onPress?: IPressHandler;
|
|
8
8
|
onPressIn?: IPressHandler;
|
|
@@ -4,6 +4,7 @@ import type { IAccessibilityProps, IAriaProps } from '@symbiote-native/component
|
|
|
4
4
|
import type { ISvelteClassValue } from '../class-value';
|
|
5
5
|
export interface ISafeAreaViewProps extends IAccessibilityProps, IAriaProps {
|
|
6
6
|
style?: IStyleProp<IViewStyle>;
|
|
7
|
+
id?: string;
|
|
7
8
|
class?: ISvelteClassValue;
|
|
8
9
|
onLayout?: (event: ISymbioteEvent) => void;
|
|
9
10
|
children?: Snippet;
|