@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,5 @@
|
|
|
1
|
+
import type { ICheckboxProps as ICheckboxBaseProps } from '@symbiote-native/components';
|
|
2
|
+
import type { IViewProps } from '../components';
|
|
3
|
+
export type ICheckboxProps = ICheckboxBaseProps & Omit<IViewProps, keyof ICheckboxBaseProps | 'children'> & {
|
|
4
|
+
className?: string;
|
|
5
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,17 +1,13 @@
|
|
|
1
|
-
import { type ComponentType, type ReactElement, type
|
|
1
|
+
import { type ComponentType, type ReactElement, type Ref } from 'react';
|
|
2
2
|
import { type ISymbioteEvent } from '@symbiote-native/engine';
|
|
3
|
-
import { type
|
|
4
|
-
import type { IAccessibilityProps, IAriaProps } from '@symbiote-native/components';
|
|
3
|
+
import { type ICellRendererComponent, type IListItemInfo, type IRenderItem, type ISeparatorComponent, type IViewabilityConfig, type IViewabilityConfigCallbackPair, type IViewableItemsChangedInfo, type IVirtualizedListHandle } from '../virtualized-list';
|
|
4
|
+
import type { IAccessibilityProps, IAriaProps, IInnerViewRef } from '@symbiote-native/components';
|
|
5
5
|
import type { IStyleProp, IViewStyle } from '../../utils/styles';
|
|
6
|
-
type IRenderItem<ItemT> = (info: {
|
|
7
|
-
item: ItemT;
|
|
8
|
-
index: number;
|
|
9
|
-
separators: ISeparators;
|
|
10
|
-
}) => ReactNode;
|
|
11
6
|
export type IFlatListHandle = IVirtualizedListHandle;
|
|
12
|
-
export
|
|
7
|
+
export type IFlatListProps<ItemT> = IAccessibilityProps & IAriaProps & {
|
|
13
8
|
data: readonly ItemT[];
|
|
14
|
-
renderItem
|
|
9
|
+
renderItem?: IRenderItem<ItemT>;
|
|
10
|
+
ListItemComponent?: ComponentType<IListItemInfo<ItemT>>;
|
|
15
11
|
keyExtractor?: (item: ItemT, index: number) => string;
|
|
16
12
|
getItemLayout?: (data: unknown, index: number) => {
|
|
17
13
|
length: number;
|
|
@@ -20,10 +16,13 @@ export interface IFlatListProps<ItemT> extends IAccessibilityProps, IAriaProps {
|
|
|
20
16
|
};
|
|
21
17
|
numColumns?: number;
|
|
22
18
|
columnWrapperStyle?: IStyleProp<IViewStyle> | string;
|
|
23
|
-
ItemSeparatorComponent?:
|
|
19
|
+
ItemSeparatorComponent?: ISeparatorComponent<ItemT>;
|
|
20
|
+
CellRendererComponent?: ICellRendererComponent<unknown>;
|
|
24
21
|
ListHeaderComponent?: ComponentType<Record<string, never>> | ReactElement;
|
|
25
22
|
ListFooterComponent?: ComponentType<Record<string, never>> | ReactElement;
|
|
26
23
|
ListEmptyComponent?: ComponentType<Record<string, never>> | ReactElement;
|
|
24
|
+
ListHeaderComponentStyle?: IStyleProp<IViewStyle>;
|
|
25
|
+
ListFooterComponentStyle?: IStyleProp<IViewStyle>;
|
|
27
26
|
horizontal?: boolean;
|
|
28
27
|
inverted?: boolean;
|
|
29
28
|
extraData?: unknown;
|
|
@@ -51,6 +50,8 @@ export interface IFlatListProps<ItemT> extends IAccessibilityProps, IAriaProps {
|
|
|
51
50
|
maxToRenderPerBatch?: number;
|
|
52
51
|
updateCellsBatchingPeriod?: number;
|
|
53
52
|
windowSize?: number;
|
|
53
|
+
disableVirtualization?: boolean;
|
|
54
|
+
stickyHeaderIndices?: number[];
|
|
54
55
|
maintainVisibleContentPosition?: {
|
|
55
56
|
minIndexForVisible: number;
|
|
56
57
|
autoscrollToTopThreshold?: number;
|
|
@@ -60,6 +61,7 @@ export interface IFlatListProps<ItemT> extends IAccessibilityProps, IAriaProps {
|
|
|
60
61
|
onScrollEndDrag?: (event: ISymbioteEvent) => void;
|
|
61
62
|
onMomentumScrollBegin?: (event: ISymbioteEvent) => void;
|
|
62
63
|
onMomentumScrollEnd?: (event: ISymbioteEvent) => void;
|
|
64
|
+
onContentSizeChange?: (width: number, height: number) => void;
|
|
63
65
|
scrollEventThrottle?: number;
|
|
64
66
|
keyboardShouldPersistTaps?: boolean | 'always' | 'never' | 'handled';
|
|
65
67
|
keyboardDismissMode?: 'none' | 'on-drag' | 'interactive';
|
|
@@ -68,8 +70,9 @@ export interface IFlatListProps<ItemT> extends IAccessibilityProps, IAriaProps {
|
|
|
68
70
|
className?: string;
|
|
69
71
|
removeClippedSubviews?: boolean;
|
|
70
72
|
nestedScrollEnabled?: boolean;
|
|
71
|
-
|
|
73
|
+
stickyHeaderHiddenOnScroll?: boolean;
|
|
74
|
+
innerViewRef?: IInnerViewRef;
|
|
75
|
+
};
|
|
72
76
|
export declare function FlatList<ItemT>(props: IFlatListProps<ItemT> & {
|
|
73
77
|
ref?: Ref<IFlatListHandle>;
|
|
74
78
|
}): ReactElement;
|
|
75
|
-
export {};
|
|
@@ -5,90 +5,104 @@
|
|
|
5
5
|
// scrolling are inherited from VirtualizedList; the data shaping and the row/viewability/separator
|
|
6
6
|
// transforms are shared from @symbiote-native/components. This file only adapts to React's lifecycle
|
|
7
7
|
// (element creation + ref threading).
|
|
8
|
-
import { createElement, } from 'react';
|
|
8
|
+
import { createElement, isValidElement, } from 'react';
|
|
9
9
|
import { Platform, dlog, resolveClassName, } from '@symbiote-native/engine';
|
|
10
|
-
import { SINGLE_COLUMN, chunkIntoRows, expandRowViewability, firstItemOfRow, lastItemOfRow, removeClippedSubviewsOrDefault, rowKeyExtractor, } from '@symbiote-native/components';
|
|
10
|
+
import { SINGLE_COLUMN, arrayLikeLength, chunkIntoRows, expandRowToken, expandRowViewability, firstItemOfRow, lastItemOfRow, removeClippedSubviewsOrDefault, rowKeyExtractor, } from '@symbiote-native/components';
|
|
11
11
|
import { VirtualizedList, } from '../virtualized-list/index.js';
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
renderItem,
|
|
33
|
-
keyExtractor,
|
|
34
|
-
onViewableItemsChanged,
|
|
35
|
-
viewabilityConfigCallbackPairs,
|
|
36
|
-
ItemSeparatorComponent,
|
|
37
|
-
...rest,
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
// Multi-column: the virtualized stream is rows. Each cell renders its items side by side in a
|
|
41
|
-
// flex-row View so windowing accounts for whole rows.
|
|
42
|
-
const rows = chunkIntoRows(data, numColumns);
|
|
43
|
-
const resolvedColumnWrapperStyle = typeof columnWrapperStyle === 'string'
|
|
44
|
-
? resolveClassName(columnWrapperStyle)
|
|
45
|
-
: columnWrapperStyle;
|
|
12
|
+
import { renderItemElement } from '../virtualized-list/list-elements.js';
|
|
13
|
+
function singleColumnList(typed, rest, ref) {
|
|
14
|
+
const { data } = typed;
|
|
15
|
+
return createElement((VirtualizedList), {
|
|
16
|
+
ref,
|
|
17
|
+
getItem: (_source, index) => data[index],
|
|
18
|
+
getItemCount: () => arrayLikeLength(data),
|
|
19
|
+
renderItem: typed.renderItem,
|
|
20
|
+
ListItemComponent: typed.ListItemComponent,
|
|
21
|
+
keyExtractor: typed.keyExtractor,
|
|
22
|
+
onViewableItemsChanged: typed.onViewableItemsChanged,
|
|
23
|
+
viewabilityConfigCallbackPairs: typed.viewabilityConfigCallbackPairs,
|
|
24
|
+
ItemSeparatorComponent: typed.ItemSeparatorComponent,
|
|
25
|
+
data,
|
|
26
|
+
...rest,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
// The row is the virtualized cell, so its items share one `separators` handle, as in RN
|
|
30
|
+
function rowRenderer(typed) {
|
|
31
|
+
const { renderItem, ListItemComponent, keyExtractor, columnWrapperStyle } = typed;
|
|
46
32
|
const rowStyle = [
|
|
47
33
|
{ flexDirection: 'row' },
|
|
48
|
-
|
|
34
|
+
typeof columnWrapperStyle === 'string'
|
|
35
|
+
? resolveClassName(columnWrapperStyle)
|
|
36
|
+
: columnWrapperStyle,
|
|
49
37
|
];
|
|
50
|
-
|
|
51
|
-
const cells = info.item.
|
|
52
|
-
const index = info.item.startIndex + column;
|
|
53
|
-
const key = keyExtractor ? keyExtractor(item, index) : String(index);
|
|
54
|
-
// The row IS the virtualized cell, so every item in it shares the row's separators handle
|
|
55
|
-
// (the divider sits between rows, not columns), like RN's multi-column FlatList.
|
|
56
|
-
return createElement('view', { key, style: { flex: 1 } }, renderItem({ item, index, separators: info.separators }));
|
|
57
|
-
});
|
|
38
|
+
return info => {
|
|
39
|
+
const cells = expandRowToken({ item: info.item, key: '', index: info.index, isViewable: true }, keyExtractor).map(({ item, index, key }) => createElement('view', { key, style: { flex: 1 } }, renderItemElement({ renderItem, ListItemComponent }, { item, index, separators: info.separators })));
|
|
58
40
|
return createElement('view', { style: rowStyle }, ...cells);
|
|
59
41
|
};
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
42
|
+
}
|
|
43
|
+
// Row reports expand back to per-item tokens through `expandRowViewability`
|
|
44
|
+
function rowViewability(typed) {
|
|
45
|
+
const { keyExtractor, onViewableItemsChanged } = typed;
|
|
46
|
+
const expand = (report) => (rowInfo) => {
|
|
47
|
+
report(expandRowViewability(rowInfo, keyExtractor));
|
|
48
|
+
};
|
|
49
|
+
return {
|
|
50
|
+
onViewableItemsChanged: onViewableItemsChanged === undefined
|
|
51
|
+
? undefined
|
|
52
|
+
: expand(onViewableItemsChanged),
|
|
53
|
+
pairs: typed.viewabilityConfigCallbackPairs?.map(pair => ({
|
|
54
|
+
viewabilityConfig: pair.viewabilityConfig,
|
|
55
|
+
onViewableItemsChanged: expand(pair.onViewableItemsChanged),
|
|
56
|
+
})),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
// The divider between rows shows the last item above and the first below, not the `IRow`
|
|
60
|
+
function rowSeparator(separator) {
|
|
61
|
+
if (separator === undefined || isValidElement(separator))
|
|
62
|
+
return separator;
|
|
63
|
+
return (rowProps) => createElement(separator, {
|
|
64
|
+
...rowProps,
|
|
65
|
+
leadingItem: lastItemOfRow(rowProps.leadingItem),
|
|
66
|
+
trailingItem: firstItemOfRow(rowProps.trailingItem),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
// The virtualized stream is rows, each cell lays its items out side by side in a flex row
|
|
70
|
+
function multiColumnList(typed, numColumns, rest, ref) {
|
|
71
|
+
const rows = chunkIntoRows(typed.data, numColumns);
|
|
72
|
+
const viewability = rowViewability(typed);
|
|
82
73
|
return createElement((VirtualizedList), {
|
|
83
74
|
ref,
|
|
84
75
|
data: rows,
|
|
85
76
|
getItem: (_source, index) => rows[index],
|
|
86
77
|
getItemCount: () => rows.length,
|
|
87
|
-
renderItem:
|
|
88
|
-
keyExtractor: (row) => rowKeyExtractor(row, keyExtractor),
|
|
89
|
-
onViewableItemsChanged:
|
|
90
|
-
viewabilityConfigCallbackPairs:
|
|
91
|
-
ItemSeparatorComponent:
|
|
78
|
+
renderItem: rowRenderer(typed),
|
|
79
|
+
keyExtractor: (row) => rowKeyExtractor(row, typed.keyExtractor),
|
|
80
|
+
onViewableItemsChanged: viewability.onViewableItemsChanged,
|
|
81
|
+
viewabilityConfigCallbackPairs: viewability.pairs,
|
|
82
|
+
ItemSeparatorComponent: rowSeparator(typed.ItemSeparatorComponent),
|
|
92
83
|
...rest,
|
|
93
84
|
});
|
|
94
85
|
}
|
|
86
|
+
export function FlatList(props) {
|
|
87
|
+
const { ref, data, renderItem, ListItemComponent, keyExtractor, numColumns = SINGLE_COLUMN, columnWrapperStyle, onViewableItemsChanged, viewabilityConfigCallbackPairs, ItemSeparatorComponent, removeClippedSubviews: authoredRemoveClippedSubviews, ...ownRest } = props;
|
|
88
|
+
// `FlatList.js` always sends it, defaulted per platform
|
|
89
|
+
const rest = {
|
|
90
|
+
...ownRest,
|
|
91
|
+
removeClippedSubviews: removeClippedSubviewsOrDefault(authoredRemoveClippedSubviews, Platform.OS),
|
|
92
|
+
};
|
|
93
|
+
const typed = {
|
|
94
|
+
data,
|
|
95
|
+
renderItem,
|
|
96
|
+
ListItemComponent,
|
|
97
|
+
keyExtractor,
|
|
98
|
+
numColumns,
|
|
99
|
+
columnWrapperStyle,
|
|
100
|
+
onViewableItemsChanged,
|
|
101
|
+
viewabilityConfigCallbackPairs,
|
|
102
|
+
ItemSeparatorComponent,
|
|
103
|
+
};
|
|
104
|
+
dlog(`FlatList over ${arrayLikeLength(data)} items, ${numColumns} column(s)`);
|
|
105
|
+
return numColumns <= SINGLE_COLUMN
|
|
106
|
+
? singleColumnList(typed, rest, ref)
|
|
107
|
+
: multiColumnList(typed, numColumns, rest, ref);
|
|
108
|
+
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
import type { IAccessibilityProps, IAriaProps } from '@symbiote-native/components';
|
|
3
|
+
import type { IColorValue } from '@symbiote-native/engine';
|
|
3
4
|
import type { IStyleProp, IViewStyle } from '../utils/styles';
|
|
4
|
-
export
|
|
5
|
+
export type IInputAccessoryViewProps = IAccessibilityProps & IAriaProps & {
|
|
5
6
|
nativeID?: string;
|
|
6
|
-
backgroundColor?:
|
|
7
|
+
backgroundColor?: IColorValue;
|
|
7
8
|
style?: IStyleProp<IViewStyle>;
|
|
8
9
|
className?: string;
|
|
9
10
|
children?: ReactNode;
|
|
10
|
-
}
|
|
11
|
+
};
|
|
@@ -4,7 +4,7 @@ import { type IKeyboardAvoidingBehavior } from '@symbiote-native/components';
|
|
|
4
4
|
import type { IAccessibilityProps, IAriaProps } from '@symbiote-native/components';
|
|
5
5
|
import type { IStyleProp, IViewStyle } from '../../utils/styles';
|
|
6
6
|
export type { IKeyboardAvoidingBehavior } from '@symbiote-native/components';
|
|
7
|
-
export
|
|
7
|
+
export type IKeyboardAvoidingViewProps = IAccessibilityProps & IAriaProps & {
|
|
8
8
|
behavior?: IKeyboardAvoidingBehavior;
|
|
9
9
|
enabled?: boolean;
|
|
10
10
|
keyboardVerticalOffset?: number;
|
|
@@ -13,5 +13,5 @@ export interface IKeyboardAvoidingViewProps extends IAccessibilityProps, IAriaPr
|
|
|
13
13
|
className?: string;
|
|
14
14
|
children?: ReactNode;
|
|
15
15
|
onLayout?: (event: ISymbioteEvent) => void;
|
|
16
|
-
}
|
|
16
|
+
};
|
|
17
17
|
export declare const KeyboardAvoidingView: FC<IKeyboardAvoidingViewProps>;
|
|
@@ -1,110 +1,71 @@
|
|
|
1
|
-
// KeyboardAvoidingView
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// Mirrors RN's Libraries/Components/Keyboard/KeyboardAvoidingView.js, as a function component.
|
|
5
|
-
//
|
|
6
|
-
// The inset math + the behavior → style/structure decision are framework-agnostic and live in
|
|
7
|
-
// @symbiote-native/components (render-keyboard-avoiding-view), shared verbatim with the Vue adapter.
|
|
8
|
-
// React supplies only the lifecycle: useState for the inset, useRef for the measured frame and
|
|
9
|
-
// the cross-fade setting, the useEffect subscriptions, and the descriptor-free element assembly
|
|
10
|
-
// around its children.
|
|
1
|
+
// KeyboardAvoidingView over the shared `createKeyboardAvoidingModel`, which holds the frame, the
|
|
2
|
+
// last keyboard event and the inset rules of RN's class
|
|
3
|
+
// React supplies the inset state, the keyboard subscriptions and the element assembly
|
|
11
4
|
import { createElement, useEffect, useRef, useState, } from 'react';
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
5
|
+
import { Keyboard, Platform, } from '@symbiote-native/engine';
|
|
6
|
+
import { createKeyboardAvoidingModel, keyboardAvoidingEventNamesFor, readPrefersCrossFadeTransitions, resolveKeyboardAvoidingLayout, DEFAULT_VERTICAL_OFFSET, } from '@symbiote-native/components';
|
|
14
7
|
import {} from '../../components.js';
|
|
15
|
-
//
|
|
16
|
-
// up with the keyboard animation; Android has no will-notifications). Resolved ONCE at module
|
|
17
|
-
// scope: Platform.OS is fixed for the process, so recomputing it per render buys nothing.
|
|
8
|
+
// Resolved once: Platform.OS is fixed for the process
|
|
18
9
|
const KEYBOARD_EVENTS = keyboardAvoidingEventNamesFor(Platform.OS);
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
// the raw aria/role + accessibility* props pass through untouched here and fold
|
|
23
|
-
// there once.
|
|
24
|
-
...accessibilityRest } = props;
|
|
10
|
+
// The model, its inset state and the keyboard subscriptions, kept apart so the component reads as
|
|
11
|
+
// the element assembly
|
|
12
|
+
function useKeyboardAvoidingModel(options) {
|
|
25
13
|
const [inset, setInset] = useState(0);
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
// A device accessibility setting, not component state: it cannot change mid-session, and
|
|
31
|
-
// learning it must not re-render. Read once per mount, then fed to every computeInset call.
|
|
14
|
+
// The model reads the options at event time, so a changed prop needs no new subscription
|
|
15
|
+
const optionsRef = useRef(options);
|
|
16
|
+
optionsRef.current = options;
|
|
17
|
+
// A device setting that cannot change mid-session, learning it must not re-render
|
|
32
18
|
const prefersCrossFadeRef = useRef(false);
|
|
19
|
+
const [model] = useState(() => createKeyboardAvoidingModel({
|
|
20
|
+
options: () => optionsRef.current,
|
|
21
|
+
setInset,
|
|
22
|
+
prefersCrossFade: () => prefersCrossFadeRef.current,
|
|
23
|
+
}));
|
|
33
24
|
useEffect(() => {
|
|
34
25
|
void readPrefersCrossFadeTransitions().then(prefers => {
|
|
35
26
|
prefersCrossFadeRef.current = prefers;
|
|
36
27
|
});
|
|
37
28
|
}, []);
|
|
38
29
|
useEffect(() => {
|
|
39
|
-
const onShow = (payload) => {
|
|
40
|
-
const keyboard = readKeyboardFrame(payload);
|
|
41
|
-
// Functional update, not a read of `inset`: this handler is built inside the effect, so a
|
|
42
|
-
// direct read would freeze the value from the render that created it — and 'height' mode
|
|
43
|
-
// feeds the LIVE inset back in to cancel the wrapper's own shrink.
|
|
44
|
-
setInset(previousInset => {
|
|
45
|
-
const next = computeInset(frameRef.current, keyboard, keyboardVerticalOffset, {
|
|
46
|
-
behavior,
|
|
47
|
-
previousInset,
|
|
48
|
-
prefersCrossFadeTransitions: prefersCrossFadeRef.current,
|
|
49
|
-
});
|
|
50
|
-
// RN's `_updateBottomIfNecessary` skips the animation entirely when the inset did not
|
|
51
|
-
// change (`if (this._bottom === height) return;`) — arming a transition for a value that
|
|
52
|
-
// moves nowhere would only ever animate other props the app changed this frame.
|
|
53
|
-
if (next !== previousInset)
|
|
54
|
-
configureKeyboardAvoidingAnimation(readKeyboardAnimationTiming(payload), enabled);
|
|
55
|
-
dlog(`KeyboardAvoidingView show -> inset ${next}`);
|
|
56
|
-
return next;
|
|
57
|
-
});
|
|
58
|
-
};
|
|
59
|
-
const onHide = () => {
|
|
60
|
-
dlog('KeyboardAvoidingView hide -> inset 0');
|
|
61
|
-
setInset(0);
|
|
62
|
-
};
|
|
63
30
|
const subscriptions = [
|
|
64
|
-
Keyboard.addListener(KEYBOARD_EVENTS.show,
|
|
65
|
-
Keyboard.addListener(KEYBOARD_EVENTS.hide,
|
|
31
|
+
Keyboard.addListener(KEYBOARD_EVENTS.show, model.keyboardShown),
|
|
32
|
+
Keyboard.addListener(KEYBOARD_EVENTS.hide, model.keyboardHidden),
|
|
66
33
|
];
|
|
67
34
|
return () => {
|
|
68
35
|
for (const subscription of subscriptions)
|
|
69
36
|
subscription.remove();
|
|
70
37
|
};
|
|
71
|
-
}, [
|
|
38
|
+
}, [model]);
|
|
39
|
+
return { inset, model };
|
|
40
|
+
}
|
|
41
|
+
// `onLayout` is widened through a typed variable (no `as`), View's public type lacks it
|
|
42
|
+
function wrapperElement(rest, wrapStyle, onLayout, content) {
|
|
43
|
+
const wrapperProps = { ...rest, style: wrapStyle, onLayout, children: content };
|
|
44
|
+
return createElement('view', wrapperProps);
|
|
45
|
+
}
|
|
46
|
+
export const KeyboardAvoidingView = props => {
|
|
47
|
+
const { behavior, enabled = true, keyboardVerticalOffset = DEFAULT_VERTICAL_OFFSET, contentContainerStyle, style, children, onLayout,
|
|
48
|
+
// The wrapper is the View FC, which folds aria/role and accessibility* once itself
|
|
49
|
+
...accessibilityRest } = props;
|
|
50
|
+
const { inset, model } = useKeyboardAvoidingModel({
|
|
51
|
+
behavior,
|
|
52
|
+
enabled,
|
|
53
|
+
keyboardVerticalOffset,
|
|
54
|
+
});
|
|
72
55
|
const handleLayout = (event) => {
|
|
73
|
-
|
|
74
|
-
if (frame !== undefined) {
|
|
75
|
-
frameRef.current = frame;
|
|
76
|
-
if (initialHeightRef.current === undefined)
|
|
77
|
-
initialHeightRef.current = frame.height;
|
|
78
|
-
}
|
|
56
|
+
model.laidOut(event.nativeEvent.layout);
|
|
79
57
|
onLayout?.(event);
|
|
80
58
|
};
|
|
81
|
-
//
|
|
82
|
-
// untouched (RN gates each bottomHeight/height computation on `enabled ?? true`).
|
|
83
|
-
const effectiveInset = enabled ? inset : 0;
|
|
59
|
+
// Disabled forces the inset to 0, so every behavior mode renders the view untouched
|
|
84
60
|
const layout = resolveKeyboardAvoidingLayout({
|
|
85
61
|
behavior,
|
|
86
|
-
effectiveInset,
|
|
87
|
-
initialHeight:
|
|
62
|
+
effectiveInset: enabled ? inset : 0,
|
|
63
|
+
initialHeight: model.initialHeight(),
|
|
88
64
|
style,
|
|
89
65
|
contentContainerStyle,
|
|
90
66
|
});
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
return renderWrapper(layout.wrapperStyle, children);
|
|
97
|
-
// The wrapper carries onLayout. The View FC's public props don't surface it, but
|
|
98
|
-
// `view` routes the base layout event at runtime; widen the props through
|
|
99
|
-
// a typed variable (no inline-literal excess-property check, no `as`) so the
|
|
100
|
-
// onLayout reaches the host without editing View's public type.
|
|
101
|
-
function renderWrapper(wrapStyle, content) {
|
|
102
|
-
const wrapperProps = {
|
|
103
|
-
...accessibilityRest,
|
|
104
|
-
style: wrapStyle,
|
|
105
|
-
onLayout: handleLayout,
|
|
106
|
-
children: content,
|
|
107
|
-
};
|
|
108
|
-
return createElement('view', wrapperProps);
|
|
109
|
-
}
|
|
67
|
+
const content = layout.kind === 'nested'
|
|
68
|
+
? createElement('view', { style: layout.innerStyle }, children)
|
|
69
|
+
: children;
|
|
70
|
+
return wrapperElement(accessibilityRest, layout.wrapperStyle, handleLayout, content);
|
|
110
71
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { type FC, type ReactNode } from 'react';
|
|
2
|
-
import { type ISymbioteEvent } from '@symbiote-native/engine';
|
|
2
|
+
import { type IColorValue, type ISymbioteEvent } from '@symbiote-native/engine';
|
|
3
3
|
import { type IAccessibilityProps, type IAriaProps, type IModalAnimationType, type IModalOrientation, type IModalPresentationStyle } from '@symbiote-native/components';
|
|
4
4
|
import type { IStyleProp, IViewStyle } from '../../utils/styles';
|
|
5
5
|
export type { IModalAnimationType, IModalPresentationStyle, IModalOrientation, IModalOrientationChangeEvent, } from '@symbiote-native/components';
|
|
6
|
-
export
|
|
6
|
+
export type IModalProps = IAccessibilityProps & IAriaProps & {
|
|
7
7
|
visible?: boolean;
|
|
8
8
|
transparent?: boolean;
|
|
9
|
-
backdropColor?:
|
|
9
|
+
backdropColor?: IColorValue;
|
|
10
10
|
animationType?: IModalAnimationType;
|
|
11
11
|
presentationStyle?: IModalPresentationStyle;
|
|
12
12
|
supportedOrientations?: ReadonlyArray<IModalOrientation>;
|
|
@@ -21,5 +21,5 @@ export interface IModalProps extends IAccessibilityProps, IAriaProps {
|
|
|
21
21
|
style?: IStyleProp<IViewStyle>;
|
|
22
22
|
className?: string;
|
|
23
23
|
children?: ReactNode;
|
|
24
|
-
}
|
|
24
|
+
};
|
|
25
25
|
export declare const Modal: FC<IModalProps>;
|
|
@@ -12,17 +12,21 @@
|
|
|
12
12
|
// through the exit transition. The native exit-animation timing is what's deferred, not the
|
|
13
13
|
// callback contract.
|
|
14
14
|
import { createElement, useEffect, useReducer, } from 'react';
|
|
15
|
-
import { dlog, Platform } from '@symbiote-native/engine';
|
|
16
|
-
import { createInitialModalState, isModalVisible, modalReducer, modalVisibilityAction, renderModal, resolveAccessibilityProps, shouldRenderModal, } from '@symbiote-native/components';
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
15
|
+
import { dlog, Platform, } from '@symbiote-native/engine';
|
|
16
|
+
import { createInitialModalState, isModalVisible, modalReducer, modalVisibilityAction, renderModal, resolveAccessibilityProps, shouldRenderModal, warnAboutModalProps, } from '@symbiote-native/components';
|
|
17
|
+
import { VirtualizedListScopeResetter } from '../virtualized-list/nested-scope.js';
|
|
18
|
+
// The user children nest UNDER the container View, never beside the host, as RN lays a modal out
|
|
19
|
+
// They sit outside the list above, so a list in them is not nested in it
|
|
20
|
+
function modalTreeOf(input) {
|
|
21
|
+
const { className, children, ...renderInput } = input;
|
|
22
|
+
const root = renderModal(renderInput);
|
|
23
|
+
const [container] = root.children;
|
|
24
|
+
if (typeof container === 'string')
|
|
25
|
+
return null;
|
|
26
|
+
return createElement(root.type, { key: root.key, ...root.props }, createElement(container.type, { key: container.key, ...container.props, className }, createElement(VirtualizedListScopeResetter, null, children)));
|
|
27
|
+
}
|
|
28
|
+
// The iOS keep-alive: armed on show, dropped only by the native dismiss
|
|
29
|
+
function useKeepAlive(visible) {
|
|
26
30
|
const isVisible = isModalVisible(visible);
|
|
27
31
|
const [state, dispatch] = useReducer(modalReducer, isVisible, createInitialModalState);
|
|
28
32
|
useEffect(() => {
|
|
@@ -30,6 +34,35 @@ export const Modal = rawProps => {
|
|
|
30
34
|
if (action !== undefined)
|
|
31
35
|
dispatch(action);
|
|
32
36
|
}, [isVisible]);
|
|
37
|
+
return { isVisible, state, dispatch };
|
|
38
|
+
}
|
|
39
|
+
// RN checks on mount and on every update, a dev build only
|
|
40
|
+
function useModalWarnings(props) {
|
|
41
|
+
const { presentationStyle, transparent, navigationBarTranslucent, statusBarTranslucent, allowSwipeDismissal, onRequestClose, } = props;
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
warnAboutModalProps({
|
|
44
|
+
presentationStyle,
|
|
45
|
+
transparent,
|
|
46
|
+
navigationBarTranslucent,
|
|
47
|
+
statusBarTranslucent,
|
|
48
|
+
allowSwipeDismissal,
|
|
49
|
+
onRequestClose,
|
|
50
|
+
});
|
|
51
|
+
}, [
|
|
52
|
+
presentationStyle,
|
|
53
|
+
transparent,
|
|
54
|
+
navigationBarTranslucent,
|
|
55
|
+
statusBarTranslucent,
|
|
56
|
+
allowSwipeDismissal,
|
|
57
|
+
onRequestClose,
|
|
58
|
+
]);
|
|
59
|
+
}
|
|
60
|
+
export const Modal = rawProps => {
|
|
61
|
+
useModalWarnings(rawProps);
|
|
62
|
+
// Aria and role fold here, the events are real DirectEvents and ride `passthrough` raw
|
|
63
|
+
// `className` is pulled out to land on the container, in `passthrough` it would reach the host
|
|
64
|
+
const { visible, transparent, backdropColor, animationType, presentationStyle, supportedOrientations, hardwareAccelerated, statusBarTranslucent, navigationBarTranslucent, allowSwipeDismissal, style, className, children, onDismiss, ...passthrough } = resolveAccessibilityProps(rawProps);
|
|
65
|
+
const { isVisible, state, dispatch } = useKeepAlive(visible);
|
|
33
66
|
if (!shouldRenderModal(isVisible, state)) {
|
|
34
67
|
dlog('Modal hidden -> no node committed');
|
|
35
68
|
return null;
|
|
@@ -41,7 +74,7 @@ export const Modal = rawProps => {
|
|
|
41
74
|
dispatch({ type: 'hide' });
|
|
42
75
|
onDismiss?.();
|
|
43
76
|
};
|
|
44
|
-
|
|
77
|
+
return modalTreeOf({
|
|
45
78
|
visible,
|
|
46
79
|
transparent,
|
|
47
80
|
backdropColor,
|
|
@@ -54,11 +87,7 @@ export const Modal = rawProps => {
|
|
|
54
87
|
allowSwipeDismissal,
|
|
55
88
|
style,
|
|
56
89
|
passthrough: { ...passthrough, onDismiss: handleDismiss },
|
|
90
|
+
className,
|
|
91
|
+
children,
|
|
57
92
|
});
|
|
58
|
-
// root = modal > [container]; the user children nest UNDER the container View, never as
|
|
59
|
-
// a direct sibling of the host (RN's modal content layout).
|
|
60
|
-
const [container] = root.children;
|
|
61
|
-
if (typeof container === 'string')
|
|
62
|
-
return null;
|
|
63
|
-
return createElement(root.type, { key: root.key, ...root.props }, createElement(container.type, { key: container.key, ...container.props, className }, children));
|
|
64
93
|
};
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
import type { IAccessibilityProps, IAriaProps } from '@symbiote-native/components';
|
|
3
|
-
|
|
3
|
+
import type { IColorValue } from '@symbiote-native/engine';
|
|
4
|
+
export type IRefreshControlProps = IAccessibilityProps & IAriaProps & {
|
|
4
5
|
refreshing: boolean;
|
|
5
6
|
id?: string;
|
|
6
7
|
onRefresh?: () => void | Promise<void>;
|
|
7
|
-
tintColor?:
|
|
8
|
+
tintColor?: IColorValue;
|
|
8
9
|
title?: string;
|
|
9
|
-
titleColor?:
|
|
10
|
+
titleColor?: IColorValue;
|
|
10
11
|
progressViewOffset?: number;
|
|
11
|
-
colors?: readonly
|
|
12
|
-
progressBackgroundColor?:
|
|
12
|
+
colors?: readonly IColorValue[];
|
|
13
|
+
progressBackgroundColor?: IColorValue;
|
|
13
14
|
size?: 'default' | 'large';
|
|
14
15
|
enabled?: boolean;
|
|
15
16
|
children?: ReactNode;
|
|
16
17
|
className?: string;
|
|
17
|
-
}
|
|
18
|
+
};
|