@symbiote-native/react 3.2.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/checkbox-props.d.ts +5 -0
- package/build/components/checkbox-props.js +1 -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/descriptor-to-react/with-children.d.ts +4 -0
- package/build/descriptor-to-react/with-children.js +6 -0
- 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-event.d.ts +6 -0
- package/build/hooks/use-event.js +15 -0
- package/build/hooks/use-native-view-controller.d.ts +3 -0
- package/build/hooks/use-native-view-controller.js +15 -0
- 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 +23 -5
- package/build/index.js +23 -14
- package/build/jsx-runtime.d.ts +17 -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,4 @@
|
|
|
1
|
+
import type { ReactElement, ReactNode } from 'react';
|
|
2
|
+
import type { IDescriptor } from '@symbiote-native/components';
|
|
3
|
+
/** The element of a descriptor with the app children after its own, for wrappers */
|
|
4
|
+
export declare function descriptorToReactWithChildren(node: IDescriptor, children: ReactNode): ReactElement;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { createElement } from 'react';
|
|
2
|
+
import { descriptorToReact } from './index.js';
|
|
3
|
+
/** The element of a descriptor with the app children after its own, for wrappers */
|
|
4
|
+
export function descriptorToReactWithChildren(node, children) {
|
|
5
|
+
return createElement(node.type, node.props, ...node.children.map(child => typeof child === 'string' ? child : descriptorToReact(child)), children);
|
|
6
|
+
}
|
|
@@ -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,6 @@
|
|
|
1
|
+
import type { IEventEmitterOf, IEventName, IEventPayload, IEventsMap } from '@symbiote-native/engine';
|
|
2
|
+
/** Calls the listener for each event, the latest one, and stops when the component unmounts */
|
|
3
|
+
export declare function useEventListener<TEventsMap extends IEventsMap, TName extends IEventName<TEventsMap>>(emitter: IEventEmitterOf<TEventsMap>, eventName: TName, listener: TEventsMap[TName]): void;
|
|
4
|
+
/** The payload of the last event, `initialValue` (or `null`) until the first one */
|
|
5
|
+
export declare function useEvent<TEventsMap extends IEventsMap, TName extends IEventName<TEventsMap>>(emitter: IEventEmitterOf<TEventsMap>, eventName: TName, initialValue: IEventPayload<TEventsMap, TName>): IEventPayload<TEventsMap, TName>;
|
|
6
|
+
export declare function useEvent<TEventsMap extends IEventsMap, TName extends IEventName<TEventsMap>>(emitter: IEventEmitterOf<TEventsMap>, eventName: TName): IEventPayload<TEventsMap, TName> | null;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// React twins of `useEvent` and `useEventListener` of the `expo` package, for any emitter
|
|
2
|
+
import { useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { bindEventListener } from '@symbiote-native/engine';
|
|
4
|
+
/** Calls the listener for each event, the latest one, and stops when the component unmounts */
|
|
5
|
+
export function useEventListener(emitter, eventName, listener) {
|
|
6
|
+
// Read at each event, so a listener that changes with every render is not a new subscription
|
|
7
|
+
const latest = useRef(listener);
|
|
8
|
+
latest.current = listener;
|
|
9
|
+
useEffect(() => bindEventListener(emitter, eventName, () => latest.current), [emitter, eventName]);
|
|
10
|
+
}
|
|
11
|
+
export function useEvent(emitter, eventName, initialValue = null) {
|
|
12
|
+
const [event, setEvent] = useState(initialValue);
|
|
13
|
+
useEffect(() => bindEventListener(emitter, eventName, () => setEvent), [emitter, eventName]);
|
|
14
|
+
return event;
|
|
15
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ReactElement, Ref } from 'react';
|
|
2
|
+
import type { ICreateNativeViewController } from '@symbiote-native/components';
|
|
3
|
+
export declare function useNativeViewController<THandle>(ref: Ref<THandle> | undefined, createController: ICreateNativeViewController<THandle>, props: object): ReactElement | null;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { useEffect, useImperativeHandle, useState } from 'react';
|
|
2
|
+
import { createHostNodeHolder } from '@symbiote-native/components';
|
|
3
|
+
import { descriptorToReact } from '../descriptor-to-react/index.js';
|
|
4
|
+
// The body of a React component over a package's native view: one controller per mounted view,
|
|
5
|
+
// its handle goes to the `ref` and its descriptor is bridged to an element
|
|
6
|
+
export function useNativeViewController(ref, createController, props) {
|
|
7
|
+
const [{ host, controller }] = useState(() => {
|
|
8
|
+
const holder = createHostNodeHolder();
|
|
9
|
+
return { host: holder, controller: createController(holder.getNode) };
|
|
10
|
+
});
|
|
11
|
+
useImperativeHandle(ref, () => controller.handle, [controller]);
|
|
12
|
+
useEffect(() => () => controller.dispose?.(), [controller]);
|
|
13
|
+
const descriptor = controller.render(props);
|
|
14
|
+
return descriptor ? descriptorToReact(host.capture(descriptor)) : null;
|
|
15
|
+
}
|
|
@@ -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;
|