diff --git a/maestro/images/expected/android/bottom_sheet_modal_custom.png b/maestro/images/expected/android/bottom_sheet_modal_custom.png index c0b019750..919a6de14 100644 Binary files a/maestro/images/expected/android/bottom_sheet_modal_custom.png and b/maestro/images/expected/android/bottom_sheet_modal_custom.png differ diff --git a/packages/pluggableWidgets/bottom-sheet-native/CHANGELOG.md b/packages/pluggableWidgets/bottom-sheet-native/CHANGELOG.md index 6cde9d37b..ece410d74 100644 --- a/packages/pluggableWidgets/bottom-sheet-native/CHANGELOG.md +++ b/packages/pluggableWidgets/bottom-sheet-native/CHANGELOG.md @@ -6,6 +6,18 @@ 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. +- Fixed ExpandingDrawer to open correctly and show all elements. + +### Changed + +- Improved performance by memoizing all callbacks with useCallback and useMemo across all components. +- BottomSheet now uses BottomSheetScrollView instead of BottomSheetView, enabling proper scrolling in expanded states. +- NativeBottomSheet now explicitly calculates snapPoints with 90% screen height cap for predictable sizing. +- CustomModalSheet now measures content height dynamically with 90% screen height cap, removing offscreen SafeAreaView measurement. + ## [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..ac67b21a7 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: 25000 + - takeScreenshot: path: "maestro/images/actual/${PLATFORM}/bottom_sheet_modal_basic_non_native" 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/__tests__/__snapshots__/BottomSheet.spec.tsx.snap b/packages/pluggableWidgets/bottom-sheet-native/src/__tests__/__snapshots__/BottomSheet.spec.tsx.snap index 0d20fc1cf..512486a56 100644 --- a/packages/pluggableWidgets/bottom-sheet-native/src/__tests__/__snapshots__/BottomSheet.spec.tsx.snap +++ b/packages/pluggableWidgets/bottom-sheet-native/src/__tests__/__snapshots__/BottomSheet.spec.tsx.snap @@ -2,104 +2,159 @@ exports[`Bottom sheet renders a custom bottom action sheet for ios (Basic modal) with custom style 1`] = `null`; -exports[`Bottom sheet renders a custom modal 1`] = ` +exports[`Bottom sheet renders a custom modal 1`] = `null`; + +exports[`Bottom sheet renders a expanding 1`] = ` - - -`; - -exports[`Bottom sheet renders a expanding 1`] = ` - - Header - - Content - + + + Content + + `; exports[`Bottom sheet renders a expanding fullscreen 1`] = ` - + > + + + Header + + + + + Content + + + + + Full screen content + + + `; exports[`Bottom sheet renders a expanding fullscreen with custom styles 1`] = ` - + > + + + Header + + + + + Content + + + + + Full screen content + + + `; diff --git a/packages/pluggableWidgets/bottom-sheet-native/src/components/CustomModalSheet.tsx b/packages/pluggableWidgets/bottom-sheet-native/src/components/CustomModalSheet.tsx index 19d21f496..cb9a1925c 100644 --- a/packages/pluggableWidgets/bottom-sheet-native/src/components/CustomModalSheet.tsx +++ b/packages/pluggableWidgets/bottom-sheet-native/src/components/CustomModalSheet.tsx @@ -1,90 +1,107 @@ -import { ReactElement, ReactNode, useEffect, 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 { ReactElement, ReactNode, useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { Dimensions, InteractionManager, LayoutChangeEvent, Modal, Pressable } from "react-native"; +import BottomSheet, { + BottomSheetBackdrop, + BottomSheetBackdropProps, + BottomSheetScrollView +} from "@gorhom/bottom-sheet"; import { EditableValue, ValueStatus } from "mendix"; -import { BottomSheetStyle, defaultPaddings } from "../ui/Styles"; +import { BottomSheetStyle } from "../ui/Styles"; interface CustomModalSheetProps { triggerAttribute?: EditableValue; content?: ReactNode; styles: BottomSheetStyle; } + let lastIndexRef = -1; export const CustomModalSheet = (props: CustomModalSheetProps): ReactElement => { const bottomSheetRef = useRef(null); - const [height, setHeight] = useState(0); + const [contentHeight, setContentHeight] = useState(0); const [currentStatus, setCurrentStatus] = useState(false); + const isAvailable = props.triggerAttribute && props.triggerAttribute.status === ValueStatus.Available; - const onLayoutFullscreenHandler = (event: LayoutChangeEvent): void => { - const layoutHeight = event.nativeEvent.layout.height; - if (layoutHeight > 0 && layoutHeight !== height) { - setHeight(layoutHeight); - } - }; + const isOpen = + props.triggerAttribute && + props.triggerAttribute.status === ValueStatus.Available && + props.triggerAttribute.value; - useEffect(() => { - if (!isAvailable) { - return; - } - if (props.triggerAttribute?.value && !currentStatus) { - InteractionManager.runAfterInteractions(() => setCurrentStatus(true)); - } else if (!props.triggerAttribute?.value && currentStatus) { - bottomSheetRef.current?.close(); - setCurrentStatus(false); + const onContentLayoutHandler = useCallback( + (event: LayoutChangeEvent): void => { + const layoutHeight = event.nativeEvent.layout.height; + if (layoutHeight > 0 && layoutHeight !== contentHeight) { + setContentHeight(layoutHeight); + } + }, + [contentHeight] + ); + + const close = useCallback(() => { + bottomSheetRef.current?.close(); + }, []); + + const renderBackdrop = useCallback( + (backdropProps: BottomSheetBackdropProps) => ( + + + + ), + [close] + ); + + const snapPoints = useMemo(() => { + if (contentHeight === 0) { + return [1]; // During measurement } - }, [props.triggerAttribute, currentStatus]); - if (height === 0) { - return ( - - - - ); - } + // Use actual measured content height, cap at 90% screen + const maxHeight = Dimensions.get("screen").height * 0.9; + const snapHeight = Math.min(contentHeight, maxHeight); + return [snapHeight]; + }, [contentHeight]); - const snapPoints = [height - Number(defaultPaddings.paddingBottom)]; + const handleSheetChanges = useCallback( + (index: number) => { + if (!isAvailable) { + return; + } - const isOpen = - props.triggerAttribute && - props.triggerAttribute.status === ValueStatus.Available && - props.triggerAttribute.value; + const hasOpened = lastIndexRef === -1 && index === 0; + const hasClosed = index === -1; + lastIndexRef = index; - const renderBackdrop = (backdropProps: BottomSheetBackdropProps) => ( - - - + if (hasOpened) { + props.triggerAttribute?.setValue(true); + } + if (hasClosed) { + props.triggerAttribute?.setValue(false); + } + }, + [isAvailable, props.triggerAttribute] ); - const handleSheetChanges = (index: number) => { + useEffect(() => { 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); + if (props.triggerAttribute?.value && !currentStatus) { + InteractionManager.runAfterInteractions(() => setCurrentStatus(true)); + } else if (!props.triggerAttribute?.value && currentStatus) { + bottomSheetRef.current?.close(); + setCurrentStatus(false); } - }; - - const close = () => { - bottomSheetRef.current?.close(); - }; + }, [props.triggerAttribute, currentStatus, isAvailable]); return ( - + handleComponent={null} handleStyle={{ display: "none" }} > - {props.content} + + {props.content} + ); diff --git a/packages/pluggableWidgets/bottom-sheet-native/src/components/ExpandingDrawer.tsx b/packages/pluggableWidgets/bottom-sheet-native/src/components/ExpandingDrawer.tsx index e08e02e9b..e356c0d9a 100644 --- a/packages/pluggableWidgets/bottom-sheet-native/src/components/ExpandingDrawer.tsx +++ b/packages/pluggableWidgets/bottom-sheet-native/src/components/ExpandingDrawer.tsx @@ -1,6 +1,6 @@ -import { ReactNode, ReactElement, useCallback, useState, useRef, Children } from "react"; -import { Dimensions, LayoutChangeEvent, SafeAreaView, StyleSheet, View } from "react-native"; -import BottomSheet, { BottomSheetView } from "@gorhom/bottom-sheet"; +import { ReactNode, ReactElement, useCallback, useMemo, useState, useRef, Children } from "react"; +import { Dimensions, LayoutChangeEvent, StyleSheet, View } from "react-native"; +import BottomSheet, { BottomSheetScrollView, BottomSheetView } from "@gorhom/bottom-sheet"; import { BottomSheetStyle } from "../ui/Styles"; interface ExpandingDrawerProps { @@ -13,124 +13,206 @@ interface ExpandingDrawerProps { } let lastIndexRef = -1; -const OFFSET_BOTTOM_SHEET = 25; +const OFFSET_BOTTOM_SHEET = 25; // A small offset for visual padding or handle export const ExpandingDrawer = (props: ExpandingDrawerProps): ReactElement => { - const [heightContent, setHeightContent] = useState(0); - const [heightHeader, setHeightHeader] = useState(0); - const [fullscreenHeight, setFullscreenHeight] = useState(0); - const [isOpen, setIsOpen] = useState(true); + // State to store measured heights + const [smallContentHeight, setSmallContentHeight] = useState(0); // Height of the smallContent (header) + const [largeContentOnlyHeight, setLargeContentOnlyHeight] = useState(0); // Height of largeContent ONLY + const [fullscreenContentOnlyHeight, setFullscreenContentOnlyHeight] = useState(0); // Height of fullscreenContent ONLY + const [isOpen, setIsOpen] = useState(true); // Tracks if the drawer is open or closed const bottomSheetRef = useRef(null); - const maxHeight = Dimensions.get("screen").height; + const screenHeight = Dimensions.get("screen").height; + const halfScreen = Math.round(screenHeight * 0.5); + const isSmallContentValid = Children.count(props.smallContent) > 0; const isLargeContentValid = Children.count(props.largeContent) > 0; + const isFullscreenContentValid = Children.count(props.fullscreenContent) > 0; + + // Handlers for measuring individual content sections + const onLayoutSmallContent = useCallback( + (event: LayoutChangeEvent): void => { + const height = event.nativeEvent.layout.height; + if (height > 0 && height !== smallContentHeight) { + setSmallContentHeight(height); + } + }, + [smallContentHeight] + ); - const onLayoutHandlerHeader = (event: LayoutChangeEvent): void => { - const height = event.nativeEvent.layout.height; - if (height > 0 && height <= maxHeight) { - setHeightHeader(height); - } - }; - - const onLayoutHandlerContent = (event: LayoutChangeEvent): void => { - const height = event.nativeEvent.layout.height; - if (height > 0) { - if (height <= maxHeight) { - setHeightContent(height); - } else if (!props.fullscreenContent) { - setHeightContent(maxHeight); + const onLayoutLargeContent = useCallback( + (event: LayoutChangeEvent): void => { + const height = event.nativeEvent.layout.height; + if (height > 0 && height !== largeContentOnlyHeight) { + setLargeContentOnlyHeight(height); } - } - }; + }, + [largeContentOnlyHeight] + ); - const onLayoutFullscreenHandler = (event: LayoutChangeEvent): void => { - const height = event.nativeEvent.layout.height; - if (height > 0) { - setFullscreenHeight(height); - } - }; + const onLayoutFullscreenContent = useCallback( + (event: LayoutChangeEvent): void => { + const height = event.nativeEvent.layout.height; + if (height > 0 && height !== fullscreenContentOnlyHeight) { + setFullscreenContentOnlyHeight(height); + } + }, + [fullscreenContentOnlyHeight] + ); + // Determine the container style based on expansion state const containerStyle = - props.fullscreenContent && isOpen ? props.styles.containerWhenExpandedFullscreen : props.styles.container; - - const renderContent = useCallback((): ReactNode => { - const content = ( - - + isFullscreenContentValid && isOpen ? props.styles.containerWhenExpandedFullscreen : props.styles.container; + + const renderMeasurementTree = useCallback((): ReactElement => { + return ( + + {/* Measure smallContent */} + {props.smallContent} - {props.largeContent} - - ); - - if (props.fullscreenContent) { - return ( - - {content} - {props.fullscreenContent} + {/* Measure largeContent */} + + {props.largeContent} - ); - } - return content; - }, [props.smallContent, props.largeContent, props.fullscreenContent, isOpen, fullscreenHeight]); - - if (props.fullscreenContent && fullscreenHeight === 0) { - return ( - - + {/* Measure fullscreenContent */} + {isFullscreenContentValid && ( + + {props.fullscreenContent} + + )} ); - } - - if (heightHeader === 0 || (isLargeContentValid && heightContent === 0)) { - return {renderContent()}; - } - - const snapPoints = - props.fullscreenContent && heightContent - ? [fullscreenHeight, heightContent, heightHeader] - : props.fullscreenContent - ? [fullscreenHeight, heightHeader] - : isLargeContentValid - ? [heightContent, heightHeader] - : [heightHeader]; - - const collapsedIndex = 0; - - const onChange = (index: number) => { - const hasOpened = lastIndexRef === -1 && index === 0; - const hasClosed = index === -1; - - if (hasOpened) { - props.onOpen?.(); - setIsOpen(true); + }, [ + props.smallContent, + props.largeContent, + props.fullscreenContent, + isFullscreenContentValid, + onLayoutSmallContent, + onLayoutLargeContent, + onLayoutFullscreenContent + ]); + + // Calculate snap points based on measured heights + const snapPoints = useMemo(() => { + const points: number[] = []; + + if (smallContentHeight > 0) { + points.push(smallContentHeight + OFFSET_BOTTOM_SHEET); + } else { + points.push(50 + OFFSET_BOTTOM_SHEET); // A reasonable default height } - if (hasClosed) { - props.onClose?.(); - setIsOpen(false); + + // Calculate the height for the intermediate snap point (largeContent + smallContent) + const combinedLargeContentHeight = smallContentHeight + largeContentOnlyHeight; + + if (isFullscreenContentValid) { + if (isLargeContentValid && largeContentOnlyHeight > 0) { + const intermediateSnapPoint = Math.min(halfScreen, combinedLargeContentHeight) + OFFSET_BOTTOM_SHEET; + if (intermediateSnapPoint > points[points.length - 1]) { + points.push(intermediateSnapPoint); + } + } + + if (fullscreenContentOnlyHeight > 0) { + const fullScreenSnapPoint = screenHeight - OFFSET_BOTTOM_SHEET; + if (fullScreenSnapPoint > points[points.length - 1]) { + points.push(fullScreenSnapPoint); + } + } + } else { + // If no fullscreenContent, the expanded state is the max of halfScreen or combined largeContent height + if (isLargeContentValid && largeContentOnlyHeight > 0) { + const expandedSnapPoint = Math.min(halfScreen, combinedLargeContentHeight) + OFFSET_BOTTOM_SHEET; + if (expandedSnapPoint > points[points.length - 1]) { + points.push(expandedSnapPoint); + } + } } - lastIndexRef = index; - }; + + // Ensure snap points are unique and sorted in ascending order + return Array.from(new Set(points)).sort((a, b) => (typeof a === "number" && typeof b === "number" ? a - b : 0)); + }, [ + smallContentHeight, + largeContentOnlyHeight, + fullscreenContentOnlyHeight, + isFullscreenContentValid, + isLargeContentValid, + halfScreen, + screenHeight + ]); + + const collapsedIndex = 0; // The initial snap point (smallContent) + + const onChange = useCallback( + (index: number) => { + // Determine if the drawer is opening or closing based on index changes + const hasOpened = lastIndexRef === -1 && index === collapsedIndex; // Initial open to collapsed + const hasClosed = index === -1; // Fully closed + const hasExpanded = index > collapsedIndex && lastIndexRef <= collapsedIndex; // Expanded from collapsed or further + const hasCollapsed = index === collapsedIndex && lastIndexRef > collapsedIndex; // Collapsed back to initial + + if (hasOpened || hasExpanded) { + props.onOpen?.(); + setIsOpen(true); + } + if (hasClosed || hasCollapsed) { + props.onClose?.(); + setIsOpen(index !== -1); // Set isOpen to false only if fully closed + } + lastIndexRef = index; + }, + [props.onOpen, props.onClose, collapsedIndex] + ); + + const hasMinimumMeasurements = !isSmallContentValid || smallContentHeight > 0; return ( - {snapPoints.length > 1 && ( + {renderMeasurementTree()} + + {hasMinimumMeasurements && snapPoints.length > 0 && ( p + OFFSET_BOTTOM_SHEET)} + index={collapsedIndex} // Start at the collapsed state + snapPoints={snapPoints} onClose={() => setIsOpen(false)} enablePanDownToClose={false} onChange={onChange} animateOnMount backgroundStyle={containerStyle} + enableDynamicSizing={false} > - {renderContent()} + {/* Sticky header (smallContent) */} + + {props.smallContent} + + + {/* Scrollable content area */} + + {/* Render largeContent and measure it if needed */} + {props.largeContent} + + {/* Render fullscreenContent only if it's enabled */} + {isFullscreenContentValid && ( + // Render and measure fullscreenContent + {props.fullscreenContent} + )} + )} diff --git a/packages/pluggableWidgets/bottom-sheet-native/src/components/NativeBottomSheet.tsx b/packages/pluggableWidgets/bottom-sheet-native/src/components/NativeBottomSheet.tsx index d4baa0e3e..966418537 100644 --- a/packages/pluggableWidgets/bottom-sheet-native/src/components/NativeBottomSheet.tsx +++ b/packages/pluggableWidgets/bottom-sheet-native/src/components/NativeBottomSheet.tsx @@ -1,7 +1,9 @@ -import { ReactElement, useCallback, useEffect, useRef } from "react"; +import { ReactElement, useCallback, useEffect, useMemo, useRef, useState } from "react"; import { ActionSheetIOS, Appearance, + Dimensions, + LayoutChangeEvent, Modal, Platform, Pressable, @@ -10,7 +12,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"; @@ -28,6 +34,7 @@ let lastIndexRef = -1; export const NativeBottomSheet = (props: NativeBottomSheetProps): ReactElement => { const bottomSheetRef = useRef(null); + const [contentHeight, setContentHeight] = useState(0); const isAvailable = props.triggerAttribute && props.triggerAttribute.status === ValueStatus.Available; const isOpen = @@ -35,19 +42,19 @@ export const NativeBottomSheet = (props: NativeBottomSheetProps): ReactElement = props.triggerAttribute.status === ValueStatus.Available && props.triggerAttribute.value; - const manageBottomSheet = () => { + const manageBottomSheet = useCallback(() => { if (props.triggerAttribute && props.triggerAttribute.status === ValueStatus.Available) { if (props.triggerAttribute.value) { - bottomSheetRef.current?.expand(); + bottomSheetRef.current?.snapToIndex(0); } else { bottomSheetRef.current?.close(); } } - }; + }, [props.triggerAttribute]); useEffect(() => { manageBottomSheet(); - }, [props.triggerAttribute]); + }, [manageBottomSheet]); useEffect(() => { // Only show the ActionSheet if using native on iOS and the trigger is active. @@ -78,16 +85,33 @@ export const NativeBottomSheet = (props: NativeBottomSheetProps): ReactElement = } }, [isOpen]); - const renderBackdrop = (backdropProps: BottomSheetBackdropProps) => ( - - - + const close = useCallback(() => { + bottomSheetRef.current?.close(); + }, []); + + const renderBackdrop = useCallback( + (backdropProps: BottomSheetBackdropProps) => ( + + + + ), + [close] + ); + + const onLayoutHandler = useCallback( + (event: LayoutChangeEvent) => { + const height = event.nativeEvent.layout.height; + if (height > 0 && height !== contentHeight) { + setContentHeight(height); + } + }, + [contentHeight] ); const actionHandler = useCallback( @@ -146,37 +170,49 @@ export const NativeBottomSheet = (props: NativeBottomSheetProps): ReactElement = return [styles.sheetContainer, props.styles.container]; }; - const handleSheetChanges = (index: number) => { - if (!isAvailable) { - return; - } - const hasOpened = lastIndexRef === -1 && index === 0; - const hasClosed = index === -1; - lastIndexRef = index; + 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); + if (hasOpened) { + props.triggerAttribute?.setValue(true); + } + if (hasClosed) { + props.triggerAttribute?.setValue(false); + } + }, + [isAvailable, props.triggerAttribute] + ); + + const snapPoints = useMemo(() => { + if (contentHeight === 0) { + return [1]; // During measurement } - }; + + // Use actual measured height, cap at 90% screen + const maxHeight = Dimensions.get("screen").height * 0.9; + + const snapHeight = Math.min(contentHeight, maxHeight); + return [snapHeight]; + }, [contentHeight]); if (props.useNative && Platform.OS === "ios") { return ; } - const close = () => { - bottomSheetRef.current?.close(); - }; - return ( 0 ? 0 : -1} + snapPoints={snapPoints} enablePanDownToClose - animateOnMount + animateOnMount={false} onClose={() => handleSheetChanges(-1)} onChange={handleSheetChanges} style={getContainerStyle()} @@ -185,9 +221,9 @@ export const NativeBottomSheet = (props: NativeBottomSheetProps): ReactElement = handleComponent={null} handleStyle={{ display: "none" }} > - + {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 @@ - +