Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions example/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { View, Text, Progress, render } from '../../packages/core';
import { View, Text, Progress, FlatList, render } from '../../packages/core';

function Section({
label,
Expand Down Expand Up @@ -71,9 +71,21 @@ class App extends React.Component {
</Section>
<Section label="Logs" growable>
<View bgColor="blue" width="100%" height="100%" testID="container">
<View marginTop={0} testID="textBox">
<Text>This should take the remaining space</Text>
</View>
<FlatList
itemHeight={3}
data={new Array(20).fill(null).map((_, i) => `Element #${i}`)}
keyExtractor={({ item }) => item}
renderItem={({ item, index }) => (
<View
paddingTop={1}
paddingBottom={1}
bgColor={index % 2 === 0 ? 'green' : 'magenta'}
width="100%"
>
<Text>{item}</Text>
</View>
)}
/>
</View>
</Section>
</View>
Expand Down
1 change: 1 addition & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"main": "build/index.js",
"files": [
"/build",
"/vendor",
"index.js",
"README.md"
],
Expand Down
151 changes: 151 additions & 0 deletions packages/core/src/components/FlatList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import React from 'react';
import View from './View';
import { OnLayoutHook, MouseEventHandler } from '../types';
import renderToJson from '../renderToJson';

type Element<T> = {
item: T;
index: number;
};

type Props<T> = {
itemHeight: number | 'infer';
data: T[];
renderItem: (element: Element<T>) => JSX.Element;
keyExtractor: (element: Element<T>) => number | string;
scrollMultiplier?: (data: {
viewportHeight: number;
itemHeight: number;
}) => number;
};

type State = {
height: number;
offset: number;
};

function throttle<T extends Function>(func: T, limit: number) {
let inThrottle: boolean;
return (...args: any[]) => {
if (!inThrottle) {
func(...args);
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
}
};
}

export default class FlatList<T> extends React.Component<Props<T>, State> {
state = {
height: 0,
offset: 0,
};

inferredItemHeight?: number;
scrollScale = 0;

getScrollScale() {
if (this.scrollScale === 0) {
this.scrollScale = this.props.scrollMultiplier
? this.props.scrollMultiplier({
viewportHeight: this.state.height,
itemHeight: this.getItemHeight(),
})
: this.getItemHeight();
}

return this.scrollScale;
}

onLayout: OnLayoutHook = layout => {
if (layout.height !== this.state.height) {
this.setState({ height: layout.height });
}
};

onWheel = throttle<MouseEventHandler>(evt => {
this.scrollBy(-(evt.direction || 0) * this.getScrollScale());
}, 16);

getItemHeight(): number {
if (typeof this.props.itemHeight === 'number') {
return this.props.itemHeight;
}

if (
this.props.itemHeight === 'infer' &&
this.inferredItemHeight === undefined
) {
const { snapshot } = renderToJson(
this.props.renderItem({ item: this.props.data[0], index: 0 }),
{ maxRenders: 1 }
);
this.inferredItemHeight = snapshot ? snapshot.height : 0;
} else if (
this.props.itemHeight === 'infer' &&
this.inferredItemHeight !== undefined
) {
return this.inferredItemHeight;
}

return 0;
}

getTotalItemsHeight(): number {
return this.props.data.length * this.getItemHeight();
}

scrollBy(offset: number) {
this.setState(state => ({
offset: Math.min(
Math.max(0, state.offset + offset),
this.getTotalItemsHeight() - state.height
),
}));
}

renderItemsInViewport() {
const { height, offset } = this.state;
const itemHeight = this.getItemHeight();

const paddingTop = -offset % itemHeight;
const elementCountInViewport = Math.round(height / itemHeight) + 1;
const sliceStart = Math.floor(offset / itemHeight);

return (
<View
paddingTop={paddingTop}
height="100%"
width="100%"
flexDirection="column"
justifyContent="flex-start"
>
{this.props.data
.slice(sliceStart, sliceStart + elementCountInViewport)
.map((item, index) => (
<View
key={this.props.keyExtractor({ item, index: index + sliceStart })}
height={itemHeight}
width="100%"
flexShrink={0}
>
{this.props.renderItem({ item, index: index + sliceStart })}
</View>
))}
</View>
);
}

render() {
return (
<View
onLayout={this.onLayout}
height="100%"
width="100%"
onWheel={this.onWheel}
>
{this.state.height > 0 ? this.renderItemsInViewport() : null}
</View>
);
}
}
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ export { default as View, Props as ViewProps } from './components/View';
export { default as Text, Props as TextProps } from './components/Text';
export { default as Progress } from './components/Progress';
export { default as Spinner } from './components/Spinner';
export { default as FlatList } from './components/FlatList';

export { TextTransform, JsonText, JsonView, JsonParagraph } from './types';
4 changes: 2 additions & 2 deletions packages/core/src/layout.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Allocator } from './vendor/stretch_layout';
import { Allocator } from '../vendor/stretch_layout';
export {
Node,
Layout,
Expand All @@ -14,6 +14,6 @@ export {
AlignContent,
AlignSelf,
JustifyContent,
} from './vendor/stretch_layout';
} from '../vendor/stretch_layout';

export const allocator = new Allocator();
Loading