react-native-gtkx 0.1.0-alpha.1

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 (213) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +73 -0
  3. package/dist/animated/composite.d.ts +5 -0
  4. package/dist/animated/composite.js +132 -0
  5. package/dist/animated/composite.js.map +1 -0
  6. package/dist/animated/create-animated.d.ts +12 -0
  7. package/dist/animated/create-animated.js +17 -0
  8. package/dist/animated/create-animated.js.map +1 -0
  9. package/dist/animated/easing.d.ts +11 -0
  10. package/dist/animated/easing.js +91 -0
  11. package/dist/animated/easing.js.map +1 -0
  12. package/dist/animated/frame-loop.d.ts +6 -0
  13. package/dist/animated/frame-loop.js +25 -0
  14. package/dist/animated/frame-loop.js.map +1 -0
  15. package/dist/animated/index.d.ts +6 -0
  16. package/dist/animated/index.js +7 -0
  17. package/dist/animated/index.js.map +1 -0
  18. package/dist/animated/interpolate.d.ts +2 -0
  19. package/dist/animated/interpolate.js +90 -0
  20. package/dist/animated/interpolate.js.map +1 -0
  21. package/dist/animated/native-driver.d.ts +1 -0
  22. package/dist/animated/native-driver.js +17 -0
  23. package/dist/animated/native-driver.js.map +1 -0
  24. package/dist/animated/spring.d.ts +3 -0
  25. package/dist/animated/spring.js +67 -0
  26. package/dist/animated/spring.js.map +1 -0
  27. package/dist/animated/timing.d.ts +3 -0
  28. package/dist/animated/timing.js +30 -0
  29. package/dist/animated/timing.js.map +1 -0
  30. package/dist/animated/types.d.ts +51 -0
  31. package/dist/animated/types.js +6 -0
  32. package/dist/animated/types.js.map +1 -0
  33. package/dist/animated/value-animation.d.ts +9 -0
  34. package/dist/animated/value-animation.js +65 -0
  35. package/dist/animated/value-animation.js.map +1 -0
  36. package/dist/animated/value.d.ts +46 -0
  37. package/dist/animated/value.js +130 -0
  38. package/dist/animated/value.js.map +1 -0
  39. package/dist/apis/alert.d.ts +16 -0
  40. package/dist/apis/alert.js +33 -0
  41. package/dist/apis/alert.js.map +1 -0
  42. package/dist/apis/app-state.d.ts +11 -0
  43. package/dist/apis/app-state.js +54 -0
  44. package/dist/apis/app-state.js.map +1 -0
  45. package/dist/apis/appearance.d.ts +12 -0
  46. package/dist/apis/appearance.js +33 -0
  47. package/dist/apis/appearance.js.map +1 -0
  48. package/dist/apis/dev-settings.d.ts +9 -0
  49. package/dist/apis/dev-settings.js +25 -0
  50. package/dist/apis/dev-settings.js.map +1 -0
  51. package/dist/apis/dimensions.d.ts +14 -0
  52. package/dist/apis/dimensions.js +80 -0
  53. package/dist/apis/dimensions.js.map +1 -0
  54. package/dist/apis/emitter.d.ts +8 -0
  55. package/dist/apis/emitter.js +35 -0
  56. package/dist/apis/emitter.js.map +1 -0
  57. package/dist/apis/hooks.d.ts +11 -0
  58. package/dist/apis/hooks.js +29 -0
  59. package/dist/apis/hooks.js.map +1 -0
  60. package/dist/apis/host.d.ts +45 -0
  61. package/dist/apis/host.gtkx.d.ts +2 -0
  62. package/dist/apis/host.gtkx.js +301 -0
  63. package/dist/apis/host.gtkx.js.map +1 -0
  64. package/dist/apis/host.js +6 -0
  65. package/dist/apis/host.js.map +1 -0
  66. package/dist/apis/index.d.ts +39 -0
  67. package/dist/apis/index.js +21 -0
  68. package/dist/apis/index.js.map +1 -0
  69. package/dist/apis/linking.d.ts +7 -0
  70. package/dist/apis/linking.js +28 -0
  71. package/dist/apis/linking.js.map +1 -0
  72. package/dist/apis/platform.d.ts +14 -0
  73. package/dist/apis/platform.js +23 -0
  74. package/dist/apis/platform.js.map +1 -0
  75. package/dist/components/activity-indicator.d.ts +10 -0
  76. package/dist/components/activity-indicator.js +21 -0
  77. package/dist/components/activity-indicator.js.map +1 -0
  78. package/dist/components/animated.d.ts +34 -0
  79. package/dist/components/animated.js +152 -0
  80. package/dist/components/animated.js.map +1 -0
  81. package/dist/components/app-registry.d.ts +14 -0
  82. package/dist/components/app-registry.js +61 -0
  83. package/dist/components/app-registry.js.map +1 -0
  84. package/dist/components/flat-list.d.ts +48 -0
  85. package/dist/components/flat-list.js +23 -0
  86. package/dist/components/flat-list.js.map +1 -0
  87. package/dist/components/host-node.d.ts +10 -0
  88. package/dist/components/host-node.js +10 -0
  89. package/dist/components/host-node.js.map +1 -0
  90. package/dist/components/image-loader.d.ts +17 -0
  91. package/dist/components/image-loader.js +54 -0
  92. package/dist/components/image-loader.js.map +1 -0
  93. package/dist/components/image.d.ts +19 -0
  94. package/dist/components/image.js +73 -0
  95. package/dist/components/image.js.map +1 -0
  96. package/dist/components/index.d.ts +14 -0
  97. package/dist/components/index.js +14 -0
  98. package/dist/components/index.js.map +1 -0
  99. package/dist/components/modal.d.ts +12 -0
  100. package/dist/components/modal.js +18 -0
  101. package/dist/components/modal.js.map +1 -0
  102. package/dist/components/pressable.d.ts +33 -0
  103. package/dist/components/pressable.js +116 -0
  104. package/dist/components/pressable.js.map +1 -0
  105. package/dist/components/rect-store.d.ts +17 -0
  106. package/dist/components/rect-store.js +40 -0
  107. package/dist/components/rect-store.js.map +1 -0
  108. package/dist/components/root.d.ts +8 -0
  109. package/dist/components/root.js +59 -0
  110. package/dist/components/root.js.map +1 -0
  111. package/dist/components/scroll-view.d.ts +56 -0
  112. package/dist/components/scroll-view.js +324 -0
  113. package/dist/components/scroll-view.js.map +1 -0
  114. package/dist/components/switch.d.ts +11 -0
  115. package/dist/components/switch.js +20 -0
  116. package/dist/components/switch.js.map +1 -0
  117. package/dist/components/text-input.d.ts +23 -0
  118. package/dist/components/text-input.js +151 -0
  119. package/dist/components/text-input.js.map +1 -0
  120. package/dist/components/text.d.ts +11 -0
  121. package/dist/components/text.js +100 -0
  122. package/dist/components/text.js.map +1 -0
  123. package/dist/components/use-layout-child.d.ts +36 -0
  124. package/dist/components/use-layout-child.js +177 -0
  125. package/dist/components/use-layout-child.js.map +1 -0
  126. package/dist/components/view.d.ts +13 -0
  127. package/dist/components/view.js +60 -0
  128. package/dist/components/view.js.map +1 -0
  129. package/dist/components/virtualized-list.d.ts +67 -0
  130. package/dist/components/virtualized-list.js +519 -0
  131. package/dist/components/virtualized-list.js.map +1 -0
  132. package/dist/contracts.d.ts +119 -0
  133. package/dist/contracts.js +5 -0
  134. package/dist/contracts.js.map +1 -0
  135. package/dist/gtkx/bridge/index.d.ts +16 -0
  136. package/dist/gtkx/bridge/index.js +25 -0
  137. package/dist/gtkx/bridge/index.js.map +1 -0
  138. package/dist/gtkx/bridge/layout-manager.d.ts +11 -0
  139. package/dist/gtkx/bridge/layout-manager.js +87 -0
  140. package/dist/gtkx/bridge/layout-manager.js.map +1 -0
  141. package/dist/gtkx/bridge/measure.d.ts +9 -0
  142. package/dist/gtkx/bridge/measure.js +20 -0
  143. package/dist/gtkx/bridge/measure.js.map +1 -0
  144. package/dist/gtkx/bridge/theme.d.ts +4 -0
  145. package/dist/gtkx/bridge/theme.js +5 -0
  146. package/dist/gtkx/bridge/theme.js.map +1 -0
  147. package/dist/gtkx/bridge/view-box.d.ts +7 -0
  148. package/dist/gtkx/bridge/view-box.js +40 -0
  149. package/dist/gtkx/bridge/view-box.js.map +1 -0
  150. package/dist/index.d.ts +6 -0
  151. package/dist/index.js +7 -0
  152. package/dist/index.js.map +1 -0
  153. package/dist/layout/apply-style.d.ts +4 -0
  154. package/dist/layout/apply-style.js +148 -0
  155. package/dist/layout/apply-style.js.map +1 -0
  156. package/dist/layout/engine.d.ts +22 -0
  157. package/dist/layout/engine.js +90 -0
  158. package/dist/layout/engine.js.map +1 -0
  159. package/dist/layout/index.d.ts +3 -0
  160. package/dist/layout/index.js +4 -0
  161. package/dist/layout/index.js.map +1 -0
  162. package/dist/layout/node.d.ts +33 -0
  163. package/dist/layout/node.js +151 -0
  164. package/dist/layout/node.js.map +1 -0
  165. package/dist/layout/yoga.d.ts +13 -0
  166. package/dist/layout/yoga.js +52 -0
  167. package/dist/layout/yoga.js.map +1 -0
  168. package/dist/metro/index.d.ts +42 -0
  169. package/dist/metro/index.js +119 -0
  170. package/dist/metro/index.js.map +1 -0
  171. package/dist/runner/host-dev.d.ts +1 -0
  172. package/dist/runner/host-dev.js +208 -0
  173. package/dist/runner/host-dev.js.map +1 -0
  174. package/dist/runner/host.d.ts +1 -0
  175. package/dist/runner/host.js +101 -0
  176. package/dist/runner/host.js.map +1 -0
  177. package/dist/runner/index.d.ts +26 -0
  178. package/dist/runner/index.js +179 -0
  179. package/dist/runner/index.js.map +1 -0
  180. package/dist/style/colors.d.ts +17 -0
  181. package/dist/style/colors.js +375 -0
  182. package/dist/style/colors.js.map +1 -0
  183. package/dist/style/dev-warning.d.ts +2 -0
  184. package/dist/style/dev-warning.js +18 -0
  185. package/dist/style/dev-warning.js.map +1 -0
  186. package/dist/style/index.d.ts +7 -0
  187. package/dist/style/index.js +14 -0
  188. package/dist/style/index.js.map +1 -0
  189. package/dist/style/registry.d.ts +12 -0
  190. package/dist/style/registry.gtkx.d.ts +2 -0
  191. package/dist/style/registry.gtkx.js +9 -0
  192. package/dist/style/registry.gtkx.js.map +1 -0
  193. package/dist/style/registry.js +23 -0
  194. package/dist/style/registry.js.map +1 -0
  195. package/dist/style/split-style.d.ts +6 -0
  196. package/dist/style/split-style.js +107 -0
  197. package/dist/style/split-style.js.map +1 -0
  198. package/dist/style/style-sheet.d.ts +43 -0
  199. package/dist/style/style-sheet.js +57 -0
  200. package/dist/style/style-sheet.js.map +1 -0
  201. package/dist/style/text-align.d.ts +7 -0
  202. package/dist/style/text-align.js +22 -0
  203. package/dist/style/text-align.js.map +1 -0
  204. package/dist/style/visual-css.d.ts +6 -0
  205. package/dist/style/visual-css.js +133 -0
  206. package/dist/style/visual-css.js.map +1 -0
  207. package/dist/vite/index.d.ts +53 -0
  208. package/dist/vite/index.js +127 -0
  209. package/dist/vite/index.js.map +1 -0
  210. package/package.json +88 -0
  211. package/react-native.config.js +21 -0
  212. package/types.d.ts +43 -0
  213. package/types.js +4 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"view.js","sourceRoot":"","sources":["../../src/components/view.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,EAAkB,MAAM,OAAO,CAAA;AAC/D,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAE3C,OAAO,EACL,mBAAmB,EAEnB,iBAAiB,GAElB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAC7C,OAAO,EACL,cAAc,EACd,cAAc,GAEf,MAAM,oBAAoB,CAAA;AAW3B,0EAA0E;AAC1E,uEAAuE;AACvE,sEAAsE;AACtE,+CAA+C;AAC/C,0EAA0E;AAC1E,uDAAuD;AACvD,kEAAkE;AAClE,wEAAwE;AACxE,uDAAuD;AACvD,4DAA4D;AAC5D,sEAAsE;AACtE,uEAAuE;AACvE,gCAAgC;AAChC,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,EACnB,KAAK,EACL,aAAa,EACb,QAAQ,EACR,QAAQ,EACR,MAAM,GACI,EAAE,EAAE;IACd,MAAM,SAAS,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAA;IAC9C,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,cAAc,CAAC,SAAS,EAAE;QACzD,KAAK;QACL,QAAQ;KACT,CAAC,CAAA;IACF,cAAc,CAAC,SAAS,EAAE,IAAI,CAAC,CAAA;IAE/B,MAAM,IAAI,GACR,aAAa,IAAI,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,aAAa,IAAI,MAAM,CAAA;IAErE,eAAe,CAAC,GAAG,EAAE;QACnB,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAA;QAChC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAM;QACR,CAAC;QACD,MAAM,CAAC,YAAY,CAAC,IAAI,KAAK,MAAM,CAAC,CAAA;QACpC,iBAAiB,CAAC,MAAM,EAAE,IAAI,KAAK,UAAU,CAAC,CAAA;IAChD,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAA;IAEV,yEAAyE;IACzE,uDAAuD;IACvD,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,CAAA;IAChC,eAAe,CAAC,GAAG,EAAE;QACnB,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAA;QAChC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAM;QACR,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,KAAK,UAAU,CAAA;QACnC,IAAI,OAAO,IAAI,UAAU,CAAC,OAAO,EAAE,CAAC;YAClC,IAAI,KAAK,GAAG,MAAM,CAAC,aAAa,EAAE,CAAA;YAClC,OAAO,KAAK,EAAE,CAAC;gBACb,KAAK,CAAC,YAAY,CAAC,CAAC,OAAO,CAAC,CAAA;gBAC5B,KAAK,GAAG,KAAK,CAAC,cAAc,EAAE,CAAA;YAChC,CAAC;QACH,CAAC;QACD,UAAU,CAAC,OAAO,GAAG,OAAO,CAAA;IAC9B,CAAC,CAAC,CAAA;IAEF,MAAM,OAAO,GAAG,mBAAmB,EAAmB,CAAA;IACtD,OAAO,CACL,KAAC,OAAO,IACN,GAAG,EAAE,SAAS,EACd,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,YAEtC,KAAC,eAAe,CAAC,QAAQ,IACvB,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,YAE9C,QAAQ,GACgB,GACnB,CACX,CAAA;AACH,CAAC,CAAA;AAED,sEAAsE;AACtE,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,CAAA;AAEhC,MAAM,CAAC,MAAM,SAAS,GAAG,GAAS,EAAE,CAAC,IAAI,CAAA","sourcesContent":["import { useLayoutEffect, useRef, type ReactNode } from \"react\"\nimport { StyleSheet } from \"../style/index\"\nimport type { PointerEventsValue, StyleProp } from \"../contracts\"\nimport {\n getViewBoxComponent,\n GtkBox,\n setBoxPassthrough,\n type Gtk,\n} from \"../gtkx/bridge/index\"\nimport { HostNodeContext } from \"./host-node\"\nimport {\n useLayoutChild,\n useRnContainer,\n type LayoutEvent,\n} from \"./use-layout-child\"\n\nexport type ViewProps = {\n style?: StyleProp\n // RN pointerEvents; the prop wins over style.pointerEvents (RN 0.71+).\n pointerEvents?: PointerEventsValue\n onLayout?: (event: LayoutEvent) => void\n children?: ReactNode\n testID?: string\n}\n\n// Every View is a GtkBox subclass (RnGtkxViewBox) driven by RnGtkxLayout:\n// Yoga computes the children's rects, the manager's allocate() applies\n// them. Visual styles arrive as a GTK CSS class produced by the style\n// system. pointerEvents maps onto GTK picking:\n// - none: can-target=false — GTK skips the widget WITHOUT descending, the\n// whole subtree is transparent (exact RN semantics);\n// - box-none: the subclass' contains() fails for this widget (see\n// view-box.ts) — the box is never the pick target while children stay\n// pickable, and toggling never remounts the subtree;\n// - box-only: direct children (and thus their subtrees) get\n// can-target=false; restored when the mode changes. Nesting another\n// pointerEvents INSIDE a box-only view is not supported (the restore\n// pass cannot know about it).\nexport const View = ({\n style,\n pointerEvents,\n onLayout,\n children,\n testID,\n}: ViewProps) => {\n const widgetRef = useRef<Gtk.Box | null>(null)\n const { host, node, cssClass } = useLayoutChild(widgetRef, {\n style,\n onLayout,\n })\n useRnContainer(widgetRef, node)\n\n const mode: PointerEventsValue =\n pointerEvents ?? StyleSheet.flatten(style)?.pointerEvents ?? \"auto\"\n\n useLayoutEffect(() => {\n const widget = widgetRef.current\n if (!widget) {\n return\n }\n widget.setCanTarget(mode !== \"none\")\n setBoxPassthrough(widget, mode === \"box-none\")\n }, [mode])\n\n // box-only walks the current children every commit (the set changes with\n // renders) and restores once when the mode moves away.\n const wasBoxOnly = useRef(false)\n useLayoutEffect(() => {\n const widget = widgetRef.current\n if (!widget) {\n return\n }\n const boxOnly = mode === \"box-only\"\n if (boxOnly || wasBoxOnly.current) {\n let child = widget.getFirstChild()\n while (child) {\n child.setCanTarget(!boxOnly)\n child = child.getNextSibling()\n }\n }\n wasBoxOnly.current = boxOnly\n })\n\n const ViewBox = getViewBoxComponent() as typeof GtkBox\n return (\n <ViewBox\n ref={widgetRef}\n name={testID}\n cssClasses={cssClass ? [cssClass] : []}\n >\n <HostNodeContext.Provider\n value={{ engine: host.engine, node, widgetRef }}\n >\n {children}\n </HostNodeContext.Provider>\n </ViewBox>\n )\n}\n\n// RN parity aliases (no notches or status bars on the Linux desktop).\nexport const SafeAreaView = View\n\nexport const StatusBar = (): null => null\n"]}
