@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.
Files changed (84) hide show
  1. package/build/batched-updates.d.ts +2 -0
  2. package/build/batched-updates.js +5 -0
  3. package/build/components/checkbox-props.d.ts +5 -0
  4. package/build/components/checkbox-props.js +1 -0
  5. package/build/components/flat-list/index.d.ts +16 -13
  6. package/build/components/flat-list/index.js +86 -72
  7. package/build/components/input-accessory-view-props.d.ts +4 -3
  8. package/build/components/keyboard-avoiding-view/index.d.ts +2 -2
  9. package/build/components/keyboard-avoiding-view/index.js +45 -84
  10. package/build/components/layout-conformance-props.d.ts +6 -0
  11. package/build/components/layout-conformance-props.js +1 -0
  12. package/build/components/modal/index.d.ts +4 -4
  13. package/build/components/modal/index.js +47 -18
  14. package/build/components/refresh-control-props.d.ts +7 -6
  15. package/build/components/scroll-view/scroll-view-props.d.ts +8 -5
  16. package/build/components/text-input/text-input-props.d.ts +4 -0
  17. package/build/components/touchable/touchable-props.d.ts +6 -5
  18. package/build/components/virtualized-list/index.d.ts +3 -70
  19. package/build/components/virtualized-list/index.js +50 -548
  20. package/build/components/virtualized-list/list-children.d.ts +13 -0
  21. package/build/components/virtualized-list/list-children.js +79 -0
  22. package/build/components/virtualized-list/list-config.d.ts +11 -0
  23. package/build/components/virtualized-list/list-config.js +14 -0
  24. package/build/components/virtualized-list/list-elements.d.ts +11 -0
  25. package/build/components/virtualized-list/list-elements.js +26 -0
  26. package/build/components/virtualized-list/list-host.d.ts +14 -0
  27. package/build/components/virtualized-list/list-host.js +24 -0
  28. package/build/components/virtualized-list/list-scroll-props.d.ts +32 -0
  29. package/build/components/virtualized-list/list-scroll-props.js +30 -0
  30. package/build/components/virtualized-list/nested-scope.d.ts +10 -0
  31. package/build/components/virtualized-list/nested-scope.js +16 -0
  32. package/build/components/virtualized-list/use-list-driver.d.ts +22 -0
  33. package/build/components/virtualized-list/use-list-driver.js +104 -0
  34. package/build/components/virtualized-list/use-list-nesting.d.ts +12 -0
  35. package/build/components/virtualized-list/use-list-nesting.js +37 -0
  36. package/build/components/virtualized-list/use-list-parts.d.ts +12 -0
  37. package/build/components/virtualized-list/use-list-parts.js +20 -0
  38. package/build/components/virtualized-list/use-scroll-handlers.d.ts +17 -0
  39. package/build/components/virtualized-list/use-scroll-handlers.js +106 -0
  40. package/build/components/virtualized-list/use-separators.d.ts +8 -0
  41. package/build/components/virtualized-list/use-separators.js +17 -0
  42. package/build/components/virtualized-list/virtualized-list-props.d.ts +85 -0
  43. package/build/components/virtualized-list/virtualized-list-props.js +2 -0
  44. package/build/components/virtualized-section-list/index.d.ts +24 -8
  45. package/build/components/virtualized-section-list/index.js +89 -93
  46. package/build/components/virtualized-section-list/section-item-cell.d.ts +23 -0
  47. package/build/components/virtualized-section-list/section-item-cell.js +48 -0
  48. package/build/components.d.ts +5 -5
  49. package/build/descriptor-to-react/with-children.d.ts +4 -0
  50. package/build/descriptor-to-react/with-children.js +6 -0
  51. package/build/hooks/use-animated-value.d.ts +7 -0
  52. package/build/hooks/use-animated-value.js +19 -0
  53. package/build/hooks/use-color-scheme.d.ts +2 -2
  54. package/build/hooks/use-color-scheme.js +4 -13
  55. package/build/hooks/use-event.d.ts +6 -0
  56. package/build/hooks/use-event.js +15 -0
  57. package/build/hooks/use-native-view-controller.d.ts +3 -0
  58. package/build/hooks/use-native-view-controller.js +15 -0
  59. package/build/hooks/use-pressability.d.ts +34 -0
  60. package/build/hooks/use-pressability.js +39 -0
  61. package/build/hooks/use-window-dimensions.js +6 -10
  62. package/build/host-config.js +6 -5
  63. package/build/index.d.ts +23 -5
  64. package/build/index.js +23 -14
  65. package/build/jsx-runtime.d.ts +17 -16
  66. package/build/modules/animated/create-animated-component.d.ts +2 -2
  67. package/build/modules/animated/create-animated-component.js +10 -22
  68. package/build/modules/animated/index.d.ts +54 -41
  69. package/build/modules/animated/index.js +2 -27
  70. package/build/modules/app-registry/index.d.ts +3 -3
  71. package/build/modules/app-registry/index.js +2 -2
  72. package/build/modules/status-bar/index.android.d.ts +1 -1
  73. package/build/modules/status-bar/index.android.js +7 -27
  74. package/build/modules/status-bar/index.ios.d.ts +1 -1
  75. package/build/modules/status-bar/index.ios.js +4 -28
  76. package/build/modules/status-bar/shared.d.ts +5 -4
  77. package/build/modules/status-bar/shared.js +33 -6
  78. package/build/native-host-tags.d.ts +2 -0
  79. package/build/native-host-tags.js +4 -0
  80. package/build/render.d.ts +1 -1
  81. package/build/render.js +3 -1
  82. package/build/root-tag-context.d.ts +1 -0
  83. package/build/root-tag-context.js +4 -0
  84. 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
