diff --git a/CHANGELOG.md b/CHANGELOG.md index b681a74b..ac7f0c81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ## [Unreleased] (Preview) +### Fixed + +- (**Reader**) Fix scrollbar appearing with "fit to widt/height/screen" page scale mode and applied safe area insets + ## [20260509.01] (r3147) - 2026-05-09 ### Fixed diff --git a/src/features/reader/Reader.types.ts b/src/features/reader/Reader.types.ts index 10c0dbea..36443097 100644 --- a/src/features/reader/Reader.types.ts +++ b/src/features/reader/Reader.types.ts @@ -350,6 +350,7 @@ export interface ReaderPagerProps | 'customFilter' | 'shouldStretchPage' | 'readerWidth' + | 'safeAreaInset' >, Pick { onLoad?: (pagesIndex: number, url: string, isPrimary?: boolean) => void; diff --git a/src/features/reader/filters/ReaderRGBAFilter.tsx b/src/features/reader/filters/ReaderRGBAFilter.tsx index 5e84ce2e..d1c832cb 100644 --- a/src/features/reader/filters/ReaderRGBAFilter.tsx +++ b/src/features/reader/filters/ReaderRGBAFilter.tsx @@ -17,6 +17,7 @@ const BaseReaderRGBAFilter = ({ readerNavBarWidth }: Pick state.customFilter.rgba); + const safeAreaInset = useReaderSettingsStore('safeAreaInset'); if (!enabled) { return null; @@ -26,10 +27,10 @@ const BaseReaderRGBAFilter = ({ readerNavBarWidth }: Pick readingMode === ReadingMode.DOUBLE_PAGE; @@ -96,3 +98,13 @@ export const getProgressBarPosition = ( return ProgressBarPosition.BOTTOM; }; + +export const getSafeAreaInsets = ( + safeAreaInset: SafeAreaInset, + direction: Exclude, +): string[] => [ + ...(direction === ScrollDirection.Y && safeAreaInset.top ? ['env(safe-area-inset-top)'] : []), + ...(direction === ScrollDirection.X && safeAreaInset.right ? ['env(safe-area-inset-right)'] : []), + ...(direction === ScrollDirection.Y && safeAreaInset.bottom ? ['env(safe-area-inset-bottom)'] : []), + ...(direction === ScrollDirection.X && safeAreaInset.left ? ['env(safe-area-inset-left)'] : []), +]; diff --git a/src/features/reader/viewer/ReaderChapterViewer.tsx b/src/features/reader/viewer/ReaderChapterViewer.tsx index 8d6ece62..1d30128c 100644 --- a/src/features/reader/viewer/ReaderChapterViewer.tsx +++ b/src/features/reader/viewer/ReaderChapterViewer.tsx @@ -52,6 +52,7 @@ const BaseReaderChapterViewer = ({ retryFailedPagesKeyPrefix, readingMode, readerWidth, + safeAreaInset, pageScaleMode, shouldOffsetDoubleSpreads, readingDirection, @@ -427,6 +428,7 @@ const BaseReaderChapterViewer = ({ customFilter={customFilter} shouldStretchPage={shouldStretchPage} readerWidth={readerWidth} + safeAreaInset={safeAreaInset} readerNavBarWidth={readerNavBarWidth} isPreloadMode={isPreloadMode} resumeMode={resumeMode} diff --git a/src/features/reader/viewer/ReaderViewer.tsx b/src/features/reader/viewer/ReaderViewer.tsx index d71fe6ae..a7fc6bd2 100644 --- a/src/features/reader/viewer/ReaderViewer.tsx +++ b/src/features/reader/viewer/ReaderViewer.tsx @@ -108,6 +108,7 @@ const BaseReaderViewer = ({ shouldStretchPage: state.shouldStretchPage.value, isStaticNav: state.isStaticNav, })); + const safeAreaInset = useReaderSettingsStore('safeAreaInset'); const { resumeMode = ReaderResumeMode.START } = useLocation().state ?? { resumeMode: ReaderResumeMode.START, }; @@ -337,6 +338,7 @@ const BaseReaderViewer = ({ retryFailedPagesKeyPrefix={retryFailedPagesKeyPrefix} readingMode={readingMode} readerWidth={readerWidth} + safeAreaInset={safeAreaInset} pageScaleMode={pageScaleMode} shouldOffsetDoubleSpreads={shouldOffsetDoubleSpreads} readingDirection={readingDirection} diff --git a/src/features/reader/viewer/components/ReaderPage.tsx b/src/features/reader/viewer/components/ReaderPage.tsx index 460724ae..f1a00a8d 100644 --- a/src/features/reader/viewer/components/ReaderPage.tsx +++ b/src/features/reader/viewer/components/ReaderPage.tsx @@ -19,6 +19,8 @@ import { applyStyles } from '@/base/utils/ApplyStyles.ts'; import { MediaQuery } from '@/base/utils/MediaQuery.tsx'; import type { NavbarContextType } from '@/features/navigation-bar/NavigationBar.types.ts'; import { useReaderScrollbarStore } from '@/features/reader/stores/ReaderStore.ts'; +import { ScrollDirection } from '@/base/Base.types.ts'; +import { getSafeAreaInsets } from '@/features/reader/settings/ReaderSettings.utils.tsx'; const getCustomFilterString = (customFilter: ReaderCustomFilter): string => Object.keys(customFilter) @@ -63,6 +65,7 @@ const BaseReaderPage = ({ pageScaleMode, shouldStretchPage, readerWidth, + safeAreaInset, onLoad, onError, setRef, @@ -70,7 +73,10 @@ const BaseReaderPage = ({ isLoaded, ...props }: Omit & - Pick & + Pick< + IReaderSettings, + 'readingMode' | 'customFilter' | 'pageScaleMode' | 'shouldStretchPage' | 'readerWidth' | 'safeAreaInset' + > & Pick & { pageIndex: number; pagesIndex: number; @@ -115,8 +121,8 @@ const BaseReaderPage = ({ shouldStretchPage, pageScaleMode, readerWidth, - readerNavBarWidth + scrollbar.ySize, - scrollbar.xSize, + [readerNavBarWidth, scrollbar.ySize, ...getSafeAreaInsets(safeAreaInset, ScrollDirection.X)], + [scrollbar.xSize, ...getSafeAreaInsets(safeAreaInset, ScrollDirection.Y)], doublePage, isTabletWidth, ), @@ -132,8 +138,8 @@ const BaseReaderPage = ({ pageScaleMode, doublePage, readerWidth, - readerNavBarWidth + scrollbar.ySize, - scrollbar.xSize, + [readerNavBarWidth, scrollbar.ySize, ...getSafeAreaInsets(safeAreaInset, ScrollDirection.X)], + [scrollbar.xSize, ...getSafeAreaInsets(safeAreaInset, ScrollDirection.Y)], ), filter: getCustomFilterString(customFilter), objectFit: 'contain', diff --git a/src/features/reader/viewer/pager/ReaderPager.utils.tsx b/src/features/reader/viewer/pager/ReaderPager.utils.tsx index 1c71b863..83f9fa3b 100644 --- a/src/features/reader/viewer/pager/ReaderPager.utils.tsx +++ b/src/features/reader/viewer/pager/ReaderPager.utils.tsx @@ -56,25 +56,28 @@ const getPageWidthPercentage = ( return 1; }; +const joinOffsetsForCssCalc = (offsets: (number | string)[], operator: '-' | '+' = '-'): string => + offsets.map((offset) => (typeof offset === 'number' ? `${offset}px` : offset)).join(` ${operator} `); + export const getImagePlaceholderStyling = ( readingMode: IReaderSettings['readingMode'], shouldStretchPage: IReaderSettings['shouldStretchPage'], pageScaleMode: IReaderSettings['pageScaleMode'], readerWidth: IReaderSettings['readerWidth'], - widthOffset: number, - heightOffset: number, + widthOffset: (number | string)[], + heightOffset: (number | string)[], isDoublePage?: boolean, isTabletWidth?: boolean, ): CSSObject => { const OVER_9000 = 9000; - const getMaxWidth = (width: string) => `calc(${width} - ${widthOffset}px)`; + const getMaxWidth = (width: string) => `calc(${width} - ${joinOffsetsForCssCalc(widthOffset)})`; const getDesktopWidth = (width: number, readerWidthValue?: number) => getMaxWidth(`${coerceIn(readerWidthValue ?? width, Math.min(width, readerWidthValue ?? OVER_9000), width)}vw`); const setReaderWidth = getSetReaderWidth(readerWidth, pageScaleMode); const fullWidth = getMaxWidth(`${Math.min(100, setReaderWidth ?? OVER_9000)}vw`); - const fullHeight = `calc(100vh - ${heightOffset}px)`; + const fullHeight = `calc(100vh - ${joinOffsetsForCssCalc(heightOffset)})`; const DEFAULT_SINGLE_PAGE_WIDTH = isTabletWidth ? fullWidth : getDesktopWidth(40, setReaderWidth); const DEFAULT_SINGLE_PAGE_HEIGHT = isTabletWidth ? fullHeight : '85vh'; @@ -151,11 +154,11 @@ const getReaderDimensionStyling = ( readingMode: IReaderSettings['readingMode'], shouldStretchPage: IReaderSettings['shouldStretchPage'], pageScaleMode: IReaderSettings['pageScaleMode'], - widthOffset: number, - heightOffset: number, + widthOffset: (number | string)[], + heightOffset: (number | string)[], ): CSSObject => { - const fullWidth = `calc((100vw - ${widthOffset}px) * ${widthPercentage})`; - const fullHeight = `calc(100vh - ${heightOffset}px)`; + const fullWidth = `calc((100vw - ${joinOffsetsForCssCalc(widthOffset)}) * ${widthPercentage})`; + const fullHeight = `calc(100vh - ${joinOffsetsForCssCalc(heightOffset)})`; switch (pageScaleMode) { case ReaderPageScaleMode.WIDTH: @@ -216,8 +219,8 @@ export const getReaderImageStyling = ( pageScaleMode: IReaderSettings['pageScaleMode'], isDoublePage: boolean, readerWidth: IReaderSettings['readerWidth'], - widthOffset: number, - heightOffset: number, + widthOffset: (number | string)[], + heightOffset: (number | string)[], ): CSSObject => { const widthPercentage = getPageWidthPercentage(pageScaleMode, isDoublePage, readerWidth, true); return getReaderDimensionStyling( @@ -292,6 +295,7 @@ export const createReaderPage = ( pageScaleMode: IReaderSettings['pageScaleMode'], shouldStretchPage: IReaderSettings['shouldStretchPage'], readerWidth: IReaderSettings['readerWidth'], + safeAreaInset: IReaderSettings['safeAreaInset'], readerNavBarWidth: NavbarContextType['readerNavBarWidth'], retryKeyPrefix?: string, position?: 'left' | 'right', @@ -322,6 +326,7 @@ export const createReaderPage = ( pageScaleMode={pageScaleMode} shouldStretchPage={shouldStretchPage} readerWidth={readerWidth} + safeAreaInset={safeAreaInset} readerNavBarWidth={readerNavBarWidth} /> ); diff --git a/src/features/reader/viewer/pager/components/BasePager.tsx b/src/features/reader/viewer/pager/components/BasePager.tsx index 4e23797e..371f867f 100644 --- a/src/features/reader/viewer/pager/components/BasePager.tsx +++ b/src/features/reader/viewer/pager/components/BasePager.tsx @@ -35,6 +35,7 @@ const BaseBasePager = ({ pageScaleMode, shouldStretchPage, readerWidth, + safeAreaInset, readerNavBarWidth, resumeMode, handleAsInitialRender, @@ -52,6 +53,7 @@ const BaseBasePager = ({ pageScaleMode: ReaderPagerProps['pageScaleMode'], shouldStretchPage: ReaderPagerProps['shouldStretchPage'], readerWidth: ReaderPagerProps['readerWidth'], + safeAreaInset: ReaderPagerProps['safeAreaInset'], readerNavBarWidth: ReaderPagerProps['readerNavBarWidth'], ) => ReactNode; slots?: { boxProps?: BoxProps }; @@ -125,6 +127,7 @@ const BaseBasePager = ({ pageScaleMode, shouldStretchPage, readerWidth, + safeAreaInset, readerNavBarWidth, ), )}