@@ -0,0 +1,67 @@
1
+ import { type ComponentType, type ReactElement } from "react";
2
+ import type { StyleProp } from "../contracts";
3
+ import { type ScrollViewHandle, type ScrollViewProps } from "./scroll-view";
4
+ export type ListRenderItemInfo<T> = {
5
+ item: T;
6
+ index: number;
7
+ };
8
+ export type ItemLayout = {
9
+ length: number;
10
+ offset: number;
11
+ index: number;
12
+ };
13
+ export type ViewToken<T = unknown> = {
14
+ item: T;
15
+ key: string;
16
+ index: number;
17
+ isViewable: boolean;
18
+ };
19
+ export type ViewabilityConfig = {
20
+ itemVisiblePercentThreshold?: number;
21
+ viewAreaCoveragePercentThreshold?: number;
22
+ minimumViewTime?: number;
23
+ };
24
+ export type VirtualizedListHandle = ScrollViewHandle & {
25
+ scrollToIndex(params: {
26
+ index: number;
27
+ viewPosition?: number;
28
+ animated?: boolean;
29
+ }): void;
30
+ scrollToItem(params: {
31
+ item: unknown;
32
+ viewPosition?: number;
33
+ animated?: boolean;
34
+ }): void;
35
+ scrollToOffset(params: {
36
+ offset: number;
37
+ animated?: boolean;
38
+ }): void;
39
+ };
40
+ export type VirtualizedListProps<T> = Omit<ScrollViewProps, "children"> & {
41
+ data: readonly T[];
42
+ renderItem: (info: ListRenderItemInfo<T>) => ReactElement | null;
43
+ keyExtractor?: (item: T, index: number) => string;
44
+ estimatedItemSize?: number;
45
+ getItemLayout?: (data: readonly T[], index: number) => ItemLayout;
46
+ windowSize?: number;
47
+ initialNumToRender?: number;
48
+ extraData?: unknown;
49
+ inverted?: boolean;
50
+ refreshing?: boolean;
51
+ onRefresh?: () => void;
52
+ ItemSeparatorComponent?: ComponentType | null;
53
+ ListHeaderComponent?: ComponentType | ReactElement | null;
54
+ ListFooterComponent?: ComponentType | ReactElement | null;
55
+ ListEmptyComponent?: ComponentType | ReactElement | null;
56
+ onEndReached?: () => void;
57
+ onEndReachedThreshold?: number;
58
+ onViewableItemsChanged?: (info: {
59
+ viewableItems: ViewToken<T>[];
60
+ changed: ViewToken<T>[];
61
+ }) => void;
62
+ viewabilityConfig?: ViewabilityConfig;
63
+ style?: StyleProp;
64
+ };
65
+ export declare const VirtualizedList: <T>(props: VirtualizedListProps<T> & {
66
+ ref?: React.Ref<VirtualizedListHandle>;
67
+ }) => ReactElement;
@@ -0,0 +1,519 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // The windowed list core (RN VirtualizedList model, leaner): only the rows
3
+ // around the viewport are mounted; the content View carries the prefix-sum
4
+ // height so the ScrollView gets the full range; every mounted cell is
5
+ // absolutely positioned at its offset. Heights come from getItemLayout (exact)
6
+ // or estimatedItemSize refined by per-cell onLayout; corrections above the
7
+ // first visible row are anchored by compensating the scroll offset in the
8
+ // same commit (docs/research/list-window.md).
9
+ import { forwardRef, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState, } from "react";
10
+ import { ActivityIndicator } from "./activity-indicator";
11
+ import { ScrollView, StickySlot, } from "./scroll-view";
12
+ import { View } from "./view";
13
+ const renderAux = (component) => {
14
+ if (component === null || component === undefined) {
15
+ return null;
16
+ }
17
+ if (typeof component === "function") {
18
+ const Aux = component;
19
+ return _jsx(Aux, {});
20
+ }
21
+ return component;
22
+ };
23
+ // First index whose offset-end is past `target` (binary search over prefix
24
+ // sums; offsets has count+1 entries).
25
+ const indexAt = (offsets, target) => {
26
+ let low = 0;
27
+ let high = offsets.length - 2;
28
+ while (low < high) {
29
+ const mid = (low + high) >> 1;
30
+ if (offsets[mid + 1] <= target) {
31
+ low = mid + 1;
32
+ }
33
+ else {
34
+ high = mid;
35
+ }
36
+ }
37
+ return low;
38
+ };
39
+ const VirtualizedListInner = forwardRef(({ data, renderItem, keyExtractor, estimatedItemSize = 44, getItemLayout, windowSize = 5, initialNumToRender = 10, extraData, inverted = false, refreshing = false, onRefresh, ItemSeparatorComponent, ListHeaderComponent, ListFooterComponent, ListEmptyComponent, onEndReached, onEndReachedThreshold = 0.1, onViewableItemsChanged, viewabilityConfig, onScroll, onContentSizeChange, onLayout, horizontal = false, stickyHeaderIndices, ...scrollProps }, ref) => {
40
+ const count = data.length;
41
+ const scrollRef = useRef(null);
42
+ const measured = useRef([]);
43
+ const [version, setVersion] = useState(0);
44
+ // Scroll position along the main axis (contentOffset.x when horizontal).
45
+ // Always in RAW adjustment space — all windowing math below is raw.
46
+ const scrollY = useRef(0);
47
+ // Viewport extent along the main axis (width when horizontal).
48
+ const viewportH = useRef(0);
49
+ // RN-space offset for inverted lists. RN's scaleY(-1) flip puts
50
+ // contentOffset 0 at the far (raw) end of the content, so the exposed
51
+ // offset counts from there: exposed = maxRaw - raw. It is the pinned
52
+ // source of truth across content growth — restoring raw from it is what
53
+ // keeps an inverted chat glued to its latest message on prepend.
54
+ const exposedOffset = useRef(0);
55
+ // Main-axis content size as GTK last reported it (the adjustment range).
56
+ // The raw scroll maximum is contentMain - viewport; before the first
57
+ // report it falls back to the list extent (header/footer not yet known).
58
+ const contentMain = useRef(0);
59
+ const pendingAnchor = useRef(0);
60
+ // scrollToIndex issued while sizes were still estimates: re-run the
61
+ // positioning once after the remount measures real sizes (≤2 scrollTo
62
+ // corrections in total).
63
+ const pendingScrollIndex = useRef(null);
64
+ const endReachedForSize = useRef(-1);
65
+ // Currently reported viewable tokens keyed by item key (isViewable true).
66
+ const viewableTokens = useRef(new Map());
67
+ // Items meeting the viewability criteria but still waiting out
68
+ // minimumViewTime; cancelled if they leave the viewport first.
69
+ const viewabilityTimers = useRef(new Map());
70
+ // Keys whose minimumViewTime elapsed while continuously visible.
71
+ const maturedKeys = useRef(new Set());
72
+ // Timers must call the LATEST closure (data may change while pending).
73
+ const recomputeViewabilityRef = useRef(() => { });
74
+ // Prefix sums; rebuilt on any size correction (O(N) — cheap even at
75
+ // 10k, see the spike). getItemLayout skips measuring entirely.
76
+ const offsets = useMemo(() => {
77
+ const out = new Array(count + 1);
78
+ out[0] = 0;
79
+ for (let index = 0; index < count; index += 1) {
80
+ const size = getItemLayout
81
+ ? getItemLayout(data, index).length
82
+ : (measured.current[index] ?? estimatedItemSize);
83
+ out[index + 1] = out[index] + size;
84
+ }
85
+ return out;
86
+ // eslint-disable-next-line react-hooks/exhaustive-deps
87
+ }, [count, version, estimatedItemSize, getItemLayout, data]);
88
+ // INVERTED PROJECTION. RN implements inverted as a scaleY(-1) flip of
89
+ // the scroller and every cell; the net layout is the data mirrored
90
+ // around the content extent C = offsets[count]: data index i occupies
91
+ // [C - offsets[i+1], C - offsets[i]) in raw scroll space, so data index
92
+ // 0 sits at the far end. The flip also reverses the scroll axis: RN's
93
+ // contentOffset counts from that far end, so the raw GTK offset and the
94
+ // exposed RN offset are complements (exposed = maxRaw - raw).
95
+ const cellStart = (index) => inverted ? offsets[count] - offsets[index + 1] : offsets[index];
96
+ const keyOf = (item, index) => keyExtractor ? keyExtractor(item, index) : String(index);
97
+ const [range, setRange] = useState(() => ({
98
+ first: 0,
99
+ last: Math.min(count, initialNumToRender) - 1,
100
+ }));
101
+ const updateRange = () => {
102
+ const overscan = Math.max(1, (windowSize - 1) / 2) * viewportH.current;
103
+ // Window in visual (scroll) coordinates.
104
+ let start = scrollY.current - overscan;
105
+ let end = scrollY.current + viewportH.current + overscan;
106
+ if (inverted) {
107
+ // Map the visual window back to data-offset space: visual position v
108
+ // covers data offsets C - v (mirror around the extent), so the
109
+ // visual window [start, end] covers data offsets [C - end, C - start].
110
+ const extent = offsets[count];
111
+ [start, end] = [extent - end, extent - start];
112
+ }
113
+ const first = indexAt(offsets, Math.max(0, start));
114
+ const last = Math.min(count - 1, indexAt(offsets, end));
115
+ setRange((current) => current.first === first && current.last === last
116
+ ? current
117
+ : { first, last });
118
+ };
119
+ // Clamp a scroll target to the valid range of the axis.
120
+ const clampOffset = (target) => {
121
+ const max = Math.max(0, offsets[count] - viewportH.current);
122
+ return Math.min(max, Math.max(0, target));
123
+ };
124
+ // Raw scroll maximum along the main axis (adjustment upper - page).
125
+ const maxRawScroll = () => Math.max(0, (contentMain.current || offsets[count]) - viewportH.current);
126
+ // Translate an RN-space main-axis offset into raw adjustment space (and
127
+ // back — the mapping is its own inverse).
128
+ const toRaw = (offset) => Math.max(0, maxRawScroll() - offset);
129
+ // Raw scroll offset that places `index` in the viewport: viewPosition 0
130
+ // aligns the cell start with the viewport start, 0.5 centers it, 1
131
+ // aligns the cell end with the viewport end. Inverted flips which edge
132
+ // viewPosition 0 means: RN computes the target in data space and the
133
+ // scaleY(-1) flip shows it from the other end, so 0 aligns the cell
134
+ // with the visual BOTTOM — in mirrored raw coordinates that is the
135
+ // complementary alignment.
136
+ const offsetForIndex = (index, viewPosition) => {
137
+ const size = offsets[index + 1] - offsets[index];
138
+ const align = inverted ? 1 - viewPosition : viewPosition;
139
+ return clampOffset(cellStart(index) - align * (viewportH.current - size));
140
+ };
141
+ const scrollToAxis = (offset, animated) => {
142
+ scrollRef.current?.scrollTo(horizontal ? { x: offset, animated } : { y: offset, animated });
143
+ };
144
+ const scrollToIndex = (params) => {
145
+ if (count === 0) {
146
+ return;
147
+ }
148
+ const index = Math.min(count - 1, Math.max(0, Math.trunc(params.index)));
149
+ const viewPosition = params.viewPosition ?? 0;
150
+ // Without getItemLayout the target may rest on estimates: remember the
151
+ // request so the version effect can re-run the positioning once the
152
+ // remounted cells have measured (the ≤2-corrections convergence).
153
+ pendingScrollIndex.current = getItemLayout
154
+ ? null
155
+ : { index, viewPosition, animated: params.animated };
156
+ scrollToAxis(offsetForIndex(index, viewPosition), params.animated);
157
+ };
158
+ useImperativeHandle(ref, () => ({
159
+ // Any direct scroll cancels a pending scrollToIndex correction. The
160
+ // caller always speaks RN-space offsets; inverted translates the main
161
+ // axis into raw adjustment space.
162
+ scrollTo: (options) => {
163
+ pendingScrollIndex.current = null;
164
+ const main = horizontal ? options.x : options.y;
165
+ if (!inverted || main === undefined) {
166
+ scrollRef.current?.scrollTo(options);
167
+ return;
168
+ }
169
+ scrollRef.current?.scrollTo({
170
+ ...options,
171
+ ...(horizontal ? { x: toRaw(main) } : { y: toRaw(main) }),
172
+ });
173
+ },
174
+ scrollToEnd: (options) => {
175
+ pendingScrollIndex.current = null;
176
+ // Inverted: the END of the data sits at raw offset 0 (visual top).
177
+ if (inverted) {
178
+ scrollToAxis(0, options?.animated);
179
+ return;
180
+ }
181
+ scrollRef.current?.scrollToEnd(options);
182
+ },
183
+ scrollToOffset: ({ offset, animated }) => {
184
+ pendingScrollIndex.current = null;
185
+ scrollToAxis(inverted ? toRaw(offset) : offset, animated);
186
+ },
187
+ scrollToIndex,
188
+ scrollToItem: ({ item, viewPosition, animated }) => {
189
+ const index = data.indexOf(item);
190
+ if (index >= 0) {
191
+ scrollToIndex({ index, viewPosition, animated });
192
+ }
193
+ },
194
+ }));
195
+ // Data changes move the window in BOTH directions: growth from an empty
196
+ // list must extend the initial range, shrinking must clamp it.
197
+ useLayoutEffect(() => {
198
+ updateRange();
199
+ // eslint-disable-next-line react-hooks/exhaustive-deps
200
+ }, [count]);
201
+ // Runs right after offsets rebuilt from new measurements. A pending
202
+ // scrollToIndex supersedes anchoring: re-issue the (now refined) target
203
+ // once and stop — this is the second and final correction.
204
+ useLayoutEffect(() => {
205
+ const pending = pendingScrollIndex.current;
206
+ if (pending) {
207
+ pendingScrollIndex.current = null;
208
+ pendingAnchor.current = 0;
209
+ scrollToAxis(offsetForIndex(pending.index, pending.viewPosition), pending.animated);
210
+ return;
211
+ }
212
+ // Anchor: apply the accumulated correction delta right after offsets
213
+ // rebuilt, in the same frame — the viewport does not visually move.
214
+ if (pendingAnchor.current !== 0) {
215
+ const delta = pendingAnchor.current;
216
+ pendingAnchor.current = 0;
217
+ scrollToAxis(scrollY.current + delta);
218
+ }
219
+ // eslint-disable-next-line react-hooks/exhaustive-deps
220
+ }, [version]);
221
+ const onItemLayout = (index, size) => {
222
+ if (getItemLayout) {
223
+ return;
224
+ }
225
+ const known = measured.current[index] ?? estimatedItemSize;
226
+ if (Math.abs(known - size) < 0.5) {
227
+ return;
228
+ }
229
+ measured.current[index] = size;
230
+ // A size change shifts every cell BEFORE the changed one in visual
231
+ // order — for a normal list that is cells above the window (index <
232
+ // first), compensated by the anchor. Inverted lists skip the anchor
233
+ // entirely: their correction is the exposed-offset restore on the
234
+ // content-size report, which re-pins the view to the far end exactly
235
+ // like RN's flip keeps contentOffset counting from there.
236
+ if (!inverted && index < range.first) {
237
+ pendingAnchor.current += size - known;
238
+ }
239
+ setVersion((value) => value + 1);
240
+ };
241
+ // VIEWABILITY. Pure math over the prefix sums — no widget queries. All
242
+ // positions are visual (scroll-space): cellStart already folds in the
243
+ // inverted mirror and scrollY/viewportH track the main axis, so the same
244
+ // code serves normal, inverted, and horizontal lists.
245
+ const recomputeViewability = () => {
246
+ if (!onViewableItemsChanged) {
247
+ return;
248
+ }
249
+ const viewport = viewportH.current;
250
+ const extent = offsets[count] ?? 0;
251
+ // Items currently meeting the visibility criteria, in index order.
252
+ const eligible = new Map();
253
+ if (count > 0 && viewport > 0 && extent > 0) {
254
+ const windowStart = scrollY.current;
255
+ const windowEnd = windowStart + viewport;
256
+ // Visible data-index range: mirror the visual window into data-offset
257
+ // space when inverted (same mapping as updateRange, zero overscan).
258
+ let start = windowStart;
259
+ let end = windowEnd;
260
+ if (inverted) {
261
+ ;
262
+ [start, end] = [extent - end, extent - start];
263
+ }
264
+ const first = indexAt(offsets, Math.max(0, start));
265
+ const last = Math.min(count - 1, indexAt(offsets, end));
266
+ const areaThreshold = viewabilityConfig?.viewAreaCoveragePercentThreshold;
267
+ const itemThreshold = viewabilityConfig?.itemVisiblePercentThreshold;
268
+ for (let index = first; index <= last; index += 1) {
269
+ const length = offsets[index + 1] - offsets[index];
270
+ const startPos = cellStart(index);
271
+ const visible = Math.min(startPos + length, windowEnd) -
272
+ Math.max(startPos, windowStart);
273
+ if (visible <= 0) {
274
+ continue;
275
+ }
276
+ let viewable;
277
+ if (visible >= length) {
278
+ // A fully visible item always counts, even when it covers less
279
+ // than the configured share (RN ViewabilityHelper).
280
+ viewable = true;
281
+ }
282
+ else if (areaThreshold !== undefined) {
283
+ viewable = (100 * visible) / viewport >= areaThreshold;
284
+ }
285
+ else if (itemThreshold !== undefined) {
286
+ viewable = (100 * visible) / length >= itemThreshold;
287
+ }
288
+ else {
289
+ // RN default: any visible pixel counts.
290
+ viewable = true;
291
+ }
292
+ if (viewable) {
293
+ const item = data[index];
294
+ eligible.set(keyOf(item, index), { item, index });
295
+ }
296
+ }
297
+ }
298
+ const minimumViewTime = viewabilityConfig?.minimumViewTime ?? 0;
299
+ // Leaving the viewport cancels a pending timer and resets maturity —
300
+ // re-entering restarts the continuous-visibility clock.
301
+ for (const [key, timer] of viewabilityTimers.current) {
302
+ if (!eligible.has(key)) {
303
+ clearTimeout(timer);
304
+ viewabilityTimers.current.delete(key);
305
+ }
306
+ }
307
+ for (const key of maturedKeys.current) {
308
+ if (!eligible.has(key)) {
309
+ maturedKeys.current.delete(key);
310
+ }
311
+ }
312
+ const next = new Map();
313
+ for (const [key, entry] of eligible) {
314
+ if (minimumViewTime <= 0 ||
315
+ viewableTokens.current.has(key) ||
316
+ maturedKeys.current.has(key)) {
317
+ next.set(key, {
318
+ item: entry.item,
319
+ key,
320
+ index: entry.index,
321
+ isViewable: true,
322
+ });
323
+ }
324
+ else if (!viewabilityTimers.current.has(key)) {
325
+ const timer = setTimeout(() => {
326
+ viewabilityTimers.current.delete(key);
327
+ maturedKeys.current.add(key);
328
+ recomputeViewabilityRef.current();
329
+ }, minimumViewTime);
330
+ viewabilityTimers.current.set(key, timer);
331
+ }
332
+ }
333
+ // Emit only when the viewable KEY SET changed: newly-viewable tokens
334
+ // first, then the ones that left (isViewable false), each index-ordered.
335
+ const changed = [];
336
+ for (const token of next.values()) {
337
+ if (!viewableTokens.current.has(token.key)) {
338
+ changed.push(token);
339
+ }
340
+ }
341
+ for (const token of viewableTokens.current.values()) {
342
+ if (!next.has(token.key)) {
343
+ changed.push({ ...token, isViewable: false });
344
+ }
345
+ }
346
+ // Keep tokens fresh (indices may shift under stable keys) but stay
347
+ // silent while the key set is unchanged.
348
+ viewableTokens.current = next;
349
+ if (changed.length === 0) {
350
+ return;
351
+ }
352
+ onViewableItemsChanged({ viewableItems: [...next.values()], changed });
353
+ };
354
+ useLayoutEffect(() => {
355
+ recomputeViewabilityRef.current = recomputeViewability;
356
+ });
357
+ const maybeFireEndReached = () => {
358
+ // Before the first layout the viewport extent is unknown — every
359
+ // trigger path below re-runs once it is.
360
+ if (!onEndReached || viewportH.current <= 0) {
361
+ return;
362
+ }
363
+ const extent = offsets[count];
364
+ const threshold = viewportH.current * Math.max(0, onEndReachedThreshold ?? 0.1);
365
+ // Distance from the viewport to the DATA end (RN semantics: the end
366
+ // of `data`, not the visual bottom). Normal lists keep the data end
367
+ // at raw position `extent`; the inverted projection places it at raw
368
+ // position 0 (the visual top), so the distance is simply the raw
369
+ // scroll offset — an inverted chat fires this while scrolling up
370
+ // into its history, which is where "load older" belongs.
371
+ // Content shorter than the viewport has a negative distance, so short
372
+ // lists fire right after the first layout without any scrolling.
373
+ const distance = inverted
374
+ ? scrollY.current
375
+ : extent - (scrollY.current + viewportH.current);
376
+ if (extent > 0 && distance <= threshold) {
377
+ if (endReachedForSize.current !== extent) {
378
+ endReachedForSize.current = extent;
379
+ onEndReached();
380
+ }
381
+ }
382
+ };
383
+ const handleScroll = (event) => {
384
+ const raw = horizontal
385
+ ? event.nativeEvent.contentOffset.x
386
+ : event.nativeEvent.contentOffset.y;
387
+ scrollY.current = raw;
388
+ if (inverted) {
389
+ exposedOffset.current = Math.max(0, maxRawScroll() - raw);
390
+ // The caller sees RN-space offsets: contentOffset 0 is the far end
391
+ // where the data starts (a chat's latest message).
392
+ onScroll?.({
393
+ nativeEvent: {
394
+ ...event.nativeEvent,
395
+ contentOffset: {
396
+ x: horizontal
397
+ ? exposedOffset.current
398
+ : event.nativeEvent.contentOffset.x,
399
+ y: horizontal
400
+ ? event.nativeEvent.contentOffset.y
401
+ : exposedOffset.current,
402
+ },
403
+ },
404
+ });
405
+ }
406
+ else {
407
+ onScroll?.(event);
408
+ }
409
+ updateRange();
410
+ recomputeViewability();
411
+ maybeFireEndReached();
412
+ };
413
+ const handleLayout = (event) => {
414
+ onLayout?.(event);
415
+ viewportH.current = horizontal
416
+ ? event.nativeEvent.layout.width
417
+ : event.nativeEvent.layout.height;
418
+ if (inverted) {
419
+ // The viewport changed under a pinned exposed offset: re-derive the
420
+ // raw position and window against the value we are about to take.
421
+ // Best effort — the adjustment may still clamp against the previous
422
+ // page size; the next content-size report settles it.
423
+ scrollY.current = toRaw(exposedOffset.current);
424
+ scrollToAxis(scrollY.current);
425
+ }
426
+ updateRange();
427
+ // First layout doubles as the initial viewability/end-reached pass.
428
+ recomputeViewability();
429
+ maybeFireEndReached();
430
+ };
431
+ const handleContentSizeChange = (width, height) => {
432
+ onContentSizeChange?.(width, height);
433
+ contentMain.current = horizontal ? width : height;
434
+ if (!inverted) {
435
+ return;
436
+ }
437
+ // The raw scroll range changed under a pinned exposed offset: restore
438
+ // the raw value from it (the adjustment is fresh — GTK emits "changed"
439
+ // only after the allocation that resized it). Content prepended to an
440
+ // inverted chat therefore appears WITHOUT the view moving, exactly
441
+ // like RN's contentOffset staying put under the flip. This also
442
+ // performs the initial positioning: exposed 0 = the data start.
443
+ const raw = toRaw(exposedOffset.current);
444
+ scrollY.current = raw;
445
+ scrollToAxis(raw);
446
+ updateRange();
447
+ };
448
+ // Data or measurement changes move content under a static viewport (no
449
+ // scroll event fires): refresh viewability and the end-reached trigger.
450
+ useEffect(() => {
451
+ recomputeViewability();
452
+ maybeFireEndReached();
453
+ // eslint-disable-next-line react-hooks/exhaustive-deps
454
+ }, [offsets]);
455
+ // Unmount: cancel every pending minimumViewTime timer.
456
+ useEffect(() => {
457
+ const timers = viewabilityTimers.current;
458
+ return () => {
459
+ for (const timer of timers.values()) {
460
+ clearTimeout(timer);
461
+ }
462
+ timers.clear();
463
+ };
464
+ }, []);
465
+ // Sticky cells (vertical, non-inverted): the ACTIVE header must stay
466
+ // mounted even when its own offset left the window — extend the mount
467
+ // set with it; geometry comes from the registry offsets, pinning is the
468
+ // ScrollView registry's job (same mechanism as plain sticky children).
469
+ const stickySorted = stickyHeaderIndices && !horizontal && !inverted
470
+ ? [...stickyHeaderIndices].sort((a, b) => a - b)
471
+ : [];
472
+ let activeStickyIndex = -1;
473
+ for (const stickyIndex of stickySorted) {
474
+ if (stickyIndex < count && cellStart(stickyIndex) <= scrollY.current) {
475
+ activeStickyIndex = stickyIndex;
476
+ }
477
+ }
478
+ const renderCell = (index) => {
479
+ const item = data[index];
480
+ const key = keyOf(item, index);
481
+ // Vertical cells stretch across the width and sit at their offset;
482
+ // horizontal cells stretch across the height and take their own
483
+ // width from content (so onLayout can measure it).
484
+ const cellStyle = horizontal
485
+ ? { position: "absolute", left: cellStart(index), top: 0, bottom: 0 }
486
+ : { position: "absolute", left: 0, right: 0, top: cellStart(index) };
487
+ const measure = (event) => onItemLayout(index, horizontal
488
+ ? event.nativeEvent.layout.width
489
+ : event.nativeEvent.layout.height);
490
+ const body = (_jsxs(_Fragment, { children: [renderItem({ item, index }), index < count - 1 && ItemSeparatorComponent ? (_jsx(ItemSeparatorComponent, {})) : null] }));
491
+ if (stickySorted.includes(index)) {
492
+ return (_jsx(StickySlot, { stickyKey: `cell-${key}`, top: cellStart(index), style: cellStyle, onLayout: measure, children: body }, key));
493
+ }
494
+ return (_jsx(View, { style: cellStyle, onLayout: measure, children: body }, key));
495
+ };
496
+ const cells = [];
497
+ if (count > 0) {
498
+ if (activeStickyIndex >= 0 && activeStickyIndex < range.first) {
499
+ cells.push(renderCell(activeStickyIndex));
500
+ }
501
+ const last = Math.min(range.last, count - 1);
502
+ for (let index = range.first; index <= last; index += 1) {
503
+ cells.push(renderCell(index));
504
+ }
505
+ }
506
+ // extraData participates in identity so memoized parents re-render rows.
507
+ void extraData;
508
+ // Accepted for RN parity only; no desktop gesture ever calls it.
509
+ void onRefresh;
510
+ return (_jsxs(ScrollView, { ...scrollProps, horizontal: horizontal, ref: scrollRef, onScroll: handleScroll, onContentSizeChange: handleContentSizeChange, onLayout: handleLayout, children: [refreshing ? (
511
+ // Desktop RefreshControl: no pull gesture exists, so `refreshing`
512
+ // simply shows an in-flow spinner row above the list content.
513
+ _jsx(View, { style: { alignItems: "center", padding: 8 }, children: _jsx(ActivityIndicator, {}) })) : null, renderAux(inverted ? ListFooterComponent : ListHeaderComponent), count === 0 && renderAux(ListEmptyComponent), count > 0 && (_jsx(View, { style: horizontal
514
+ ? { width: offsets[count] }
515
+ : { height: offsets[count] }, children: cells })), renderAux(inverted ? ListHeaderComponent : ListFooterComponent)] }));
516
+ });
517
+ VirtualizedListInner.displayName = "VirtualizedList";
518
+ export const VirtualizedList = VirtualizedListInner;
519
+ //# sourceMappingURL=virtualized-list.js.map