@swan-io/lake 1.0.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 (288) hide show
  1. package/HISTORY.md +3 -0
  2. package/LICENSE +21 -0
  3. package/README.md +49 -0
  4. package/package.json +60 -0
  5. package/src/components/Alert.d.ts +10 -0
  6. package/src/components/Alert.js +36 -0
  7. package/src/components/AppOpeningAnimation.d.ts +10 -0
  8. package/src/components/AppOpeningAnimation.js +50 -0
  9. package/src/components/AutoWidthImage.d.ts +8 -0
  10. package/src/components/AutoWidthImage.js +26 -0
  11. package/src/components/Avatar.d.ts +7 -0
  12. package/src/components/Avatar.js +42 -0
  13. package/src/components/BorderedButton.d.ts +16 -0
  14. package/src/components/BorderedButton.js +98 -0
  15. package/src/components/BorderedIcon.d.ts +12 -0
  16. package/src/components/BorderedIcon.js +25 -0
  17. package/src/components/BottomPanel.d.ts +9 -0
  18. package/src/components/BottomPanel.js +94 -0
  19. package/src/components/Box.d.ts +65 -0
  20. package/src/components/Box.js +31 -0
  21. package/src/components/Breadcrumbs.d.ts +18 -0
  22. package/src/components/Breadcrumbs.js +362 -0
  23. package/src/components/Button.d.ts +15 -0
  24. package/src/components/Button.js +83 -0
  25. package/src/components/Caption.d.ts +6 -0
  26. package/src/components/Caption.js +11 -0
  27. package/src/components/Checkbox.d.ts +12 -0
  28. package/src/components/Checkbox.js +83 -0
  29. package/src/components/ChoicePicker.d.ts +11 -0
  30. package/src/components/ChoicePicker.js +99 -0
  31. package/src/components/Combobox.d.ts +29 -0
  32. package/src/components/Combobox.js +182 -0
  33. package/src/components/FailureIcon.d.ts +8 -0
  34. package/src/components/FailureIcon.js +4 -0
  35. package/src/components/FileTile.d.ts +11 -0
  36. package/src/components/FileTile.js +37 -0
  37. package/src/components/Fill.d.ts +8 -0
  38. package/src/components/Fill.js +24 -0
  39. package/src/components/FilterChooser.d.ts +15 -0
  40. package/src/components/FilterChooser.js +52 -0
  41. package/src/components/Filters.d.ts +57 -0
  42. package/src/components/Filters.js +229 -0
  43. package/src/components/FixedListView.d.ts +104 -0
  44. package/src/components/FixedListView.js +821 -0
  45. package/src/components/FixedListViewCells.d.ts +55 -0
  46. package/src/components/FixedListViewCells.js +157 -0
  47. package/src/components/Flag.d.ts +8 -0
  48. package/src/components/Flag.js +36 -0
  49. package/src/components/FlowPresentation.d.ts +12 -0
  50. package/src/components/FlowPresentation.js +70 -0
  51. package/src/components/FocusTrap.d.ts +16 -0
  52. package/src/components/FocusTrap.js +90 -0
  53. package/src/components/Form.d.ts +8 -0
  54. package/src/components/Form.js +17 -0
  55. package/src/components/FullViewportLayer.d.ts +7 -0
  56. package/src/components/FullViewportLayer.js +91 -0
  57. package/src/components/Grid.d.ts +13 -0
  58. package/src/components/Grid.js +33 -0
  59. package/src/components/Heading.d.ts +61 -0
  60. package/src/components/Heading.js +27 -0
  61. package/src/components/Icon.d.ts +191 -0
  62. package/src/components/Icon.js +11 -0
  63. package/src/components/Input.d.ts +34 -0
  64. package/src/components/Input.js +115 -0
  65. package/src/components/InputError.d.ts +8 -0
  66. package/src/components/InputError.js +16 -0
  67. package/src/components/Label.d.ts +10 -0
  68. package/src/components/Label.js +19 -0
  69. package/src/components/LakeAlert.d.ts +14 -0
  70. package/src/components/LakeAlert.js +75 -0
  71. package/src/components/LakeButton.d.ts +36 -0
  72. package/src/components/LakeButton.js +171 -0
  73. package/src/components/LakeCheckbox.d.ts +16 -0
  74. package/src/components/LakeCheckbox.js +54 -0
  75. package/src/components/LakeCombobox.d.ts +28 -0
  76. package/src/components/LakeCombobox.js +166 -0
  77. package/src/components/LakeCopyButton.d.ts +10 -0
  78. package/src/components/LakeCopyButton.js +16 -0
  79. package/src/components/LakeDownloadButton.d.ts +8 -0
  80. package/src/components/LakeDownloadButton.js +6 -0
  81. package/src/components/LakeHeading.d.ts +10 -0
  82. package/src/components/LakeHeading.js +19 -0
  83. package/src/components/LakeLabel.d.ts +19 -0
  84. package/src/components/LakeLabel.js +43 -0
  85. package/src/components/LakeModal.d.ts +14 -0
  86. package/src/components/LakeModal.js +132 -0
  87. package/src/components/LakeRadio.d.ts +9 -0
  88. package/src/components/LakeRadio.js +44 -0
  89. package/src/components/LakeScrollView.d.ts +10 -0
  90. package/src/components/LakeScrollView.js +35 -0
  91. package/src/components/LakeSearchField.d.ts +10 -0
  92. package/src/components/LakeSearchField.js +111 -0
  93. package/src/components/LakeSelect.d.ts +30 -0
  94. package/src/components/LakeSelect.js +183 -0
  95. package/src/components/LakeSlider.d.ts +12 -0
  96. package/src/components/LakeSlider.js +31 -0
  97. package/src/components/LakeStepper.d.ts +21 -0
  98. package/src/components/LakeStepper.js +134 -0
  99. package/src/components/LakeText.d.ts +19 -0
  100. package/src/components/LakeText.js +20 -0
  101. package/src/components/LakeTextInput.d.ts +36 -0
  102. package/src/components/LakeTextInput.js +154 -0
  103. package/src/components/LakeTooltip.d.ts +24 -0
  104. package/src/components/LakeTooltip.js +188 -0
  105. package/src/components/Link.d.ts +138 -0
  106. package/src/components/Link.js +23 -0
  107. package/src/components/ListRightPanel.d.ts +17 -0
  108. package/src/components/ListRightPanel.js +79 -0
  109. package/src/components/LoadingView.d.ts +9 -0
  110. package/src/components/LoadingView.js +24 -0
  111. package/src/components/Modal.d.ts +12 -0
  112. package/src/components/Modal.js +80 -0
  113. package/src/components/MultiSelect.d.ts +27 -0
  114. package/src/components/MultiSelect.js +223 -0
  115. package/src/components/MultilineInput.d.ts +15 -0
  116. package/src/components/MultilineInput.js +55 -0
  117. package/src/components/Picker.d.ts +26 -0
  118. package/src/components/Picker.js +116 -0
  119. package/src/components/PlainListView.d.ts +36 -0
  120. package/src/components/PlainListView.js +208 -0
  121. package/src/components/Popover.d.ts +23 -0
  122. package/src/components/Popover.js +147 -0
  123. package/src/components/PopoverContent.d.ts +8 -0
  124. package/src/components/PopoverContent.js +9 -0
  125. package/src/components/Portal.d.ts +7 -0
  126. package/src/components/Portal.js +9 -0
  127. package/src/components/Pressable.d.ts +348 -0
  128. package/src/components/Pressable.js +91 -0
  129. package/src/components/ProgressBar.d.ts +11 -0
  130. package/src/components/ProgressBar.js +46 -0
  131. package/src/components/ProjectEnvTag.d.ts +7 -0
  132. package/src/components/ProjectEnvTag.js +12 -0
  133. package/src/components/QuickActions.d.ts +15 -0
  134. package/src/components/QuickActions.js +38 -0
  135. package/src/components/RadioGroup.d.ts +16 -0
  136. package/src/components/RadioGroup.js +34 -0
  137. package/src/components/ReadOnlyFieldList.d.ts +6 -0
  138. package/src/components/ReadOnlyFieldList.js +8 -0
  139. package/src/components/ResponsiveContainer.d.ts +13 -0
  140. package/src/components/ResponsiveContainer.js +23 -0
  141. package/src/components/RightPanel.d.ts +10 -0
  142. package/src/components/RightPanel.js +102 -0
  143. package/src/components/SegmentedControl.d.ts +19 -0
  144. package/src/components/SegmentedControl.js +74 -0
  145. package/src/components/Separator.d.ts +10 -0
  146. package/src/components/Separator.js +19 -0
  147. package/src/components/SidebarNavigationTracker.d.ts +13 -0
  148. package/src/components/SidebarNavigationTracker.js +93 -0
  149. package/src/components/Slider.d.ts +11 -0
  150. package/src/components/Slider.js +119 -0
  151. package/src/components/SmsOpeningAnimation.d.ts +8 -0
  152. package/src/components/SmsOpeningAnimation.js +52 -0
  153. package/src/components/Space.d.ts +10 -0
  154. package/src/components/Space.js +23 -0
  155. package/src/components/Stack.d.ts +12 -0
  156. package/src/components/Stack.js +23 -0
  157. package/src/components/StepDots.d.ts +7 -0
  158. package/src/components/StepDots.js +24 -0
  159. package/src/components/Stepper.d.ts +9 -0
  160. package/src/components/Stepper.js +67 -0
  161. package/src/components/SuccessIcon.d.ts +8 -0
  162. package/src/components/SuccessIcon.js +4 -0
  163. package/src/components/Svg.d.ts +145 -0
  164. package/src/components/Svg.js +24 -0
  165. package/src/components/SwanLogo.d.ts +8 -0
  166. package/src/components/SwanLogo.js +11 -0
  167. package/src/components/Switch.d.ts +9 -0
  168. package/src/components/Switch.js +74 -0
  169. package/src/components/TabView.d.ts +16 -0
  170. package/src/components/TabView.js +398 -0
  171. package/src/components/Table.d.ts +34 -0
  172. package/src/components/Table.js +79 -0
  173. package/src/components/Tag.d.ts +17 -0
  174. package/src/components/Tag.js +76 -0
  175. package/src/components/Tile.d.ts +34 -0
  176. package/src/components/Tile.js +130 -0
  177. package/src/components/TilePlaceholder.d.ts +6 -0
  178. package/src/components/TilePlaceholder.js +51 -0
  179. package/src/components/ToastStack.d.ts +2 -0
  180. package/src/components/ToastStack.js +96 -0
  181. package/src/components/Tooltip.d.ts +18 -0
  182. package/src/components/Tooltip.js +162 -0
  183. package/src/components/TransitionGroupView.d.ts +12 -0
  184. package/src/components/TransitionGroupView.js +43 -0
  185. package/src/components/TransitionView.d.ts +12 -0
  186. package/src/components/TransitionView.js +43 -0
  187. package/src/components/WithCurrentColor.d.ts +12 -0
  188. package/src/components/WithCurrentColor.js +65 -0
  189. package/src/components/WithPartnerAccentColor.d.ts +7 -0
  190. package/src/components/WithPartnerAccentColor.js +91 -0
  191. package/src/constants/colors.d.ts +42 -0
  192. package/src/constants/colors.js +42 -0
  193. package/src/constants/commonStyles.d.ts +66 -0
  194. package/src/constants/commonStyles.js +45 -0
  195. package/src/constants/design.d.ts +168 -0
  196. package/src/constants/design.js +564 -0
  197. package/src/constants/insets.d.ts +10 -0
  198. package/src/constants/insets.js +22 -0
  199. package/src/constants/typography.d.ts +26 -0
  200. package/src/constants/typography.js +54 -0
  201. package/src/hooks/useAnimatedValue.d.ts +2 -0
  202. package/src/hooks/useAnimatedValue.js +3 -0
  203. package/src/hooks/useBodyClassName.d.ts +3 -0
  204. package/src/hooks/useBodyClassName.js +14 -0
  205. package/src/hooks/useBoolean.d.ts +8 -0
  206. package/src/hooks/useBoolean.js +12 -0
  207. package/src/hooks/useComputedColors.d.ts +10 -0
  208. package/src/hooks/useComputedColors.js +42 -0
  209. package/src/hooks/useDebounce.d.ts +1 -0
  210. package/src/hooks/useDebounce.js +12 -0
  211. package/src/hooks/useDisclosure.d.ts +8 -0
  212. package/src/hooks/useDisclosure.js +12 -0
  213. package/src/hooks/useFirstMountState.d.ts +1 -0
  214. package/src/hooks/useFirstMountState.js +9 -0
  215. package/src/hooks/useForceableState.d.ts +1 -0
  216. package/src/hooks/useForceableState.js +6 -0
  217. package/src/hooks/useHover.d.ts +11 -0
  218. package/src/hooks/useHover.js +4 -0
  219. package/src/hooks/useInterval.d.ts +1 -0
  220. package/src/hooks/useInterval.js +11 -0
  221. package/src/hooks/useLazyRef.d.ts +2 -0
  222. package/src/hooks/useLazyRef.js +9 -0
  223. package/src/hooks/useMergeRefs.d.ts +2 -0
  224. package/src/hooks/useMergeRefs.js +5 -0
  225. package/src/hooks/useNativeProp.d.ts +2 -0
  226. package/src/hooks/useNativeProp.js +9 -0
  227. package/src/hooks/useOutsideClick.d.ts +8 -0
  228. package/src/hooks/useOutsideClick.js +54 -0
  229. package/src/hooks/usePersistedState.d.ts +1 -0
  230. package/src/hooks/usePersistedState.js +21 -0
  231. package/src/hooks/usePressEvents.d.ts +31 -0
  232. package/src/hooks/usePressEvents.js +4 -0
  233. package/src/hooks/usePreviousValue.d.ts +1 -0
  234. package/src/hooks/usePreviousValue.js +8 -0
  235. package/src/hooks/useResponsive.d.ts +7 -0
  236. package/src/hooks/useResponsive.js +20 -0
  237. package/src/hooks/useUpdateEffect.d.ts +2 -0
  238. package/src/hooks/useUpdateEffect.js +10 -0
  239. package/src/hooks/useUrqlMutation.d.ts +4 -0
  240. package/src/hooks/useUrqlMutation.js +30 -0
  241. package/src/hooks/useUrqlQuery.d.ts +19 -0
  242. package/src/hooks/useUrqlQuery.js +73 -0
  243. package/src/state/toasts.d.ts +21 -0
  244. package/src/state/toasts.js +61 -0
  245. package/src/utils/__tests__/array.test.d.ts +1 -0
  246. package/src/utils/__tests__/array.test.js +127 -0
  247. package/src/utils/__tests__/base64.test.d.ts +1 -0
  248. package/src/utils/__tests__/base64.test.js +27 -0
  249. package/src/utils/__tests__/function.test.d.ts +1 -0
  250. package/src/utils/__tests__/function.test.js +36 -0
  251. package/src/utils/__tests__/object.test.d.ts +1 -0
  252. package/src/utils/__tests__/object.test.js +17 -0
  253. package/src/utils/__tests__/rifm.test.d.ts +1 -0
  254. package/src/utils/__tests__/rifm.test.js +124 -0
  255. package/src/utils/__tests__/string.test.d.ts +1 -0
  256. package/src/utils/__tests__/string.test.js +16 -0
  257. package/src/utils/a11y.d.ts +1 -0
  258. package/src/utils/a11y.js +18 -0
  259. package/src/utils/array.d.ts +6 -0
  260. package/src/utils/array.js +71 -0
  261. package/src/utils/base64.d.ts +2 -0
  262. package/src/utils/base64.js +20 -0
  263. package/src/utils/file.d.ts +2 -0
  264. package/src/utils/file.js +10 -0
  265. package/src/utils/flagCountry.d.ts +1 -0
  266. package/src/utils/flagCountry.js +1 -0
  267. package/src/utils/function.d.ts +4 -0
  268. package/src/utils/function.js +19 -0
  269. package/src/utils/math.d.ts +16 -0
  270. package/src/utils/math.js +47 -0
  271. package/src/utils/nullish.d.ts +10 -0
  272. package/src/utils/nullish.js +8 -0
  273. package/src/utils/object.d.ts +2 -0
  274. package/src/utils/object.js +7 -0
  275. package/src/utils/popper.d.ts +3 -0
  276. package/src/utils/popper.js +30 -0
  277. package/src/utils/rifm.d.ts +14 -0
  278. package/src/utils/rifm.js +53 -0
  279. package/src/utils/string.d.ts +4 -0
  280. package/src/utils/string.js +233 -0
  281. package/src/utils/timer.d.ts +11 -0
  282. package/src/utils/timer.js +46 -0
  283. package/src/utils/types.d.ts +8 -0
  284. package/src/utils/types.js +1 -0
  285. package/src/utils/userAgent.d.ts +4 -0
  286. package/src/utils/userAgent.js +9 -0
  287. package/src/utils/viewport.d.ts +1 -0
  288. package/src/utils/viewport.js +12 -0