+ };
@@ -0,0 +1,2 @@
1
+ // Public prop types of the React VirtualizedList
2
+ export {};
@@ -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 ISeparators, type ISeparatorProps } from '../virtualized-list';
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 interface IVirtualizedSectionListProps<ItemT> extends IAccessibilityProps, IAriaProps {
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?: ComponentType<Record<string, never>> | ReactElement;
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?: ComponentType<ISeparatorProps<ItemT>>;
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, } from '@symbiote-native/engine';
10
- import { flattenSections, resolveStickySectionHeaders, scrollLocationToFlatIndex, sectionEntryKey, unwrapEntryItem, } from '@symbiote-native/components';
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
- function resolveSeparator(component) {
13
- if (component === undefined)
14
- return undefined;
15
- if (typeof component === 'function')
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 flatIndex = scrollLocationToFlatIndex(headerIndices, params.sectionIndex, params.itemIndex);
37
- if (flatIndex === undefined) {
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 ${flatIndex}`);
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
- }), [headerIndices]);
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
- // The user's ItemSeparatorComponent is typed on ItemT; the inner stream is the entry wrapper, so
84
- // unwrap each entry back to its ItemT (shared unwrapEntryItem) before handing it to the user.
85
- const entrySeparatorComponent = ItemSeparatorComponent === undefined
86
- ? undefined
87
- : (entryProps) => createElement(ItemSeparatorComponent, {
88
- ...entryProps,
89
- leadingItem: unwrapEntryItem(entryProps.leadingItem),
90
- trailingItem: unwrapEntryItem(entryProps.trailingItem),
91
- });
92
- const entryKeyExtractor = (entry, index) => sectionEntryKey(entry, index, keyExtractor);
93
- // Hand the callback `sections`, not the entries: RN's inner VirtualizedList gets
94
- // `data={this.props.sections}` (VirtualizedSectionList.js:216) while ours streams the FLATTENED
95
- // entries, so the same user code would otherwise see a different argument here than on RN.
96
- //
97
- // UPSTREAM-DIVERGENCE(react-native): the flat INDEX matches RN's (two rows per section, header
98
- // and footer) only while SectionSeparatorComponent is unset. With it, flattenSections emits an
99
- // extra 'section-separator' row per boundary that RN renders inside the neighbouring cell, so
100
- // indices shift by one per boundary from the second section on. Deliberate - that row is how this
101
- // adapter paints the separator; a caller combining the two must account for it.
102
- const entryItemLayout = getItemLayout === undefined
103
- ? undefined
104
- : (_entries, index) => getItemLayout(sections, index);
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: renderEntry,
111
- keyExtractor: entryKeyExtractor,
112
- getItemLayout: entryItemLayout,
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
- ItemSeparatorComponent: entrySeparatorComponent,
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
+ }
@@ -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 interface IViewProps extends IAccessibilityProps, IAriaProps, IResponderProps {
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 interface ITextProps extends IAccessibilityProps, IAriaProps {
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 { type IColorSchemeName } from '@symbiote-native/engine';
2
- export declare function useColorScheme(): IColorSchemeName | null;
1
+ import type { IColorSchemeName } from '@symbiote-native/engine';
2
+ export declare const useColorScheme: () => IColorSchemeName | null | undefined;
@@ -1,13 +1,4 @@
1
- // useColorScheme subscribes a component to the device color scheme and re-renders
2
- // on change. Mirrors RN's Libraries/Utilities/useColorScheme.js: a thin
3
- // useSyncExternalStore over Appearance's change event + current scheme read.
4
- import { useSyncExternalStore } from 'react';
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;