diff --git a/maestro/images/expected/android/bottom_sheet_modal_basic_non_native.png b/maestro/images/expected/android/bottom_sheet_modal_basic_non_native.png index 4e1d3be77..46baa8886 100644 Binary files a/maestro/images/expected/android/bottom_sheet_modal_basic_non_native.png and b/maestro/images/expected/android/bottom_sheet_modal_basic_non_native.png differ diff --git a/packages/pluggableWidgets/bottom-sheet-native/CHANGELOG.md b/packages/pluggableWidgets/bottom-sheet-native/CHANGELOG.md index 6cde9d37b..7628223f1 100644 --- a/packages/pluggableWidgets/bottom-sheet-native/CHANGELOG.md +++ b/packages/pluggableWidgets/bottom-sheet-native/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Fixed + +- Fixed the "non‑worklet function called on the UI thread” error in bottom sheet coming from @gorhom/bottom-sheet usage. + ## [5.0.3] - 2025-12-15 - Updated react-native-reanimated to v3.17.5. This addresses compatibility issues with React Native 0.78 and later versions. diff --git a/packages/pluggableWidgets/bottom-sheet-native/e2e/specs/maestro/Modal_basic_non_native.yaml b/packages/pluggableWidgets/bottom-sheet-native/e2e/specs/maestro/Modal_basic_non_native.yaml index cd7d34ef0..bdcb519de 100644 --- a/packages/pluggableWidgets/bottom-sheet-native/e2e/specs/maestro/Modal_basic_non_native.yaml +++ b/packages/pluggableWidgets/bottom-sheet-native/e2e/specs/maestro/Modal_basic_non_native.yaml @@ -10,10 +10,12 @@ appId: "${APP_ID}" text: "Modal basic non native" - tapOn: text: "Open" -# Because the image loading can take some time due to resources on emulator, we need to wait for the image to be visible -- extendedWaitUntil: - visible: randText # Any random text that does not exist in the UI - optional: true # This should be true so that the test won't fail - timeout: 10000 # 10 seconds + +# Wait until the sheet is actually open (button label changes) +- extendedWaitUntil: + visible: randText + optional: true + timeout: 50000 + - takeScreenshot: - path: "maestro/images/actual/${PLATFORM}/bottom_sheet_modal_basic_non_native" + path: "maestro/images/actual/${PLATFORM}/bottom_sheet_modal_basic_non_native" \ No newline at end of file diff --git a/packages/pluggableWidgets/bottom-sheet-native/package.json b/packages/pluggableWidgets/bottom-sheet-native/package.json index 11c4aaab3..ab2055e6f 100644 --- a/packages/pluggableWidgets/bottom-sheet-native/package.json +++ b/packages/pluggableWidgets/bottom-sheet-native/package.json @@ -1,7 +1,7 @@ { "name": "bottom-sheet-native", "widgetName": "BottomSheet", - "version": "5.0.3", + "version": "5.0.4", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/pluggableWidgets/bottom-sheet-native/src/components/CustomModalSheet.tsx b/packages/pluggableWidgets/bottom-sheet-native/src/components/CustomModalSheet.tsx index 19d21f496..ea4efbbdd 100644 --- a/packages/pluggableWidgets/bottom-sheet-native/src/components/CustomModalSheet.tsx +++ b/packages/pluggableWidgets/bottom-sheet-native/src/components/CustomModalSheet.tsx @@ -1,4 +1,4 @@ -import { ReactElement, ReactNode, useEffect, useRef, useState } from "react"; +import { ReactElement, ReactNode, useCallback, useEffect, useMemo, useRef, useState } from "react"; import { InteractionManager, LayoutChangeEvent, Modal, Pressable, SafeAreaView, StyleSheet, View } from "react-native"; import BottomSheet, { BottomSheetBackdrop, BottomSheetBackdropProps, BottomSheetView } from "@gorhom/bottom-sheet"; import { EditableValue, ValueStatus } from "mendix"; @@ -9,14 +9,21 @@ interface CustomModalSheetProps { content?: ReactNode; styles: BottomSheetStyle; } + let lastIndexRef = -1; export const CustomModalSheet = (props: CustomModalSheetProps): ReactElement => { const bottomSheetRef = useRef(null); const [height, setHeight] = useState(0); const [currentStatus, setCurrentStatus] = useState(false); + const isAvailable = props.triggerAttribute && props.triggerAttribute.status === ValueStatus.Available; + const isOpen = + props.triggerAttribute && + props.triggerAttribute.status === ValueStatus.Available && + props.triggerAttribute.value; + const onLayoutFullscreenHandler = (event: LayoutChangeEvent): void => { const layoutHeight = event.nativeEvent.layout.height; if (layoutHeight > 0 && layoutHeight !== height) { @@ -24,6 +31,52 @@ export const CustomModalSheet = (props: CustomModalSheetProps): ReactElement => } }; + const close = useCallback(() => { + bottomSheetRef.current?.close(); + }, []); + + const renderBackdrop = useCallback( + (backdropProps: BottomSheetBackdropProps) => ( + + + + ), + [close] + ); + + const snapPoints = useMemo(() => { + if (height === 0) { + return [0]; + } + return [height - Number(defaultPaddings.paddingBottom)]; + }, [height]); + + const handleSheetChanges = useCallback( + (index: number) => { + if (!isAvailable) { + return; + } + + const hasOpened = lastIndexRef === -1 && index === 0; + const hasClosed = index === -1; + lastIndexRef = index; + + if (hasOpened) { + props.triggerAttribute?.setValue(true); + } + if (hasClosed) { + props.triggerAttribute?.setValue(false); + } + }, + [isAvailable, props.triggerAttribute] + ); + useEffect(() => { if (!isAvailable) { return; @@ -34,7 +87,7 @@ export const CustomModalSheet = (props: CustomModalSheetProps): ReactElement => bottomSheetRef.current?.close(); setCurrentStatus(false); } - }, [props.triggerAttribute, currentStatus]); + }, [props.triggerAttribute, currentStatus, isAvailable]); if (height === 0) { return ( @@ -44,47 +97,8 @@ export const CustomModalSheet = (props: CustomModalSheetProps): ReactElement => ); } - const snapPoints = [height - Number(defaultPaddings.paddingBottom)]; - - const isOpen = - props.triggerAttribute && - props.triggerAttribute.status === ValueStatus.Available && - props.triggerAttribute.value; - - const renderBackdrop = (backdropProps: BottomSheetBackdropProps) => ( - - - - ); - - const handleSheetChanges = (index: number) => { - if (!isAvailable) { - return; - } - const hasOpened = lastIndexRef === -1 && index === 0; - const hasClosed = index === -1; - lastIndexRef = index; - - if (hasOpened) { - props.triggerAttribute?.setValue(true); - } - if (hasClosed) { - props.triggerAttribute?.setValue(false); - } - }; - - const close = () => { - bottomSheetRef.current?.close(); - }; - return ( - + { return content; }, [props.smallContent, props.largeContent, props.fullscreenContent, isOpen, fullscreenHeight]); - if (props.fullscreenContent && fullscreenHeight === 0) { - return ( - - - - ); - } - - if (heightHeader === 0 || (isLargeContentValid && heightContent === 0)) { - return {renderContent()}; - } + const snapPoints = useMemo(() => { + if (props.fullscreenContent && heightContent) { + return [fullscreenHeight, heightContent, heightHeader]; + } + if (props.fullscreenContent) { + return [fullscreenHeight, heightHeader]; + } + if (isLargeContentValid) { + return [heightContent, heightHeader]; + } + return [heightHeader]; + }, [props.fullscreenContent, fullscreenHeight, heightContent, heightHeader, isLargeContentValid]); - const snapPoints = - props.fullscreenContent && heightContent - ? [fullscreenHeight, heightContent, heightHeader] - : props.fullscreenContent - ? [fullscreenHeight, heightHeader] - : isLargeContentValid - ? [heightContent, heightHeader] - : [heightHeader]; + const snapPointsWithOffset = useMemo(() => { + return snapPoints.map(p => p + OFFSET_BOTTOM_SHEET); + }, [snapPoints]); const collapsedIndex = 0; @@ -117,13 +113,25 @@ export const ExpandingDrawer = (props: ExpandingDrawerProps): ReactElement => { lastIndexRef = index; }; + if (props.fullscreenContent && fullscreenHeight === 0) { + return ( + + + + ); + } + + if (heightHeader === 0 || (isLargeContentValid && heightContent === 0)) { + return {renderContent()}; + } + return ( {snapPoints.length > 1 && ( p + OFFSET_BOTTOM_SHEET)} + snapPoints={snapPointsWithOffset} onClose={() => setIsOpen(false)} enablePanDownToClose={false} onChange={onChange} diff --git a/packages/pluggableWidgets/bottom-sheet-native/src/components/NativeBottomSheet.tsx b/packages/pluggableWidgets/bottom-sheet-native/src/components/NativeBottomSheet.tsx index d4baa0e3e..e56dc36cc 100644 --- a/packages/pluggableWidgets/bottom-sheet-native/src/components/NativeBottomSheet.tsx +++ b/packages/pluggableWidgets/bottom-sheet-native/src/components/NativeBottomSheet.tsx @@ -10,7 +10,11 @@ import { TouchableHighlight, View } from "react-native"; -import BottomSheet, { BottomSheetBackdrop, BottomSheetBackdropProps, BottomSheetView } from "@gorhom/bottom-sheet"; +import BottomSheet, { + BottomSheetBackdrop, + BottomSheetBackdropProps, + BottomSheetScrollView +} from "@gorhom/bottom-sheet"; import { EditableValue, ValueStatus } from "mendix"; import { ItemsBasicType } from "../../typings/BottomSheetProps"; import { BottomSheetStyle, ModalItemContainerStyle } from "../ui/Styles"; @@ -78,16 +82,23 @@ export const NativeBottomSheet = (props: NativeBottomSheetProps): ReactElement = } }, [isOpen]); - const renderBackdrop = (backdropProps: BottomSheetBackdropProps) => ( - - - + const close = useCallback(() => { + bottomSheetRef.current?.close(); + }, []); + + const renderBackdrop = useCallback( + (backdropProps: BottomSheetBackdropProps) => ( + + + + ), + [close] ); const actionHandler = useCallback( @@ -166,10 +177,6 @@ export const NativeBottomSheet = (props: NativeBottomSheetProps): ReactElement = return ; } - const close = () => { - bottomSheetRef.current?.close(); - }; - return ( - + {props.itemsBasic.map((item, index) => renderItem(item, index))} - + ); diff --git a/packages/pluggableWidgets/bottom-sheet-native/src/package.xml b/packages/pluggableWidgets/bottom-sheet-native/src/package.xml index 6f741eabd..69139cc5c 100644 --- a/packages/pluggableWidgets/bottom-sheet-native/src/package.xml +++ b/packages/pluggableWidgets/bottom-sheet-native/src/package.xml @@ -1,6 +1,6 @@ - +