@symbiote-native/react 3.3.0 → 3.4.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/batched-updates.d.ts +2 -0
- package/build/batched-updates.js +5 -0
- package/build/components/flat-list/index.d.ts +16 -13
- package/build/components/flat-list/index.js +86 -72
- package/build/components/input-accessory-view-props.d.ts +4 -3
- package/build/components/keyboard-avoiding-view/index.d.ts +2 -2
- package/build/components/keyboard-avoiding-view/index.js +45 -84
- package/build/components/layout-conformance-props.d.ts +6 -0
- package/build/components/layout-conformance-props.js +1 -0
- package/build/components/modal/index.d.ts +4 -4
- package/build/components/modal/index.js +47 -18
- package/build/components/refresh-control-props.d.ts +7 -6
- package/build/components/scroll-view/scroll-view-props.d.ts +8 -5
- package/build/components/text-input/text-input-props.d.ts +4 -0
- package/build/components/touchable/touchable-props.d.ts +6 -5
- package/build/components/virtualized-list/index.d.ts +3 -70
- package/build/components/virtualized-list/index.js +50 -548
- package/build/components/virtualized-list/list-children.d.ts +13 -0
- package/build/components/virtualized-list/list-children.js +79 -0
- package/build/components/virtualized-list/list-config.d.ts +11 -0
- package/build/components/virtualized-list/list-config.js +14 -0
- package/build/components/virtualized-list/list-elements.d.ts +11 -0
- package/build/components/virtualized-list/list-elements.js +26 -0
- package/build/components/virtualized-list/list-host.d.ts +14 -0
- package/build/components/virtualized-list/list-host.js +24 -0
- package/build/components/virtualized-list/list-scroll-props.d.ts +32 -0
- package/build/components/virtualized-list/list-scroll-props.js +30 -0
- package/build/components/virtualized-list/nested-scope.d.ts +10 -0
- package/build/components/virtualized-list/nested-scope.js +16 -0
- package/build/components/virtualized-list/use-list-driver.d.ts +22 -0
- package/build/components/virtualized-list/use-list-driver.js +104 -0
- package/build/components/virtualized-list/use-list-nesting.d.ts +12 -0
- package/build/components/virtualized-list/use-list-nesting.js +37 -0
- package/build/components/virtualized-list/use-list-parts.d.ts +12 -0
- package/build/components/virtualized-list/use-list-parts.js +20 -0
- package/build/components/virtualized-list/use-scroll-handlers.d.ts +17 -0
- package/build/components/virtualized-list/use-scroll-handlers.js +106 -0
- package/build/components/virtualized-list/use-separators.d.ts +8 -0
- package/build/components/virtualized-list/use-separators.js +17 -0
- package/build/components/virtualized-list/virtualized-list-props.d.ts +85 -0
- package/build/components/virtualized-list/virtualized-list-props.js +2 -0
- package/build/components/virtualized-section-list/index.d.ts +24 -8
- package/build/components/virtualized-section-list/index.js +89 -93
- package/build/components/virtualized-section-list/section-item-cell.d.ts +23 -0
- package/build/components/virtualized-section-list/section-item-cell.js +48 -0
- package/build/components.d.ts +5 -5
- package/build/hooks/use-animated-value.d.ts +7 -0
- package/build/hooks/use-animated-value.js +19 -0
- package/build/hooks/use-color-scheme.d.ts +2 -2
- package/build/hooks/use-color-scheme.js +4 -13
- package/build/hooks/use-pressability.d.ts +34 -0
- package/build/hooks/use-pressability.js +39 -0
- package/build/hooks/use-window-dimensions.js +6 -10
- package/build/host-config.js +6 -5
- package/build/index.d.ts +19 -5
- package/build/index.js +20 -14
- package/build/jsx-runtime.d.ts +15 -16
- package/build/modules/animated/create-animated-component.d.ts +2 -2
- package/build/modules/animated/create-animated-component.js +10 -22
- package/build/modules/animated/index.d.ts +54 -41
- package/build/modules/animated/index.js +2 -27
- package/build/modules/app-registry/index.d.ts +3 -3
- package/build/modules/app-registry/index.js +2 -2
- package/build/modules/status-bar/index.android.d.ts +1 -1
- package/build/modules/status-bar/index.android.js +7 -27
- package/build/modules/status-bar/index.ios.d.ts +1 -1
- package/build/modules/status-bar/index.ios.js +4 -28
- package/build/modules/status-bar/shared.d.ts +5 -4
- package/build/modules/status-bar/shared.js +33 -6
- package/build/native-host-tags.d.ts +2 -0
- package/build/native-host-tags.js +4 -0
- package/build/render.d.ts +1 -1
- package/build/render.js +3 -1
- package/build/root-tag-context.d.ts +1 -0
- package/build/root-tag-context.js +4 -0
- package/package.json +6 -6
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { ComponentType, ReactElement, ReactNode } from 'react';
|
|
2
|
+
import type { ISymbioteEvent } from '@symbiote-native/engine';
|
|
3
|
+
import type { IAccessibilityProps, IAriaProps, ICellRendererBaseProps, IInnerViewRef, ISeparatorProps, ISeparators, IViewabilityConfig, IViewabilityConfigCallbackPair, IViewableItemsChangedInfo } from '@symbiote-native/components';
|
|
4
|
+
import type { IStyleProp, IViewStyle } from '../../utils/styles';
|
|
5
|
+
export type IListItemInfo<ItemT> = {
|
|
6
|
+
item: ItemT;
|
|
7
|
+
index: number;
|
|
8
|
+
separators: ISeparators;
|
|
9
|
+
};
|
|
10
|
+
export type IRenderItem<ItemT> = (info: IListItemInfo<ItemT>) => ReactNode;
|
|
11
|
+
export type ICellRendererProps<ItemT> = ICellRendererBaseProps<ItemT> & {
|
|
12
|
+
children?: ReactNode;
|
|
13
|
+
};
|
|
14
|
+
export type ICellRendererComponent<ItemT> = (props: ICellRendererProps<ItemT>) => ReactNode;
|
|
15
|
+
export type IListSlot = ComponentType<Record<string, never>> | ReactElement | undefined;
|
|
16
|
+
export type ISeparatorComponent<ItemT> = ComponentType<ISeparatorProps<ItemT>> | ReactElement;
|
|
17
|
+
export type IVirtualizedListProps<ItemT> = IAccessibilityProps & IAriaProps & {
|
|
18
|
+
data: unknown;
|
|
19
|
+
getItem: (data: unknown, index: number) => ItemT;
|
|
20
|
+
getItemCount: (data: unknown) => number;
|
|
21
|
+
renderItem?: IRenderItem<ItemT>;
|
|
22
|
+
ListItemComponent?: ComponentType<IListItemInfo<ItemT>>;
|
|
23
|
+
keyExtractor?: (item: ItemT, index: number) => string;
|
|
24
|
+
getItemLayout?: (data: unknown, index: number) => {
|
|
25
|
+
length: number;
|
|
26
|
+
offset: number;
|
|
27
|
+
index: number;
|
|
28
|
+
};
|
|
29
|
+
ItemSeparatorComponent?: ISeparatorComponent<ItemT>;
|
|
30
|
+
CellRendererComponent?: ICellRendererComponent<ItemT>;
|
|
31
|
+
ListHeaderComponent?: IListSlot;
|
|
32
|
+
ListFooterComponent?: IListSlot;
|
|
33
|
+
ListEmptyComponent?: IListSlot;
|
|
34
|
+
ListHeaderComponentStyle?: IStyleProp<IViewStyle>;
|
|
35
|
+
ListFooterComponentStyle?: IStyleProp<IViewStyle>;
|
|
36
|
+
horizontal?: boolean;
|
|
37
|
+
inverted?: boolean;
|
|
38
|
+
extraData?: unknown;
|
|
39
|
+
onEndReached?: (info: {
|
|
40
|
+
distanceFromEnd: number;
|
|
41
|
+
}) => void;
|
|
42
|
+
onEndReachedThreshold?: number;
|
|
43
|
+
onStartReached?: (info: {
|
|
44
|
+
distanceFromStart: number;
|
|
45
|
+
}) => void;
|
|
46
|
+
onStartReachedThreshold?: number;
|
|
47
|
+
onRefresh?: () => void;
|
|
48
|
+
refreshing?: boolean | null;
|
|
49
|
+
progressViewOffset?: number;
|
|
50
|
+
onViewableItemsChanged?: (info: IViewableItemsChangedInfo<ItemT>) => void;
|
|
51
|
+
viewabilityConfig?: IViewabilityConfig;
|
|
52
|
+
viewabilityConfigCallbackPairs?: IViewabilityConfigCallbackPair<ItemT>[];
|
|
53
|
+
onScrollToIndexFailed?: (info: {
|
|
54
|
+
index: number;
|
|
55
|
+
highestMeasuredFrameIndex: number;
|
|
56
|
+
averageItemLength: number;
|
|
57
|
+
}) => void;
|
|
58
|
+
initialNumToRender?: number;
|
|
59
|
+
initialScrollIndex?: number;
|
|
60
|
+
maxToRenderPerBatch?: number;
|
|
61
|
+
updateCellsBatchingPeriod?: number;
|
|
62
|
+
windowSize?: number;
|
|
63
|
+
disableVirtualization?: boolean;
|
|
64
|
+
stickyHeaderIndices?: number[];
|
|
65
|
+
maintainVisibleContentPosition?: {
|
|
66
|
+
minIndexForVisible: number;
|
|
67
|
+
autoscrollToTopThreshold?: number;
|
|
68
|
+
};
|
|
69
|
+
onScroll?: (event: ISymbioteEvent) => void;
|
|
70
|
+
onScrollBeginDrag?: (event: ISymbioteEvent) => void;
|
|
71
|
+
onScrollEndDrag?: (event: ISymbioteEvent) => void;
|
|
72
|
+
onMomentumScrollBegin?: (event: ISymbioteEvent) => void;
|
|
73
|
+
onMomentumScrollEnd?: (event: ISymbioteEvent) => void;
|
|
74
|
+
onContentSizeChange?: (width: number, height: number) => void;
|
|
75
|
+
scrollEventThrottle?: number;
|
|
76
|
+
keyboardShouldPersistTaps?: boolean | 'always' | 'never' | 'handled';
|
|
77
|
+
keyboardDismissMode?: 'none' | 'on-drag' | 'interactive';
|
|
78
|
+
removeClippedSubviews?: boolean;
|
|
79
|
+
nestedScrollEnabled?: boolean;
|
|
80
|
+
stickyHeaderHiddenOnScroll?: boolean;
|
|
81
|
+
innerViewRef?: IInnerViewRef;
|
|
82
|
+
style?: IStyleProp<IViewStyle>;
|
|
83
|
+
contentContainerStyle?: IStyleProp<IViewStyle>;
|
|
84
|
+
className?: string;
|
|
85
|
+
};
|
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
import { type ComponentType, type ReactElement, type ReactNode, type Ref } from 'react';
|
|
2
2
|
import { type ISymbioteEvent } from '@symbiote-native/engine';
|
|
3
|
-
import { type ISection, type IVirtualizedSectionListHandle } from '@symbiote-native/components';
|
|
4
|
-
import { type
|
|
5
|
-
import type { IAccessibilityProps, IAriaProps } from '@symbiote-native/components';
|
|
3
|
+
import { type ISection as ICoreSection, type IVirtualizedSectionListHandle } from '@symbiote-native/components';
|
|
4
|
+
import { type ICellRendererComponent, type ISeparatorComponent, type ISeparators } from '../virtualized-list';
|
|
5
|
+
import type { IAccessibilityProps, IAriaProps, IInnerViewRef } from '@symbiote-native/components';
|
|
6
6
|
import type { IStyleProp, IViewStyle } from '../../utils/styles';
|
|
7
|
-
export type { ISection } from '@symbiote-native/components';
|
|
8
7
|
export type { IVirtualizedSectionListHandle };
|
|
9
|
-
export
|
|
8
|
+
export type ISection<ItemT> = ICoreSection<ItemT> & {
|
|
9
|
+
renderItem?: (info: {
|
|
10
|
+
item: ItemT;
|
|
11
|
+
index: number;
|
|
12
|
+
section: ISection<ItemT>;
|
|
13
|
+
separators: ISeparators;
|
|
14
|
+
}) => ReactNode;
|
|
15
|
+
ItemSeparatorComponent?: ISeparatorComponent<ItemT>;
|
|
16
|
+
};
|
|
17
|
+
export type IVirtualizedSectionListProps<ItemT> = IAccessibilityProps & IAriaProps & {
|
|
10
18
|
sections: ReadonlyArray<ISection<ItemT>>;
|
|
11
19
|
renderItem: (info: {
|
|
12
20
|
item: ItemT;
|
|
@@ -20,7 +28,7 @@ export interface IVirtualizedSectionListProps<ItemT> extends IAccessibilityProps
|
|
|
20
28
|
renderSectionFooter?: (info: {
|
|
21
29
|
section: ISection<ItemT>;
|
|
22
30
|
}) => ReactNode;
|
|
23
|
-
SectionSeparatorComponent?:
|
|
31
|
+
SectionSeparatorComponent?: ISeparatorComponent<ItemT>;
|
|
24
32
|
keyExtractor?: (item: ItemT, index: number) => string;
|
|
25
33
|
getItemLayout?: (data: ReadonlyArray<ISection<ItemT>> | null, index: number) => {
|
|
26
34
|
length: number;
|
|
@@ -29,10 +37,13 @@ export interface IVirtualizedSectionListProps<ItemT> extends IAccessibilityProps
|
|
|
29
37
|
};
|
|
30
38
|
stickySectionHeadersEnabled?: boolean;
|
|
31
39
|
extraData?: unknown;
|
|
32
|
-
ItemSeparatorComponent?:
|
|
40
|
+
ItemSeparatorComponent?: ISeparatorComponent<ItemT>;
|
|
41
|
+
CellRendererComponent?: ICellRendererComponent<unknown>;
|
|
33
42
|
ListHeaderComponent?: ComponentType<Record<string, never>> | ReactElement;
|
|
34
43
|
ListFooterComponent?: ComponentType<Record<string, never>> | ReactElement;
|
|
35
44
|
ListEmptyComponent?: ComponentType<Record<string, never>> | ReactElement;
|
|
45
|
+
ListHeaderComponentStyle?: IStyleProp<IViewStyle>;
|
|
46
|
+
ListFooterComponentStyle?: IStyleProp<IViewStyle>;
|
|
36
47
|
onEndReached?: (info: {
|
|
37
48
|
distanceFromEnd: number;
|
|
38
49
|
}) => void;
|
|
@@ -49,7 +60,9 @@ export interface IVirtualizedSectionListProps<ItemT> extends IAccessibilityProps
|
|
|
49
60
|
maxToRenderPerBatch?: number;
|
|
50
61
|
updateCellsBatchingPeriod?: number;
|
|
51
62
|
windowSize?: number;
|
|
63
|
+
disableVirtualization?: boolean;
|
|
52
64
|
inverted?: boolean;
|
|
65
|
+
horizontal?: boolean;
|
|
53
66
|
maintainVisibleContentPosition?: {
|
|
54
67
|
minIndexForVisible: number;
|
|
55
68
|
autoscrollToTopThreshold?: number;
|
|
@@ -59,15 +72,18 @@ export interface IVirtualizedSectionListProps<ItemT> extends IAccessibilityProps
|
|
|
59
72
|
onScrollEndDrag?: (event: ISymbioteEvent) => void;
|
|
60
73
|
onMomentumScrollBegin?: (event: ISymbioteEvent) => void;
|
|
61
74
|
onMomentumScrollEnd?: (event: ISymbioteEvent) => void;
|
|
75
|
+
onContentSizeChange?: (width: number, height: number) => void;
|
|
62
76
|
scrollEventThrottle?: number;
|
|
63
77
|
keyboardShouldPersistTaps?: boolean | 'always' | 'never' | 'handled';
|
|
64
78
|
keyboardDismissMode?: 'none' | 'on-drag' | 'interactive';
|
|
65
79
|
removeClippedSubviews?: boolean;
|
|
66
80
|
nestedScrollEnabled?: boolean;
|
|
81
|
+
stickyHeaderHiddenOnScroll?: boolean;
|
|
82
|
+
innerViewRef?: IInnerViewRef;
|
|
67
83
|
style?: IStyleProp<IViewStyle>;
|
|
68
84
|
contentContainerStyle?: IStyleProp<IViewStyle>;
|
|
69
85
|
className?: string;
|
|
70
|
-
}
|
|
86
|
+
};
|
|
71
87
|
export declare function VirtualizedSectionList<ItemT>(props: IVirtualizedSectionListProps<ItemT> & {
|
|
72
88
|
ref?: Ref<IVirtualizedSectionListHandle>;
|
|
73
89
|
}): ReactElement;
|
|
@@ -5,113 +5,109 @@
|
|
|
5
5
|
// mapping are shared from @symbiote-native/components; this file wires React's lifecycle (refs +
|
|
6
6
|
// useImperativeHandle + the per-entry render dispatch). Lower layer in RN's
|
|
7
7
|
// SectionList -> VirtualizedSectionList -> VirtualizedList stack.
|
|
8
|
-
import { createElement, useImperativeHandle, useRef, } from 'react';
|
|
9
|
-
import { dlog, Platform
|
|
10
|
-
import { flattenSections, resolveStickySectionHeaders,
|
|
8
|
+
import { createElement, useImperativeHandle, useRef, useState, } from 'react';
|
|
9
|
+
import { dlog, Platform } from '@symbiote-native/engine';
|
|
10
|
+
import { cellGapsFor, createSeparatorBoard, flattenSections, resolveStickySectionHeaders, layoutOverSections, renderSectionEntry, resolveScrollLocation, routeScrollHandle, sectionEntryKey, } from '@symbiote-native/components';
|
|
11
11
|
import { VirtualizedList, } from '../virtualized-list/index.js';
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return createElement(component, {});
|
|
17
|
-
return component;
|
|
18
|
-
}
|
|
19
|
-
export function VirtualizedSectionList(props) {
|
|
20
|
-
const { ref, sections, renderItem, renderSectionHeader, renderSectionFooter, SectionSeparatorComponent,
|
|
21
|
-
// Pulled out of `rest`: the user's separator is typed on ItemT, but the inner VirtualizedList
|
|
22
|
-
// streams ISectionEntry<ItemT>, so we wrap it to unwrap each entry back to its ItemT.
|
|
23
|
-
ItemSeparatorComponent, keyExtractor,
|
|
24
|
-
// Also pulled out of `rest`: the inner list streams entries, the user's callback expects
|
|
25
|
-
// sections. See entryItemLayout below.
|
|
26
|
-
getItemLayout, stickySectionHeadersEnabled, ...rest } = props;
|
|
27
|
-
const { entries, headerIndices } = flattenSections(sections, SectionSeparatorComponent !== undefined);
|
|
28
|
-
// RN sticks section headers by default only on iOS; Android does not unless asked. headerIndices
|
|
29
|
-
// are the flat positions of every section header; VirtualizedList forwards the in-window ones.
|
|
30
|
-
const stickyHeaderIndices = resolveStickySectionHeaders(stickySectionHeadersEnabled, headerIndices, Platform.OS);
|
|
31
|
-
// The handle reaches into the inner VirtualizedList to drive scrollToIndex.
|
|
32
|
-
const listRef = useRef(null);
|
|
33
|
-
dlog(`VirtualizedSectionList: ${sections.length} sections flattened to ${entries.length} entries`);
|
|
34
|
-
useImperativeHandle(ref ?? null, () => ({
|
|
12
|
+
import { SectionItemCell } from './section-item-cell.js';
|
|
13
|
+
// Kept apart so the component body stays readable: the handle only forwards to the inner list
|
|
14
|
+
function buildSectionHandle(listRef, headerIndices, stickyHeaderIndices) {
|
|
15
|
+
return {
|
|
35
16
|
scrollToLocation: (params) => {
|
|
36
|
-
const
|
|
37
|
-
if (
|
|
17
|
+
const target = resolveScrollLocation(headerIndices, stickyHeaderIndices, params);
|
|
18
|
+
if (target === undefined) {
|
|
38
19
|
dlog(`VirtualizedSectionList scrollToLocation: section ${params.sectionIndex} out of range`);
|
|
39
20
|
return;
|
|
40
21
|
}
|
|
41
22
|
dlog(`VirtualizedSectionList scrollToLocation section=${params.sectionIndex} ` +
|
|
42
|
-
`item=${params.itemIndex} -> flat ${
|
|
43
|
-
listRef.current?.scrollToIndex(
|
|
44
|
-
index: flatIndex,
|
|
45
|
-
viewOffset: params.viewOffset,
|
|
46
|
-
viewPosition: params.viewPosition,
|
|
47
|
-
animated: params.animated,
|
|
48
|
-
});
|
|
49
|
-
},
|
|
50
|
-
flashScrollIndicators: () => {
|
|
51
|
-
listRef.current?.flashScrollIndicators();
|
|
52
|
-
},
|
|
53
|
-
getNativeScrollRef: () => listRef.current?.getNativeScrollRef() ?? null,
|
|
54
|
-
getScrollableNode: () => listRef.current?.getScrollableNode() ?? null,
|
|
55
|
-
getScrollResponder: () => listRef.current?.getScrollResponder() ?? null,
|
|
56
|
-
getScrollNode: () => listRef.current?.getScrollNode() ?? null,
|
|
57
|
-
recordInteraction: () => {
|
|
58
|
-
listRef.current?.recordInteraction();
|
|
23
|
+
`item=${params.itemIndex} -> flat ${target.index}`);
|
|
24
|
+
listRef.current?.scrollToIndex(target);
|
|
59
25
|
},
|
|
60
|
-
|
|
61
|
-
const renderEntry = (info) => {
|
|
62
|
-
const entry = info.item;
|
|
63
|
-
if (entry.kind === 'header') {
|
|
64
|
-
return renderSectionHeader
|
|
65
|
-
? renderSectionHeader({ section: entry.section })
|
|
66
|
-
: undefined;
|
|
67
|
-
}
|
|
68
|
-
if (entry.kind === 'footer') {
|
|
69
|
-
return renderSectionFooter
|
|
70
|
-
? renderSectionFooter({ section: entry.section })
|
|
71
|
-
: undefined;
|
|
72
|
-
}
|
|
73
|
-
if (entry.kind === 'section-separator') {
|
|
74
|
-
return resolveSeparator(SectionSeparatorComponent);
|
|
75
|
-
}
|
|
76
|
-
return renderItem({
|
|
77
|
-
item: entry.item,
|
|
78
|
-
index: entry.itemIndex,
|
|
79
|
-
section: entry.section,
|
|
80
|
-
separators: info.separators,
|
|
81
|
-
});
|
|
26
|
+
...routeScrollHandle(() => listRef.current),
|
|
82
27
|
};
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
28
|
+
}
|
|
29
|
+
// The handle reaches into the inner list to drive `scrollToIndex`, so it owns the list ref
|
|
30
|
+
function useSectionHandle(ref, headerIndices, stickyHeaderIndices) {
|
|
31
|
+
const listRef = useRef(null);
|
|
32
|
+
useImperativeHandle(ref ?? null, () => buildSectionHandle(listRef, headerIndices, stickyHeaderIndices), [headerIndices, stickyHeaderIndices]);
|
|
33
|
+
return listRef;
|
|
34
|
+
}
|
|
35
|
+
function flattenLogged(sections) {
|
|
36
|
+
const flat = flattenSections(sections);
|
|
37
|
+
dlog(`VirtualizedSectionList: ${sections.length} sections flattened to ${flat.entries.length} entries`);
|
|
38
|
+
return flat;
|
|
39
|
+
}
|
|
40
|
+
// The flat stream plus the board its item cells share their separator state through
|
|
41
|
+
function useSectionEntries(sections, keyExtractor, stickySectionHeadersEnabled) {
|
|
42
|
+
const [board] = useState(() => createSeparatorBoard());
|
|
43
|
+
const keyOf = (entry) => sectionEntryKey(entry, keyExtractor);
|
|
44
|
+
const { entries, headerIndices } = flattenLogged(sections);
|
|
45
|
+
// RN sticks section headers by default only on iOS; Android does not unless asked
|
|
46
|
+
const stickyHeaderIndices = resolveStickySectionHeaders(stickySectionHeadersEnabled, headerIndices, Platform.OS);
|
|
47
|
+
return { entries, headerIndices, stickyHeaderIndices, board, keyOf };
|
|
48
|
+
}
|
|
49
|
+
// A separator component by the kind of gap it sits in: the section one, or the item one where a
|
|
50
|
+
// section's own `ItemSeparatorComponent` beats the list's
|
|
51
|
+
function separatorFor(gap, renderers) {
|
|
52
|
+
if (gap.kind === 'section')
|
|
53
|
+
return renderers.SectionSeparatorComponent;
|
|
54
|
+
if (gap.kind === 'item')
|
|
55
|
+
return (gap.props.section.ItemSeparatorComponent ??
|
|
56
|
+
renderers.ItemSeparatorComponent);
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
// The inner list's single `renderItem`, one renderer per entry kind. An item entry becomes a
|
|
60
|
+
// cell that paints its own separators, so the inner list gets no `ItemSeparatorComponent`
|
|
61
|
+
function entryRendererFor(renderers) {
|
|
62
|
+
return info => renderSectionEntry({
|
|
63
|
+
header: ({ section }) => renderers.renderSectionHeader?.({ section }),
|
|
64
|
+
footer: ({ section }) => renderers.renderSectionFooter?.({ section }),
|
|
65
|
+
item: entry => {
|
|
66
|
+
const gaps = cellGapsFor(renderers.entries, info.index);
|
|
67
|
+
const previous = renderers.entries[info.index - 1];
|
|
68
|
+
return createElement((SectionItemCell), {
|
|
69
|
+
board: renderers.board,
|
|
70
|
+
cellKey: renderers.keyOf(entry),
|
|
71
|
+
prevCellKey: previous === undefined ? undefined : renderers.keyOf(previous),
|
|
72
|
+
item: entry.item,
|
|
73
|
+
index: entry.itemIndex,
|
|
74
|
+
section: entry.section,
|
|
75
|
+
renderItem: entry.section.renderItem ?? renderers.renderItem,
|
|
76
|
+
leadingGap: gaps.leading,
|
|
77
|
+
trailingGap: gaps.trailing,
|
|
78
|
+
LeadingSeparatorComponent: separatorFor(gaps.leading, renderers),
|
|
79
|
+
TrailingSeparatorComponent: separatorFor(gaps.trailing, renderers),
|
|
80
|
+
inverted: renderers.inverted === true,
|
|
81
|
+
});
|
|
82
|
+
},
|
|
83
|
+
}, info.item);
|
|
84
|
+
}
|
|
85
|
+
export function VirtualizedSectionList(props) {
|
|
86
|
+
const { ref, sections, renderItem, renderSectionHeader, renderSectionFooter, SectionSeparatorComponent,
|
|
87
|
+
// Pulled out of `rest`, these are typed on ItemT while the inner list streams entries
|
|
88
|
+
ItemSeparatorComponent, keyExtractor, getItemLayout, stickySectionHeadersEnabled, inverted, ...rest } = props;
|
|
89
|
+
const { entries, headerIndices, stickyHeaderIndices, board, keyOf } = useSectionEntries(sections, keyExtractor, stickySectionHeadersEnabled);
|
|
90
|
+
const listRef = useSectionHandle(ref, headerIndices, stickyHeaderIndices);
|
|
105
91
|
return createElement((VirtualizedList), {
|
|
106
92
|
ref: listRef,
|
|
107
93
|
data: entries,
|
|
108
94
|
getItem: (_source, index) => entries[index],
|
|
109
95
|
getItemCount: () => entries.length,
|
|
110
|
-
renderItem:
|
|
111
|
-
|
|
112
|
-
|
|
96
|
+
renderItem: entryRendererFor({
|
|
97
|
+
renderItem,
|
|
98
|
+
renderSectionHeader,
|
|
99
|
+
renderSectionFooter,
|
|
100
|
+
SectionSeparatorComponent,
|
|
101
|
+
ItemSeparatorComponent,
|
|
102
|
+
inverted,
|
|
103
|
+
entries,
|
|
104
|
+
board,
|
|
105
|
+
keyOf,
|
|
106
|
+
}),
|
|
107
|
+
keyExtractor: keyOf,
|
|
108
|
+
getItemLayout: layoutOverSections(getItemLayout, sections),
|
|
113
109
|
stickyHeaderIndices,
|
|
114
|
-
|
|
110
|
+
inverted,
|
|
115
111
|
...rest,
|
|
116
112
|
});
|
|
117
113
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type ReactElement, type ReactNode } from 'react';
|
|
2
|
+
import { type ISeparatorBoard, type ISeparatorGap } from '@symbiote-native/components';
|
|
3
|
+
import type { ISeparatorComponent, ISeparators } from '../virtualized-list';
|
|
4
|
+
export type ISectionItemCellProps<ItemT, SectionT> = {
|
|
5
|
+
board: ISeparatorBoard<Record<string, unknown>>;
|
|
6
|
+
cellKey: string;
|
|
7
|
+
prevCellKey: string | undefined;
|
|
8
|
+
item: ItemT;
|
|
9
|
+
index: number;
|
|
10
|
+
section: SectionT;
|
|
11
|
+
renderItem: (info: {
|
|
12
|
+
item: ItemT;
|
|
13
|
+
index: number;
|
|
14
|
+
section: SectionT;
|
|
15
|
+
separators: ISeparators;
|
|
16
|
+
}) => ReactNode;
|
|
17
|
+
leadingGap: ISeparatorGap<ItemT, SectionT>;
|
|
18
|
+
trailingGap: ISeparatorGap<ItemT, SectionT>;
|
|
19
|
+
LeadingSeparatorComponent: ISeparatorComponent<ItemT> | undefined;
|
|
20
|
+
TrailingSeparatorComponent: ISeparatorComponent<ItemT> | undefined;
|
|
21
|
+
inverted: boolean;
|
|
22
|
+
};
|
|
23
|
+
export declare function SectionItemCell<ItemT, SectionT>(props: ISectionItemCellProps<ItemT, SectionT>): ReactElement;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Item cell with its separators around it, as in RN's `ItemWithSeparator`
|
|
2
|
+
// Their state sits in a shared board, т.к. `highlight()` also lights the previous cell's one
|
|
3
|
+
import { createElement, Fragment, isValidElement, useEffect, useSyncExternalStore, } from 'react';
|
|
4
|
+
import { createCellSeparators, } from '@symbiote-native/components';
|
|
5
|
+
function paint(separator) {
|
|
6
|
+
const { Component, gap, isHighlighted, override } = separator;
|
|
7
|
+
if (Component === undefined)
|
|
8
|
+
return null;
|
|
9
|
+
if (isValidElement(Component))
|
|
10
|
+
return Component;
|
|
11
|
+
return createElement(Component, {
|
|
12
|
+
highlighted: isHighlighted,
|
|
13
|
+
...gap.props,
|
|
14
|
+
...override,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
export function SectionItemCell(props) {
|
|
18
|
+
const { board, cellKey, prevCellKey } = props;
|
|
19
|
+
const state = useSyncExternalStore(listener => board.subscribe(cellKey, listener), () => board.read(cellKey));
|
|
20
|
+
useEffect(() => () => board.release(cellKey), [board, cellKey]);
|
|
21
|
+
const separators = createCellSeparators(board, () => ({
|
|
22
|
+
cellKey,
|
|
23
|
+
prevCellKey,
|
|
24
|
+
has: {
|
|
25
|
+
leading: props.LeadingSeparatorComponent !== undefined,
|
|
26
|
+
trailing: props.TrailingSeparatorComponent !== undefined,
|
|
27
|
+
},
|
|
28
|
+
}));
|
|
29
|
+
const leading = paint({
|
|
30
|
+
Component: props.LeadingSeparatorComponent,
|
|
31
|
+
gap: props.leadingGap,
|
|
32
|
+
isHighlighted: state.leadingHighlighted,
|
|
33
|
+
override: state.leadingOverride,
|
|
34
|
+
});
|
|
35
|
+
const trailing = paint({
|
|
36
|
+
Component: props.TrailingSeparatorComponent,
|
|
37
|
+
gap: props.trailingGap,
|
|
38
|
+
isHighlighted: state.trailingHighlighted,
|
|
39
|
+
override: state.trailingOverride,
|
|
40
|
+
});
|
|
41
|
+
const element = props.renderItem({
|
|
42
|
+
item: props.item,
|
|
43
|
+
index: props.index,
|
|
44
|
+
section: props.section,
|
|
45
|
+
separators,
|
|
46
|
+
});
|
|
47
|
+
return createElement(Fragment, null, props.inverted ? trailing : leading, element, props.inverted ? leading : trailing);
|
|
48
|
+
}
|
package/build/components.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { Ref, ReactNode } from 'react';
|
|
2
2
|
import type { ISymbioteEvent } from '@symbiote-native/engine';
|
|
3
3
|
import type { IHostInstance } from './host-instance';
|
|
4
|
-
import type { IAccessibilityProps, IAriaProps } from '@symbiote-native/components';
|
|
4
|
+
import type { IAccessibilityProps, IAriaProps, ITextNativeOnlyProps } from '@symbiote-native/components';
|
|
5
5
|
import type { IResponderProps } from './utils/responder-props';
|
|
6
6
|
import type { IStyleProp, ITextStyle, IViewStyle } from './utils/styles';
|
|
7
|
-
export
|
|
7
|
+
export type IViewProps = IAccessibilityProps & IAriaProps & IResponderProps & {
|
|
8
8
|
style?: IStyleProp<IViewStyle>;
|
|
9
9
|
className?: string;
|
|
10
10
|
onPress?: (event: ISymbioteEvent) => void;
|
|
@@ -29,8 +29,8 @@ export interface IViewProps extends IAccessibilityProps, IAriaProps, IResponderP
|
|
|
29
29
|
needsOffscreenAlphaCompositing?: boolean;
|
|
30
30
|
ref?: Ref<IHostInstance>;
|
|
31
31
|
children?: ReactNode;
|
|
32
|
-
}
|
|
33
|
-
export
|
|
32
|
+
};
|
|
33
|
+
export type ITextProps = IAccessibilityProps & IAriaProps & ITextNativeOnlyProps & {
|
|
34
34
|
style?: IStyleProp<ITextStyle>;
|
|
35
35
|
className?: string;
|
|
36
36
|
onPress?: (event: ISymbioteEvent) => void;
|
|
@@ -50,4 +50,4 @@ export interface ITextProps extends IAccessibilityProps, IAriaProps {
|
|
|
50
50
|
selectionColor?: string;
|
|
51
51
|
ref?: Ref<IHostInstance>;
|
|
52
52
|
children?: ReactNode;
|
|
53
|
-
}
|
|
53
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type AnimatedColor, type AnimatedValue, type AnimatedValueXY, type IAnimatedValueConfig, type IColorInput } from '@symbiote-native/engine';
|
|
2
|
+
export declare function useAnimatedValue(initialValue: number, config?: IAnimatedValueConfig): AnimatedValue;
|
|
3
|
+
export declare function useAnimatedValueXY(initialValue: {
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
|
+
}, config?: IAnimatedValueConfig): AnimatedValueXY;
|
|
7
|
+
export declare function useAnimatedColor(inputValue?: IColorInput, config?: IAnimatedValueConfig): AnimatedColor;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// RN's `useAnimatedValue`, `useAnimatedValueXY` and `useAnimatedColor`: the instance is built on
|
|
2
|
+
// the first render and kept, so later renders ignore their arguments
|
|
3
|
+
import { useRef } from 'react';
|
|
4
|
+
import { createAnimatedColor, createAnimatedValue, createAnimatedValueXY, } from '@symbiote-native/engine';
|
|
5
|
+
// `useRef(factory())` would build a throwaway instance on every render
|
|
6
|
+
function useOnce(create) {
|
|
7
|
+
const ref = useRef(null);
|
|
8
|
+
ref.current ??= create();
|
|
9
|
+
return ref.current;
|
|
10
|
+
}
|
|
11
|
+
export function useAnimatedValue(initialValue, config) {
|
|
12
|
+
return useOnce(() => createAnimatedValue(initialValue, config));
|
|
13
|
+
}
|
|
14
|
+
export function useAnimatedValueXY(initialValue, config) {
|
|
15
|
+
return useOnce(() => createAnimatedValueXY(initialValue, config));
|
|
16
|
+
}
|
|
17
|
+
export function useAnimatedColor(inputValue, config) {
|
|
18
|
+
return useOnce(() => createAnimatedColor(inputValue, config));
|
|
19
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export declare
|
|
1
|
+
import type { IColorSchemeName } from '@symbiote-native/engine';
|
|
2
|
+
export declare const useColorScheme: () => IColorSchemeName | null | undefined;
|
|
@@ -1,13 +1,4 @@
|
|
|
1
|
-
// useColorScheme
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import { Appearance } from '@symbiote-native/engine';
|
|
6
|
-
const subscribe = (onStoreChange) => {
|
|
7
|
-
const subscription = Appearance.addChangeListener(onStoreChange);
|
|
8
|
-
return () => subscription.remove();
|
|
9
|
-
};
|
|
10
|
-
const getSnapshot = () => Appearance.getColorScheme();
|
|
11
|
-
export function useColorScheme() {
|
|
12
|
-
return useSyncExternalStore(subscribe, getSnapshot);
|
|
13
|
-
}
|
|
1
|
+
// RN's own `useColorScheme`, typed here because the source ships none
|
|
2
|
+
// @ts-expect-error - untyped Flow source
|
|
3
|
+
import useColorSchemeUpstream from 'react-native/Libraries/Utilities/useColorScheme';
|
|
4
|
+
export const useColorScheme = useColorSchemeUpstream;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type IPressabilityHandlers, type ISymbioteEvent } from '@symbiote-native/engine';
|
|
2
|
+
export type { IPressabilityHandlers } from '@symbiote-native/engine';
|
|
3
|
+
type IRectOrSize = number | {
|
|
4
|
+
top?: number;
|
|
5
|
+
left?: number;
|
|
6
|
+
bottom?: number;
|
|
7
|
+
right?: number;
|
|
8
|
+
};
|
|
9
|
+
type IPressHandler = (event: ISymbioteEvent) => unknown;
|
|
10
|
+
export type IPressabilityConfig = {
|
|
11
|
+
cancelable?: boolean | null;
|
|
12
|
+
disabled?: boolean | null;
|
|
13
|
+
hitSlop?: IRectOrSize | null;
|
|
14
|
+
pressRectOffset?: IRectOrSize | null;
|
|
15
|
+
android_disableSound?: boolean | null;
|
|
16
|
+
delayHoverIn?: number | null;
|
|
17
|
+
delayHoverOut?: number | null;
|
|
18
|
+
delayLongPress?: number | null;
|
|
19
|
+
delayPressIn?: number | null;
|
|
20
|
+
delayPressOut?: number | null;
|
|
21
|
+
minPressDuration?: number | null;
|
|
22
|
+
onBlur?: IPressHandler | null;
|
|
23
|
+
onFocus?: IPressHandler | null;
|
|
24
|
+
onHoverIn?: IPressHandler | null;
|
|
25
|
+
onHoverOut?: IPressHandler | null;
|
|
26
|
+
onLongPress?: IPressHandler | null;
|
|
27
|
+
onPress?: IPressHandler | null;
|
|
28
|
+
onPressIn?: IPressHandler | null;
|
|
29
|
+
onPressMove?: IPressHandler | null;
|
|
30
|
+
onPressOut?: IPressHandler | null;
|
|
31
|
+
blockNativeResponder?: boolean | null;
|
|
32
|
+
};
|
|
33
|
+
export declare function usePressability(config: IPressabilityConfig): IPressabilityHandlers;
|
|
34
|
+
export declare function usePressability(config: IPressabilityConfig | null | undefined): IPressabilityHandlers | null;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// RN's `usePressability`, over RN's own `Pressability` class, which `bootstrapHost` hands to the
|
|
2
|
+
// engine. RN's class expects its synthetic events, so each handler gets the event with `persist()`
|
|
3
|
+
import { useInsertionEffect, useRef } from 'react';
|
|
4
|
+
import { loadPressability, } from '@symbiote-native/engine';
|
|
5
|
+
// An event of ours answers everything RN's class reads, `persist` apart: nothing is pooled here
|
|
6
|
+
function withPersist(event) {
|
|
7
|
+
return Object.assign(Object.create(event), { persist: () => { } });
|
|
8
|
+
}
|
|
9
|
+
function persistingHandlers(handlers) {
|
|
10
|
+
return Object.fromEntries(Object.entries(handlers).map(([name, handler]) => [
|
|
11
|
+
name,
|
|
12
|
+
(event) => handler(withPersist(event)),
|
|
13
|
+
]));
|
|
14
|
+
}
|
|
15
|
+
export function usePressability(config) {
|
|
16
|
+
const ref = useRef(null);
|
|
17
|
+
if (config != null && ref.current === null) {
|
|
18
|
+
const Pressability = loadPressability();
|
|
19
|
+
const pressability = new Pressability(config);
|
|
20
|
+
ref.current = {
|
|
21
|
+
pressability,
|
|
22
|
+
handlers: persistingHandlers(pressability.getEventHandlers()),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
const instance = ref.current;
|
|
26
|
+
// A no-op on the first mount, on an update it points `Pressability` at the new config
|
|
27
|
+
useInsertionEffect(() => {
|
|
28
|
+
if (config != null && instance !== null) {
|
|
29
|
+
instance.pressability.configure(config);
|
|
30
|
+
}
|
|
31
|
+
}, [config, instance]);
|
|
32
|
+
// On unmount only, not when `config` changes: pending timers and state go
|
|
33
|
+
useInsertionEffect(() => {
|
|
34
|
+
if (instance === null)
|
|
35
|
+
return undefined;
|
|
36
|
+
return () => instance.pressability.reset();
|
|
37
|
+
}, [instance]);
|
|
38
|
+
return instance === null ? null : instance.handlers;
|
|
39
|
+
}
|
|
@@ -1,26 +1,22 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
// Dimensions.get('window'), subscribes to 'change', and re-checks once after
|
|
4
|
-
// subscribing to close the gap between the render-time get and the effect-time
|
|
5
|
-
// listener. Only the window metrics changing triggers a re-render.
|
|
1
|
+
// Port of RN's `useWindowDimensions`: seeds from `Dimensions.get('window')`, subscribes to 'change'
|
|
2
|
+
// TODO(rn-port): RN's module loads `NativeDeviceInfo` eagerly, which needs a bridge itests lack
|
|
6
3
|
import { useEffect, useState } from 'react';
|
|
7
4
|
import { Dimensions, } from '@symbiote-native/engine';
|
|
8
5
|
export function useWindowDimensions() {
|
|
9
6
|
const [dimensions, setDimensions] = useState(() => Dimensions.get('window'));
|
|
10
7
|
useEffect(() => {
|
|
11
8
|
function handleChange(window) {
|
|
12
|
-
|
|
9
|
+
const hasChanged = dimensions.width !== window.width ||
|
|
13
10
|
dimensions.height !== window.height ||
|
|
14
11
|
dimensions.scale !== window.scale ||
|
|
15
|
-
dimensions.fontScale !== window.fontScale
|
|
12
|
+
dimensions.fontScale !== window.fontScale;
|
|
13
|
+
if (hasChanged)
|
|
16
14
|
setDimensions(window);
|
|
17
|
-
}
|
|
18
15
|
}
|
|
19
16
|
const subscription = Dimensions.addEventListener('change', (set) => {
|
|
20
17
|
handleChange(set.window);
|
|
21
18
|
});
|
|
22
|
-
//
|
|
23
|
-
// here; re-check now. If nothing changed, React filters the no-op set.
|
|
19
|
+
// An update may land between `get` in render and the subscription, so re-check now
|
|
24
20
|
handleChange(Dimensions.get('window'));
|
|
25
21
|
return () => {
|
|
26
22
|
subscription.remove();
|