diff --git a/packages/overlayscrollbars/src/environment/environment.ts b/packages/overlayscrollbars/src/environment/environment.ts index 1505647..1a81a8c 100644 --- a/packages/overlayscrollbars/src/environment/environment.ts +++ b/packages/overlayscrollbars/src/environment/environment.ts @@ -12,7 +12,8 @@ import { removeElements, windowSize, runEach, - equalWH, + equalBCRWH, + getBoundingClientRect, } from 'support'; import { classNameEnvironment, @@ -87,14 +88,14 @@ const getRtlScrollBehavior = (parentElm: HTMLElement, childElm: HTMLElement): { const getFlexboxGlue = (parentElm: HTMLElement, childElm: HTMLElement): boolean => { addClass(parentElm, classNameEnvironmentFlexboxGlue); - const minOffsetsizeParent = offsetSize(parentElm); - const minOffsetsize = offsetSize(childElm); - const supportsMin = equalWH(minOffsetsize, minOffsetsizeParent); + const minOffsetsizeParent = getBoundingClientRect(parentElm); + const minOffsetsize = getBoundingClientRect(childElm); + const supportsMin = equalBCRWH(minOffsetsize, minOffsetsizeParent, true); addClass(parentElm, classNameEnvironmentFlexboxGlueMax); - const maxOffsetsizeParent = offsetSize(parentElm); - const maxOffsetsize = offsetSize(childElm); - const supportsMax = equalWH(maxOffsetsize, maxOffsetsizeParent); + const maxOffsetsizeParent = getBoundingClientRect(parentElm); + const maxOffsetsize = getBoundingClientRect(childElm); + const supportsMax = equalBCRWH(maxOffsetsize, maxOffsetsizeParent, true); return supportsMin && supportsMax; }; diff --git a/packages/overlayscrollbars/src/lifecycles/overflowLifecycle.ts b/packages/overlayscrollbars/src/lifecycles/overflowLifecycle.ts index bfef191..c2920f3 100644 --- a/packages/overlayscrollbars/src/lifecycles/overflowLifecycle.ts +++ b/packages/overlayscrollbars/src/lifecycles/overflowLifecycle.ts @@ -12,6 +12,8 @@ import { addClass, removeClass, clientSize, + offsetSize, + getBoundingClientRect, } from 'support'; import { LifecycleHub, Lifecycle } from 'lifecycles/lifecycleHub'; import { getEnvironment } from 'environment'; @@ -21,6 +23,10 @@ import { classNameViewportScrollbarStyling } from 'classnames'; const overlaidScrollbarsHideOffset = 42; const overlaidScrollbarsHideBorderStyle = `${overlaidScrollbarsHideOffset}px solid transparent`; +interface ContentScrollSizeCacheContext { + _viewportSize: WH; + _viewportScrollSize: WH; +} interface OverflowAmountCacheContext { _contentScrollSize: WH; _viewportSize: WH; @@ -29,26 +35,48 @@ interface OverflowAmountCacheContext { export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle => { const { _structureSetup, _getPaddingStyle } = lifecycleHub; const { _host, _padding, _viewport, _content, _contentArrange } = _structureSetup._targetObj; - const { _update: updateContentScrollSizeCache, _current: getCurrentContentScrollSizeCache } = createCache>( - () => scrollSize(_content || _viewport), + const { _update: updateContentScrollSizeCache, _current: getCurrentContentScrollSizeCache } = createCache< + WH, + ContentScrollSizeCacheContext + >( + (ctx) => { + const { _viewportSize, _viewportScrollSize } = ctx; + const contentViewportScrollSize = scrollSize(_content || _viewport); + + return _content ? fixScrollSizeRounding(contentViewportScrollSize, _viewportSize, _viewportScrollSize) : contentViewportScrollSize; + }, { _equal: equalWH } ); const { _update: updateOverflowAmountCache, _current: getCurrentOverflowAmountCache } = createCache, OverflowAmountCacheContext>( (ctx) => ({ - x: Math.max(0, Math.round((ctx._contentScrollSize.w - ctx._viewportSize.w) * 100) / 100), - y: Math.max(0, Math.round((ctx._contentScrollSize.h - ctx._viewportSize.h) * 100) / 100), + x: Math.max(0, ctx._contentScrollSize.w - ctx._viewportSize.w), + y: Math.max(0, ctx._contentScrollSize.h - ctx._viewportSize.h), }), { _equal: equalXY } ); + const fixScrollSizeRounding = (contentScrollSize: WH, viewportSize: WH, viewportScrollSize: WH): WH => { + const equalViewportSizes = viewportSize.w === viewportScrollSize.w || viewportSize.h === viewportScrollSize.h; + const contentViewportScrollSizeOverflow = contentScrollSize.w > viewportScrollSize.w || contentScrollSize.h > viewportScrollSize.h; + + if (equalViewportSizes && contentViewportScrollSizeOverflow) { + const viewportRect = getBoundingClientRect(_viewport); + const viewportOffsetSize = offsetSize(_viewport); + + return { + w: contentScrollSize.w - Math.ceil(Math.max(0, viewportRect.width - viewportOffsetSize.w)), + h: contentScrollSize.h - Math.ceil(Math.max(0, viewportRect.height - viewportOffsetSize.h)), + }; + } + + return contentScrollSize; + }; + const setViewportOverflowStyle = (horizontal: boolean, amount: number, behavior: OverflowBehavior, styleObj: StyleObject) => { const overflowKey = horizontal ? 'overflowX' : 'overflowY'; - //const scrollMaxKey = horizontal ? 'scrollLeftMax' : 'scrollTopMax'; const behaviorIsScroll = behavior === 'scroll'; const behaviorIsVisibleScroll = behavior === 'visible-scroll'; const hideOverflow = behaviorIsScroll || behavior === 'hidden'; - //const scrollMax = _viewport[scrollMaxKey]; - //const scrollMaxOverflow = isNumber(scrollMax) ? scrollMax > 0 : true; const applyStyle = amount > 0 && hideOverflow; if (applyStyle) { @@ -156,7 +184,11 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle = const contentClientSize = clientSize(_content || _viewport); // needs to be client Size because applied border for content arrange on content const contentArrangeOffsetSize = clientSize(_contentArrange); // can be offset size aswell - contentScrollSizeCache = updateContentScrollSizeCache(force); + contentScrollSizeCache = updateContentScrollSizeCache(force, { + _viewportSize: viewportSize, + _viewportScrollSize: viewportScrollSize, + }); + const { _value: contentScrollSize } = contentScrollSizeCache; overflowAmuntCache = updateOverflowAmountCache(force, { _contentScrollSize: { @@ -230,8 +262,8 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle = // TODO: Test without content // TODO: Test without padding // TODO: hide host || padding overflow if scroll x or y - // TODO: fix false overflow bug (fractal scroll size) // TODO: add trinsic lifecycle + // TODO: IE max-width fix not always working // TODO: remove lifecycleHub get set padding if not needed style(_viewport, viewportStyle); diff --git a/packages/overlayscrollbars/src/support/utils/equal.ts b/packages/overlayscrollbars/src/support/utils/equal.ts index fc11521..f5529eb 100644 --- a/packages/overlayscrollbars/src/support/utils/equal.ts +++ b/packages/overlayscrollbars/src/support/utils/equal.ts @@ -8,11 +8,18 @@ import { PlainObject } from 'typings'; * @param b Object b. * @param props The props which shall be compared. */ -export const equal = (a: T | undefined, b: T | undefined, props: Array): boolean => { +export const equal = ( + a: T | undefined, + b: T | undefined, + props: Array, + propMutation?: ((value: any) => any) | null | false +): boolean => { if (a && b) { let result = true; each(props, (prop) => { - if (a[prop] !== b[prop]) { + const compareA = propMutation ? propMutation(a[prop]) : a[prop]; + const compareB = propMutation ? propMutation(b[prop]) : b[prop]; + if (compareA !== compareB) { result = false; } }); @@ -44,3 +51,13 @@ export const equalXY = (a?: XY, b?: XY) => equal(a, b, ['x', 'y']); * @param b Object b. */ export const equalTRBL = (a?: TRBL, b?: TRBL) => equal(a, b, ['t', 'r', 'b', 'l']); + +/** + * Compares two DOM Rects for their equality of their width and height properties + * Also returns false if one of the DOM Rects is undefined or null. + * @param a DOM Rect a. + * @param b DOM Rect b. + * @param round Whether the values should be rounded. + */ +export const equalBCRWH = (a?: DOMRect, b?: DOMRect, round?: boolean) => + equal(a, b, ['width', 'height'], round && ((value) => Math.round(value)));