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
2 changes: 2 additions & 0 deletions packages/pluggableWidgets/gallery-native/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## [Unreleased]

- We've fixed an issue where Gallery widget does not display data.

## [2.2.1] - 2026-6-10

### Changed
Expand Down
2 changes: 1 addition & 1 deletion packages/pluggableWidgets/gallery-native/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "gallery-native",
"widgetName": "Gallery",
"version": "2.2.1",
"version": "2.2.2",
"description": "A flexible gallery widget that renders columns, rows and layouts.",
"copyright": "© Mendix Technology BV 2022. All rights reserved.",
"license": "Apache-2.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import { ReactElement, ReactNode, useCallback, useMemo } from "react";
import { Text, Pressable, View, ViewProps, Platform, TouchableOpacity, useWindowDimensions } from "react-native";
import { ReactElement, ReactNode, useCallback, useEffect, useMemo, useRef, useState } from "react";
import {
LayoutChangeEvent,
Text,
Pressable,
View,
ViewProps,
Platform,
TouchableOpacity,
useWindowDimensions
} from "react-native";
import { ObjectItem, DynamicValue } from "mendix";
import DeviceInfo from "react-native-device-info";
import { GalleryStyle } from "../ui/Styles";
Expand Down Expand Up @@ -35,6 +44,13 @@ export const Gallery = <T extends ObjectItem>(props: GalleryProps<T>): ReactElem
const lastItemId = props.items?.[props.items.length - 1]?.id;
const { name, style, itemRenderer } = props;
const { width } = useWindowDimensions();
// FlashList requires a non-zero height to render items (it's virtualized and needs viewport dimensions).
// When the parent provides height (e.g. via flex), we use flex: 1. When it doesn't (e.g. a Container
// widget with no flex/height), flex: 1 resolves to 0 and nothing renders. In that case, we fall back
// to minHeight based on FlashList's reported content size. We measure the FlashList area specifically so we get the height of the list area, not the entire Gallery (which may include filters and padding).
const listAreaRef = useRef<View>(null);
const [contentHeight, setContentHeight] = useState(0);
const [layoutDecision, setLayoutDecision] = useState<"minHeight" | "flex" | null>(null);

const onEndReached = (): void => {
if (props.pagination === "virtualScrolling" && props.hasMoreItems) {
Expand Down Expand Up @@ -136,29 +152,62 @@ export const Gallery = <T extends ObjectItem>(props: GalleryProps<T>): ReactElem
[props.style.emptyPlaceholder, props.emptyPlaceholder]
);

const handleListAreaLayout = useCallback(
(event: LayoutChangeEvent) => {
const { height } = event.nativeEvent.layout;
if (height > 0 && layoutDecision === null) {
setLayoutDecision("flex");
}
},
[layoutDecision]
);

useEffect(() => {
if (contentHeight > 0 && layoutDecision === null && listAreaRef.current) {
listAreaRef.current.measure((_x, _y, _width, height) => {
setLayoutDecision(height > 0 ? "flex" : "minHeight");
});
}
}, [contentHeight, layoutDecision]);

const containerStyle = isScrollDirectionVertical
? layoutDecision === "minHeight"
? [{ minHeight: Math.max(contentHeight, 1) }, props.style.container]
: [{ flex: 1 }, props.style.container]
: props.style.container;

const listStyle = isScrollDirectionVertical ? [{ flex: 1 }, props.style.list] : props.style.list;

return (
<View testID={`${name}`} style={props.style.container}>
<View testID={`${name}`} style={containerStyle}>
{props.filters ? <View>{props.filters}</View> : null}
<FlashList
{...(isScrollDirectionVertical && props.pullDown ? { onRefresh: props.pullDown } : {})}
{...(isScrollDirectionVertical ? { numColumns } : {})}
ListFooterComponent={loadMoreButton}
ListFooterComponentStyle={{
...props.style.loadMoreButtonContainer,
...(isScrollDirectionVertical ? { marginTop: 8 } : { marginStart: 8 })
}}
refreshing={props.pullDownIsExecuting}
data={props.items}
horizontal={!isScrollDirectionVertical}
keyExtractor={item => item.id}
ListEmptyComponent={renderEmptyPlaceholder}
onEndReached={onEndReached}
onEndReachedThreshold={0.6}
scrollEventThrottle={50}
renderItem={renderItem}
style={props.style.list}
testID={`${name}-list`}
/>
<View
ref={listAreaRef}
style={isScrollDirectionVertical ? { flex: 1 } : undefined}
onLayout={isScrollDirectionVertical ? handleListAreaLayout : undefined}
>
<FlashList
{...(isScrollDirectionVertical && props.pullDown ? { onRefresh: props.pullDown } : {})}
{...(isScrollDirectionVertical ? { numColumns } : {})}
ListFooterComponent={loadMoreButton}
ListFooterComponentStyle={{
...props.style.loadMoreButtonContainer,
...(isScrollDirectionVertical ? { marginTop: 8 } : { marginStart: 8 })
}}
refreshing={props.pullDownIsExecuting}
data={props.items}
horizontal={!isScrollDirectionVertical}
keyExtractor={item => item.id}
ListEmptyComponent={renderEmptyPlaceholder}
onEndReached={onEndReached}
onEndReachedThreshold={0.6}
scrollEventThrottle={50}
renderItem={renderItem}
style={listStyle}
testID={`${name}-list`}
onContentSizeChange={isScrollDirectionVertical ? (_w, h) => setContentHeight(h) : undefined}
/>
</View>
</View>
);
};
Loading
Loading