@expo/ui 58.0.6 → 58.0.8
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/CHANGELOG.md +20 -0
- package/android/build.gradle +2 -2
- package/android/src/main/java/expo/modules/ui/ExpoUIModule.kt +9 -0
- package/android/src/main/java/expo/modules/ui/LazyColumnView.kt +6 -1
- package/android/src/main/java/expo/modules/ui/LazyItemsView.kt +313 -0
- package/android/src/main/java/expo/modules/ui/LazyRowView.kt +6 -1
- package/android/src/main/java/expo/modules/ui/ModifierRegistry.kt +11 -3
- package/android/src/main/java/expo/modules/ui/RNHostView.kt +18 -4
- package/android/src/main/java/expo/modules/ui/SemanticsModifier.kt +2 -1
- package/build/jetpack-compose/LazyColumn/index.d.ts +6 -0
- package/build/jetpack-compose/LazyColumn/index.js +2 -0
- package/build/jetpack-compose/LazyColumn/index.js.map +1 -1
- package/build/jetpack-compose/LazyItems/index.d.ts +54 -0
- package/build/jetpack-compose/LazyItems/index.js +63 -0
- package/build/jetpack-compose/LazyItems/index.js.map +1 -0
- package/build/jetpack-compose/LazyRow/index.d.ts +6 -0
- package/build/jetpack-compose/LazyRow/index.js +2 -0
- package/build/jetpack-compose/LazyRow/index.js.map +1 -1
- package/build/jetpack-compose/index.d.ts +1 -0
- package/build/jetpack-compose/index.js.map +1 -1
- package/build/jetpack-compose/modifiers/index.d.ts +15 -3
- package/build/jetpack-compose/modifiers/index.js.map +1 -1
- package/build/recycling/useRecycledRows.d.ts +52 -0
- package/build/recycling/useRecycledRows.js +121 -0
- package/build/recycling/useRecycledRows.js.map +1 -0
- package/build/recycling/window.js.map +1 -0
- package/build/swift-ui/List/DataListForEach.d.ts +2 -9
- package/build/swift-ui/List/DataListForEach.js +15 -106
- package/build/swift-ui/List/DataListForEach.js.map +1 -1
- package/build/swift-ui/List/ListForEach.js +3 -2
- package/build/swift-ui/List/ListForEach.js.map +1 -1
- package/build/swift-ui/modifiers/index.d.ts +5 -1
- package/build/swift-ui/modifiers/index.js.map +1 -1
- package/build/swift-ui/modifiers/presentationModifiers.d.ts +8 -0
- package/build/swift-ui/modifiers/presentationModifiers.js +9 -0
- package/build/swift-ui/modifiers/presentationModifiers.js.map +1 -1
- package/build/universal/State.android.d.ts +1 -3
- package/build/universal/State.d.ts +2 -3
- package/build/universal/State.ios.d.ts +1 -3
- package/build/universal/State.js +10 -4
- package/build/universal/State.js.map +1 -1
- package/build/universal/transformStyle.ios.js +4 -3
- package/build/universal/transformStyle.ios.js.map +1 -1
- package/build/universal/types.d.ts +24 -0
- package/build/universal/types.js +8 -2
- package/build/universal/types.js.map +1 -1
- package/expo-module.config.json +1 -1
- package/ios/Modifiers/PresentationModifiers.swift +10 -0
- package/ios/Modifiers/ViewModifierRegistry.swift +4 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/{58.0.6/expo.modules.ui-58.0.6-sources.jar → 58.0.8/expo.modules.ui-58.0.8-sources.jar} +0 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.8/expo.modules.ui-58.0.8-sources.jar.md5 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.8/expo.modules.ui-58.0.8-sources.jar.sha1 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.8/expo.modules.ui-58.0.8-sources.jar.sha256 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.8/expo.modules.ui-58.0.8-sources.jar.sha512 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.8/expo.modules.ui-58.0.8.aar +0 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.8/expo.modules.ui-58.0.8.aar.md5 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.8/expo.modules.ui-58.0.8.aar.sha1 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.8/expo.modules.ui-58.0.8.aar.sha256 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.8/expo.modules.ui-58.0.8.aar.sha512 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/{58.0.6/expo.modules.ui-58.0.6.module → 58.0.8/expo.modules.ui-58.0.8.module} +24 -24
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.8/expo.modules.ui-58.0.8.module.md5 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.8/expo.modules.ui-58.0.8.module.sha1 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.8/expo.modules.ui-58.0.8.module.sha256 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.8/expo.modules.ui-58.0.8.module.sha512 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/{58.0.6/expo.modules.ui-58.0.6.pom → 58.0.8/expo.modules.ui-58.0.8.pom} +3 -3
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.8/expo.modules.ui-58.0.8.pom.md5 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.8/expo.modules.ui-58.0.8.pom.sha1 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.8/expo.modules.ui-58.0.8.pom.sha256 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.8/expo.modules.ui-58.0.8.pom.sha512 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml +4 -4
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml.md5 +1 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml.sha1 +1 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml.sha256 +1 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml.sha512 +1 -1
- package/package.json +4 -4
- package/prebuilds/output/debug/xcframeworks/ExpoUI.tar.gz +0 -0
- package/prebuilds/output/release/xcframeworks/ExpoUI.tar.gz +0 -0
- package/src/jetpack-compose/LazyColumn/index.tsx +3 -0
- package/src/jetpack-compose/LazyItems/index.tsx +121 -0
- package/src/jetpack-compose/LazyRow/index.tsx +3 -0
- package/src/jetpack-compose/index.ts +1 -0
- package/src/jetpack-compose/modifiers/index.ts +16 -1
- package/src/recycling/useRecycledRows.tsx +200 -0
- package/src/swift-ui/List/DataListForEach.tsx +21 -142
- package/src/swift-ui/List/ListForEach.tsx +3 -2
- package/src/swift-ui/modifiers/index.ts +5 -1
- package/src/swift-ui/modifiers/presentationModifiers.ts +10 -0
- package/src/universal/State.android.ts +1 -1
- package/src/universal/State.ios.ts +1 -1
- package/src/universal/State.ts +12 -4
- package/src/universal/transformStyle.ios.ts +5 -2
- package/src/universal/types.ts +27 -0
- package/build/swift-ui/List/window.js.map +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.6/expo.modules.ui-58.0.6-sources.jar.md5 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.6/expo.modules.ui-58.0.6-sources.jar.sha1 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.6/expo.modules.ui-58.0.6-sources.jar.sha256 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.6/expo.modules.ui-58.0.6-sources.jar.sha512 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.6/expo.modules.ui-58.0.6.aar +0 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.6/expo.modules.ui-58.0.6.aar.md5 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.6/expo.modules.ui-58.0.6.aar.sha1 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.6/expo.modules.ui-58.0.6.aar.sha256 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.6/expo.modules.ui-58.0.6.aar.sha512 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.6/expo.modules.ui-58.0.6.module.md5 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.6/expo.modules.ui-58.0.6.module.sha1 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.6/expo.modules.ui-58.0.6.module.sha256 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.6/expo.modules.ui-58.0.6.module.sha512 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.6/expo.modules.ui-58.0.6.pom.md5 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.6/expo.modules.ui-58.0.6.pom.sha1 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.6/expo.modules.ui-58.0.6.pom.sha256 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.6/expo.modules.ui-58.0.6.pom.sha512 +0 -1
- /package/build/{swift-ui/List → recycling}/window.d.ts +0 -0
- /package/build/{swift-ui/List → recycling}/window.js +0 -0
- /package/src/{swift-ui/List → recycling}/window.ts +0 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { requireNativeView } from 'expo';
|
|
2
|
+
import { Fragment, type ReactElement } from 'react';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
useItemKeys,
|
|
6
|
+
useRecycledRows,
|
|
7
|
+
type RecycledSlotProps,
|
|
8
|
+
type RenderItem,
|
|
9
|
+
type WindowChangeEvent,
|
|
10
|
+
} from '../../recycling/useRecycledRows';
|
|
11
|
+
import { type ViewEvent } from '../../types';
|
|
12
|
+
|
|
13
|
+
export interface LazyItemsProps<ItemT> {
|
|
14
|
+
/** Items to display. Replace the array when updating data. */
|
|
15
|
+
data: readonly ItemT[];
|
|
16
|
+
/** Returns a stable, unique string key, also used as the lazy list item key. */
|
|
17
|
+
keyExtractor: (item: ItemT, index: number) => string;
|
|
18
|
+
/**
|
|
19
|
+
* Renders a row. Wrap it in `useCallback`, or every row re-renders on each parent render.
|
|
20
|
+
* Recycled rows are reused for other items, so their local state (`useState`) carries over.
|
|
21
|
+
* Reset it when the item changes, or keep the state outside the row.
|
|
22
|
+
*/
|
|
23
|
+
children: RenderItem<ItemT>;
|
|
24
|
+
/**
|
|
25
|
+
* Renders only the rows near the visible range and reuses them while scrolling. Set to `false` to
|
|
26
|
+
* render every row at once. Set it once; changing it remounts the rows.
|
|
27
|
+
* @default true
|
|
28
|
+
*/
|
|
29
|
+
recycling?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Extra rows to prepare on each side of the visible rows. Must be a non-negative integer.
|
|
32
|
+
* Ignored when `recycling` is `false`.
|
|
33
|
+
* @default 10
|
|
34
|
+
*/
|
|
35
|
+
overscanCount?: number;
|
|
36
|
+
/**
|
|
37
|
+
* Placeholder size in dp along the scroll axis, until a row is measured. Must be positive.
|
|
38
|
+
* Measurements reset when the data changes. In `LazyColumn.Items` they also reset when the list
|
|
39
|
+
* width changes. Ignored when `recycling` is `false`.
|
|
40
|
+
* @default 64
|
|
41
|
+
*/
|
|
42
|
+
estimatedItemSize?: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
type NativeLazyItemsProps = ViewEvent<'onWindowChange', WindowChangeEvent> & {
|
|
46
|
+
itemKeys: string[];
|
|
47
|
+
revision: number;
|
|
48
|
+
estimatedItemSize: number;
|
|
49
|
+
children: ReactElement;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const LazyItemsNativeView: React.ComponentType<NativeLazyItemsProps> =
|
|
53
|
+
requireNativeView<NativeLazyItemsProps>('ExpoUI', 'LazyItemsView');
|
|
54
|
+
const LazyItemsSlotNativeView: React.ComponentType<RecycledSlotProps> =
|
|
55
|
+
requireNativeView<RecycledSlotProps>('ExpoUI', 'LazyItemsSlotView');
|
|
56
|
+
const LazyItemsPoolNativeView: React.ComponentType<{ children: ReactElement[] }> =
|
|
57
|
+
requireNativeView('ExpoUI', 'LazyItemsPoolView');
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* A block of recycled rows inside `LazyColumn` or `LazyRow`, mirroring the Compose
|
|
61
|
+
* `items(count, key)` builder. Only a small pool of rows around the visible range is mounted,
|
|
62
|
+
* so large data sets stay cheap. Rows that are not ready yet show a placeholder of
|
|
63
|
+
* `estimatedItemSize`, or of the size last measured for that item.
|
|
64
|
+
*
|
|
65
|
+
* Mount it as a direct child of `LazyColumn` or `LazyRow`. Plain children of the same list stay
|
|
66
|
+
* single items, so static and recycled content can be mixed in order.
|
|
67
|
+
* @platform android
|
|
68
|
+
*/
|
|
69
|
+
export const LazyItems = createLazyItems('LazyColumn.Items');
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @hidden
|
|
73
|
+
* Creates the `Items` component of one lazy list, so its errors name that list.
|
|
74
|
+
*/
|
|
75
|
+
export function createLazyItems(componentName: string) {
|
|
76
|
+
function Items<ItemT>({ recycling = true, ...props }: LazyItemsProps<ItemT>) {
|
|
77
|
+
return recycling ? <RecycledItems {...props} /> : <StaticItems {...props} />;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Each row is a plain child of the lazy list, so it becomes one lazy item.
|
|
81
|
+
function StaticItems<ItemT>({
|
|
82
|
+
data,
|
|
83
|
+
keyExtractor,
|
|
84
|
+
children: renderItem,
|
|
85
|
+
}: Omit<LazyItemsProps<ItemT>, 'recycling'>) {
|
|
86
|
+
const itemKeys = useItemKeys(componentName, data, keyExtractor);
|
|
87
|
+
return data.map((item, index) => (
|
|
88
|
+
<Fragment key={itemKeys[index]}>{renderItem({ item, index })}</Fragment>
|
|
89
|
+
));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function RecycledItems<ItemT>({
|
|
93
|
+
data,
|
|
94
|
+
keyExtractor,
|
|
95
|
+
children: renderItem,
|
|
96
|
+
overscanCount,
|
|
97
|
+
estimatedItemSize = 64,
|
|
98
|
+
}: Omit<LazyItemsProps<ItemT>, 'recycling'>) {
|
|
99
|
+
const { itemKeys, revision, rows, onWindowChange } = useRecycledRows({
|
|
100
|
+
componentName,
|
|
101
|
+
Slot: LazyItemsSlotNativeView,
|
|
102
|
+
data,
|
|
103
|
+
keyExtractor,
|
|
104
|
+
renderItem,
|
|
105
|
+
overscanCount,
|
|
106
|
+
estimatedItemSize,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<LazyItemsNativeView
|
|
111
|
+
itemKeys={itemKeys}
|
|
112
|
+
revision={revision}
|
|
113
|
+
estimatedItemSize={estimatedItemSize}
|
|
114
|
+
onWindowChange={({ nativeEvent }) => onWindowChange(nativeEvent)}>
|
|
115
|
+
<LazyItemsPoolNativeView>{rows}</LazyItemsPoolNativeView>
|
|
116
|
+
</LazyItemsNativeView>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
Items.displayName = componentName;
|
|
120
|
+
return Items;
|
|
121
|
+
}
|
|
@@ -2,6 +2,7 @@ import { requireNativeView } from 'expo';
|
|
|
2
2
|
|
|
3
3
|
import type { ExpoModifier } from '../../types';
|
|
4
4
|
import { type ContentPadding } from '../LazyColumn';
|
|
5
|
+
import { createLazyItems } from '../LazyItems';
|
|
5
6
|
import { createViewModifierEventListener } from '../modifiers/utils';
|
|
6
7
|
|
|
7
8
|
export interface LazyRowProps {
|
|
@@ -56,3 +57,5 @@ function transformProps(props: LazyRowProps): NativeLazyRowProps {
|
|
|
56
57
|
export function LazyRow(props: LazyRowProps) {
|
|
57
58
|
return <LazyRowNativeView {...transformProps(props)} />;
|
|
58
59
|
}
|
|
60
|
+
|
|
61
|
+
LazyRow.Items = createLazyItems('LazyRow.Items');
|
|
@@ -17,6 +17,7 @@ export * from './ExposedDropdownMenuBox';
|
|
|
17
17
|
export * from './Divider';
|
|
18
18
|
export * from './Host';
|
|
19
19
|
export * from './LazyColumn';
|
|
20
|
+
export { type LazyItemsProps } from './LazyItems';
|
|
20
21
|
export * from './LazyRow';
|
|
21
22
|
export * from './ListItem';
|
|
22
23
|
export * from './RNHostView';
|
|
@@ -454,10 +454,25 @@ export const onGloballyPositioned = (
|
|
|
454
454
|
*/
|
|
455
455
|
export const testID = (tag: string) => createModifier('testID', { testID: tag });
|
|
456
456
|
|
|
457
|
+
/**
|
|
458
|
+
* Options for the `semantics` modifier.
|
|
459
|
+
*/
|
|
460
|
+
export type SemanticsConfig = {
|
|
461
|
+
/**
|
|
462
|
+
* An autofill hint, such as `'email'` or `'password'`.
|
|
463
|
+
*/
|
|
464
|
+
contentType?: string;
|
|
465
|
+
/**
|
|
466
|
+
* What accessibility services such as TalkBack announce for the element. Useful when the visible
|
|
467
|
+
* content does not read well aloud, as with an abbreviation or a glyph.
|
|
468
|
+
*/
|
|
469
|
+
contentDescription?: string;
|
|
470
|
+
};
|
|
471
|
+
|
|
457
472
|
/**
|
|
458
473
|
* Applies semantic properties. Wraps `Modifier.semantics { ... }`.
|
|
459
474
|
*/
|
|
460
|
-
export const semantics = (params:
|
|
475
|
+
export const semantics = (params: SemanticsConfig) => createModifier('semantics', params);
|
|
461
476
|
|
|
462
477
|
// =============================================================================
|
|
463
478
|
// Clip Modifier & Shapes
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import {
|
|
2
|
+
memo,
|
|
3
|
+
useLayoutEffect,
|
|
4
|
+
useMemo,
|
|
5
|
+
useRef,
|
|
6
|
+
useState,
|
|
7
|
+
type ComponentType,
|
|
8
|
+
type ReactElement,
|
|
9
|
+
} from 'react';
|
|
10
|
+
|
|
11
|
+
import { getSlotIndices, getWindow } from './window';
|
|
12
|
+
|
|
13
|
+
/** Payload of the native event asking for the rows around the visible range. */
|
|
14
|
+
export type WindowChangeEvent = { first: number; last: number; revision: number };
|
|
15
|
+
|
|
16
|
+
/** Props of the native view that holds one recycled row. */
|
|
17
|
+
export type RecycledSlotProps = {
|
|
18
|
+
itemKey: string;
|
|
19
|
+
index: number;
|
|
20
|
+
revision: number;
|
|
21
|
+
children: ReactElement;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/** Renders one row of the data set. */
|
|
25
|
+
export type RenderItem<ItemT> = (info: { item: ItemT; index: number }) => ReactElement;
|
|
26
|
+
|
|
27
|
+
type RecycledRowProps<ItemT> = Omit<RecycledSlotProps, 'children'> & {
|
|
28
|
+
Slot: ComponentType<RecycledSlotProps>;
|
|
29
|
+
item: ItemT;
|
|
30
|
+
renderItem: RenderItem<ItemT>;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Skip unchanged rows when the window moves.
|
|
34
|
+
const RecycledRow = memo(function RecycledRow<ItemT>({
|
|
35
|
+
Slot,
|
|
36
|
+
item,
|
|
37
|
+
index,
|
|
38
|
+
itemKey,
|
|
39
|
+
revision,
|
|
40
|
+
renderItem,
|
|
41
|
+
}: RecycledRowProps<ItemT>) {
|
|
42
|
+
return (
|
|
43
|
+
<Slot itemKey={itemKey} index={index} revision={revision}>
|
|
44
|
+
{renderItem({ item, index })}
|
|
45
|
+
</Slot>
|
|
46
|
+
);
|
|
47
|
+
}) as <ItemT>(props: RecycledRowProps<ItemT>) => ReactElement;
|
|
48
|
+
|
|
49
|
+
export type RecycledRows = {
|
|
50
|
+
/** Keys of every item, so the native list can size and identify the full data set. */
|
|
51
|
+
itemKeys: string[];
|
|
52
|
+
/** Increases whenever the data changes, so late native events can be dropped. */
|
|
53
|
+
revision: number;
|
|
54
|
+
/** The pooled rows to mount, one per slot. */
|
|
55
|
+
rows: ReactElement[];
|
|
56
|
+
/** Applies a native window request. */
|
|
57
|
+
onWindowChange: (event: WindowChangeEvent) => void;
|
|
58
|
+
/** Whether a native event was sent for the data currently committed to native. */
|
|
59
|
+
isCurrentRevision: (revision: number) => boolean;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Returns one key per item. Keys follow `data` only: an inline `keyExtractor` changes identity on
|
|
64
|
+
* every parent render, and recomputing keys for it would bump the revision and re-render every row.
|
|
65
|
+
*/
|
|
66
|
+
export function useItemKeys<ItemT>(
|
|
67
|
+
componentName: string,
|
|
68
|
+
data: readonly ItemT[],
|
|
69
|
+
keyExtractor: (item: ItemT, index: number) => string
|
|
70
|
+
): string[] {
|
|
71
|
+
const keyExtractorRef = useRef(keyExtractor);
|
|
72
|
+
keyExtractorRef.current = keyExtractor;
|
|
73
|
+
return useMemo(() => {
|
|
74
|
+
const keys = data.map((item, index) => keyExtractorRef.current(item, index));
|
|
75
|
+
if (keys.some((key) => typeof key !== 'string') || new Set(keys).size !== keys.length) {
|
|
76
|
+
throw new Error(`${componentName} keyExtractor must return a unique string for every item.`);
|
|
77
|
+
}
|
|
78
|
+
return keys;
|
|
79
|
+
}, [data]);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Keeps a fixed pool of rows centered on the range the native list asks for.
|
|
84
|
+
* Item `index` always renders in slot `index % capacity`, so moving the window
|
|
85
|
+
* only re-renders the slots whose item changed.
|
|
86
|
+
*/
|
|
87
|
+
export function useRecycledRows<ItemT>({
|
|
88
|
+
componentName,
|
|
89
|
+
Slot,
|
|
90
|
+
data,
|
|
91
|
+
keyExtractor,
|
|
92
|
+
renderItem,
|
|
93
|
+
overscanCount = 10,
|
|
94
|
+
estimatedItemSize = 64,
|
|
95
|
+
}: {
|
|
96
|
+
/** Name used in error messages, for example `List.ForEach`. */
|
|
97
|
+
componentName: string;
|
|
98
|
+
/** Native view that holds one recycled row. */
|
|
99
|
+
Slot: ComponentType<RecycledSlotProps>;
|
|
100
|
+
data: readonly ItemT[];
|
|
101
|
+
keyExtractor: (item: ItemT, index: number) => string;
|
|
102
|
+
renderItem: RenderItem<ItemT>;
|
|
103
|
+
overscanCount?: number;
|
|
104
|
+
estimatedItemSize?: number;
|
|
105
|
+
}): RecycledRows {
|
|
106
|
+
if (!Number.isSafeInteger(overscanCount) || overscanCount < 0) {
|
|
107
|
+
throw new Error(`${componentName} overscanCount must be a non-negative integer.`);
|
|
108
|
+
}
|
|
109
|
+
if (!Number.isFinite(estimatedItemSize) || estimatedItemSize <= 0) {
|
|
110
|
+
throw new Error(`${componentName} estimatedItemSize must be a positive finite number.`);
|
|
111
|
+
}
|
|
112
|
+
const itemKeys = useItemKeys(componentName, data, keyExtractor);
|
|
113
|
+
const [state, setState] = useState({
|
|
114
|
+
keys: itemKeys,
|
|
115
|
+
first: 0,
|
|
116
|
+
last: 0,
|
|
117
|
+
revision: 0,
|
|
118
|
+
capacity: 0,
|
|
119
|
+
overscanCount,
|
|
120
|
+
});
|
|
121
|
+
let current = state;
|
|
122
|
+
if (state.keys !== itemKeys) {
|
|
123
|
+
const previousKey = state.keys[state.first];
|
|
124
|
+
const anchor = previousKey === undefined ? 0 : Math.max(0, itemKeys.indexOf(previousKey));
|
|
125
|
+
current = {
|
|
126
|
+
...state,
|
|
127
|
+
keys: itemKeys,
|
|
128
|
+
first: Math.min(anchor, Math.max(0, data.length - 1)),
|
|
129
|
+
last: Math.min(anchor + state.last - state.first, Math.max(0, data.length - 1)),
|
|
130
|
+
revision: state.revision + 1,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
if (current.overscanCount !== overscanCount) {
|
|
134
|
+
current = { ...current, overscanCount, capacity: 0 };
|
|
135
|
+
}
|
|
136
|
+
if (current !== state) setState(current);
|
|
137
|
+
const { start, capacity } = getWindow(
|
|
138
|
+
data.length,
|
|
139
|
+
current.first,
|
|
140
|
+
current.last,
|
|
141
|
+
overscanCount,
|
|
142
|
+
current.capacity
|
|
143
|
+
);
|
|
144
|
+
const committedRevision = useRef(current.revision);
|
|
145
|
+
useLayoutEffect(() => {
|
|
146
|
+
committedRevision.current = current.revision;
|
|
147
|
+
}, [current.revision]);
|
|
148
|
+
|
|
149
|
+
const rows = getSlotIndices(start, capacity).map((index, slot) => {
|
|
150
|
+
const item = data[index];
|
|
151
|
+
const itemKey = itemKeys[index];
|
|
152
|
+
if (item === undefined || itemKey === undefined) {
|
|
153
|
+
throw new Error(`${componentName} could not resolve the item at index ${index}.`);
|
|
154
|
+
}
|
|
155
|
+
return (
|
|
156
|
+
<RecycledRow
|
|
157
|
+
key={slot}
|
|
158
|
+
Slot={Slot}
|
|
159
|
+
item={item}
|
|
160
|
+
index={index}
|
|
161
|
+
itemKey={itemKey}
|
|
162
|
+
revision={current.revision}
|
|
163
|
+
renderItem={renderItem}
|
|
164
|
+
/>
|
|
165
|
+
);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
itemKeys,
|
|
170
|
+
revision: current.revision,
|
|
171
|
+
rows,
|
|
172
|
+
isCurrentRevision: (revision) => revision === committedRevision.current,
|
|
173
|
+
onWindowChange: (event) => {
|
|
174
|
+
if (
|
|
175
|
+
!Number.isSafeInteger(event.first) ||
|
|
176
|
+
!Number.isSafeInteger(event.last) ||
|
|
177
|
+
event.last < event.first
|
|
178
|
+
) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
setState((previous) => {
|
|
182
|
+
// Ignore requests from before a data change.
|
|
183
|
+
if (event.revision !== previous.revision) return previous;
|
|
184
|
+
const maximum = Math.max(0, previous.keys.length - 1);
|
|
185
|
+
const first = Math.max(0, Math.min(event.first, maximum));
|
|
186
|
+
const last = Math.max(first, Math.min(event.last, maximum));
|
|
187
|
+
const { capacity } = getWindow(
|
|
188
|
+
previous.keys.length,
|
|
189
|
+
first,
|
|
190
|
+
last,
|
|
191
|
+
previous.overscanCount,
|
|
192
|
+
previous.capacity
|
|
193
|
+
);
|
|
194
|
+
return first === previous.first && last === previous.last && capacity === previous.capacity
|
|
195
|
+
? previous
|
|
196
|
+
: { ...previous, first, last, capacity };
|
|
197
|
+
});
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { requireNativeView } from 'expo';
|
|
2
|
-
import {
|
|
2
|
+
import { type ReactElement } from 'react';
|
|
3
3
|
|
|
4
|
+
import {
|
|
5
|
+
useRecycledRows,
|
|
6
|
+
type RecycledSlotProps,
|
|
7
|
+
type WindowChangeEvent,
|
|
8
|
+
} from '../../recycling/useRecycledRows';
|
|
4
9
|
import { type ViewEvent } from '../../types';
|
|
5
10
|
import { createViewModifierEventListener } from '../modifiers/utils';
|
|
6
11
|
import { type CommonViewModifierProps } from '../types';
|
|
7
|
-
import { getSlotIndices, getWindow } from './window';
|
|
8
12
|
|
|
9
13
|
export interface ListForEachProps<ItemT> extends CommonViewModifierProps {
|
|
10
14
|
/** Called with deleted indices from this group's `data` array. */
|
|
@@ -46,9 +50,8 @@ export interface ListForEachProps<ItemT> extends CommonViewModifierProps {
|
|
|
46
50
|
estimatedItemSize?: number;
|
|
47
51
|
}
|
|
48
52
|
|
|
49
|
-
type WindowEvent = { first: number; last: number; revision: number };
|
|
50
53
|
type NativeProps = CommonViewModifierProps &
|
|
51
|
-
ViewEvent<'onWindowChange',
|
|
54
|
+
ViewEvent<'onWindowChange', WindowChangeEvent> &
|
|
52
55
|
ViewEvent<'onDelete', { indices: number[]; revision: number }> &
|
|
53
56
|
ViewEvent<'onMove', { sourceIndices: number[]; destination: number; revision: number }> & {
|
|
54
57
|
deleteEnabled: boolean;
|
|
@@ -58,59 +61,14 @@ type NativeProps = CommonViewModifierProps &
|
|
|
58
61
|
estimatedItemSize: number;
|
|
59
62
|
children: ReactElement;
|
|
60
63
|
};
|
|
61
|
-
type SlotProps = {
|
|
62
|
-
itemKey: string;
|
|
63
|
-
index: number;
|
|
64
|
-
revision: number;
|
|
65
|
-
children: ReactElement;
|
|
66
|
-
};
|
|
67
64
|
|
|
68
65
|
const NativeList = requireNativeView<NativeProps>('ExpoUI', 'DataListForEachView');
|
|
69
|
-
export const NativeSlot = requireNativeView<
|
|
66
|
+
export const NativeSlot = requireNativeView<RecycledSlotProps>('ExpoUI', 'DataListForEachItemView');
|
|
70
67
|
const NativePool = requireNativeView<{ children: ReactElement[] }>(
|
|
71
68
|
'ExpoUI',
|
|
72
69
|
'DataListForEachPoolView'
|
|
73
70
|
);
|
|
74
71
|
|
|
75
|
-
// Skip unchanged rows when the window moves.
|
|
76
|
-
const RecycledRow = memo(function RecycledRow<ItemT>({
|
|
77
|
-
item,
|
|
78
|
-
index,
|
|
79
|
-
itemKey,
|
|
80
|
-
revision,
|
|
81
|
-
renderItem,
|
|
82
|
-
}: SlotPropsWithoutChildren & {
|
|
83
|
-
item: ItemT;
|
|
84
|
-
renderItem: ListForEachProps<ItemT>['children'];
|
|
85
|
-
}) {
|
|
86
|
-
return (
|
|
87
|
-
<NativeSlot itemKey={itemKey} index={index} revision={revision}>
|
|
88
|
-
{renderItem({ item, index })}
|
|
89
|
-
</NativeSlot>
|
|
90
|
-
);
|
|
91
|
-
}) as <ItemT>(
|
|
92
|
-
props: SlotPropsWithoutChildren & {
|
|
93
|
-
item: ItemT;
|
|
94
|
-
renderItem: ListForEachProps<ItemT>['children'];
|
|
95
|
-
}
|
|
96
|
-
) => ReactElement;
|
|
97
|
-
type SlotPropsWithoutChildren = Omit<SlotProps, 'children'>;
|
|
98
|
-
|
|
99
|
-
export function useItemKeys<ItemT>(
|
|
100
|
-
data: readonly ItemT[],
|
|
101
|
-
keyExtractor: ListForEachProps<ItemT>['keyExtractor']
|
|
102
|
-
): string[] {
|
|
103
|
-
const keyExtractorRef = useRef(keyExtractor);
|
|
104
|
-
keyExtractorRef.current = keyExtractor;
|
|
105
|
-
return useMemo(() => {
|
|
106
|
-
const keys = data.map((item, index) => keyExtractorRef.current(item, index));
|
|
107
|
-
if (keys.some((key) => typeof key !== 'string') || new Set(keys).size !== keys.length) {
|
|
108
|
-
throw new Error('List.ForEach keyExtractor must return a unique string for every item.');
|
|
109
|
-
}
|
|
110
|
-
return keys;
|
|
111
|
-
}, [data]);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
72
|
/**
|
|
115
73
|
* Renders a window of recycled rows inside List or Section.
|
|
116
74
|
* Rows show placeholders while JS prepares content. Native keys still cover the full data set.
|
|
@@ -120,56 +78,22 @@ export function DataListForEach<ItemT>({
|
|
|
120
78
|
data,
|
|
121
79
|
keyExtractor,
|
|
122
80
|
children: renderItem,
|
|
123
|
-
overscanCount
|
|
81
|
+
overscanCount,
|
|
124
82
|
estimatedItemSize = 64,
|
|
125
83
|
modifiers,
|
|
126
84
|
onDelete,
|
|
127
85
|
onMove,
|
|
128
86
|
...props
|
|
129
87
|
}: ListForEachProps<ItemT>) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const itemKeys = useItemKeys(data, keyExtractor);
|
|
137
|
-
const [state, setState] = useState({
|
|
138
|
-
keys: itemKeys,
|
|
139
|
-
first: 0,
|
|
140
|
-
last: 0,
|
|
141
|
-
revision: 0,
|
|
142
|
-
capacity: 0,
|
|
88
|
+
const { itemKeys, revision, rows, onWindowChange, isCurrentRevision } = useRecycledRows({
|
|
89
|
+
componentName: 'List.ForEach',
|
|
90
|
+
Slot: NativeSlot,
|
|
91
|
+
data,
|
|
92
|
+
keyExtractor,
|
|
93
|
+
renderItem,
|
|
143
94
|
overscanCount,
|
|
95
|
+
estimatedItemSize,
|
|
144
96
|
});
|
|
145
|
-
let current = state;
|
|
146
|
-
if (state.keys !== itemKeys) {
|
|
147
|
-
const previousKey = state.keys[state.first];
|
|
148
|
-
const anchor = previousKey === undefined ? 0 : Math.max(0, itemKeys.indexOf(previousKey));
|
|
149
|
-
current = {
|
|
150
|
-
...state,
|
|
151
|
-
keys: itemKeys,
|
|
152
|
-
first: Math.min(anchor, Math.max(0, data.length - 1)),
|
|
153
|
-
last: Math.min(anchor + state.last - state.first, Math.max(0, data.length - 1)),
|
|
154
|
-
revision: state.revision + 1,
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
if (current.overscanCount !== overscanCount) {
|
|
158
|
-
current = { ...current, overscanCount, capacity: 0 };
|
|
159
|
-
}
|
|
160
|
-
if (current !== state) setState(current);
|
|
161
|
-
const { start, capacity } = getWindow(
|
|
162
|
-
data.length,
|
|
163
|
-
current.first,
|
|
164
|
-
current.last,
|
|
165
|
-
overscanCount,
|
|
166
|
-
current.capacity
|
|
167
|
-
);
|
|
168
|
-
const committedRevision = useRef(current.revision);
|
|
169
|
-
useLayoutEffect(() => {
|
|
170
|
-
committedRevision.current = current.revision;
|
|
171
|
-
}, [current.revision]);
|
|
172
|
-
const indices = getSlotIndices(start, capacity);
|
|
173
97
|
|
|
174
98
|
return (
|
|
175
99
|
<NativeList
|
|
@@ -181,69 +105,24 @@ export function DataListForEach<ItemT>({
|
|
|
181
105
|
onDelete={
|
|
182
106
|
onDelete
|
|
183
107
|
? ({ nativeEvent }) => {
|
|
184
|
-
if (nativeEvent.revision
|
|
108
|
+
if (isCurrentRevision(nativeEvent.revision)) onDelete(nativeEvent.indices);
|
|
185
109
|
}
|
|
186
110
|
: undefined
|
|
187
111
|
}
|
|
188
112
|
onMove={
|
|
189
113
|
onMove
|
|
190
114
|
? ({ nativeEvent }) => {
|
|
191
|
-
if (nativeEvent.revision
|
|
115
|
+
if (isCurrentRevision(nativeEvent.revision)) {
|
|
192
116
|
onMove(nativeEvent.sourceIndices, nativeEvent.destination);
|
|
193
117
|
}
|
|
194
118
|
}
|
|
195
119
|
: undefined
|
|
196
120
|
}
|
|
197
121
|
itemKeys={itemKeys}
|
|
198
|
-
revision={
|
|
122
|
+
revision={revision}
|
|
199
123
|
estimatedItemSize={estimatedItemSize}
|
|
200
|
-
onWindowChange={({ nativeEvent
|
|
201
|
-
|
|
202
|
-
!Number.isSafeInteger(event.first) ||
|
|
203
|
-
!Number.isSafeInteger(event.last) ||
|
|
204
|
-
event.last < event.first
|
|
205
|
-
) {
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
setState((previous) => {
|
|
209
|
-
// Ignore requests from before a data change.
|
|
210
|
-
if (event.revision !== previous.revision) return previous;
|
|
211
|
-
const maximum = Math.max(0, previous.keys.length - 1);
|
|
212
|
-
const first = Math.max(0, Math.min(event.first, maximum));
|
|
213
|
-
const last = Math.max(first, Math.min(event.last, maximum));
|
|
214
|
-
const { capacity } = getWindow(
|
|
215
|
-
previous.keys.length,
|
|
216
|
-
first,
|
|
217
|
-
last,
|
|
218
|
-
previous.overscanCount,
|
|
219
|
-
previous.capacity
|
|
220
|
-
);
|
|
221
|
-
return first === previous.first &&
|
|
222
|
-
last === previous.last &&
|
|
223
|
-
capacity === previous.capacity
|
|
224
|
-
? previous
|
|
225
|
-
: { ...previous, first, last, capacity };
|
|
226
|
-
});
|
|
227
|
-
}}>
|
|
228
|
-
<NativePool>
|
|
229
|
-
{indices.map((index, slot) => {
|
|
230
|
-
const item = data[index];
|
|
231
|
-
const itemKey = itemKeys[index];
|
|
232
|
-
if (item === undefined || itemKey === undefined) {
|
|
233
|
-
throw new Error(`List.ForEach could not resolve the item at index ${index}.`);
|
|
234
|
-
}
|
|
235
|
-
return (
|
|
236
|
-
<RecycledRow
|
|
237
|
-
key={slot}
|
|
238
|
-
item={item}
|
|
239
|
-
index={index}
|
|
240
|
-
itemKey={itemKey}
|
|
241
|
-
revision={current.revision}
|
|
242
|
-
renderItem={renderItem}
|
|
243
|
-
/>
|
|
244
|
-
);
|
|
245
|
-
})}
|
|
246
|
-
</NativePool>
|
|
124
|
+
onWindowChange={({ nativeEvent }) => onWindowChange(nativeEvent)}>
|
|
125
|
+
<NativePool>{rows}</NativePool>
|
|
247
126
|
</NativeList>
|
|
248
127
|
);
|
|
249
128
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { requireNativeView } from 'expo';
|
|
2
2
|
|
|
3
|
+
import { useItemKeys } from '../../recycling/useRecycledRows';
|
|
3
4
|
import { type ViewEvent } from '../../types';
|
|
4
5
|
import { type CommonViewModifierProps } from '../types';
|
|
5
|
-
import { DataListForEach, NativeSlot,
|
|
6
|
+
import { DataListForEach, NativeSlot, type ListForEachProps } from './DataListForEach';
|
|
6
7
|
|
|
7
8
|
export { type ListForEachProps };
|
|
8
9
|
|
|
@@ -94,7 +95,7 @@ function StaticListForEach<ItemT>({
|
|
|
94
95
|
estimatedItemSize: _estimatedItemSize,
|
|
95
96
|
...props
|
|
96
97
|
}: Omit<ListForEachProps<ItemT>, 'recycling'>) {
|
|
97
|
-
const itemKeys = useItemKeys(data, keyExtractor);
|
|
98
|
+
const itemKeys = useItemKeys('List.ForEach', data, keyExtractor);
|
|
98
99
|
return (
|
|
99
100
|
<ChildrenListForEach {...props}>
|
|
100
101
|
{data.map((item, index) => (
|
|
@@ -106,7 +106,11 @@ export const matchedGeometryEffect = (
|
|
|
106
106
|
*/
|
|
107
107
|
export const geometryGroup = () => createModifier('geometryGroup', {});
|
|
108
108
|
|
|
109
|
-
|
|
109
|
+
/**
|
|
110
|
+
* The alignment of a view inside the frame that `frame()` creates.
|
|
111
|
+
* Most values have no visible effect when the frame is the same size as the view.
|
|
112
|
+
*/
|
|
113
|
+
export type FrameAlignment =
|
|
110
114
|
| 'center'
|
|
111
115
|
| 'leading'
|
|
112
116
|
| 'trailing'
|
|
@@ -57,6 +57,16 @@ export const presentationDetents = (
|
|
|
57
57
|
export const presentationDragIndicator = (visibility: 'automatic' | 'visible' | 'hidden') =>
|
|
58
58
|
createModifier('presentationDragIndicator', { visibility });
|
|
59
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Sets the corner radius of a sheet presentation.
|
|
62
|
+
* @param cornerRadius - The corner radius in points, or `null` to use the system default.
|
|
63
|
+
* @platform ios 16.4+
|
|
64
|
+
* @platform tvos 16.4+
|
|
65
|
+
* @see Official [SwiftUI documentation](https://developer.apple.com/documentation/swiftui/view/presentationcornerradius(_:)).
|
|
66
|
+
*/
|
|
67
|
+
export const presentationCornerRadius = (cornerRadius: number | null) =>
|
|
68
|
+
createModifier('presentationCornerRadius', { cornerRadius });
|
|
69
|
+
|
|
60
70
|
/**
|
|
61
71
|
* Presentation background interaction type.
|
|
62
72
|
*/
|