@@ -0,0 +1,821 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * ## FixedListView
4
+ *
5
+ * The FixedListView is a component designed to render big amounts of tabular data.
6
+ *
7
+ * For usability, the data can be display in three types of columns:
8
+ *
9
+ * - Sticked to start columns (usually the main identifier, always visible)
10
+ * - Center columns (scrollable columns, with additional information)
11
+ * - Sticked to end columns (so that some actions are always accesible at the end of each line)
12
+ *
13
+ * ┌────────────────────────────────────────────────────────────────────────────────────────┐
14
+ * │ ┌────────────────┐ ┌────────────────────────────────────────┐ ┌────────────────┐ │
15
+ * │ │ ╔════════════╗ │ │ ╔════════════════════════════════════╗ │ │ ╔════════════╗ │ │
16
+ * │ │ ║ Header ║ │ │ ║ Header ║ │ │ ║ Header ║ │ ▲ │
17
+ * │ │ ║ ║ │ │ ║◀──────────────────────────────────▶║ │ │ ║ ║ │ │ │
18
+ * │ │ ╚════════════╝ │ │ ╚════════════════════════════════════╝ │ │ ╚════════════╝ │ │ │
19
+ * │ │ │ │ │ │ │ │ │
20
+ * │ │ ┌───────────┐ │ │ ┌────────────────────────────────────┐ │ │ ┌────────────┐ │ │ │
21
+ * │ │ │ Cell A1 │──┼─┼▷│ Cell B1 ├─┼─┼─▷ Cell C1 │ │ │ │
22
+ * │ │ └───────────┘ │ │ └────────────────────────────────────┘ │ │ └────────────┘ │ │ │
23
+ * │ │ ┌───────────┐ │ │ ┌────────────────────────────────────┐ │ │ ┌────────────┐ │ │ │
24
+ * │ │ │ Cell A2 │──┼─┼▷│ Cell B2 │─┼─┼─▷ Cell C2 │ │ │ │
25
+ * │ │ └───────────┘ │ │ └────────────────────────────────────┘ │ │ └────────────┘ │ │ │
26
+ * │ │ │ │ │ │ │ │ │
27
+ * │ │ │ │ ◀────────────────────────────────────▶ │ │ │ ▼ │
28
+ * └─┴────────────────┴─┴────────────────────────────────────────┴─┴────────────────┴───────┘
29
+ *
30
+ * ╔════╗
31
+ * ║ ║ Sticky
32
+ * ╚════╝
33
+ * ◀────▶ Scrollable
34
+ * ─────▷ Emulated tab order
35
+ *
36
+ */
37
+ import { cloneElement, Fragment, memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } from "react";
38
+ import { ScrollView, StyleSheet, View, } from "react-native";
39
+ import { match } from "ts-pattern";
40
+ import { v4 as uuid } from "uuid";
41
+ import { backgroundColor, colors, invariantColors, radii, shadows, spacings, } from "../constants/design";
42
+ import { useHover } from "../hooks/useHover";
43
+ import { getFocusableElements } from "../utils/a11y";
44
+ import { first, last, sortedIndexOf } from "../utils/array";
45
+ import { noop } from "../utils/function";
46
+ import { isNotNullish, isNullish } from "../utils/nullish";
47
+ import { BorderedIcon } from "./BorderedIcon";
48
+ import { Icon } from "./Icon";
49
+ import { LakeHeading } from "./LakeHeading";
50
+ import { LakeText } from "./LakeText";
51
+ import { Space } from "./Space";
52
+ const HORIZONTAL_SAFE_AREA = 10;
53
+ const SCROLLBAR_RESERVED_SPACE = 20;
54
+ const styles = StyleSheet.create({
55
+ root: {
56
+ height: 1,
57
+ alignSelf: "stretch",
58
+ flexGrow: 1,
59
+ },
60
+ container: {
61
+ height: 1,
62
+ alignSelf: "stretch",
63
+ flexGrow: 1,
64
+ },
65
+ containerTile: {
66
+ marginHorizontal: -HORIZONTAL_SAFE_AREA,
67
+ },
68
+ contentContainer: {
69
+ flexDirection: "row",
70
+ alignItems: "stretch",
71
+ flexGrow: 1,
72
+ },
73
+ scrollContentContainer: {
74
+ flexGrow: 1,
75
+ flexDirection: "row",
76
+ alignItems: "stretch",
77
+ },
78
+ centerColumnsContainer: {
79
+ width: 1,
80
+ flexGrow: 1,
81
+ flexDirection: "column",
82
+ alignItems: "stretch",
83
+ },
84
+ centerColumns: {
85
+ flexGrow: 1,
86
+ },
87
+ centerColumnsContentContainer: {
88
+ flexDirection: "column",
89
+ flexGrow: 1,
90
+ },
91
+ stickyColumn: {
92
+ flexGrow: 0,
93
+ zIndex: 1,
94
+ },
95
+ stickyColumnStartOverflow: {
96
+ position: "absolute",
97
+ top: 0,
98
+ bottom: 0,
99
+ right: "100%",
100
+ width: HORIZONTAL_SAFE_AREA,
101
+ backgroundColor: backgroundColor.default,
102
+ },
103
+ stickyColumnEndOverflow: {
104
+ position: "absolute",
105
+ top: 0,
106
+ bottom: 0,
107
+ left: "100%",
108
+ width: HORIZONTAL_SAFE_AREA,
109
+ backgroundColor: backgroundColor.default,
110
+ },
111
+ rowSegment: {
112
+ position: "absolute",
113
+ left: 0,
114
+ right: 0,
115
+ display: "flex",
116
+ flexGrow: 1,
117
+ alignSelf: "stretch",
118
+ flexDirection: "row",
119
+ alignItems: "stretch",
120
+ transitionProperty: "top",
121
+ transitionDuration: "300ms",
122
+ transitionTimingFunction: "ease-in-out",
123
+ overflow: "hidden",
124
+ },
125
+ headingSegment: {
126
+ position: "sticky",
127
+ top: 0,
128
+ flexDirection: "row",
129
+ alignItems: "stretch",
130
+ zIndex: 2,
131
+ backgroundColor: backgroundColor.default,
132
+ },
133
+ segment: {
134
+ flexDirection: "row",
135
+ alignItems: "stretch",
136
+ overflow: "hidden",
137
+ },
138
+ segmentOverflow: {
139
+ overflow: "hidden",
140
+ flexDirection: "row",
141
+ alignItems: "stretch",
142
+ },
143
+ visibleGradient: {
144
+ opacity: 1,
145
+ },
146
+ leftToRightGradient: {
147
+ position: "absolute",
148
+ left: 0,
149
+ top: 0,
150
+ height: "100%",
151
+ width: 10,
152
+ backgroundImage: "linear-gradient(to right, rgba(0, 0, 0, 0.06), rgba(0, 0, 0, 0))",
153
+ opacity: 0,
154
+ transition: "150ms ease-in-out opacity",
155
+ },
156
+ rightToLeftGradient: {
157
+ position: "absolute",
158
+ right: 0,
159
+ top: 0,
160
+ height: "100%",
161
+ width: 10,
162
+ backgroundImage: "linear-gradient(to left, rgba(0, 0, 0, 0.06), rgba(0, 0, 0, 0))",
163
+ opacity: 0,
164
+ transition: "150ms ease-in-out opacity",
165
+ },
166
+ horizontalScrollbar: {
167
+ position: "sticky",
168
+ bottom: 0,
169
+ borderBottomWidth: 5,
170
+ borderBottomColor: invariantColors.transparent,
171
+ flexGrow: 0,
172
+ height: SCROLLBAR_RESERVED_SPACE,
173
+ },
174
+ cell: {
175
+ flexDirection: "row",
176
+ alignItems: "stretch",
177
+ },
178
+ stickyRow: {
179
+ // overflow: "hidden",
180
+ },
181
+ evenRow: {
182
+ backgroundColor: backgroundColor.accented,
183
+ },
184
+ oddRow: {
185
+ backgroundColor: backgroundColor.default,
186
+ },
187
+ rowShadow: {
188
+ boxShadow: shadows.tile,
189
+ },
190
+ hoveredRowShadow: {
191
+ boxShadow: shadows.tileHover,
192
+ },
193
+ highlightedRow: {
194
+ borderColor: colors.current.primary,
195
+ borderWidth: 1,
196
+ },
197
+ segmentHeaderCell: {
198
+ display: "flex",
199
+ flexDirection: "row",
200
+ flexGrow: 1,
201
+ alignItems: "center",
202
+ },
203
+ rowBackground: {
204
+ borderRadius: radii[4],
205
+ transitionProperty: "top",
206
+ transitionDuration: "300ms",
207
+ transitionTimingFunction: "ease-in-out",
208
+ },
209
+ rowPlainBackground: {
210
+ transitionProperty: "top",
211
+ transitionDuration: "300ms",
212
+ transitionTimingFunction: "ease-in-out",
213
+ },
214
+ rowBackgroundContainer: {
215
+ position: "absolute",
216
+ left: 0,
217
+ right: 0,
218
+ display: "flex",
219
+ flexDirection: "column",
220
+ alignItems: "stretch",
221
+ justifyContent: "center",
222
+ transitionProperty: "top",
223
+ transitionDuration: "300ms",
224
+ transitionTimingFunction: "ease-in-out",
225
+ },
226
+ rowBackgroundContainerPlain: {
227
+ backgroundColor: backgroundColor.default,
228
+ left: -10,
229
+ right: -10,
230
+ boxShadow: `inset 0 -1px ${colors.gray[100]}`,
231
+ },
232
+ backgroundRows: {
233
+ position: "absolute",
234
+ left: HORIZONTAL_SAFE_AREA,
235
+ right: HORIZONTAL_SAFE_AREA,
236
+ top: 0,
237
+ bottom: 0,
238
+ },
239
+ placeholderRowContainer: {
240
+ flexDirection: "row",
241
+ alignItems: "center",
242
+ position: "absolute",
243
+ top: 0,
244
+ left: 20,
245
+ right: 20,
246
+ animationKeyframes: {
247
+ "50%": {
248
+ opacity: 0.6,
249
+ },
250
+ },
251
+ animationDuration: "2000ms",
252
+ animationTimingFunction: "linear",
253
+ animationIterationCount: "infinite",
254
+ },
255
+ placeholderRowContainerPlain: {
256
+ left: 10,
257
+ },
258
+ placeholderRow: {
259
+ height: 14,
260
+ width: "30%",
261
+ backgroundColor: colors.gray[200],
262
+ borderRadius: radii[6],
263
+ },
264
+ placeholderRowEnd: {
265
+ flexGrow: 1,
266
+ flexDirection: "row",
267
+ alignItems: "center",
268
+ justifyContent: "flex-end",
269
+ },
270
+ smallPlaceholderRow: {
271
+ width: "10%",
272
+ },
273
+ centerSegmentContainer: {
274
+ flexGrow: 1,
275
+ },
276
+ rowLeftRadii: {
277
+ borderTopLeftRadius: radii[4],
278
+ borderBottomLeftRadius: radii[4],
279
+ },
280
+ rowRightRadii: {
281
+ borderTopRightRadius: radii[4],
282
+ borderBottomRightRadius: radii[4],
283
+ },
284
+ emptyListContainer: {
285
+ position: "absolute",
286
+ top: 0,
287
+ left: 0,
288
+ right: 0,
289
+ bottom: 0,
290
+ flexDirection: "column",
291
+ alignItems: "center",
292
+ justifyContent: "center",
293
+ padding: spacings[48],
294
+ backgroundColor: backgroundColor.default,
295
+ },
296
+ emptyList: {
297
+ flexDirection: "column",
298
+ alignItems: "center",
299
+ justifyContent: "center",
300
+ },
301
+ loadingPlaceholder: {
302
+ position: "absolute",
303
+ left: 0,
304
+ right: 0,
305
+ },
306
+ topGradient: {
307
+ height: 30,
308
+ position: "absolute",
309
+ left: 0,
310
+ right: 0,
311
+ top: "100%",
312
+ backgroundImage: `linear-gradient(to bottom, ${backgroundColor.default}, ${backgroundColor.defaultTransparent})`,
313
+ opacity: 0,
314
+ transition: "200ms ease-in-out opacity",
315
+ },
316
+ visibleTopGradient: {
317
+ opacity: 1,
318
+ },
319
+ });
320
+ const RowBackground = ({ absoluteIndex, id, isHovered, isHighlighted, top, rowHeight, rowVerticalSpacing, onMouseEnter, onMouseLeave, mode, }) => {
321
+ const containerRef = useRef(null);
322
+ useHover(containerRef, {
323
+ onHoverStart: () => onMouseEnter(id),
324
+ onHoverEnd: () => onMouseLeave(),
325
+ });
326
+ return (_jsx(View, { style: [
327
+ styles.rowBackgroundContainer,
328
+ mode === "plain" && styles.rowBackgroundContainerPlain,
329
+ { top, paddingVertical: rowVerticalSpacing / 2 },
330
+ ], ref: containerRef, children: mode === "tile" ? (_jsx(View, { style: [
331
+ styles.rowBackground,
332
+ isHovered ? styles.hoveredRowShadow : styles.rowShadow,
333
+ isHighlighted && styles.highlightedRow,
334
+ absoluteIndex % 2 === 0 ? styles.evenRow : styles.oddRow,
335
+ { height: rowHeight },
336
+ ] })) : (_jsx(View, { style: [styles.rowPlainBackground, { height: rowHeight }] })) }));
337
+ };
338
+ const MemoizedRowBackground = memo(RowBackground);
339
+ const SEGMENTS_MAP = {
340
+ start: "0",
341
+ center: "1",
342
+ end: "2",
343
+ };
344
+ const RowSegment = ({ columns, item, style, absoluteIndex, viewId, segmentColumn, rowVerticalSpacing, width, id, isHovered, extraInfo, onMouseEnter, onMouseLeave, createRowWrapper, focusId, top, totalRowHeight, minWidth, }) => {
345
+ const containerRef = useRef(null);
346
+ useHover(containerRef, {
347
+ onHoverStart: () => onMouseEnter(id),
348
+ onHoverEnd: () => onMouseLeave(),
349
+ });
350
+ const wrapper = createRowWrapper({ item, absoluteIndex, extraInfo });
351
+ return cloneElement(wrapper, {
352
+ focusable: true,
353
+ nativeID: focusId,
354
+ style: [
355
+ styles.rowSegment,
356
+ {
357
+ top,
358
+ height: totalRowHeight,
359
+ width: isNullish(minWidth) ? width : undefined,
360
+ minWidth,
361
+ },
362
+ ],
363
+ }, _jsx(View, { style: [
364
+ styles.segment,
365
+ { width, paddingVertical: rowVerticalSpacing / 2 },
366
+ segmentColumn === "center" && styles.centerSegmentContainer,
367
+ segmentColumn === "start" && {
368
+ borderTopLeftRadius: radii[4],
369
+ borderBottomLeftRadius: radii[4],
370
+ },
371
+ segmentColumn === "end" && {
372
+ borderTopRightRadius: radii[4],
373
+ borderBottomRightRadius: radii[4],
374
+ },
375
+ ], ref: containerRef, accessibilityHidden: false, children: _jsx(View, { style: [styles.segmentOverflow, style], children: columns.map(({ id, width, renderCell }, index) => {
376
+ const columnId = `${viewId}_${id}`;
377
+ const paddedIndex = String(absoluteIndex).padStart(10, "0");
378
+ const paddedCellIndex = String(index).padStart(10, "0");
379
+ // The reason we use this shape is so that the IDs alphabetical order matches the semantical one:
380
+ // 1. Row index
381
+ // 2. Column index
382
+ // 3. Cell index
383
+ // -> See the `onKeyDown` handler
384
+ const focusId = `${viewId}__Row_${paddedIndex}_Segment_${SEGMENTS_MAP[segmentColumn]}_Cell_${paddedCellIndex}`;
385
+ return (_jsx(View, { style: [styles.cell, { width }], accessibilityDescribedBy: columnId, nativeID: focusId, children: renderCell({ columnId, item, index: absoluteIndex, extraInfo, isHovered }) }, columnId));
386
+ }) }) }));
387
+ };
388
+ const MemoizedRowSegment = memo(RowSegment);
389
+ const HeaderSegment = ({ columns, viewId, extraInfo, width, }) => {
390
+ return (_jsx(View, { style: [styles.segment, { width }], children: columns.map(({ id, width, title, renderTitle }) => {
391
+ const columnId = `${viewId}_${id}`;
392
+ return (_jsx(View, { style: [styles.segmentHeaderCell, { width }], nativeID: columnId, children: renderTitle({ title, extraInfo, id }) }, columnId));
393
+ }) }));
394
+ };
395
+ const findNextFocusableElement = (sortedCellIds, currentCellIndex, direction) => {
396
+ let index = currentCellIndex + direction;
397
+ while (index >= 0 && index < sortedCellIds.length) {
398
+ const nextCellId = sortedCellIds[index];
399
+ if (isNotNullish(nextCellId)) {
400
+ const previousCell = document.getElementById(nextCellId);
401
+ if (isNotNullish(previousCell)) {
402
+ const focusableElements = getFocusableElements(previousCell, false);
403
+ const nextFocusableElement = direction === -1 ? last(focusableElements) : first(focusableElements);
404
+ if (isNotNullish(nextFocusableElement)) {
405
+ return nextFocusableElement;
406
+ }
407
+ }
408
+ }
409
+ index = index + direction;
410
+ }
411
+ };
412
+ const EMPTY_COLUMNS = [];
413
+ const ZERO = 0;
414
+ export const FixedListView = ({ data: originalData, mode = "tile", keyExtractor, highlightedRowId, rowHeight, rowVerticalSpacing, headerHeight, renderThreshold = 1000, stickedToStartColumns: initialStickedToStartColumns = EMPTY_COLUMNS, columns: initialColumns, stickedToEndColumns: initialStickedToEndColumns = EMPTY_COLUMNS, extraInfo, onEndReached, onEndReachedThresholdPx = 200, getRowLink, renderEmptyList, loading, }) => {
415
+ const [viewId] = useState(() => uuid());
416
+ // Those three refs are used to synchronize the horizontal scroll in the center columns
417
+ const centerHeadersRef = useRef(null);
418
+ const centerColumnsRef = useRef(null);
419
+ const horizontalScrollbarRef = useRef(null);
420
+ const totalRowHeight = rowHeight + rowVerticalSpacing;
421
+ const totalHeight = originalData.length * totalRowHeight + headerHeight;
422
+ // It might seem off to use the range in state instead of storing scroll/layout and deriving it,
423
+ // but it saves a lot of render phases by allowing to bail out from rendering when the range doesn't change
424
+ const [{ data, range: [renderedRangeStartIndex, renderedRangeEndIndex], }, setDataAndRenderRange,] = useState({ data: originalData, range: [0, 20] });
425
+ const startFocusAnchorRef = useRef(null);
426
+ const endFocusAnchorRef = useRef(null);
427
+ const [hasHorizontalScroll, setHasHorizontalScroll] = useState(false);
428
+ const [shouldAvoidStickyColumns, setShouldAvoidStickyColumns] = useState(false);
429
+ const [shouldShowStartGradient, setShouldShowStartGradient] = useState(false);
430
+ const [shouldShowEndGradient, setShouldShowEndGradient] = useState(true);
431
+ const [hoveredRow, setHoveredRow] = useState(undefined);
432
+ const currentScrollY = useRef(0);
433
+ const lastKnownHeight = useRef(0);
434
+ const { stickedToStartColumns, columns, stickedToEndColumns } = useMemo(() => {
435
+ if (shouldAvoidStickyColumns) {
436
+ return {
437
+ stickedToStartColumns: [],
438
+ columns: [
439
+ ...initialStickedToStartColumns,
440
+ ...initialColumns,
441
+ ...initialStickedToEndColumns,
442
+ ],
443
+ stickedToEndColumns: [],
444
+ };
445
+ }
446
+ else {
447
+ return {
448
+ stickedToStartColumns: initialStickedToStartColumns,
449
+ columns: initialColumns,
450
+ stickedToEndColumns: initialStickedToEndColumns,
451
+ };
452
+ }
453
+ }, [
454
+ initialStickedToStartColumns,
455
+ initialColumns,
456
+ initialStickedToEndColumns,
457
+ shouldAvoidStickyColumns,
458
+ ]);
459
+ const [isScrolled, setIsScrolled] = useState(false);
460
+ const removeHoveredRow = useCallback(() => {
461
+ setHoveredRow(undefined);
462
+ }, []);
463
+ const initialStickedToStartColumnsWidth = useMemo(() => initialStickedToStartColumns.reduce((total, { width }) => total + width, 0), [initialStickedToStartColumns]);
464
+ const initialStickedToEndColumnsWidth = useMemo(() => initialStickedToEndColumns.reduce((total, { width }) => total + width, 0), [initialStickedToEndColumns]);
465
+ const stickedToStartColumnsWidth = useMemo(() => stickedToStartColumns.reduce((total, { width }) => total + width, 0), [stickedToStartColumns]);
466
+ const stickedToEndColumnsWidth = useMemo(() => stickedToEndColumns.reduce((total, { width }) => total + width, 0), [stickedToEndColumns]);
467
+ const centerColumnsWidth = useMemo(() => columns.reduce((total, { width }) => total + width, 0), [columns]);
468
+ const centerSegmentStyle = useMemo(() => [
469
+ stickedToStartColumns.length === 0 && styles.rowLeftRadii,
470
+ stickedToEndColumns.length === 0 && styles.rowRightRadii,
471
+ ], [stickedToStartColumns, stickedToEndColumns]);
472
+ const createRowWrapper = useCallback(({ item, absoluteIndex, extraInfo, }) => {
473
+ const customLinkElement = getRowLink?.({ item, index: absoluteIndex, extraInfo });
474
+ return isNullish(customLinkElement) ? _jsx(View, {}) : customLinkElement;
475
+ }, [getRowLink]);
476
+ const [backgroundRows, startRows, centerRows, endRows] = useMemo(() => {
477
+ const length = Math.max(0, renderedRangeEndIndex - renderedRangeStartIndex);
478
+ const backgroundRows = Array(length);
479
+ const startRows = Array(length);
480
+ const centerRows = Array(length);
481
+ const endRows = Array(length);
482
+ let index = -1;
483
+ while (++index < length) {
484
+ const absoluteIndex = renderedRangeStartIndex + index;
485
+ const item = data[absoluteIndex];
486
+ if (isNullish(item)) {
487
+ continue;
488
+ }
489
+ const key = keyExtractor(item, absoluteIndex);
490
+ const top = absoluteIndex * totalRowHeight;
491
+ const isHoveredRow = hoveredRow === key;
492
+ const isHighlightedRow = highlightedRowId === key;
493
+ const paddedIndex = String(absoluteIndex).padStart(10, "0");
494
+ const focusId = `${viewId}__Row_${paddedIndex}`;
495
+ backgroundRows[index] = (_jsx(MemoizedRowBackground, { mode: mode, isHovered: isHoveredRow, isHighlighted: isHighlightedRow, absoluteIndex: absoluteIndex, top: top, id: key, rowHeight: rowHeight, rowVerticalSpacing: rowVerticalSpacing, onMouseEnter: setHoveredRow, onMouseLeave: removeHoveredRow }, key));
496
+ if (stickedToStartColumns.length > 0) {
497
+ startRows[index] = (_jsx(MemoizedRowSegment, { createRowWrapper: createRowWrapper, focusId: focusId, top: top, totalRowHeight: totalRowHeight, id: key, style: styles.rowLeftRadii, onMouseEnter: setHoveredRow, onMouseLeave: removeHoveredRow, segmentColumn: "start", columns: stickedToStartColumns, width: stickedToStartColumnsWidth, item: item, absoluteIndex: absoluteIndex, rowVerticalSpacing: rowVerticalSpacing, viewId: viewId, isHovered: isHoveredRow, extraInfo: extraInfo }, key));
498
+ }
499
+ centerRows[index] = (_jsx(MemoizedRowSegment, { mode: mode, createRowWrapper: createRowWrapper, focusId: focusId, top: top, totalRowHeight: totalRowHeight, minWidth: centerColumnsWidth, id: key, style: centerSegmentStyle, onMouseEnter: setHoveredRow, onMouseLeave: removeHoveredRow, segmentColumn: "center", columns: columns, width: centerColumnsWidth, item: item, absoluteIndex: absoluteIndex, rowVerticalSpacing: rowVerticalSpacing, viewId: viewId, isHovered: isHoveredRow, extraInfo: extraInfo }, key));
500
+ if (stickedToEndColumns.length > 0) {
501
+ endRows[index] = (_jsx(MemoizedRowSegment, { createRowWrapper: createRowWrapper, focusId: focusId, top: top, totalRowHeight: totalRowHeight, id: key, style: styles.rowLeftRadii, onMouseEnter: setHoveredRow, onMouseLeave: removeHoveredRow, segmentColumn: "end", columns: stickedToEndColumns, width: stickedToEndColumnsWidth, item: item, absoluteIndex: absoluteIndex, rowVerticalSpacing: rowVerticalSpacing, viewId: viewId, isHovered: isHoveredRow, extraInfo: extraInfo }, key));
502
+ }
503
+ }
504
+ return [backgroundRows, startRows, centerRows, endRows];
505
+ }, [
506
+ data,
507
+ renderedRangeStartIndex,
508
+ renderedRangeEndIndex,
509
+ keyExtractor,
510
+ highlightedRowId,
511
+ rowHeight,
512
+ totalRowHeight,
513
+ columns,
514
+ stickedToEndColumns,
515
+ stickedToStartColumns,
516
+ viewId,
517
+ stickedToStartColumnsWidth,
518
+ centerColumnsWidth,
519
+ stickedToEndColumnsWidth,
520
+ hoveredRow,
521
+ rowVerticalSpacing,
522
+ extraInfo,
523
+ removeHoveredRow,
524
+ centerSegmentStyle,
525
+ createRowWrapper,
526
+ mode,
527
+ ]);
528
+ // Used to fix some scrollbar behavior. See `main.css`.
529
+ useLayoutEffect(() => {
530
+ if (centerHeadersRef.current instanceof Element) {
531
+ centerHeadersRef.current.setAttribute("data-hide-scrollbar", String(true));
532
+ }
533
+ if (centerColumnsRef.current instanceof Element) {
534
+ centerColumnsRef.current.setAttribute("data-hide-scrollbar", String(true));
535
+ }
536
+ if (horizontalScrollbarRef.current instanceof Element) {
537
+ horizontalScrollbarRef.current.setAttribute("data-force-scrollbar", String(true));
538
+ }
539
+ }, []);
540
+ // To synchronize scrolls, we keep track of the initiator in order to ignore the scroll events
541
+ // we provoke ourselves with the sync.
542
+ const lastHorizontalScroll = useRef({
543
+ initiator: "columns",
544
+ date: 0,
545
+ });
546
+ useEffect(() => {
547
+ if (isNotNullish(centerHeadersRef.current) &&
548
+ isNotNullish(centerColumnsRef.current) &&
549
+ isNotNullish(horizontalScrollbarRef.current)) {
550
+ const SCROLL_THRESHOLD_MS = 500;
551
+ const centerColumns = centerColumnsRef.current;
552
+ const centerHeaders = centerHeadersRef.current;
553
+ const horizontalScrollbar = horizontalScrollbarRef.current;
554
+ const onColumnsScroll = () => {
555
+ const now = Date.now();
556
+ if (lastHorizontalScroll.current.initiator === "columns" ||
557
+ now - lastHorizontalScroll.current.date > SCROLL_THRESHOLD_MS) {
558
+ const scrollLeft = centerColumns.scrollLeft;
559
+ setShouldShowStartGradient(scrollLeft > 0);
560
+ setShouldShowEndGradient(centerColumns.scrollWidth - HORIZONTAL_SAFE_AREA * 2 >=
561
+ scrollLeft + centerColumns.clientWidth);
562
+ centerHeaders.scrollLeft = scrollLeft;
563
+ horizontalScrollbar.scrollLeft = scrollLeft;
564
+ lastHorizontalScroll.current = { initiator: "columns", date: now };
565
+ }
566
+ };
567
+ const onHeadersScroll = () => {
568
+ const now = Date.now();
569
+ if (lastHorizontalScroll.current.initiator === "headers" ||
570
+ now - lastHorizontalScroll.current.date > SCROLL_THRESHOLD_MS) {
571
+ const scrollLeft = centerHeaders.scrollLeft;
572
+ setShouldShowStartGradient(scrollLeft > 0);
573
+ setShouldShowEndGradient(centerHeaders.scrollWidth - HORIZONTAL_SAFE_AREA * 2 >=
574
+ scrollLeft + centerHeaders.clientWidth);
575
+ centerColumns.scrollLeft = scrollLeft;
576
+ horizontalScrollbar.scrollLeft = scrollLeft;
577
+ lastHorizontalScroll.current = { initiator: "headers", date: now };
578
+ }
579
+ };
580
+ const onScrollbarScroll = () => {
581
+ const now = Date.now();
582
+ if (lastHorizontalScroll.current.initiator === "scrollbar" ||
583
+ now - lastHorizontalScroll.current.date > SCROLL_THRESHOLD_MS) {
584
+ const scrollLeft = horizontalScrollbar.scrollLeft;
585
+ setShouldShowStartGradient(scrollLeft > 0);
586
+ setShouldShowEndGradient(horizontalScrollbar.scrollWidth - HORIZONTAL_SAFE_AREA * 2 >=
587
+ scrollLeft + horizontalScrollbar.clientWidth);
588
+ centerHeaders.scrollLeft = scrollLeft;
589
+ centerColumns.scrollLeft = scrollLeft;
590
+ lastHorizontalScroll.current = { initiator: "scrollbar", date: now };
591
+ }
592
+ };
593
+ centerColumns.addEventListener("scroll", onColumnsScroll, { passive: true });
594
+ centerHeaders.addEventListener("scroll", onHeadersScroll, { passive: true });
595
+ horizontalScrollbar.addEventListener("scroll", onScrollbarScroll, { passive: true });
596
+ return () => {
597
+ centerColumns.removeEventListener("scroll", onColumnsScroll);
598
+ centerHeaders.removeEventListener("scroll", onHeadersScroll);
599
+ horizontalScrollbar.removeEventListener("scroll", onScrollbarScroll);
600
+ };
601
+ }
602
+ }, []);
603
+ const onKeyDown = useCallback((event) => {
604
+ const target = event.nativeEvent.target;
605
+ const currentTarget = event.nativeEvent.currentTarget;
606
+ const currentCell = target.closest(`[id^="${viewId}__Row"]`);
607
+ const currentCellId = currentCell?.id;
608
+ if (event.nativeEvent.key === "Tab" && isNotNullish(currentCell)) {
609
+ const focusableElements = getFocusableElements(currentCell, false);
610
+ const firstFocusableElement = first(focusableElements);
611
+ const lastFocusableElement = last(focusableElements);
612
+ const sortedCellIds = Array.from(currentTarget.querySelectorAll(`[id^="${viewId}__Row"]`), item => item.id).sort();
613
+ const currentCellIndex = sortedIndexOf(sortedCellIds, currentCellId);
614
+ const isTargetFirst = isNullish(firstFocusableElement) || firstFocusableElement === target;
615
+ const isTargetLast = isNullish(lastFocusableElement) || lastFocusableElement === target;
616
+ if (isTargetFirst && event.nativeEvent.shiftKey && first(sortedCellIds) !== currentCellId) {
617
+ const lastFocusableElement = findNextFocusableElement(sortedCellIds, currentCellIndex, -1);
618
+ if (isNotNullish(lastFocusableElement)) {
619
+ event.preventDefault();
620
+ lastFocusableElement.focus();
621
+ }
622
+ else {
623
+ event.preventDefault();
624
+ startFocusAnchorRef.current?.focus();
625
+ }
626
+ }
627
+ if (isTargetLast && !event.nativeEvent.shiftKey && last(sortedCellIds) !== currentCellId) {
628
+ const firstFocusableElement = findNextFocusableElement(sortedCellIds, currentCellIndex, 1);
629
+ if (isNotNullish(firstFocusableElement)) {
630
+ event.preventDefault();
631
+ firstFocusableElement.focus();
632
+ }
633
+ else {
634
+ event.preventDefault();
635
+ endFocusAnchorRef.current?.focus();
636
+ }
637
+ }
638
+ }
639
+ }, [viewId]);
640
+ useEffect(() => {
641
+ const renderedRangeStartIndex = Math.max(0, Math.floor((currentScrollY.current - renderThreshold) / totalRowHeight));
642
+ const renderedRangeEndIndex = Math.min(originalData.length, renderedRangeStartIndex +
643
+ Math.ceil((lastKnownHeight.current + renderThreshold * 2) / totalRowHeight));
644
+ setDataAndRenderRange(prevRenderRange => {
645
+ const { data, range: [prevRenderedRangeStartIndex, prevRenderedRangeEndIndex], } = prevRenderRange;
646
+ if (prevRenderedRangeStartIndex === renderedRangeStartIndex &&
647
+ prevRenderedRangeEndIndex === renderedRangeEndIndex &&
648
+ data === originalData) {
649
+ return prevRenderRange;
650
+ }
651
+ return { data: originalData, range: [renderedRangeStartIndex, renderedRangeEndIndex] };
652
+ });
653
+ }, [originalData, renderThreshold, totalRowHeight]);
654
+ const onLayout = useCallback(({ nativeEvent: { layout: { height, width }, }, }) => {
655
+ lastKnownHeight.current = height;
656
+ const renderedRangeStartIndex = Math.max(0, Math.floor((currentScrollY.current - renderThreshold) / totalRowHeight));
657
+ const renderedRangeEndIndex = Math.min(originalData.length, renderedRangeStartIndex + Math.ceil((height + renderThreshold * 2) / totalRowHeight));
658
+ setDataAndRenderRange(prevRenderRange => {
659
+ const { data, range: [prevRenderedRangeStartIndex, prevRenderedRangeEndIndex], } = prevRenderRange;
660
+ if (prevRenderedRangeStartIndex === renderedRangeStartIndex &&
661
+ prevRenderedRangeEndIndex === renderedRangeEndIndex &&
662
+ data === originalData) {
663
+ return prevRenderRange;
664
+ }
665
+ return { data: originalData, range: [renderedRangeStartIndex, renderedRangeEndIndex] };
666
+ });
667
+ if (isNotNullish(onEndReached) &&
668
+ !hasEndReachedBeenCalled.current &&
669
+ height >= totalHeight - onEndReachedThresholdPx) {
670
+ hasEndReachedBeenCalled.current = true;
671
+ onEndReached();
672
+ }
673
+ setShouldAvoidStickyColumns(width - (initialStickedToStartColumnsWidth + initialStickedToEndColumnsWidth) < 300);
674
+ }, [
675
+ originalData,
676
+ renderThreshold,
677
+ totalRowHeight,
678
+ onEndReached,
679
+ onEndReachedThresholdPx,
680
+ totalHeight,
681
+ initialStickedToStartColumnsWidth,
682
+ initialStickedToEndColumnsWidth,
683
+ ]);
684
+ const scrollTimeoutRef = useRef(undefined);
685
+ const scrollContentsRef = useRef(null);
686
+ const hasEndReachedBeenCalled = useRef(false);
687
+ useEffect(() => {
688
+ if (isNotNullish(onEndReached) &&
689
+ !hasEndReachedBeenCalled.current &&
690
+ lastKnownHeight.current >= totalHeight - onEndReachedThresholdPx) {
691
+ hasEndReachedBeenCalled.current = true;
692
+ onEndReached();
693
+ return;
694
+ }
695
+ hasEndReachedBeenCalled.current = false;
696
+ }, [data, onEndReached, onEndReachedThresholdPx, totalHeight]);
697
+ const onScroll = useCallback(({ nativeEvent: { contentOffset: { y }, layoutMeasurement: { height }, contentSize: { height: contentHeight }, }, }) => {
698
+ setIsScrolled(y > 0);
699
+ lastKnownHeight.current = height;
700
+ currentScrollY.current = y;
701
+ if (isNotNullish(scrollTimeoutRef.current)) {
702
+ clearTimeout(scrollTimeoutRef.current);
703
+ }
704
+ if (scrollContentsRef.current instanceof HTMLElement) {
705
+ scrollContentsRef.current.style.pointerEvents = "none";
706
+ }
707
+ scrollTimeoutRef.current = window.setTimeout(() => {
708
+ if (scrollContentsRef.current instanceof HTMLElement) {
709
+ scrollContentsRef.current.style.pointerEvents = "auto";
710
+ }
711
+ }, 100);
712
+ const renderedRangeStartIndex = Math.max(0, Math.floor((currentScrollY.current - renderThreshold) / totalRowHeight));
713
+ const renderedRangeEndIndex = Math.min(data.length, renderedRangeStartIndex + Math.ceil((height + renderThreshold * 2) / totalRowHeight));
714
+ setDataAndRenderRange(prevRenderRange => {
715
+ const { data, range: [prevRenderedRangeStartIndex, prevRenderedRangeEndIndex], } = prevRenderRange;
716
+ return prevRenderedRangeStartIndex === renderedRangeStartIndex &&
717
+ prevRenderedRangeEndIndex === renderedRangeEndIndex &&
718
+ data === data
719
+ ? prevRenderRange
720
+ : { data, range: [renderedRangeStartIndex, renderedRangeEndIndex] };
721
+ });
722
+ if (isNotNullish(onEndReached) &&
723
+ !hasEndReachedBeenCalled.current &&
724
+ y + height >= contentHeight - onEndReachedThresholdPx) {
725
+ hasEndReachedBeenCalled.current = true;
726
+ onEndReached();
727
+ }
728
+ }, [data.length, renderThreshold, totalRowHeight, onEndReached, onEndReachedThresholdPx]);
729
+ const onCenterTrackLayout = useCallback(({ nativeEvent: { layout: { width }, }, }) => {
730
+ setHasHorizontalScroll(centerColumnsWidth > width);
731
+ }, [centerColumnsWidth]);
732
+ const isLoading = isNotNullish(loading) && loading.isLoading;
733
+ return (_jsxs(View, { style: styles.root, children: [_jsx(View, { ref: startFocusAnchorRef, focusable: true }), _jsxs(ScrollView, { onKeyDown: onKeyDown, onLayout: onLayout, onScroll: onScroll, scrollEventThrottle: 32, style: [styles.container, mode === "tile" && styles.containerTile], contentContainerStyle: [
734
+ styles.contentContainer,
735
+ {
736
+ height: totalHeight +
737
+ SCROLLBAR_RESERVED_SPACE +
738
+ (isLoading ? loading.count * (rowHeight + rowVerticalSpacing) : 0),
739
+ },
740
+ ], children: [_jsx(View, { accessibilityBusy: isLoading, style: [
741
+ styles.loadingPlaceholder,
742
+ {
743
+ top: totalHeight,
744
+ marginLeft: HORIZONTAL_SAFE_AREA * 2,
745
+ marginRight: HORIZONTAL_SAFE_AREA * 2,
746
+ },
747
+ ], children: isLoading
748
+ ? match(mode)
749
+ .with("tile", () => (_jsx(FixedListViewPlaceholder, { count: loading.count, rowHeight: rowHeight, rowVerticalSpacing: rowVerticalSpacing, paddingHorizontal: 0 })))
750
+ .with("plain", () => (_jsx(PlainListViewPlaceholder, { count: loading.count, rowHeight: rowHeight, rowVerticalSpacing: rowVerticalSpacing, paddingHorizontal: 0 })))
751
+ .exhaustive()
752
+ : null }), _jsx(View, { style: [styles.backgroundRows, { top: headerHeight }], children: backgroundRows }), _jsxs(View, { style: styles.scrollContentContainer, ref: scrollContentsRef, children: [stickedToStartColumns.length > 0 ? (_jsxs(View, { style: [
753
+ styles.stickyColumn,
754
+ {
755
+ width: stickedToStartColumnsWidth + HORIZONTAL_SAFE_AREA,
756
+ paddingLeft: HORIZONTAL_SAFE_AREA,
757
+ },
758
+ ], children: [_jsxs(View, { style: [styles.headingSegment, { height: headerHeight }], children: [_jsx(HeaderSegment, { columns: stickedToStartColumns, extraInfo: extraInfo, viewId: viewId, width: stickedToStartColumnsWidth }), _jsx(View, { style: styles.stickyColumnStartOverflow }), _jsx(View, { style: [styles.topGradient, isScrolled && styles.visibleTopGradient], pointerEvents: "none" })] }), _jsx(View, { style: [styles.stickyRow, { height: totalHeight }], children: startRows })] })) : null, _jsxs(View, { style: [
759
+ styles.centerColumnsContainer,
760
+ {
761
+ paddingLeft: stickedToStartColumns.length === 0 ? HORIZONTAL_SAFE_AREA : ZERO,
762
+ paddingRight: stickedToEndColumns.length === 0 ? HORIZONTAL_SAFE_AREA : ZERO,
763
+ },
764
+ ], children: [_jsxs(View, { style: [styles.headingSegment, { height: headerHeight }], children: [_jsx(ScrollView, { ref: centerHeadersRef, horizontal: true, onLayout: onCenterTrackLayout, style: styles.centerColumns, contentContainerStyle: {
765
+ minWidth: centerColumnsWidth +
766
+ (stickedToStartColumns.length === 0 ? HORIZONTAL_SAFE_AREA : 0) +
767
+ (stickedToEndColumns.length === 0 ? HORIZONTAL_SAFE_AREA : 0),
768
+ }, children: _jsx(HeaderSegment, { columns: columns, extraInfo: extraInfo, viewId: viewId, width: centerColumnsWidth }) }), _jsx(View, { style: [styles.topGradient, isScrolled && styles.visibleTopGradient], pointerEvents: "none" })] }), _jsx(ScrollView, { horizontal: true, ref: centerColumnsRef, style: styles.centerColumns, contentContainerStyle: [
769
+ styles.centerColumnsContentContainer,
770
+ {
771
+ minWidth: centerColumnsWidth +
772
+ (stickedToStartColumns.length === 0 ? HORIZONTAL_SAFE_AREA : 0) +
773
+ (stickedToEndColumns.length === 0 ? HORIZONTAL_SAFE_AREA : 0),
774
+ },
775
+ ], children: centerRows }), _jsx(ScrollView, { ref: horizontalScrollbarRef, horizontal: true, style: styles.horizontalScrollbar, contentContainerStyle: {
776
+ minWidth: centerColumnsWidth +
777
+ (stickedToStartColumns.length === 0 ? HORIZONTAL_SAFE_AREA : 0) +
778
+ (stickedToEndColumns.length === 0 ? HORIZONTAL_SAFE_AREA : 0),
779
+ } }), stickedToStartColumns.length > 0 && hasHorizontalScroll ? (_jsx(View, { pointerEvents: "none", style: [
780
+ styles.leftToRightGradient,
781
+ {
782
+ maxHeight: data.length * totalRowHeight,
783
+ top: headerHeight,
784
+ bottom: SCROLLBAR_RESERVED_SPACE + rowVerticalSpacing / 2,
785
+ },
786
+ shouldShowStartGradient && styles.visibleGradient,
787
+ ] })) : null, stickedToEndColumns.length > 0 && hasHorizontalScroll ? (_jsx(View, { pointerEvents: "none", style: [
788
+ styles.rightToLeftGradient,
789
+ {
790
+ maxHeight: data.length * totalRowHeight,
791
+ top: headerHeight,
792
+ bottom: SCROLLBAR_RESERVED_SPACE + rowVerticalSpacing / 2,
793
+ },
794
+ shouldShowEndGradient && styles.visibleGradient,
795
+ ] })) : null] }), stickedToEndColumns.length > 0 ? (_jsxs(View, { style: [
796
+ styles.stickyColumn,
797
+ {
798
+ width: stickedToEndColumnsWidth + HORIZONTAL_SAFE_AREA,
799
+ paddingRight: HORIZONTAL_SAFE_AREA,
800
+ },
801
+ ], children: [_jsxs(View, { style: [styles.headingSegment, { height: headerHeight }], children: [_jsx(View, { style: styles.stickyColumnEndOverflow }), _jsx(HeaderSegment, { columns: stickedToEndColumns, extraInfo: extraInfo, viewId: viewId, width: stickedToEndColumnsWidth }), _jsx(View, { style: [styles.topGradient, isScrolled && styles.visibleTopGradient], pointerEvents: "none" })] }), _jsx(View, { style: [styles.stickyRow, { height: totalHeight }], children: endRows })] })) : null] })] }), data.length === 0 && isNotNullish(renderEmptyList) && !isLoading ? (_jsx(View, { style: styles.emptyListContainer, children: renderEmptyList() })) : null, _jsx(View, { ref: endFocusAnchorRef, focusable: true })] }));
802
+ };
803
+ export const FixedListViewPlaceholder = ({ count, rowHeight, rowVerticalSpacing, groupHeaderHeight, headerHeight, paddingHorizontal = HORIZONTAL_SAFE_AREA, }) => {
804
+ const totalRowHeight = rowHeight + rowVerticalSpacing;
805
+ return (_jsxs(View, { style: [styles.container, styles.containerTile, { paddingHorizontal }], children: [isNotNullish(headerHeight) ? _jsx(View, { style: { height: headerHeight } }) : null, isNotNullish(groupHeaderHeight) ? _jsx(View, { style: { height: headerHeight } }) : null, _jsx(View, { children: Array.from({ length: count }, (_, index) => {
806
+ const top = index * totalRowHeight + rowVerticalSpacing / 2;
807
+ return (_jsxs(Fragment, { children: [_jsx(MemoizedRowBackground, { isHovered: false, isHighlighted: false, absoluteIndex: index, top: top, rowVerticalSpacing: rowVerticalSpacing, id: String(index), rowHeight: rowHeight, onMouseEnter: noop, onMouseLeave: noop, mode: "tile" }), _jsxs(View, { style: [styles.placeholderRowContainer, { top, height: totalRowHeight }], children: [_jsx(View, { style: styles.placeholderRow }), _jsx(Space, { width: 32 }), _jsx(View, { style: [styles.placeholderRow, styles.smallPlaceholderRow] }), _jsx(Space, { width: 32 }), _jsx(View, { style: styles.placeholderRowEnd, children: _jsx(View, { style: [styles.placeholderRow, styles.smallPlaceholderRow] }) })] }, String(index))] }, String(index)));
808
+ }) })] }));
809
+ };
810
+ export const PlainListViewPlaceholder = ({ count, rowHeight, rowVerticalSpacing, groupHeaderHeight, headerHeight, paddingHorizontal = HORIZONTAL_SAFE_AREA, }) => {
811
+ const totalRowHeight = rowHeight + rowVerticalSpacing;
812
+ return (_jsxs(View, { style: [styles.container, { paddingHorizontal }], children: [isNotNullish(headerHeight) ? _jsx(View, { style: { height: headerHeight } }) : null, isNotNullish(groupHeaderHeight) ? _jsx(View, { style: { height: headerHeight } }) : null, _jsx(View, { children: Array.from({ length: count }, (_, index) => {
813
+ const top = index * totalRowHeight + rowVerticalSpacing / 2;
814
+ return (_jsxs(Fragment, { children: [_jsx(MemoizedRowBackground, { isHovered: false, isHighlighted: false, absoluteIndex: index, top: top, rowVerticalSpacing: rowVerticalSpacing, id: String(index), rowHeight: rowHeight, onMouseEnter: noop, onMouseLeave: noop, mode: "plain" }), _jsxs(View, { style: [
815
+ styles.placeholderRowContainer,
816
+ styles.placeholderRowContainerPlain,
817
+ { top, height: totalRowHeight },
818
+ ], children: [_jsx(View, { style: styles.placeholderRow }), _jsx(Space, { width: 32 }), _jsx(View, { style: [styles.placeholderRow, styles.smallPlaceholderRow] }), _jsx(Space, { width: 32 }), _jsx(View, { style: styles.placeholderRowEnd, children: _jsx(View, { style: [styles.placeholderRow, styles.smallPlaceholderRow] }) })] }, String(index))] }, String(index)));
819
+ }) })] }));
820
+ };
821
+ export const FixedListViewEmpty = ({ icon, borderedIcon = false, title, subtitle, children, }) => (_jsxs(View, { style: styles.emptyList, children: [borderedIcon ? (_jsx(BorderedIcon, { name: icon })) : (_jsx(Icon, { name: icon, size: 96, color: colors.current.primary })), _jsx(Space, { height: 32 }), isNotNullish(title) && (_jsx(LakeHeading, { level: 3, variant: "h3", color: colors.gray[700], align: "center", children: title })), _jsx(Space, { height: 8 }), isNotNullish(subtitle) && _jsx(LakeText, { align: "center", children: subtitle }), _jsx(Space, { height: 8 }), children] }));