diff --git a/.gitignore b/.gitignore index cfe2bb4..148db35 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ DerivedData *.ipa *.xcuserstate project.xcworkspace +.xcode.env.local # Android/IJ # diff --git a/README.md b/README.md index 2a307bf..31bbbaf 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ It invokes Yoga for precise layout measurements of Shadow Nodes and constructs a | Nested ShadowList (ScrollView) | ✅ | ❌ | | Natively Inverted List Support | ✅ | ❌ | | Smooth Scrolling | ✅ | ❌ | +| Dynamic Components | ❌ | ✅ | ## Scroll Performance | Number of Items | ShadowList | FlatList | FlashList | @@ -28,18 +29,39 @@ It invokes Yoga for precise layout measurements of Shadow Nodes and constructs a ## Important Note Shadowlist doesn't support state updates or dynamic prop calculations inside the renderItem function. Any changes to child components should be made through the data prop. This also applies to animations. This restriction will be addressed in future updates. -One temporary way to mitigate this is by implementing list pagination until the [following problem is addressed](https://github.com/reactwg/react-native-new-architecture/discussions/223). - ## Installation - CLI: Add the package to your project via `yarn add shadowlist` and run `pod install` in the `ios` directory. - Expo: Add the package to your project via `npx expo install shadowlist` and run `npx expo prebuild` in the root directory. - ## Usage ```js import {Shadowlist} from 'shadowlist'; +const stringify = (str: string) => `{{${str}}}`; + +type ElementProps = { + data: Array; +}; + +const Element = (props: ElementProps) => { + const handlePress = (event: GestureResponderEvent) => { + const elementDataIndex = __NATIVE_getRegistryElementMapping( + event.nativeEvent.target + ); + props.data[elementDataIndex]; + }; + + return ( + + + {stringify('id')} + {stringify('text')} + index: {stringify('position')} + + ); +}; + { void setInverted(T view, boolean value); void setHorizontal(T view, boolean value); void setInitialNumToRender(T view, int value); + void setNumColumns(T view, int value); void setInitialScrollIndex(T view, int value); void scrollToIndex(T view, int index, boolean animated); void scrollToOffset(T view, int offset, boolean animated); diff --git a/android/shadowlist/jni/CMakeLists.txt b/android/shadowlist/jni/CMakeLists.txt index e4dca5c..eef5447 100644 --- a/android/shadowlist/jni/CMakeLists.txt +++ b/android/shadowlist/jni/CMakeLists.txt @@ -15,6 +15,7 @@ file(GLOB LIB_CODEGEN_SRCS CONFIGURE_DEPENDS file(GLOB LIB_INCLUDES_SRCS CONFIGURE_DEPENDS ${LIB_CPP_DIR}/fenwick/*.cpp ${LIB_CPP_DIR}/json/*.hpp + ${LIB_CPP_DIR}/helpers/*.cpp ) add_library( @@ -28,6 +29,7 @@ target_include_directories(react_codegen_RNShadowlistSpec PUBLIC ${LIB_JNI_DIR}/ ${LIB_CPP_DIR}/fenwick ${LIB_CPP_DIR}/json + ${LIB_CPP_DIR}/helpers ${LIB_CPP_DIR}/react/renderer/components/SLContainerSpec ${LIB_CPP_DIR}/react/renderer/components/SLElementSpec ${LIB_CPP_DIR}/react/renderer/components/RNShadowlistSpec diff --git a/android/src/main/java/com/shadowlist/SLContainerManager.java b/android/src/main/java/com/shadowlist/SLContainerManager.java index 6f68312..2036ef8 100644 --- a/android/src/main/java/com/shadowlist/SLContainerManager.java +++ b/android/src/main/java/com/shadowlist/SLContainerManager.java @@ -122,6 +122,11 @@ public void setHorizontal(SLContainer view, boolean horizontal) { public void setInitialNumToRender(SLContainer view, int initialNumToRender) { } + @ReactProp(name = "numColumns") + @Override + public void setNumColumns(SLContainer view, int numColumns) { + } + @ReactProp(name = "initialScrollIndex") @Override public void setInitialScrollIndex(SLContainer view, int initialScrollIndex) { diff --git a/cpp/helpers/Offsetter.h b/cpp/helpers/Offsetter.h new file mode 100644 index 0000000..9e6d47b --- /dev/null +++ b/cpp/helpers/Offsetter.h @@ -0,0 +1,42 @@ +class Offsetter { + private: + float* offsets; + int columns; + + public: + Offsetter(int numColumns, float headerOffset = 0.0f) : columns(numColumns) { + offsets = new float[columns](); + for (int i = 0; i < columns; ++i) { + offsets[i] = headerOffset; + } + } + + void add(int column, float px) { + if (column >= 0 && column < columns) { + offsets[column] += px; + } + } + + float get(int column) const { + if (column >= 0 && column < columns) { + return offsets[column]; + } + return 0; + } + + float max() const { + if (columns == 0) return 0; + + float maxOffset = offsets[0]; + for (int i = 1; i < columns; ++i) { + if (offsets[i] > maxOffset) { + maxOffset = offsets[i]; + } + } + return maxOffset; + } + + ~Offsetter() { + delete[] offsets; + } +}; \ No newline at end of file diff --git a/cpp/react/renderer/components/SLContainerSpec/SLContainerProps.cpp b/cpp/react/renderer/components/SLContainerSpec/SLContainerProps.cpp index 1fcdb55..532916c 100644 --- a/cpp/react/renderer/components/SLContainerSpec/SLContainerProps.cpp +++ b/cpp/react/renderer/components/SLContainerSpec/SLContainerProps.cpp @@ -4,69 +4,41 @@ namespace facebook::react { -nlohmann::json convertDataProp( - const PropsParserContext& context, - const RawProps& rawProps, - const char* name, - const nlohmann::json sourceValue, - const nlohmann::json defaultValue) { - try { - std::string content = convertRawProp(context, rawProps, name, sourceValue, defaultValue); - nlohmann::json json = nlohmann::json::parse(content); - - if (!json.is_array()) { - throw std::runtime_error("data prop must be an array"); - } - - return json; - } catch (...) { - return nlohmann::json::array(); - } -} - -std::vector convertUniqueIdsProp( - const PropsParserContext& context, - const RawProps& rawProps, - const char* name, - const nlohmann::json sourceValue, - const nlohmann::json defaultValue) { - try { - std::vector uniqueIds; +SLContainerProps::SLContainerProps( + const PropsParserContext &context, + const SLContainerProps &sourceProps, + const RawProps &rawProps): ViewProps(context, sourceProps, rawProps), - if (!defaultValue.is_array()) { - throw std::runtime_error("data prop must be an array"); + data(convertRawProp(context, rawProps, "data", sourceProps.data, "[]")), + inverted(convertRawProp(context, rawProps, "inverted", sourceProps.inverted, false)), + horizontal(convertRawProp(context, rawProps, "horizontal", sourceProps.horizontal, false)), + initialNumToRender(convertRawProp(context, rawProps, "initialNumToRender", sourceProps.initialNumToRender, 10)), + numColumns(convertRawProp(context, rawProps, "numColumns", sourceProps.numColumns, 1)), + initialScrollIndex(convertRawProp(context, rawProps, "initialScrollIndex", sourceProps.initialScrollIndex, 0)) + { + try { + uniqueIds = {}; + parsed = nlohmann::json::parse(data).get(); + } catch (const nlohmann::json::parse_error& e) { + parsed = nlohmann::json::array(); + std::cerr << "SLContainerProps data parse: " << e.what() << ", at: " << e.byte << std::endl; + } catch (...) { + parsed = nlohmann::json::array(); + std::cerr << "SLContainerProps data parse: unknown" << std::endl; } - for (const auto& item : defaultValue) { + for (const auto& item : parsed) { if (item.contains("id") && item["id"].is_string()) { uniqueIds.push_back(item["id"].get()); } } - - return uniqueIds; - } catch (...) { - return std::vector(); } -} - -SLContainerProps::SLContainerProps( - const PropsParserContext &context, - const SLContainerProps &sourceProps, - const RawProps &rawProps): ViewProps(context, sourceProps, rawProps), - - data(convertDataProp(context, rawProps, "data", sourceProps.data, nlohmann::json::array())), - uniqueIds(convertUniqueIdsProp(context, rawProps, "uniqueIds", sourceProps.uniqueIds, data)), - inverted(convertRawProp(context, rawProps, "inverted", sourceProps.inverted, {})), - horizontal(convertRawProp(context, rawProps, "horizontal", sourceProps.horizontal, {})), - initialNumToRender(convertRawProp(context, rawProps, "initialNumToRender", sourceProps.initialNumToRender, {})), - initialScrollIndex(convertRawProp(context, rawProps, "initialScrollIndex", sourceProps.initialScrollIndex, {})) - {} const SLContainerProps::SLContainerDataItem& SLContainerProps::getElementByIndex(int index) const { - if (index < 0 || index >= data.size()) { + if (index < 0 || index >= parsed.size()) { throw std::out_of_range("Index out of range"); } - return data[index]; + return parsed[index]; } std::string SLContainerProps::getElementValueByPath(const SLContainerDataItem& element, const SLContainerDataItemPath& path) { diff --git a/cpp/react/renderer/components/SLContainerSpec/SLContainerProps.h b/cpp/react/renderer/components/SLContainerSpec/SLContainerProps.h index fea855a..61d53a3 100644 --- a/cpp/react/renderer/components/SLContainerSpec/SLContainerProps.h +++ b/cpp/react/renderer/components/SLContainerSpec/SLContainerProps.h @@ -5,6 +5,13 @@ #include "SLKeyExtractor.h" #include "json.hpp" +#ifndef RCT_DEBUG +#include +#ifdef ANDROID +#include +#endif +#endif + namespace facebook::react { class SLContainerProps final : public ViewProps { @@ -16,11 +23,13 @@ class SLContainerProps final : public ViewProps { using SLContainerDataItem = nlohmann::json; using SLContainerDataItemPath = std::string; - nlohmann::json data; + std::string data; + nlohmann::json parsed; std::vector uniqueIds; bool inverted = false; bool horizontal = false; int initialNumToRender = 10; + int numColumns = 1; int initialScrollIndex = 0; const SLContainerDataItem& getElementByIndex(int index) const; diff --git a/cpp/react/renderer/components/SLContainerSpec/SLContainerShadowNode.cpp b/cpp/react/renderer/components/SLContainerSpec/SLContainerShadowNode.cpp index ebf31bc..9865aeb 100644 --- a/cpp/react/renderer/components/SLContainerSpec/SLContainerShadowNode.cpp +++ b/cpp/react/renderer/components/SLContainerSpec/SLContainerShadowNode.cpp @@ -1,5 +1,6 @@ #include "SLContainerShadowNode.h" #include "SLTemplate.h" +#include "Offsetter.h" namespace facebook::react { @@ -51,9 +52,9 @@ void SLContainerShadowNode::layout(LayoutContext layoutContext) { */ auto containerShadowNodeChildren = std::make_shared(); - bool elementsDataPrepended = elementsDataSize && nextStateData.firstChildUniqueId.size() && + bool elementsDataPrepended = elementsDataSize && nextStateData.firstChildUniqueId.size() && props.uniqueIds.size() && nextStateData.firstChildUniqueId != props.uniqueIds.front(); - bool elementsDataAppended = elementsDataSize && nextStateData.lastChildUniqueId.size() && + bool elementsDataAppended = elementsDataSize && nextStateData.lastChildUniqueId.size() && props.uniqueIds.size() && nextStateData.lastChildUniqueId != props.uniqueIds.back(); if (elementsDataPrepended) { @@ -117,7 +118,7 @@ void SLContainerShadowNode::layout(LayoutContext layoutContext) { } // Prevent re-measuring if the height is already defined, as layouting is expensive - auto elementSize = layoutElement(layoutContext, componentRegistry[elementDataUniqueKey]); + auto elementSize = layoutElement(layoutContext, componentRegistry[elementDataUniqueKey], props.numColumns); nextStateData.childrenMeasurementsTree[elementDataIndex] = getRelativeSizeFromSize(elementSize.frame.size); return ComponentRegistryItem{ @@ -131,7 +132,7 @@ void SLContainerShadowNode::layout(LayoutContext layoutContext) { componentRegistry[elementDataUniqueKey] = templateRegistry[elementDataUniqueKey].back()->clone({}); // Prevent re-measuring if the height is already defined, as layouting is expensive - auto elementSize = layoutElement(layoutContext, componentRegistry[elementDataUniqueKey]); + auto elementSize = layoutElement(layoutContext, componentRegistry[elementDataUniqueKey], 0); nextStateData.templateMeasurementsTree[templateDataIndex] = getRelativeSizeFromSize(elementSize.frame.size); return ComponentRegistryItem{ @@ -152,12 +153,15 @@ void SLContainerShadowNode::layout(LayoutContext layoutContext) { containerShadowNodeChildren->push_back(componentRegistry["ListHeaderComponentUniqueId"]); } + + /* * Calculate sequence of indices above and below the current scroll index */ int scrollContentAboveIndex = 0; - float scrollContentAboveOffset = nextStateData.templateMeasurementsTree[0]; - auto scrollContentAboveComponents = std::views::iota(0, nextStateData.scrollIndex) + int scrollContentAboveBound = std::min(nextStateData.scrollIndex, (int) props.uniqueIds.size()); + Offsetter scrollContentAboveOffset{props.numColumns, nextStateData.templateMeasurementsTree[0]}; + auto scrollContentAboveComponents = std::views::iota(0, scrollContentAboveBound) | std::views::transform(transformElementComponent); /* @@ -165,11 +169,12 @@ void SLContainerShadowNode::layout(LayoutContext layoutContext) { * no longer visible on the screen */ int scrollContentBelowIndex = 0; - float scrollContentBelowOffset = 0; - auto scrollContentBelowComponents = std::views::iota(nextStateData.scrollIndex, elementsDataSize) + int scrollContentBelowBound = std::min(elementsDataSize, (int) props.uniqueIds.size()); + Offsetter scrollContentBelowOffset{props.numColumns}; + auto scrollContentBelowComponents = std::views::iota(nextStateData.scrollIndex, scrollContentBelowBound) | std::views::take_while([&](int i) { float scrollContentNextOffset = getRelativePointFromPoint(nextStateData.scrollPosition) + viewportOffset; - return scrollContentBelowOffset < scrollContentNextOffset; + return scrollContentBelowOffset.get(i % props.numColumns) < scrollContentNextOffset; }) | std::views::transform(transformElementComponent); @@ -179,14 +184,17 @@ void SLContainerShadowNode::layout(LayoutContext layoutContext) { */ for (ComponentRegistryItem componentRegistryItem : scrollContentAboveComponents) { auto elementMetrics = adjustElement( - scrollContentAboveOffset + scrollContentBelowOffset, + { + .x = (componentRegistryItem.index % props.numColumns) * (getLayoutMetrics().frame.size.width / props.numColumns), + .y = scrollContentAboveOffset.get(componentRegistryItem.index % props.numColumns) + scrollContentBelowOffset.get(componentRegistryItem.index % props.numColumns) + }, componentRegistry[componentRegistryItem.elementDataUniqueKey]); - scrollContentAboveOffset += componentRegistryItem.size; + scrollContentAboveOffset.add(componentRegistryItem.index % props.numColumns, componentRegistryItem.size); scrollContentAboveIndex = componentRegistryItem.index; int scrollPosition = nextStateData.scrollPositionUpdated ? ( - scrollContentAboveOffset + getRelativePointFromPoint(nextStateData.scrollPosition) - nextStateData.templateMeasurementsTree[0] + scrollContentAboveOffset.get(componentRegistryItem.index % props.numColumns) + getRelativePointFromPoint(nextStateData.scrollPosition) - nextStateData.templateMeasurementsTree[0] ) : getRelativePointFromPoint(nextStateData.scrollPosition); if (getRelativePointFromPoint(elementMetrics.frame.origin) <= (scrollPosition + getRelativeSizeFromSize(nextStateData.scrollContainer) + viewportOffset) && @@ -202,14 +210,17 @@ void SLContainerShadowNode::layout(LayoutContext layoutContext) { for (ComponentRegistryItem componentRegistryItem : scrollContentBelowComponents) { auto elementShadowNodeLayoutable = std::static_pointer_cast(componentRegistry[componentRegistryItem.elementDataUniqueKey]); auto elementMetrics = adjustElement( - scrollContentAboveOffset + scrollContentBelowOffset, + { + .x = (componentRegistryItem.index % props.numColumns) * (getLayoutMetrics().frame.size.width / props.numColumns), + .y = scrollContentAboveOffset.get(componentRegistryItem.index % props.numColumns) + scrollContentBelowOffset.get(componentRegistryItem.index % props.numColumns) + }, componentRegistry[componentRegistryItem.elementDataUniqueKey]); - scrollContentBelowOffset += componentRegistryItem.size; + scrollContentBelowOffset.add(componentRegistryItem.index % props.numColumns, componentRegistryItem.size); scrollContentBelowIndex = componentRegistryItem.index; int scrollPosition = nextStateData.scrollPositionUpdated ? ( - scrollContentAboveOffset + getRelativePointFromPoint(nextStateData.scrollPosition) - nextStateData.templateMeasurementsTree[0] + scrollContentAboveOffset.get(componentRegistryItem.index % props.numColumns) + getRelativePointFromPoint(nextStateData.scrollPosition) - nextStateData.templateMeasurementsTree[0] ) : getRelativePointFromPoint(nextStateData.scrollPosition); if (getRelativePointFromPoint(elementMetrics.frame.origin) <= (scrollPosition + getRelativeSizeFromSize(nextStateData.scrollContainer) + viewportOffset) && @@ -218,6 +229,20 @@ void SLContainerShadowNode::layout(LayoutContext layoutContext) { } } + if (!props.uniqueIds.size()) { + auto templateRegistryItem = transformTemplateComponent("ListEmptyComponentUniqueId", 1); + containerShadowNodeChildren->push_back(componentRegistry["ListEmptyComponentUniqueId"]); + + adjustElement( + { + .x = 0, + .y = scrollContentAboveOffset.max() + scrollContentBelowOffset.max() + }, + componentRegistry["ListEmptyComponentUniqueId"]); + + scrollContentBelowOffset.add(1, templateRegistryItem.size); + } + /* * Render and adjust origin of Footer template */ @@ -226,14 +251,20 @@ void SLContainerShadowNode::layout(LayoutContext layoutContext) { containerShadowNodeChildren->push_back(componentRegistry["ListHeaderComponentUniqueId"]); adjustElement( - scrollContentAboveOffset + scrollContentBelowOffset, + { + .x = 0, + .y = scrollContentAboveOffset.max() + scrollContentBelowOffset.max() + }, componentRegistry["ListHeaderComponentUniqueId"]); } else { transformTemplateComponent("ListFooterComponentUniqueId", 1); containerShadowNodeChildren->push_back(componentRegistry["ListFooterComponentUniqueId"]); adjustElement( - scrollContentAboveOffset + scrollContentBelowOffset, + { + .x = 0, + .y = scrollContentAboveOffset.max() + scrollContentBelowOffset.max() + }, componentRegistry["ListFooterComponentUniqueId"]); } @@ -243,6 +274,13 @@ void SLContainerShadowNode::layout(LayoutContext layoutContext) { this->children_ = containerShadowNodeChildren; yogaNode_.setDirty(true); + /* + * Bailout without any state update + */ + if (!props.uniqueIds.size()) { + return; + } + nextStateData.firstChildUniqueId = props.uniqueIds.front(); nextStateData.lastChildUniqueId = props.uniqueIds.back(); nextStateData.scrollContainer = getLayoutMetrics().frame.size; @@ -250,15 +288,17 @@ void SLContainerShadowNode::layout(LayoutContext layoutContext) { if (props.horizontal) { nextStateData.scrollContent.height = getLayoutMetrics().frame.size.height; nextStateData.scrollContent.width = ( - nextStateData.childrenMeasurementsTree.sum(elementsDataSize) + - nextStateData.templateMeasurementsTree.sum(2) + scrollContentAboveOffset.max() + + scrollContentBelowOffset.max() + + nextStateData.templateMeasurementsTree[1] ); nextStateData.scrollContentUpdated = true; } else if (!props.horizontal) { nextStateData.scrollContent.width = getLayoutMetrics().frame.size.width; nextStateData.scrollContent.height = ( - nextStateData.childrenMeasurementsTree.sum(elementsDataSize) + - nextStateData.templateMeasurementsTree.sum(2) + scrollContentAboveOffset.max() + + scrollContentBelowOffset.max() + + nextStateData.templateMeasurementsTree[1] ); nextStateData.scrollContentUpdated = true; } @@ -269,22 +309,22 @@ void SLContainerShadowNode::layout(LayoutContext layoutContext) { if (!nextStateData.scrollIndexUpdated && nextStateData.scrollPositionUpdated && props.horizontal) { nextStateData.scrollPosition.y = 0; nextStateData.scrollPosition.x = ( - scrollContentAboveOffset + getRelativePointFromPoint(nextStateData.scrollPosition) - nextStateData.templateMeasurementsTree[0] + scrollContentAboveOffset.max() + getRelativePointFromPoint(nextStateData.scrollPosition) - nextStateData.templateMeasurementsTree[0] ); } else if (!nextStateData.scrollIndexUpdated && nextStateData.scrollPositionUpdated && !props.horizontal) { nextStateData.scrollPosition.x = 0; nextStateData.scrollPosition.y = ( - scrollContentAboveOffset + getRelativePointFromPoint(nextStateData.scrollPosition) - nextStateData.templateMeasurementsTree[0] + scrollContentAboveOffset.max() + getRelativePointFromPoint(nextStateData.scrollPosition) - nextStateData.templateMeasurementsTree[0] ); } if (nextStateData.scrollIndexUpdated && nextStateData.scrollPositionUpdated && props.horizontal) { nextStateData.scrollPosition.y = 0; - nextStateData.scrollPosition.x = scrollContentAboveOffset; + nextStateData.scrollPosition.x = scrollContentAboveOffset.max(); nextStateData.scrollIndexUpdated = false; } else if (nextStateData.scrollIndexUpdated && nextStateData.scrollPositionUpdated && !props.horizontal) { nextStateData.scrollPosition.x = 0; - nextStateData.scrollPosition.y = scrollContentAboveOffset; + nextStateData.scrollPosition.y = scrollContentAboveOffset.max(); nextStateData.scrollIndexUpdated = false; } @@ -320,7 +360,7 @@ void SLContainerShadowNode::replaceChild( ConcreteShadowNode::replaceChild(oldChild, newChild, suggestedIndex); } -LayoutMetrics SLContainerShadowNode::layoutElement(LayoutContext layoutContext, ShadowNode::Unshared shadowNode) { +LayoutMetrics SLContainerShadowNode::layoutElement(LayoutContext layoutContext, ShadowNode::Unshared shadowNode, int numColumns) { auto elementShadowNodeLayoutable = std::static_pointer_cast(shadowNode); if (getRelativeSizeFromSize(elementShadowNodeLayoutable->getLayoutMetrics().frame.size)) { @@ -328,20 +368,29 @@ LayoutMetrics SLContainerShadowNode::layoutElement(LayoutContext layoutContext, } LayoutConstraints layoutConstraints = {}; - layoutConstraints.minimumSize.width = getLayoutMetrics().frame.size.width; - layoutConstraints.maximumSize.width = getLayoutMetrics().frame.size.width; + + if (numColumns > 0) { + layoutConstraints.minimumSize.width = getLayoutMetrics().frame.size.width / numColumns; + layoutConstraints.maximumSize.width = getLayoutMetrics().frame.size.width / numColumns; + } else { + layoutConstraints.minimumSize.width = getLayoutMetrics().frame.size.width; + layoutConstraints.maximumSize.width = getLayoutMetrics().frame.size.width; + } + elementShadowNodeLayoutable->layoutTree(layoutContext, layoutConstraints); return elementShadowNodeLayoutable->getLayoutMetrics(); } -LayoutMetrics SLContainerShadowNode::adjustElement(float origin, ShadowNode::Unshared shadowNode) { +LayoutMetrics SLContainerShadowNode::adjustElement(Point origin, ShadowNode::Unshared shadowNode) { auto elementShadowNodeLayoutable = std::static_pointer_cast(shadowNode); LayoutMetrics layoutMetrics = elementShadowNodeLayoutable->getLayoutMetrics(); if (getConcreteProps().horizontal) { - layoutMetrics.frame.origin.x = origin; + layoutMetrics.frame.origin.x = origin.x; + layoutMetrics.frame.origin.y = origin.y; } else { - layoutMetrics.frame.origin.y = origin; + layoutMetrics.frame.origin.y = origin.y; + layoutMetrics.frame.origin.x = origin.x; } elementShadowNodeLayoutable->setLayoutMetrics(layoutMetrics); diff --git a/cpp/react/renderer/components/SLContainerSpec/SLContainerShadowNode.h b/cpp/react/renderer/components/SLContainerSpec/SLContainerShadowNode.h index f4b891b..0fa89ab 100644 --- a/cpp/react/renderer/components/SLContainerSpec/SLContainerShadowNode.h +++ b/cpp/react/renderer/components/SLContainerSpec/SLContainerShadowNode.h @@ -46,8 +46,8 @@ class SLContainerShadowNode final : public ConcreteViewShadowNode< const ShadowNode::Shared& newChild, size_t suggestedIndex = SIZE_MAX) override; - LayoutMetrics layoutElement(LayoutContext layoutContext, ShadowNode::Unshared shadowNode); - LayoutMetrics adjustElement(float origin, ShadowNode::Unshared shadowNode); + LayoutMetrics layoutElement(LayoutContext layoutContext, ShadowNode::Unshared shadowNode, int numColumns); + LayoutMetrics adjustElement(Point origin, ShadowNode::Unshared shadowNode); float getRelativeSizeFromSize(Size size); float getRelativePointFromPoint(Point point); diff --git a/example/src/App.tsx b/example/src/App.tsx index 77d98f6..ae90df9 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -33,7 +33,7 @@ const ListFooterComponent = () => ( const ListEmptyComponent = () => ( - Footer + Empty ); @@ -99,6 +99,7 @@ export default function App() { inverted={IS_INVERTED} horizontal={IS_HORIZONTAL} initialScrollIndex={INITIAL_SCROLL_INDEX} + numColumns={2} /> ); diff --git a/src/SLContainer.tsx b/src/SLContainer.tsx index fd66a4e..5965b00 100644 --- a/src/SLContainer.tsx +++ b/src/SLContainer.tsx @@ -11,8 +11,13 @@ import SLContainerNativeComponent, { // @ts-ignore // import ReactNativeInterface from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface'; +export type ItemProp = { + id: string; + [key: string]: any; +}; + export type SLContainerWrapperProps = { - data: Array; + data: Array; }; export type SLContainerInstance = InstanceType< diff --git a/src/SLContainerNativeComponent.ts b/src/SLContainerNativeComponent.ts index 40eb422..5bdb43a 100644 --- a/src/SLContainerNativeComponent.ts +++ b/src/SLContainerNativeComponent.ts @@ -46,6 +46,7 @@ export interface SLContainerNativeProps extends ViewProps { horizontal?: boolean; initialNumToRender?: Int32; initialScrollIndex?: Int32; + numColumns?: Int32; onVisibleChange?: DirectEventHandler>; onStartReached?: DirectEventHandler>; onEndReached?: DirectEventHandler>; diff --git a/src/Shadowlist.tsx b/src/Shadowlist.tsx index 8353621..fc78e0d 100644 --- a/src/Shadowlist.tsx +++ b/src/Shadowlist.tsx @@ -2,6 +2,7 @@ import React, { type Ref } from 'react'; import { type ViewStyle } from 'react-native'; import { SLContainer } from './SLContainer'; import { SLElement } from './SLElement'; +import type { ItemProp } from './SLContainer'; import type { SLContainerNativeCommands, SLContainerNativeProps, @@ -19,9 +20,9 @@ const invoker = (Component: Component) => { }; export type ShadowlistProps = { - data: Array; + data: Array; renderItem: () => React.ReactElement; - keyExtractor: (item: any, index: number) => string; + keyExtractor: (item: ItemProp, index: number) => string; contentContainerStyle?: ViewStyle; ListHeaderComponent?: Component; ListHeaderComponentStyle?: ViewStyle;