@swan-io/lake 8.18.3 → 8.18.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swan-io/lake",
3
- "version": "8.18.3",
3
+ "version": "8.18.4",
4
4
  "engines": {
5
5
  "node": ">=20.9.0",
6
6
  "yarn": "^1.22.0"
@@ -3,7 +3,7 @@ import { Option } from "@swan-io/boxed";
3
3
  import { cloneElement, memo, useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, } from "react";
4
4
  import { StyleSheet, View } from "react-native";
5
5
  import { commonStyles } from "../constants/commonStyles";
6
- import { backgroundColor as backgroundColorVariants, colors, negativeSpacings, spacings, } from "../constants/design";
6
+ import { backgroundColor as backgroundColorVariants, colors, spacings } from "../constants/design";
7
7
  import { useHover } from "../hooks/useHover";
8
8
  import { ScrollView } from "./ScrollView";
9
9
  import { Space } from "./Space";
@@ -20,28 +20,27 @@ const styles = StyleSheet.create({
20
20
  flexDirection: "row",
21
21
  alignItems: "stretch",
22
22
  zIndex: 2,
23
- paddingHorizontal: spacings[HORIZONTAL_ROW_PADDING],
24
23
  },
25
24
  cellsContainer: {
26
25
  flexDirection: "row",
27
26
  transform: "translateZ(0)",
28
- marginHorizontal: negativeSpacings[HORIZONTAL_ROW_PADDING],
29
- paddingHorizontal: spacings[HORIZONTAL_ROW_PADDING],
30
27
  },
31
28
  stickedToStartColumnGroup: {
32
29
  position: "sticky",
33
30
  left: 0,
34
- marginLeft: negativeSpacings[HORIZONTAL_ROW_PADDING],
35
- paddingLeft: spacings[HORIZONTAL_ROW_PADDING],
36
31
  zIndex: 1,
37
32
  },
33
+ stickedToStartColumnGroupLocked: {
34
+ position: "relative",
35
+ },
38
36
  stickedToEndColumnGroup: {
39
37
  position: "sticky",
40
38
  right: 0,
41
- marginRight: negativeSpacings[HORIZONTAL_ROW_PADDING],
42
- paddingRight: spacings[HORIZONTAL_ROW_PADDING],
43
39
  zIndex: 1,
44
40
  },
41
+ stickedToEndColumnGroupLocked: {
42
+ position: "relative",
43
+ },
45
44
  rowsContainer: {
46
45
  position: "relative",
47
46
  },
@@ -51,7 +50,10 @@ const styles = StyleSheet.create({
51
50
  right: 0,
52
51
  flexDirection: "row",
53
52
  alignItems: "stretch",
54
- paddingHorizontal: spacings[HORIZONTAL_ROW_PADDING],
53
+ boxShadow: `0 -1px ${colors.gray[100]}`,
54
+ transitionProperty: "top",
55
+ transitionDuration: "300ms",
56
+ transitionTimingFunction: "ease-in-out",
55
57
  },
56
58
  headerCell: {
57
59
  display: "flex",
@@ -64,7 +66,6 @@ const styles = StyleSheet.create({
64
66
  flexDirection: "row",
65
67
  flexGrow: 1,
66
68
  alignItems: "stretch",
67
- boxShadow: `inset 0 -1px ${colors.gray[100]}`,
68
69
  },
69
70
  shadowsLayerContainer: {
70
71
  position: "absolute",
@@ -162,17 +163,28 @@ export const VirtualizedList = ({ variant, data, stickedToStartColumns, columns,
162
163
  .flatMap(({ isLoading, count }) => (isLoading ? Option.Some(rowHeight * count) : Option.None()))
163
164
  .getOr(0);
164
165
  const containerContainerHeight = headerHeight + fullDataHeight + loadingDataPlaceholderHeight;
166
+ const stickedToStartFirstCellLeftPadding = Option.fromNullable(stickedToStartColumns)
167
+ .map(() => HORIZONTAL_ROW_PADDING)
168
+ .getOr(0);
169
+ const centerFirstCellLeftPadding = Option.fromNullable(stickedToStartColumns)
170
+ .map(() => 0)
171
+ .getOr(HORIZONTAL_ROW_PADDING);
172
+ const centerLastCellLeftPadding = Option.fromNullable(stickedToEndColumns)
173
+ .map(() => 0)
174
+ .getOr(HORIZONTAL_ROW_PADDING);
175
+ const stickedToEndLastCellRightPadding = Option.fromNullable(stickedToEndColumns)
176
+ .map(() => HORIZONTAL_ROW_PADDING)
177
+ .getOr(0);
165
178
  const stickedToStartColumnsWidth = useMemo(() => Option.fromNullable(stickedToStartColumns)
166
179
  .map(columns => columns.reduce((acc, column) => acc + column.width, 0))
167
- .getOr(0), [stickedToStartColumns]);
168
- const centerColumnsWidth = useMemo(() => columns.reduce((acc, column) => acc + column.width, 0), [columns]);
180
+ .getOr(0) + stickedToStartFirstCellLeftPadding, [stickedToStartColumns, stickedToStartFirstCellLeftPadding]);
181
+ const centerColumnsWidth = useMemo(() => columns.reduce((acc, column) => acc + column.width, 0) +
182
+ centerFirstCellLeftPadding +
183
+ centerLastCellLeftPadding, [columns, centerFirstCellLeftPadding, centerLastCellLeftPadding]);
169
184
  const stickedToEndColumnsWidth = useMemo(() => Option.fromNullable(stickedToEndColumns)
170
185
  .map(columns => columns.reduce((acc, column) => acc + column.width, 0))
171
- .getOr(0), [stickedToEndColumns]);
172
- const contentContainerWidth = stickedToStartColumnsWidth +
173
- centerColumnsWidth +
174
- stickedToEndColumnsWidth +
175
- HORIZONTAL_ROW_PADDING * 2;
186
+ .getOr(0) + stickedToEndLastCellRightPadding, [stickedToEndColumns, stickedToEndLastCellRightPadding]);
187
+ const contentContainerWidth = stickedToStartColumnsWidth + centerColumnsWidth + stickedToEndColumnsWidth;
176
188
  const backgroundColor = backgroundColorVariants[variant];
177
189
  // We store the `startIndex` and `endIndex` rather than the scroll position
178
190
  // so that it triggers way less re-renders
@@ -180,18 +192,21 @@ export const VirtualizedList = ({ variant, data, stickedToStartColumns, columns,
180
192
  const [clientHeight, setClientHeight] = useState(undefined);
181
193
  const [horizontalScrollPosition, setHasHorizontalScrollPosition] = useState(undefined);
182
194
  const rowsToRender = useMemo(() => {
183
- return Option.fromNullable(rangeToRender).map(({ startIndex, endIndex }) => ({
184
- startIndex,
185
- endIndex,
186
- data: data.slice(startIndex, endIndex),
187
- }));
195
+ return Option.fromNullable(rangeToRender).map(({ startIndex, endIndex }) => {
196
+ const clampedEndIndex = Math.min(data.length, endIndex);
197
+ return {
198
+ startIndex,
199
+ endIndex: clampedEndIndex,
200
+ data: data.slice(startIndex, clampedEndIndex),
201
+ };
202
+ });
188
203
  }, [data, rangeToRender]);
189
- useLayoutEffect(() => {
204
+ const onLayoutUpdate = useCallback(() => {
190
205
  const element = Option.fromNullable(scrollViewRef.current).flatMap(ref => Option.fromNullable(ref.element));
191
206
  setRangeToRender(previousRangeToRender => element
192
207
  .map(scrollView => {
193
208
  const startIndex = Math.max(0, Math.floor((scrollView.scrollTop - renderThreshold) / rowHeight));
194
- const endIndex = Math.min(data.length, startIndex + Math.ceil((scrollView.scrollHeight + renderThreshold * 2) / rowHeight));
209
+ const endIndex = startIndex + Math.ceil((scrollView.scrollHeight + renderThreshold * 2) / rowHeight);
195
210
  if ((previousRangeToRender === null || previousRangeToRender === void 0 ? void 0 : previousRangeToRender.startIndex) === startIndex &&
196
211
  previousRangeToRender.endIndex === endIndex) {
197
212
  return previousRangeToRender;
@@ -201,7 +216,8 @@ export const VirtualizedList = ({ variant, data, stickedToStartColumns, columns,
201
216
  .toUndefined());
202
217
  setClientHeight(element.map(scrollView => scrollView.clientHeight).toUndefined());
203
218
  setHasHorizontalScrollPosition(element
204
- .map(scrollView => scrollView.scrollWidth === scrollView.clientWidth
219
+ .map(scrollView => scrollView.scrollWidth === scrollView.clientWidth ||
220
+ scrollView.clientWidth - (stickedToEndColumnsWidth + stickedToStartColumnsWidth) < 400
205
221
  ? "NoScroll"
206
222
  : scrollView.scrollLeft <= 0
207
223
  ? "Start"
@@ -209,11 +225,13 @@ export const VirtualizedList = ({ variant, data, stickedToStartColumns, columns,
209
225
  ? "End"
210
226
  : "Middle")
211
227
  .toUndefined());
212
- }, [data, renderThreshold, rowHeight]);
228
+ }, [data, renderThreshold, rowHeight, stickedToStartColumnsWidth, stickedToEndColumnsWidth]);
229
+ useLayoutEffect(() => {
230
+ onLayoutUpdate();
231
+ }, [onLayoutUpdate]);
213
232
  const scrollTimeoutRef = useRef(undefined);
214
233
  const rowsContainerRef = useRef(null);
215
234
  const onScroll = useCallback(() => {
216
- const element = Option.fromNullable(scrollViewRef.current).flatMap(ref => Option.fromNullable(ref.element));
217
235
  // Disable interactions in cells during scroll, avoids useless
218
236
  // re-renders
219
237
  if (scrollTimeoutRef.current != null) {
@@ -227,27 +245,20 @@ export const VirtualizedList = ({ variant, data, stickedToStartColumns, columns,
227
245
  rowsContainerRef.current.style.pointerEvents = "auto";
228
246
  }
229
247
  }, 100);
230
- setRangeToRender(previousRangeToRender => element
231
- .map(scrollView => {
232
- const startIndex = Math.max(0, Math.floor((scrollView.scrollTop - renderThreshold) / rowHeight));
233
- const endIndex = Math.min(data.length, startIndex + Math.ceil((scrollView.scrollHeight + renderThreshold * 2) / rowHeight));
234
- if ((previousRangeToRender === null || previousRangeToRender === void 0 ? void 0 : previousRangeToRender.startIndex) === startIndex &&
235
- previousRangeToRender.endIndex === endIndex) {
236
- return previousRangeToRender;
237
- }
238
- return { startIndex, endIndex };
248
+ onLayoutUpdate();
249
+ }, [onLayoutUpdate]);
250
+ useEffect(() => {
251
+ const element = Option.fromNullable(scrollViewRef.current).flatMap(ref => Option.fromNullable(ref.element));
252
+ return element
253
+ .map(element => {
254
+ const resizeObserver = new ResizeObserver(() => {
255
+ onLayoutUpdate();
256
+ });
257
+ resizeObserver.observe(element);
258
+ return () => resizeObserver.unobserve(element);
239
259
  })
240
- .toUndefined());
241
- setHasHorizontalScrollPosition(element
242
- .map(scrollView => scrollView.scrollWidth === scrollView.clientWidth
243
- ? "NoScroll"
244
- : scrollView.scrollLeft <= 0
245
- ? "Start"
246
- : scrollView.scrollLeft >= scrollView.scrollWidth - scrollView.clientWidth
247
- ? "End"
248
- : "Middle")
249
- .toUndefined());
250
- }, [data, renderThreshold, rowHeight]);
260
+ .toUndefined();
261
+ }, [onLayoutUpdate]);
251
262
  // tracks if the threshold to initiate the next data load is reached
252
263
  useEffect(() => {
253
264
  const scrollTracker = scrollTrackerRef.current;
@@ -271,22 +282,31 @@ export const VirtualizedList = ({ variant, data, stickedToStartColumns, columns,
271
282
  .map(columns => (_jsx(View, { style: [
272
283
  styles.cellsContainer,
273
284
  styles.stickedToStartColumnGroup,
285
+ horizontalScrollPosition === "NoScroll" && styles.stickedToStartColumnGroupLocked,
274
286
  { width: stickedToStartColumnsWidth, backgroundColor },
275
- ], children: columns.map(({ id, width, title, renderTitle }) => {
287
+ ], children: columns.map(({ id, width, title, renderTitle }, index) => {
276
288
  const columnId = `${viewId}_${id}`;
277
- return (_jsx(View, { style: [styles.headerCell, { width }], id: columnId, children: renderTitle({ title, extraInfo, id }) }, columnId));
289
+ const paddingLeft = index === 0 ? stickedToStartFirstCellLeftPadding : 0;
290
+ return (_jsx(View, { style: [styles.headerCell, { width: width + paddingLeft, paddingLeft }], id: columnId, children: renderTitle({ title, extraInfo, id }) }, columnId));
278
291
  }) })))
279
- .toNull(), _jsx(View, { style: [styles.cellsContainer, { width: centerColumnsWidth, backgroundColor }], children: columns.map(({ id, width, title, renderTitle }) => {
292
+ .toNull(), _jsx(View, { style: [styles.cellsContainer, { width: centerColumnsWidth, backgroundColor }], children: columns.map(({ id, width, title, renderTitle }, index) => {
280
293
  const columnId = `${viewId}_${id}`;
281
- return (_jsx(View, { style: [styles.headerCell, { width }], id: columnId, children: renderTitle({ title, extraInfo, id }) }, columnId));
294
+ const paddingLeft = index === 0 ? centerFirstCellLeftPadding : 0;
295
+ const paddingRight = index === columns.length - 1 ? centerLastCellLeftPadding : 0;
296
+ return (_jsx(View, { style: [
297
+ styles.headerCell,
298
+ { width: width + paddingLeft + paddingRight, paddingLeft, paddingRight },
299
+ ], id: columnId, children: renderTitle({ title, extraInfo, id }) }, columnId));
282
300
  }) }), Option.fromNullable(stickedToEndColumns)
283
301
  .map(columns => (_jsx(View, { style: [
284
302
  styles.cellsContainer,
285
303
  styles.stickedToEndColumnGroup,
304
+ horizontalScrollPosition === "NoScroll" && styles.stickedToEndColumnGroupLocked,
286
305
  { width: stickedToEndColumnsWidth, backgroundColor },
287
- ], children: columns.map(({ id, width, title, renderTitle }) => {
306
+ ], children: columns.map(({ id, width, title, renderTitle }, index) => {
288
307
  const columnId = `${viewId}_${id}`;
289
- return (_jsx(View, { style: [styles.headerCell, { width }], id: columnId, children: renderTitle({ title, extraInfo, id }) }, columnId));
308
+ const paddingRight = index === columns.length - 1 ? stickedToEndLastCellRightPadding : 0;
309
+ return (_jsx(View, { style: [styles.headerCell, { width: width + paddingRight, paddingRight }], id: columnId, children: renderTitle({ title, extraInfo, id }) }, columnId));
290
310
  }) })))
291
311
  .toNull()] }));
292
312
  }, [
@@ -300,6 +320,11 @@ export const VirtualizedList = ({ variant, data, stickedToStartColumns, columns,
300
320
  columns,
301
321
  stickedToEndColumns,
302
322
  viewId,
323
+ horizontalScrollPosition,
324
+ stickedToStartFirstCellLeftPadding,
325
+ centerFirstCellLeftPadding,
326
+ centerLastCellLeftPadding,
327
+ stickedToEndLastCellRightPadding,
303
328
  ]);
304
329
  const startColumnShadow = useMemo(() => {
305
330
  if (stickedToStartColumnsWidth === 0) {
@@ -333,7 +358,7 @@ export const VirtualizedList = ({ variant, data, stickedToStartColumns, columns,
333
358
  height: containerContainerHeight,
334
359
  width: contentContainerWidth,
335
360
  }, children: [header, rowsToRender
336
- .map(({ startIndex, endIndex, data }) => (_jsxs(View, { style: styles.rowsContainer, ref: rowsContainerRef, children: [data.map((item, index) => (_jsx(VirtualizedRow, { viewId: viewId, item: item, rowHeight: rowHeight, absoluteIndex: startIndex + index, variant: variant, stickedToStartColumnsWidth: stickedToStartColumnsWidth, centerColumnsWidth: centerColumnsWidth, stickedToEndColumnsWidth: stickedToEndColumnsWidth, stickedToStartColumns: stickedToStartColumns, columns: columns, stickedToEndColumns: stickedToEndColumns, extraInfo: extraInfo }, keyExtractor(item, startIndex + index)))), Option.fromNullable(loading)
361
+ .map(({ startIndex, endIndex, data }) => (_jsxs(View, { style: styles.rowsContainer, ref: rowsContainerRef, children: [data.map((item, index) => (_jsx(VirtualizedRow, { viewId: viewId, item: item, rowHeight: rowHeight, absoluteIndex: startIndex + index, variant: variant, stickedToStartColumnsWidth: stickedToStartColumnsWidth, centerColumnsWidth: centerColumnsWidth, stickedToEndColumnsWidth: stickedToEndColumnsWidth, stickedToStartColumns: stickedToStartColumns, columns: columns, stickedToEndColumns: stickedToEndColumns, extraInfo: extraInfo, horizontalScrollPosition: horizontalScrollPosition !== null && horizontalScrollPosition !== void 0 ? horizontalScrollPosition : "NoScroll", stickedToStartFirstCellLeftPadding: stickedToStartFirstCellLeftPadding, centerFirstCellLeftPadding: centerFirstCellLeftPadding, centerLastCellLeftPadding: centerLastCellLeftPadding, stickedToEndLastCellRightPadding: stickedToEndLastCellRightPadding }, keyExtractor(item, startIndex + index)))), Option.fromNullable(loading)
337
362
  .flatMap(({ isLoading, count }) => (isLoading ? Option.Some(count) : Option.None()))
338
363
  .map(count => (_jsx(View, { "aria-busy": true, style: [
339
364
  styles.loadingPlaceholder,
@@ -346,7 +371,7 @@ export const VirtualizedList = ({ variant, data, stickedToStartColumns, columns,
346
371
  .map(clientHeight => (_jsx(View, { style: styles.shadowsLayerContainer, children: _jsxs(View, { style: [styles.shadowsLayer, { height: clientHeight - 12 }], children: [startColumnShadow.toNull(), _jsx(View, { style: { width: centerColumnsWidth } }), endColumnShadow.toNull()] }) })))
347
372
  .toNull(), _jsx(View, { style: [styles.scrollTracker, { height: onEndReachedThreshold }], ref: scrollTrackerRef })] }));
348
373
  };
349
- const RawVirtualizedRow = ({ viewId, rowHeight, absoluteIndex, variant, stickedToStartColumnsWidth, centerColumnsWidth, stickedToEndColumnsWidth, stickedToStartColumns, columns, stickedToEndColumns, extraInfo, item, getRowLink, }) => {
374
+ const RawVirtualizedRow = ({ viewId, rowHeight, absoluteIndex, variant, stickedToStartColumnsWidth, centerColumnsWidth, stickedToEndColumnsWidth, stickedToStartColumns, columns, stickedToEndColumns, extraInfo, item, horizontalScrollPosition, getRowLink, stickedToStartFirstCellLeftPadding, centerFirstCellLeftPadding, centerLastCellLeftPadding, stickedToEndLastCellRightPadding, }) => {
350
375
  var _a;
351
376
  const [isHovered, setIsHovered] = useState(false);
352
377
  const elementRef = useRef(null);
@@ -359,30 +384,32 @@ const RawVirtualizedRow = ({ viewId, rowHeight, absoluteIndex, variant, stickedT
359
384
  ref: elementRef,
360
385
  style: [
361
386
  styles.row,
362
- isHovered && {
363
- backgroundColor: variant === "accented"
364
- ? backgroundColorVariants.default
365
- : backgroundColorVariants.accented,
366
- },
367
387
  {
368
388
  backgroundColor: backgroundColorVariants[variant],
369
389
  top: absoluteIndex * rowHeight,
370
390
  height: rowHeight,
371
391
  },
392
+ isHovered && {
393
+ backgroundColor: variant === "accented"
394
+ ? backgroundColorVariants.default
395
+ : backgroundColorVariants.accented,
396
+ },
372
397
  ],
373
398
  children: (_jsxs(_Fragment, { children: [Option.fromNullable(stickedToStartColumns)
374
399
  .map(columns => (_jsx(View, { style: [
375
400
  styles.cellsContainer,
376
401
  styles.stickedToStartColumnGroup,
402
+ horizontalScrollPosition === "NoScroll" && styles.stickedToStartColumnGroupLocked,
377
403
  {
378
404
  width: stickedToStartColumnsWidth,
379
405
  backgroundColor: isHovered
380
406
  ? backgroundColorVariants[variant === "accented" ? "default" : "accented"]
381
407
  : backgroundColorVariants[variant],
382
408
  },
383
- ], children: columns.map(({ id, width, renderCell }) => {
409
+ ], children: columns.map(({ id, width, renderCell }, index) => {
384
410
  const columnId = `${viewId}_${id}`;
385
- return (_jsx(View, { style: [styles.cell, { width }], "aria-describedby": columnId, children: renderCell({
411
+ const paddingLeft = index === 0 ? stickedToStartFirstCellLeftPadding : 0;
412
+ return (_jsx(View, { style: [styles.cell, { width: width + paddingLeft, paddingLeft }], "aria-describedby": columnId, children: renderCell({
386
413
  columnId,
387
414
  item,
388
415
  index: absoluteIndex,
@@ -398,9 +425,14 @@ const RawVirtualizedRow = ({ viewId, rowHeight, absoluteIndex, variant, stickedT
398
425
  ? backgroundColorVariants[variant === "accented" ? "default" : "accented"]
399
426
  : backgroundColorVariants[variant],
400
427
  },
401
- ], children: columns.map(({ id, width, renderCell }) => {
428
+ ], children: columns.map(({ id, width, renderCell }, index) => {
402
429
  const columnId = `${viewId}_${id}`;
403
- return (_jsx(View, { style: [styles.cell, { width }], "aria-describedby": columnId, children: renderCell({
430
+ const paddingLeft = index === 0 ? centerFirstCellLeftPadding : 0;
431
+ const paddingRight = index === columns.length - 1 ? centerLastCellLeftPadding : 0;
432
+ return (_jsx(View, { style: [
433
+ styles.cell,
434
+ { width: width + paddingLeft + paddingRight, paddingLeft, paddingRight },
435
+ ], "aria-describedby": columnId, children: renderCell({
404
436
  columnId,
405
437
  item,
406
438
  index: absoluteIndex,
@@ -411,15 +443,17 @@ const RawVirtualizedRow = ({ viewId, rowHeight, absoluteIndex, variant, stickedT
411
443
  .map(columns => (_jsx(View, { style: [
412
444
  styles.cellsContainer,
413
445
  styles.stickedToEndColumnGroup,
446
+ horizontalScrollPosition === "NoScroll" && styles.stickedToEndColumnGroupLocked,
414
447
  {
415
448
  width: stickedToEndColumnsWidth,
416
449
  backgroundColor: isHovered
417
450
  ? backgroundColorVariants[variant === "accented" ? "default" : "accented"]
418
451
  : backgroundColorVariants[variant],
419
452
  },
420
- ], children: columns.map(({ id, width, renderCell }) => {
453
+ ], children: columns.map(({ id, width, renderCell }, index) => {
421
454
  const columnId = `${viewId}_${id}`;
422
- return (_jsx(View, { style: [styles.cell, { width }], "aria-describedby": columnId, children: renderCell({
455
+ const paddingRight = index === columns.length - 1 ? stickedToEndLastCellRightPadding : 0;
456
+ return (_jsx(View, { style: [styles.cell, { width: width + paddingRight, paddingRight }], "aria-describedby": columnId, children: renderCell({
423
457
  columnId,
424
458
  item,
425
459
  index: absoluteIndex,