@oxyhq/bloom 0.24.1 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/list/index.js +94 -0
- package/lib/commonjs/list/index.js.map +1 -0
- package/lib/commonjs/list/index.web.js +216 -0
- package/lib/commonjs/list/index.web.js.map +1 -0
- package/lib/commonjs/list/types.js +6 -0
- package/lib/commonjs/list/types.js.map +1 -0
- package/lib/module/list/index.js +89 -0
- package/lib/module/list/index.js.map +1 -0
- package/lib/module/list/index.web.js +211 -0
- package/lib/module/list/index.web.js.map +1 -0
- package/lib/module/list/types.js +4 -0
- package/lib/module/list/types.js.map +1 -0
- package/lib/typescript/commonjs/list/index.d.ts +29 -0
- package/lib/typescript/commonjs/list/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/list/index.web.d.ts +45 -0
- package/lib/typescript/commonjs/list/index.web.d.ts.map +1 -0
- package/lib/typescript/commonjs/list/types.d.ts +77 -0
- package/lib/typescript/commonjs/list/types.d.ts.map +1 -0
- package/lib/typescript/module/list/index.d.ts +29 -0
- package/lib/typescript/module/list/index.d.ts.map +1 -0
- package/lib/typescript/module/list/index.web.d.ts +45 -0
- package/lib/typescript/module/list/index.web.d.ts.map +1 -0
- package/lib/typescript/module/list/types.d.ts +77 -0
- package/lib/typescript/module/list/types.d.ts.map +1 -0
- package/package.json +20 -1
- package/src/__tests__/VirtualList.native.test.tsx +67 -0
- package/src/__tests__/VirtualList.web.test.tsx +127 -0
- package/src/list/index.tsx +121 -0
- package/src/list/index.web.tsx +261 -0
- package/src/list/types.ts +85 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["React","StyleSheet","View","useWindowVirtualizer","jsxs","_jsxs","jsx","_jsx","DEFAULT_ESTIMATED_ITEM_SIZE","DEFAULT_OVERSCAN","ROW_REF_CACHE_CAP","renderSlot","slot","VirtualListWebInner","props","ref","data","renderItem","keyExtractor","ListHeaderComponent","ListEmptyComponent","ListFooterComponent","style","contentContainerStyle","estimatedItemSize","overscan","testID","items","count","length","wrapperRef","useRef","scrollMargin","setScrollMargin","useState","measureScrollMargin","useCallback","node","current","window","top","getBoundingClientRect","scrollY","prev","useLayoutEffect","estimate","virtualizer","estimateSize","getItemKey","index","item","undefined","useEffect","sync","measure","addEventListener","removeEventListener","useImperativeHandle","scrollToOffset","params","scrollTo","offset","behavior","animated","y","measureElement","rowRefCallbacks","Map","getRowRef","key","cache","existing","get","size","clear","cb","set","flatStyle","flatten","flatContentStyle","header","footer","minHeight","children","virtualItems","getVirtualItems","totalSize","getTotalSize","lastItem","lastItemEnd","start","options","spacerHeight","Math","max","height","width","position","map","virtualRow","String","left","transform","VirtualList","forwardRef"],"sourceRoot":"../../../src","sources":["list/index.web.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":[],"sourceRoot":"../../../src","sources":["list/types.ts"],"mappings":"","ignoreList":[]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `VirtualList` — the canonical cross-platform virtualized list for the Oxy
|
|
3
|
+
* ecosystem. Every app uses this ONE implementation for grouped/stacked row
|
|
4
|
+
* lists (who-to-follow, starter packs, connections, settings rows, …) so the
|
|
5
|
+
* virtualization behaviour is identical everywhere.
|
|
6
|
+
*
|
|
7
|
+
* NATIVE variant: a thin, correctly-typed wrapper over React Native `FlatList`.
|
|
8
|
+
* The consumer lists this component replaces are short and uniform, so the
|
|
9
|
+
* platform's own virtualization is the simplest robust choice — no
|
|
10
|
+
* `@legendapp/list`, no app-specific scroll bridge, nothing for a consumer to
|
|
11
|
+
* wire up. Bloom stays app-agnostic.
|
|
12
|
+
*
|
|
13
|
+
* WEB variant (`index.web.tsx`, selected via the `"browser"` export condition):
|
|
14
|
+
* a document-scroll window virtualizer built on `@tanstack/react-virtual`.
|
|
15
|
+
*
|
|
16
|
+
* This default file has NO platform-only imports, so consumer `tsc` and web
|
|
17
|
+
* bundlers resolve it cleanly (mirrors the `../scroll` / `../content-panel`
|
|
18
|
+
* fork pattern). The public API is shared via `./types`, so both forks expose
|
|
19
|
+
* an identical, generic, strongly-typed surface.
|
|
20
|
+
*/
|
|
21
|
+
import * as React from 'react';
|
|
22
|
+
import type { VirtualListHandle, VirtualListProps } from './types';
|
|
23
|
+
export type { VirtualListHandle, VirtualListProps, VirtualListRenderItem, VirtualListRenderItemInfo, VirtualListSlot, } from './types';
|
|
24
|
+
declare const VirtualList: <T>(props: VirtualListProps<T> & {
|
|
25
|
+
ref?: React.Ref<VirtualListHandle>;
|
|
26
|
+
}) => React.ReactElement;
|
|
27
|
+
export { VirtualList };
|
|
28
|
+
export default VirtualList;
|
|
29
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/list/index.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,KAAK,EACV,iBAAiB,EACjB,gBAAgB,EAEjB,MAAM,SAAS,CAAC;AAEjB,YAAY,EACV,iBAAiB,EACjB,gBAAgB,EAChB,qBAAqB,EACrB,yBAAyB,EACzB,eAAe,GAChB,MAAM,SAAS,CAAC;AAgFjB,QAAA,MAAM,WAAW,EAA+C,CAAC,CAAC,EAChE,KAAK,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG;IAAE,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAA;CAAE,KAChE,KAAK,CAAC,YAAY,CAAC;AAExB,OAAO,EAAE,WAAW,EAAE,CAAC;AACvB,eAAe,WAAW,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `VirtualList` — WEB variant. A document-scroll (window) virtualizer built on
|
|
3
|
+
* `@tanstack/react-virtual`'s `useWindowVirtualizer`, mirroring the proven
|
|
4
|
+
* Mention feed virtualizer. The BODY scrolls (so scrolling works from anywhere,
|
|
5
|
+
* including over sticky side columns) and only the rows in the virtual window
|
|
6
|
+
* are mounted, so the DOM stays bounded.
|
|
7
|
+
*
|
|
8
|
+
* Why this exists (the bug it fixes): a previous app-local list used the same
|
|
9
|
+
* hook but rendered ZERO rows in production — the wrapper grew to the right
|
|
10
|
+
* height (`getTotalSize()` was correct) yet `getVirtualItems()` came back empty,
|
|
11
|
+
* so only the header painted. Root cause: `useWindowVirtualizer` can latch an
|
|
12
|
+
* initial frame in which the window rect measured as `0` (or `scrollMargin` was
|
|
13
|
+
* still stale), which leaves the computed range empty; a STATIC, finite list
|
|
14
|
+
* re-renders too few times to self-correct and sticks on that bad frame. (The
|
|
15
|
+
* feed only survived by incidental re-render churn — data/loading updates and
|
|
16
|
+
* scroll/intersection observers — which a plain list does not have.)
|
|
17
|
+
*
|
|
18
|
+
* This implementation is SELF-SUFFICIENT and does not rely on incidental churn:
|
|
19
|
+
*
|
|
20
|
+
* 1. `scrollMargin` (the wrapper's offset from the document top) is measured in
|
|
21
|
+
* a layout effect AND re-synced on window `resize`.
|
|
22
|
+
* 2. An explicit post-mount recompute (`virtualizer.measure()`) forces the
|
|
23
|
+
* range to be recomputed once the real viewport height + offset are known —
|
|
24
|
+
* guaranteeing a short list mounts EVERY row in the first viewport even with
|
|
25
|
+
* zero further re-renders. Re-run on resize.
|
|
26
|
+
* 3. The measured spacer is `Math.max(totalSize, lastItemEnd)`, so it always
|
|
27
|
+
* contains every absolutely-positioned row even when `scrollMargin` is
|
|
28
|
+
* momentarily stale; the document therefore always grows to the full content
|
|
29
|
+
* height (keeping sticky side rails pinned) and the window-scroll geometry
|
|
30
|
+
* the virtualizer reads stays consistent.
|
|
31
|
+
* 4. Per-row `measureElement` refs are STABLE (cached per key), so rows are not
|
|
32
|
+
* detached/re-measured on every render.
|
|
33
|
+
*
|
|
34
|
+
* Native bundlers use `./index.tsx` (a `FlatList` wrapper); web bundlers select
|
|
35
|
+
* this file via the `"browser"` export condition in `package.json`.
|
|
36
|
+
*/
|
|
37
|
+
import * as React from 'react';
|
|
38
|
+
import type { VirtualListHandle, VirtualListProps } from './types';
|
|
39
|
+
export type { VirtualListHandle, VirtualListProps, VirtualListRenderItem, VirtualListRenderItemInfo, VirtualListSlot, } from './types';
|
|
40
|
+
declare const VirtualList: <T>(props: VirtualListProps<T> & {
|
|
41
|
+
ref?: React.Ref<VirtualListHandle>;
|
|
42
|
+
}) => React.ReactElement;
|
|
43
|
+
export { VirtualList };
|
|
44
|
+
export default VirtualList;
|
|
45
|
+
//# sourceMappingURL=index.web.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.web.d.ts","sourceRoot":"","sources":["../../../../src/list/index.web.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAK/B,OAAO,KAAK,EACV,iBAAiB,EACjB,gBAAgB,EAEjB,MAAM,SAAS,CAAC;AAEjB,YAAY,EACV,iBAAiB,EACjB,gBAAgB,EAChB,qBAAqB,EACrB,yBAAyB,EACzB,eAAe,GAChB,MAAM,SAAS,CAAC;AA0MjB,QAAA,MAAM,WAAW,EAA4C,CAAC,CAAC,EAC7D,KAAK,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG;IAAE,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAA;CAAE,KAChE,KAAK,CAAC,YAAY,CAAC;AAExB,OAAO,EAAE,WAAW,EAAE,CAAC;AACvB,eAAe,WAAW,CAAC"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { ReactElement } from 'react';
|
|
2
|
+
import type { StyleProp, ViewStyle } from 'react-native';
|
|
3
|
+
/** Info passed to {@link VirtualListProps.renderItem} for each row. */
|
|
4
|
+
export interface VirtualListRenderItemInfo<T> {
|
|
5
|
+
item: T;
|
|
6
|
+
index: number;
|
|
7
|
+
}
|
|
8
|
+
/** Row renderer. Returns the element for a single item (or `null` to skip). */
|
|
9
|
+
export type VirtualListRenderItem<T> = (info: VirtualListRenderItemInfo<T>) => ReactElement | null;
|
|
10
|
+
/**
|
|
11
|
+
* A header / empty / footer slot. Either a ready element or a thunk that
|
|
12
|
+
* produces one — both `<X />` and `() => <X />` call sites are supported,
|
|
13
|
+
* matching React Native `FlatList` slot ergonomics.
|
|
14
|
+
*/
|
|
15
|
+
export type VirtualListSlot = ReactElement | (() => ReactElement | null) | null | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Props for {@link VirtualList} — the canonical cross-platform virtualized list
|
|
18
|
+
* for the Oxy ecosystem. The shape is a precise drop-in for the `FlatList` /
|
|
19
|
+
* `@legendapp/list` call sites it replaces: every accepted prop is listed
|
|
20
|
+
* explicitly (no index signature, no `any`).
|
|
21
|
+
*/
|
|
22
|
+
export interface VirtualListProps<T> {
|
|
23
|
+
/** Row data. */
|
|
24
|
+
data?: readonly T[] | null;
|
|
25
|
+
/** Renders one row. */
|
|
26
|
+
renderItem?: VirtualListRenderItem<T>;
|
|
27
|
+
/** Stable key for a row; strongly recommended for correct virtualization. */
|
|
28
|
+
keyExtractor?: (item: T, index: number) => string;
|
|
29
|
+
/** Rendered above the rows; scrolls away with the content. */
|
|
30
|
+
ListHeaderComponent?: VirtualListSlot;
|
|
31
|
+
/** Rendered (in place of rows) when `data` is empty. */
|
|
32
|
+
ListEmptyComponent?: VirtualListSlot;
|
|
33
|
+
/** Rendered below the rows. */
|
|
34
|
+
ListFooterComponent?: VirtualListSlot;
|
|
35
|
+
/** Style for the outer list container. */
|
|
36
|
+
style?: StyleProp<ViewStyle>;
|
|
37
|
+
/** Style for the inner content wrapper (e.g. padding). */
|
|
38
|
+
contentContainerStyle?: StyleProp<ViewStyle>;
|
|
39
|
+
/**
|
|
40
|
+
* First-paint row-height estimate (px) used before a row is measured. On web
|
|
41
|
+
* each row is measured after mount and this is only the initial guess.
|
|
42
|
+
*/
|
|
43
|
+
estimatedItemSize?: number;
|
|
44
|
+
/** Web: extra rows mounted beyond the viewport (virtualization overscan). */
|
|
45
|
+
overscan?: number;
|
|
46
|
+
/** Called when the end of the list is approached (native pagination). */
|
|
47
|
+
onEndReached?: () => void;
|
|
48
|
+
/** Native: viewport-relative distance from the end that fires `onEndReached`. */
|
|
49
|
+
onEndReachedThreshold?: number;
|
|
50
|
+
/** Pull-to-refresh active flag (native). */
|
|
51
|
+
refreshing?: boolean;
|
|
52
|
+
/** Pull-to-refresh handler (native). */
|
|
53
|
+
onRefresh?: () => void;
|
|
54
|
+
/** Test id applied to the outer container. */
|
|
55
|
+
testID?: string;
|
|
56
|
+
removeClippedSubviews?: boolean;
|
|
57
|
+
maxToRenderPerBatch?: number;
|
|
58
|
+
windowSize?: number;
|
|
59
|
+
initialNumToRender?: number;
|
|
60
|
+
recycleItems?: boolean;
|
|
61
|
+
maintainVisibleContentPosition?: boolean;
|
|
62
|
+
}
|
|
63
|
+
/** Imperative handle exposed via `ref`. */
|
|
64
|
+
export interface VirtualListHandle {
|
|
65
|
+
/** Scroll to an absolute offset (px). Web scrolls the document. */
|
|
66
|
+
scrollToOffset: (params?: {
|
|
67
|
+
offset?: number;
|
|
68
|
+
animated?: boolean;
|
|
69
|
+
}) => void;
|
|
70
|
+
/** Scroll to a coordinate. Web scrolls the document to `y`. */
|
|
71
|
+
scrollTo: (params?: {
|
|
72
|
+
x?: number;
|
|
73
|
+
y?: number;
|
|
74
|
+
animated?: boolean;
|
|
75
|
+
}) => void;
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/list/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzD,uEAAuE;AACvE,MAAM,WAAW,yBAAyB,CAAC,CAAC;IAC1C,IAAI,EAAE,CAAC,CAAC;IACR,KAAK,EAAE,MAAM,CAAC;CACf;AAED,+EAA+E;AAC/E,MAAM,MAAM,qBAAqB,CAAC,CAAC,IAAI,CACrC,IAAI,EAAE,yBAAyB,CAAC,CAAC,CAAC,KAC/B,YAAY,GAAG,IAAI,CAAC;AAEzB;;;;GAIG;AACH,MAAM,MAAM,eAAe,GACvB,YAAY,GACZ,CAAC,MAAM,YAAY,GAAG,IAAI,CAAC,GAC3B,IAAI,GACJ,SAAS,CAAC;AAEd;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB,CAAC,CAAC;IACjC,gBAAgB;IAChB,IAAI,CAAC,EAAE,SAAS,CAAC,EAAE,GAAG,IAAI,CAAC;IAC3B,uBAAuB;IACvB,UAAU,CAAC,EAAE,qBAAqB,CAAC,CAAC,CAAC,CAAC;IACtC,6EAA6E;IAC7E,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAClD,8DAA8D;IAC9D,mBAAmB,CAAC,EAAE,eAAe,CAAC;IACtC,wDAAwD;IACxD,kBAAkB,CAAC,EAAE,eAAe,CAAC;IACrC,+BAA+B;IAC/B,mBAAmB,CAAC,EAAE,eAAe,CAAC;IACtC,0CAA0C;IAC1C,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,0DAA0D;IAC1D,qBAAqB,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7C;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yEAAyE;IACzE,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,iFAAiF;IACjF,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,4CAA4C;IAC5C,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,8CAA8C;IAC9C,MAAM,CAAC,EAAE,MAAM,CAAC;IAMhB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,8BAA8B,CAAC,EAAE,OAAO,CAAC;CAC1C;AAED,2CAA2C;AAC3C,MAAM,WAAW,iBAAiB;IAChC,mEAAmE;IACnE,cAAc,EAAE,CAAC,MAAM,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAC;IAC3E,+DAA+D;IAC/D,QAAQ,EAAE,CAAC,MAAM,CAAC,EAAE;QAAE,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAC;CAC7E"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `VirtualList` — the canonical cross-platform virtualized list for the Oxy
|
|
3
|
+
* ecosystem. Every app uses this ONE implementation for grouped/stacked row
|
|
4
|
+
* lists (who-to-follow, starter packs, connections, settings rows, …) so the
|
|
5
|
+
* virtualization behaviour is identical everywhere.
|
|
6
|
+
*
|
|
7
|
+
* NATIVE variant: a thin, correctly-typed wrapper over React Native `FlatList`.
|
|
8
|
+
* The consumer lists this component replaces are short and uniform, so the
|
|
9
|
+
* platform's own virtualization is the simplest robust choice — no
|
|
10
|
+
* `@legendapp/list`, no app-specific scroll bridge, nothing for a consumer to
|
|
11
|
+
* wire up. Bloom stays app-agnostic.
|
|
12
|
+
*
|
|
13
|
+
* WEB variant (`index.web.tsx`, selected via the `"browser"` export condition):
|
|
14
|
+
* a document-scroll window virtualizer built on `@tanstack/react-virtual`.
|
|
15
|
+
*
|
|
16
|
+
* This default file has NO platform-only imports, so consumer `tsc` and web
|
|
17
|
+
* bundlers resolve it cleanly (mirrors the `../scroll` / `../content-panel`
|
|
18
|
+
* fork pattern). The public API is shared via `./types`, so both forks expose
|
|
19
|
+
* an identical, generic, strongly-typed surface.
|
|
20
|
+
*/
|
|
21
|
+
import * as React from 'react';
|
|
22
|
+
import type { VirtualListHandle, VirtualListProps } from './types';
|
|
23
|
+
export type { VirtualListHandle, VirtualListProps, VirtualListRenderItem, VirtualListRenderItemInfo, VirtualListSlot, } from './types';
|
|
24
|
+
declare const VirtualList: <T>(props: VirtualListProps<T> & {
|
|
25
|
+
ref?: React.Ref<VirtualListHandle>;
|
|
26
|
+
}) => React.ReactElement;
|
|
27
|
+
export { VirtualList };
|
|
28
|
+
export default VirtualList;
|
|
29
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/list/index.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,KAAK,EACV,iBAAiB,EACjB,gBAAgB,EAEjB,MAAM,SAAS,CAAC;AAEjB,YAAY,EACV,iBAAiB,EACjB,gBAAgB,EAChB,qBAAqB,EACrB,yBAAyB,EACzB,eAAe,GAChB,MAAM,SAAS,CAAC;AAgFjB,QAAA,MAAM,WAAW,EAA+C,CAAC,CAAC,EAChE,KAAK,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG;IAAE,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAA;CAAE,KAChE,KAAK,CAAC,YAAY,CAAC;AAExB,OAAO,EAAE,WAAW,EAAE,CAAC;AACvB,eAAe,WAAW,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `VirtualList` — WEB variant. A document-scroll (window) virtualizer built on
|
|
3
|
+
* `@tanstack/react-virtual`'s `useWindowVirtualizer`, mirroring the proven
|
|
4
|
+
* Mention feed virtualizer. The BODY scrolls (so scrolling works from anywhere,
|
|
5
|
+
* including over sticky side columns) and only the rows in the virtual window
|
|
6
|
+
* are mounted, so the DOM stays bounded.
|
|
7
|
+
*
|
|
8
|
+
* Why this exists (the bug it fixes): a previous app-local list used the same
|
|
9
|
+
* hook but rendered ZERO rows in production — the wrapper grew to the right
|
|
10
|
+
* height (`getTotalSize()` was correct) yet `getVirtualItems()` came back empty,
|
|
11
|
+
* so only the header painted. Root cause: `useWindowVirtualizer` can latch an
|
|
12
|
+
* initial frame in which the window rect measured as `0` (or `scrollMargin` was
|
|
13
|
+
* still stale), which leaves the computed range empty; a STATIC, finite list
|
|
14
|
+
* re-renders too few times to self-correct and sticks on that bad frame. (The
|
|
15
|
+
* feed only survived by incidental re-render churn — data/loading updates and
|
|
16
|
+
* scroll/intersection observers — which a plain list does not have.)
|
|
17
|
+
*
|
|
18
|
+
* This implementation is SELF-SUFFICIENT and does not rely on incidental churn:
|
|
19
|
+
*
|
|
20
|
+
* 1. `scrollMargin` (the wrapper's offset from the document top) is measured in
|
|
21
|
+
* a layout effect AND re-synced on window `resize`.
|
|
22
|
+
* 2. An explicit post-mount recompute (`virtualizer.measure()`) forces the
|
|
23
|
+
* range to be recomputed once the real viewport height + offset are known —
|
|
24
|
+
* guaranteeing a short list mounts EVERY row in the first viewport even with
|
|
25
|
+
* zero further re-renders. Re-run on resize.
|
|
26
|
+
* 3. The measured spacer is `Math.max(totalSize, lastItemEnd)`, so it always
|
|
27
|
+
* contains every absolutely-positioned row even when `scrollMargin` is
|
|
28
|
+
* momentarily stale; the document therefore always grows to the full content
|
|
29
|
+
* height (keeping sticky side rails pinned) and the window-scroll geometry
|
|
30
|
+
* the virtualizer reads stays consistent.
|
|
31
|
+
* 4. Per-row `measureElement` refs are STABLE (cached per key), so rows are not
|
|
32
|
+
* detached/re-measured on every render.
|
|
33
|
+
*
|
|
34
|
+
* Native bundlers use `./index.tsx` (a `FlatList` wrapper); web bundlers select
|
|
35
|
+
* this file via the `"browser"` export condition in `package.json`.
|
|
36
|
+
*/
|
|
37
|
+
import * as React from 'react';
|
|
38
|
+
import type { VirtualListHandle, VirtualListProps } from './types';
|
|
39
|
+
export type { VirtualListHandle, VirtualListProps, VirtualListRenderItem, VirtualListRenderItemInfo, VirtualListSlot, } from './types';
|
|
40
|
+
declare const VirtualList: <T>(props: VirtualListProps<T> & {
|
|
41
|
+
ref?: React.Ref<VirtualListHandle>;
|
|
42
|
+
}) => React.ReactElement;
|
|
43
|
+
export { VirtualList };
|
|
44
|
+
export default VirtualList;
|
|
45
|
+
//# sourceMappingURL=index.web.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.web.d.ts","sourceRoot":"","sources":["../../../../src/list/index.web.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAK/B,OAAO,KAAK,EACV,iBAAiB,EACjB,gBAAgB,EAEjB,MAAM,SAAS,CAAC;AAEjB,YAAY,EACV,iBAAiB,EACjB,gBAAgB,EAChB,qBAAqB,EACrB,yBAAyB,EACzB,eAAe,GAChB,MAAM,SAAS,CAAC;AA0MjB,QAAA,MAAM,WAAW,EAA4C,CAAC,CAAC,EAC7D,KAAK,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG;IAAE,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAA;CAAE,KAChE,KAAK,CAAC,YAAY,CAAC;AAExB,OAAO,EAAE,WAAW,EAAE,CAAC;AACvB,eAAe,WAAW,CAAC"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { ReactElement } from 'react';
|
|
2
|
+
import type { StyleProp, ViewStyle } from 'react-native';
|
|
3
|
+
/** Info passed to {@link VirtualListProps.renderItem} for each row. */
|
|
4
|
+
export interface VirtualListRenderItemInfo<T> {
|
|
5
|
+
item: T;
|
|
6
|
+
index: number;
|
|
7
|
+
}
|
|
8
|
+
/** Row renderer. Returns the element for a single item (or `null` to skip). */
|
|
9
|
+
export type VirtualListRenderItem<T> = (info: VirtualListRenderItemInfo<T>) => ReactElement | null;
|
|
10
|
+
/**
|
|
11
|
+
* A header / empty / footer slot. Either a ready element or a thunk that
|
|
12
|
+
* produces one — both `<X />` and `() => <X />` call sites are supported,
|
|
13
|
+
* matching React Native `FlatList` slot ergonomics.
|
|
14
|
+
*/
|
|
15
|
+
export type VirtualListSlot = ReactElement | (() => ReactElement | null) | null | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Props for {@link VirtualList} — the canonical cross-platform virtualized list
|
|
18
|
+
* for the Oxy ecosystem. The shape is a precise drop-in for the `FlatList` /
|
|
19
|
+
* `@legendapp/list` call sites it replaces: every accepted prop is listed
|
|
20
|
+
* explicitly (no index signature, no `any`).
|
|
21
|
+
*/
|
|
22
|
+
export interface VirtualListProps<T> {
|
|
23
|
+
/** Row data. */
|
|
24
|
+
data?: readonly T[] | null;
|
|
25
|
+
/** Renders one row. */
|
|
26
|
+
renderItem?: VirtualListRenderItem<T>;
|
|
27
|
+
/** Stable key for a row; strongly recommended for correct virtualization. */
|
|
28
|
+
keyExtractor?: (item: T, index: number) => string;
|
|
29
|
+
/** Rendered above the rows; scrolls away with the content. */
|
|
30
|
+
ListHeaderComponent?: VirtualListSlot;
|
|
31
|
+
/** Rendered (in place of rows) when `data` is empty. */
|
|
32
|
+
ListEmptyComponent?: VirtualListSlot;
|
|
33
|
+
/** Rendered below the rows. */
|
|
34
|
+
ListFooterComponent?: VirtualListSlot;
|
|
35
|
+
/** Style for the outer list container. */
|
|
36
|
+
style?: StyleProp<ViewStyle>;
|
|
37
|
+
/** Style for the inner content wrapper (e.g. padding). */
|
|
38
|
+
contentContainerStyle?: StyleProp<ViewStyle>;
|
|
39
|
+
/**
|
|
40
|
+
* First-paint row-height estimate (px) used before a row is measured. On web
|
|
41
|
+
* each row is measured after mount and this is only the initial guess.
|
|
42
|
+
*/
|
|
43
|
+
estimatedItemSize?: number;
|
|
44
|
+
/** Web: extra rows mounted beyond the viewport (virtualization overscan). */
|
|
45
|
+
overscan?: number;
|
|
46
|
+
/** Called when the end of the list is approached (native pagination). */
|
|
47
|
+
onEndReached?: () => void;
|
|
48
|
+
/** Native: viewport-relative distance from the end that fires `onEndReached`. */
|
|
49
|
+
onEndReachedThreshold?: number;
|
|
50
|
+
/** Pull-to-refresh active flag (native). */
|
|
51
|
+
refreshing?: boolean;
|
|
52
|
+
/** Pull-to-refresh handler (native). */
|
|
53
|
+
onRefresh?: () => void;
|
|
54
|
+
/** Test id applied to the outer container. */
|
|
55
|
+
testID?: string;
|
|
56
|
+
removeClippedSubviews?: boolean;
|
|
57
|
+
maxToRenderPerBatch?: number;
|
|
58
|
+
windowSize?: number;
|
|
59
|
+
initialNumToRender?: number;
|
|
60
|
+
recycleItems?: boolean;
|
|
61
|
+
maintainVisibleContentPosition?: boolean;
|
|
62
|
+
}
|
|
63
|
+
/** Imperative handle exposed via `ref`. */
|
|
64
|
+
export interface VirtualListHandle {
|
|
65
|
+
/** Scroll to an absolute offset (px). Web scrolls the document. */
|
|
66
|
+
scrollToOffset: (params?: {
|
|
67
|
+
offset?: number;
|
|
68
|
+
animated?: boolean;
|
|
69
|
+
}) => void;
|
|
70
|
+
/** Scroll to a coordinate. Web scrolls the document to `y`. */
|
|
71
|
+
scrollTo: (params?: {
|
|
72
|
+
x?: number;
|
|
73
|
+
y?: number;
|
|
74
|
+
animated?: boolean;
|
|
75
|
+
}) => void;
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/list/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzD,uEAAuE;AACvE,MAAM,WAAW,yBAAyB,CAAC,CAAC;IAC1C,IAAI,EAAE,CAAC,CAAC;IACR,KAAK,EAAE,MAAM,CAAC;CACf;AAED,+EAA+E;AAC/E,MAAM,MAAM,qBAAqB,CAAC,CAAC,IAAI,CACrC,IAAI,EAAE,yBAAyB,CAAC,CAAC,CAAC,KAC/B,YAAY,GAAG,IAAI,CAAC;AAEzB;;;;GAIG;AACH,MAAM,MAAM,eAAe,GACvB,YAAY,GACZ,CAAC,MAAM,YAAY,GAAG,IAAI,CAAC,GAC3B,IAAI,GACJ,SAAS,CAAC;AAEd;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB,CAAC,CAAC;IACjC,gBAAgB;IAChB,IAAI,CAAC,EAAE,SAAS,CAAC,EAAE,GAAG,IAAI,CAAC;IAC3B,uBAAuB;IACvB,UAAU,CAAC,EAAE,qBAAqB,CAAC,CAAC,CAAC,CAAC;IACtC,6EAA6E;IAC7E,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAClD,8DAA8D;IAC9D,mBAAmB,CAAC,EAAE,eAAe,CAAC;IACtC,wDAAwD;IACxD,kBAAkB,CAAC,EAAE,eAAe,CAAC;IACrC,+BAA+B;IAC/B,mBAAmB,CAAC,EAAE,eAAe,CAAC;IACtC,0CAA0C;IAC1C,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,0DAA0D;IAC1D,qBAAqB,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7C;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yEAAyE;IACzE,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,iFAAiF;IACjF,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,4CAA4C;IAC5C,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,8CAA8C;IAC9C,MAAM,CAAC,EAAE,MAAM,CAAC;IAMhB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,8BAA8B,CAAC,EAAE,OAAO,CAAC;CAC1C;AAED,2CAA2C;AAC3C,MAAM,WAAW,iBAAiB;IAChC,mEAAmE;IACnE,cAAc,EAAE,CAAC,MAAM,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAC;IAC3E,+DAA+D;IAC/D,QAAQ,EAAE,CAAC,MAAM,CAAC,EAAE;QAAE,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAC;CAC7E"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oxyhq/bloom",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
4
4
|
"description": "Bloom UI — Oxy ecosystem component library for React Native + Expo + Web",
|
|
5
5
|
"main": "lib/commonjs/index.js",
|
|
6
6
|
"module": "lib/module/index.js",
|
|
@@ -805,6 +805,22 @@
|
|
|
805
805
|
"default": "./lib/commonjs/content-panel/index.js"
|
|
806
806
|
}
|
|
807
807
|
},
|
|
808
|
+
"./list": {
|
|
809
|
+
"react-native": "./src/list/index.tsx",
|
|
810
|
+
"browser": {
|
|
811
|
+
"types": "./lib/typescript/module/list/index.web.d.ts",
|
|
812
|
+
"import": "./lib/module/list/index.web.js",
|
|
813
|
+
"require": "./lib/commonjs/list/index.web.js"
|
|
814
|
+
},
|
|
815
|
+
"import": {
|
|
816
|
+
"types": "./lib/typescript/module/list/index.d.ts",
|
|
817
|
+
"default": "./lib/module/list/index.js"
|
|
818
|
+
},
|
|
819
|
+
"require": {
|
|
820
|
+
"types": "./lib/typescript/commonjs/list/index.d.ts",
|
|
821
|
+
"default": "./lib/commonjs/list/index.js"
|
|
822
|
+
}
|
|
823
|
+
},
|
|
808
824
|
"./package.json": "./package.json"
|
|
809
825
|
},
|
|
810
826
|
"files": [
|
|
@@ -860,6 +876,8 @@
|
|
|
860
876
|
"label",
|
|
861
877
|
"kbd",
|
|
862
878
|
"item",
|
|
879
|
+
"list",
|
|
880
|
+
"virtual-list",
|
|
863
881
|
"ai"
|
|
864
882
|
],
|
|
865
883
|
"repository": {
|
|
@@ -992,6 +1010,7 @@
|
|
|
992
1010
|
]
|
|
993
1011
|
},
|
|
994
1012
|
"dependencies": {
|
|
1013
|
+
"@tanstack/react-virtual": "^3.14.3",
|
|
995
1014
|
"nanoid": "^5.1.5",
|
|
996
1015
|
"react-native-css": "3.0.7",
|
|
997
1016
|
"react-remove-scroll-bar": "^2.3.8"
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Text } from 'react-native';
|
|
3
|
+
import { render } from '@testing-library/react-native';
|
|
4
|
+
|
|
5
|
+
import { VirtualList, type VirtualListHandle } from '../list';
|
|
6
|
+
|
|
7
|
+
interface Row {
|
|
8
|
+
id: string;
|
|
9
|
+
label: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
describe('VirtualList (native)', () => {
|
|
13
|
+
it('renders the header and every row through FlatList', () => {
|
|
14
|
+
const data: Row[] = [
|
|
15
|
+
{ id: 'a', label: 'Alpha' },
|
|
16
|
+
{ id: 'b', label: 'Bravo' },
|
|
17
|
+
{ id: 'c', label: 'Charlie' },
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
const { getByText } = render(
|
|
21
|
+
<VirtualList<Row>
|
|
22
|
+
data={data}
|
|
23
|
+
keyExtractor={(item) => item.id}
|
|
24
|
+
renderItem={({ item }) => <Text>{item.label}</Text>}
|
|
25
|
+
ListHeaderComponent={<Text>Header row</Text>}
|
|
26
|
+
/>,
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
expect(getByText('Header row')).toBeTruthy();
|
|
30
|
+
expect(getByText('Alpha')).toBeTruthy();
|
|
31
|
+
expect(getByText('Bravo')).toBeTruthy();
|
|
32
|
+
expect(getByText('Charlie')).toBeTruthy();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('renders the empty slot (and no rows) when data is empty', () => {
|
|
36
|
+
const { getByText, queryByText } = render(
|
|
37
|
+
<VirtualList<Row>
|
|
38
|
+
data={[]}
|
|
39
|
+
keyExtractor={(item) => item.id}
|
|
40
|
+
renderItem={({ item }) => <Text>{item.label}</Text>}
|
|
41
|
+
ListEmptyComponent={<Text>Nothing here</Text>}
|
|
42
|
+
/>,
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
expect(getByText('Nothing here')).toBeTruthy();
|
|
46
|
+
expect(queryByText('Alpha')).toBeNull();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('exposes an imperative scroll handle via ref', () => {
|
|
50
|
+
const ref = React.createRef<VirtualListHandle>();
|
|
51
|
+
|
|
52
|
+
render(
|
|
53
|
+
<VirtualList<Row>
|
|
54
|
+
ref={ref}
|
|
55
|
+
data={[{ id: 'a', label: 'Alpha' }]}
|
|
56
|
+
keyExtractor={(item) => item.id}
|
|
57
|
+
renderItem={({ item }) => <Text>{item.label}</Text>}
|
|
58
|
+
/>,
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
expect(typeof ref.current?.scrollToOffset).toBe('function');
|
|
62
|
+
expect(typeof ref.current?.scrollTo).toBe('function');
|
|
63
|
+
// The FlatList mock does not implement scrollToOffset; calling the handle
|
|
64
|
+
// must be a safe no-op (optional-chained internal ref), never a throw.
|
|
65
|
+
expect(() => ref.current?.scrollToOffset({ offset: 0 })).not.toThrow();
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment jsdom
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import { act } from 'react';
|
|
7
|
+
import { createRoot, type Root } from 'react-dom/client';
|
|
8
|
+
import { getByText, queryByText } from '@testing-library/dom';
|
|
9
|
+
import '@testing-library/jest-dom';
|
|
10
|
+
|
|
11
|
+
import { VirtualList } from '../list/index.web';
|
|
12
|
+
|
|
13
|
+
// `@tanstack/react-virtual` measures rows via ResizeObserver + getBoundingClientRect,
|
|
14
|
+
// neither of which jsdom implements with a real layout engine. Stub both so the
|
|
15
|
+
// window virtualizer computes a deterministic, non-empty range against jsdom's
|
|
16
|
+
// default 768px-tall window — exactly the path that regressed in production
|
|
17
|
+
// (rows present, wrapper sized, but `getVirtualItems()` came back empty).
|
|
18
|
+
class ResizeObserverStub {
|
|
19
|
+
constructor(_cb: ResizeObserverCallback) {}
|
|
20
|
+
observe(): void {}
|
|
21
|
+
unobserve(): void {}
|
|
22
|
+
disconnect(): void {}
|
|
23
|
+
}
|
|
24
|
+
globalThis.ResizeObserver = ResizeObserverStub;
|
|
25
|
+
|
|
26
|
+
const ROW_HEIGHT = 64;
|
|
27
|
+
|
|
28
|
+
beforeAll(() => {
|
|
29
|
+
jest
|
|
30
|
+
.spyOn(Element.prototype, 'getBoundingClientRect')
|
|
31
|
+
.mockImplementation((): DOMRect => ({
|
|
32
|
+
x: 0,
|
|
33
|
+
y: 0,
|
|
34
|
+
top: 0,
|
|
35
|
+
left: 0,
|
|
36
|
+
right: 0,
|
|
37
|
+
bottom: ROW_HEIGHT,
|
|
38
|
+
width: 0,
|
|
39
|
+
height: ROW_HEIGHT,
|
|
40
|
+
toJSON: () => ({}),
|
|
41
|
+
}));
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
afterAll(() => {
|
|
45
|
+
jest.restoreAllMocks();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
|
49
|
+
|
|
50
|
+
let container: HTMLDivElement;
|
|
51
|
+
let root: Root;
|
|
52
|
+
|
|
53
|
+
function mount(ui: React.ReactElement): HTMLElement {
|
|
54
|
+
act(() => {
|
|
55
|
+
root.render(ui);
|
|
56
|
+
});
|
|
57
|
+
return container;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
beforeEach(() => {
|
|
61
|
+
container = document.createElement('div');
|
|
62
|
+
document.body.appendChild(container);
|
|
63
|
+
root = createRoot(container);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
afterEach(() => {
|
|
67
|
+
act(() => root.unmount());
|
|
68
|
+
container.remove();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
interface Row {
|
|
72
|
+
id: string;
|
|
73
|
+
label: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
describe('VirtualList (web)', () => {
|
|
77
|
+
it('mounts EVERY row of a short list in the first viewport (the empty-rows regression)', () => {
|
|
78
|
+
const data: Row[] = Array.from({ length: 5 }, (_, i) => ({
|
|
79
|
+
id: String(i),
|
|
80
|
+
label: `Row ${i}`,
|
|
81
|
+
}));
|
|
82
|
+
|
|
83
|
+
const c = mount(
|
|
84
|
+
<VirtualList<Row>
|
|
85
|
+
data={data}
|
|
86
|
+
keyExtractor={(item) => item.id}
|
|
87
|
+
renderItem={({ item }) => <span>{item.label}</span>}
|
|
88
|
+
ListHeaderComponent={<div>List header</div>}
|
|
89
|
+
/>,
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
expect(getByText(c, 'List header')).toBeTruthy();
|
|
93
|
+
for (const row of data) {
|
|
94
|
+
expect(getByText(c, row.label)).toBeTruthy();
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('renders the header + empty slot (and no rows) when data is empty', () => {
|
|
99
|
+
const c = mount(
|
|
100
|
+
<VirtualList<Row>
|
|
101
|
+
data={[]}
|
|
102
|
+
keyExtractor={(item) => item.id}
|
|
103
|
+
renderItem={({ item }) => <span>{item.label}</span>}
|
|
104
|
+
ListHeaderComponent={<div>List header</div>}
|
|
105
|
+
ListEmptyComponent={<div>Nothing here</div>}
|
|
106
|
+
/>,
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
expect(getByText(c, 'List header')).toBeTruthy();
|
|
110
|
+
expect(getByText(c, 'Nothing here')).toBeTruthy();
|
|
111
|
+
expect(queryByText(c, 'Row 0')).toBeNull();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('supports a thunk slot for the header', () => {
|
|
115
|
+
const c = mount(
|
|
116
|
+
<VirtualList<Row>
|
|
117
|
+
data={[{ id: 'a', label: 'Only row' }]}
|
|
118
|
+
keyExtractor={(item) => item.id}
|
|
119
|
+
renderItem={({ item }) => <span>{item.label}</span>}
|
|
120
|
+
ListHeaderComponent={() => <div>Thunk header</div>}
|
|
121
|
+
/>,
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
expect(getByText(c, 'Thunk header')).toBeTruthy();
|
|
125
|
+
expect(getByText(c, 'Only row')).toBeTruthy();
|
|
126
|
+
});
|
|
127
|
+
});
|