diff --git a/packages/overlayscrollbars/src/lifecycles/lifecycleHub.ts b/packages/overlayscrollbars/src/lifecycles/lifecycleHub.ts index 46e68f5..575b66d 100644 --- a/packages/overlayscrollbars/src/lifecycles/lifecycleHub.ts +++ b/packages/overlayscrollbars/src/lifecycles/lifecycleHub.ts @@ -160,7 +160,7 @@ export const createLifecycleHub = (options: OSOptions, structureSetup: Structure _heightIntrinsic || (trinsicObserver ? trinsicObserver._getCurrentCacheValues(force)._heightIntrinsic : heightIntrinsicCacheValuesFallback); const checkOption: LifecycleCheckOption = (path) => ({ _value: getPropByPath(options, path), - _changed: force || getPropByPath(changedOptions, path) !== undefined, + _changed: force || (!!changedOptions && getPropByPath(changedOptions, path) !== undefined), }); const adjustScrollOffset = doViewportArrange || !_flexboxGlue; const scrollOffsetX = adjustScrollOffset && scrollLeft(_viewport); @@ -262,6 +262,8 @@ export const createLifecycleHub = (options: OSOptions, structureSetup: Structure const envUpdateListener = update.bind(null, null, true); addEnvironmentListener(envUpdateListener); + console.log(getEnvironment()); + return { _update: update, _destroy() { diff --git a/packages/overlayscrollbars/src/lifecycles/overflowLifecycle.ts b/packages/overlayscrollbars/src/lifecycles/overflowLifecycle.ts index a371f7d..96ff721 100644 --- a/packages/overlayscrollbars/src/lifecycles/overflowLifecycle.ts +++ b/packages/overlayscrollbars/src/lifecycles/overflowLifecycle.ts @@ -100,11 +100,12 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle = const hostBCR = getBoundingClientRect(_host); const hostOffsetSize = offsetSize(_host); const hostClientSize = clientSize(_host); - const paddingAbsoluteVertical = paddingAbsolute ? padding.b + padding.t : 0; + // padding subtraction is only needed if padding is absolute or if viewport is content-box + const paddingVertical = paddingAbsolute || style(_viewport, 'boxSizing') === 'content-box' ? padding.b + padding.t : 0; const clientSizeWithoutRounding = hostClientSize.h + (hostBCR.height - hostOffsetSize.h); style(_viewport, { - height: clientSizeWithoutRounding + (_overflowScroll.x ? _scrollbarsHideOffset.x : 0) - paddingAbsoluteVertical, + height: clientSizeWithoutRounding + (_overflowScroll.x ? _scrollbarsHideOffset.x : 0) - paddingVertical, }); } }; @@ -156,18 +157,20 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle = ): ViewportOverflowState => { const setPartialStylePerAxis = (horizontal: boolean, overflowAmount: number, behavior: OverflowBehavior, styleObj: StyleObject) => { const overflowKey: keyof StyleObject = horizontal ? 'overflowX' : 'overflowY'; + const behaviorIsVisible = behavior.indexOf('visible') === 0; + const behaviorIsVisibleHidden = behavior === 'visible-hidden'; const behaviorIsScroll = behavior === 'scroll'; - const behaviorIsVisibleScroll = behavior === 'visible-scroll'; - const hideOverflow = behaviorIsScroll || behavior === 'hidden'; - const applyStyle = overflowAmount > 0 && hideOverflow; - if (applyStyle) { + if (behaviorIsVisible) { + styleObj[overflowKey] = 'visible'; + } + if (behaviorIsScroll && overflowAmount > 0) { styleObj[overflowKey] = behavior; } return { - _visible: !applyStyle, - _behavior: behaviorIsVisibleScroll ? 'scroll' : 'hidden', + _visible: behaviorIsVisible, + _behavior: behaviorIsVisibleHidden ? 'hidden' : 'scroll', }; }; const { _visible: xVisible, _behavior: xVisibleBehavior } = setPartialStylePerAxis(true, overflowAmount!.x, overflow.x, viewportStyleObj); @@ -245,11 +248,9 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle = viewportArrange: boolean, viewportStyleObj: StyleObject ) => { - const { _nativeScrollbarStyling } = getEnvironment(); - const { _overflowScroll, _scrollbarsHideOffset, _scrollbarsHideOffsetArrange } = viewportOverflowState; + const { _scrollbarsHideOffset, _scrollbarsHideOffsetArrange } = viewportOverflowState; const { x: arrangeX, y: arrangeY } = _scrollbarsHideOffsetArrange; const { x: hideOffsetX, y: hideOffsetY } = _scrollbarsHideOffset; - const { x: scrollX, y: scrollY } = _overflowScroll; const paddingStyle = _getViewportPaddingStyle(); const horizontalMarginKey: keyof StyleObject = directionIsRTL ? 'marginLeft' : 'marginRight'; const viewportHorizontalPaddingKey: keyof StyleObject = directionIsRTL ? 'paddingLeft' : 'paddingRight'; @@ -270,22 +271,20 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle = viewportStyleObj[viewportHorizontalPaddingKey] = horizontalPaddingValue + (arrangeY ? hideOffsetY : 0); viewportStyleObj.paddingBottom = verticalPaddingValue + (arrangeX ? hideOffsetX : 0); } - - // hide overflowing scrollbars if there are any - if (!_nativeScrollbarStyling) { - style(_padding || _host, { - overflow: scrollX || scrollY ? 'hidden' : '', - }); - } }; /** * Removes all styles applied because of the viewport arrange strategy. * @param showNativeOverlaidScrollbars Whether native overlaid scrollbars are shown instead of hidden. + * @param directionIsRTL Whether the direction is RTL or not. * @param viewportOverflowState The currentviewport overflow state or undefined if it has to be determined. * @returns A object with a function which applies all the removed styles and the determined viewport vverflow state. */ - const undoViewportArrange = (showNativeOverlaidScrollbars: boolean, viewportOverflowState?: ViewportOverflowState): UndoViewportArrangeResult => { + const undoViewportArrange = ( + showNativeOverlaidScrollbars: boolean, + directionIsRTL: boolean, + viewportOverflowState?: ViewportOverflowState + ): UndoViewportArrangeResult => { if (_doViewportArrange) { const finalViewportOverflowState = viewportOverflowState || getViewportOverflowState(showNativeOverlaidScrollbars); const paddingStyle = _getViewportPaddingStyle(); @@ -316,6 +315,7 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle = return { _redoViewportArrange: () => { + hideNativeScrollbars(finalViewportOverflowState, directionIsRTL, _doViewportArrange, prevStyle); style(_viewport, prevStyle); addClass(_viewport, classNameViewportArrange); }, @@ -358,6 +358,7 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle = if (_sizeChanged || _paddingStyleChanged || _contentMutation || showNativeOverlaidScrollbarsChanged || directionChanged) { const { _redoViewportArrange, _viewportOverflowState: undoViewportArrangeOverflowState } = undoViewportArrange( showNativeOverlaidScrollbars, + directionIsRTL!, preMeasureViewportOverflowState ); const contentSize = clientSize(_viewport); @@ -431,6 +432,7 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle = // TODO: hide host overflow if scroll x or y and no padding element there // TODO: Test without content // TODO: Test without padding + // TODO: overflow: visible on padding / host if overflow visible on both axis style(_viewport, viewportStyle); diff --git a/packages/overlayscrollbars/src/lifecycles/trinsicLifecycle.ts b/packages/overlayscrollbars/src/lifecycles/trinsicLifecycle.ts index 2059491..b5a786a 100644 --- a/packages/overlayscrollbars/src/lifecycles/trinsicLifecycle.ts +++ b/packages/overlayscrollbars/src/lifecycles/trinsicLifecycle.ts @@ -16,7 +16,8 @@ export const createTrinsicLifecycle = (lifecycleHub: LifecycleHub): Lifecycle => if (heightIntrinsicChanged) { style(_content, { - height: heightIntrinsic ? 'auto' : '100%', + height: heightIntrinsic ? '' : '100%', + display: heightIntrinsic ? '' : 'inline', }); } diff --git a/packages/overlayscrollbars/src/options.ts b/packages/overlayscrollbars/src/options.ts index bb0fe8e..0a1fc1d 100644 --- a/packages/overlayscrollbars/src/options.ts +++ b/packages/overlayscrollbars/src/options.ts @@ -8,7 +8,7 @@ import { export type ResizeBehavior = 'none' | 'both' | 'horizontal' | 'vertical'; -export type OverflowBehavior = 'hidden' | 'scroll' | 'visible-hidden' | 'visible-scroll'; +export type OverflowBehavior = 'hidden' | 'scroll' | 'visible' | 'visible-hidden'; export type VisibilityBehavior = 'visible' | 'hidden' | 'auto'; @@ -111,7 +111,7 @@ const booleanTrueTemplate: OptionsWithOptionsTemplateValue = [true, oTy const booleanFalseTemplate: OptionsWithOptionsTemplateValue = [false, oTypes.boolean]; // const callbackTemplate: OptionsWithOptionsTemplateValue = [null, [oTypes.function, oTypes.null]]; const resizeAllowedValues: OptionsTemplateValue = 'none both horizontal vertical'; -const overflowAllowedValues: OptionsTemplateValue = 'visible-hidden visible-scroll scroll hidden'; +const overflowAllowedValues: OptionsTemplateValue = 'hidden scroll visible visible-hidden'; const scrollbarsVisibilityAllowedValues: OptionsTemplateValue = 'visible hidden auto'; const scrollbarsAutoHideAllowedValues: OptionsTemplateValue = 'never scroll leavemove'; diff --git a/packages/overlayscrollbars/src/styles/overlayscrollbars.scss b/packages/overlayscrollbars/src/styles/overlayscrollbars.scss index ded922b..8c263e8 100644 --- a/packages/overlayscrollbars/src/styles/overlayscrollbars.scss +++ b/packages/overlayscrollbars/src/styles/overlayscrollbars.scss @@ -52,21 +52,6 @@ } } -/* fix restricted measuring */ -.os-environment:before, -.os-environment:after, -.os-content:before, -.os-content:after { - content: ''; - display: table; - width: 0.01px; - height: 0.01px; - line-height: 0; - font-size: 0; - flex-grow: 0; - flex-shrink: 0; - visibility: hidden; -} .os-environment, .os-viewport { -ms-overflow-style: scrollbar !important; @@ -104,7 +89,6 @@ padding: 0; margin: 0; border: none; - overflow: visible; max-width: 100%; z-index: 0; } @@ -125,6 +109,12 @@ } } +.os-host, +.os-padding, +.os-viewport { + overflow: hidden; +} + .os-content { box-sizing: inherit; } diff --git a/packages/overlayscrollbars/src/styles/sizeobserver.scss b/packages/overlayscrollbars/src/styles/sizeobserver.scss index a269a15..f1f2afa 100644 --- a/packages/overlayscrollbars/src/styles/sizeobserver.scss +++ b/packages/overlayscrollbars/src/styles/sizeobserver.scss @@ -1,4 +1,4 @@ -$inflate-cushion: 100px; +$scrollbars-cushion: 100px; // assume no scrollbar takes more than 100px space $inflate-margin: 100px; .os-size-observer, @@ -29,11 +29,12 @@ $inflate-margin: 100px; padding: inherit; border: inherit; box-sizing: inherit; - margin: 0; - top: -$inflate-cushion; - right: -$inflate-cushion; - bottom: -$inflate-cushion; - left: -$inflate-cushion; + margin: -$inflate-margin; + top: 0; + right: 0; + bottom: 0; + left: 0; + transform: scale(0.1); &::before, &::after { @@ -42,9 +43,9 @@ $inflate-margin: 100px; box-sizing: inherit; } &::before { - padding: 1px; - width: 1px; - height: 1px; + padding: 10px; + width: 10px; + height: 10px; } &::after { content: ''; @@ -74,10 +75,10 @@ $inflate-margin: 100px; } & > .os-size-observer-listener-item { - top: -$inflate-cushion; - right: -$inflate-cushion; - bottom: -$inflate-cushion; - left: -$inflate-cushion; + top: -$scrollbars-cushion; + right: -$scrollbars-cushion; + bottom: -$scrollbars-cushion; + left: -$scrollbars-cushion; } } diff --git a/packages/overlayscrollbars/tests/browser/lifecycles/structureLifecycle/index.browser.ts b/packages/overlayscrollbars/tests/browser/lifecycles/structureLifecycle/index.browser.ts index 4ce7b3a..f98128b 100644 --- a/packages/overlayscrollbars/tests/browser/lifecycles/structureLifecycle/index.browser.ts +++ b/packages/overlayscrollbars/tests/browser/lifecycles/structureLifecycle/index.browser.ts @@ -1,18 +1,31 @@ import 'styles/overlayscrollbars.scss'; import './index.scss'; - +import should from 'should'; import { resize } from '@/testing-browser/Resize'; +import { setTestResult, waitForOrFailTest } from '@/testing-browser/TestResult'; import { generateClassChangeSelectCallback, iterateSelect } from '@/testing-browser/Select'; +import { timeout } from '@/testing-browser/timeout'; import { OverlayScrollbars } from 'overlayscrollbars'; -import { from, style } from 'support'; +import { createTrinsicObserver } from 'observers/trinsicObserver'; +import { from, getBoundingClientRect, style, parent, addClass } from 'support'; -const targetElm = document.querySelector('#target') as HTMLElement; -const osInstance = (window.os = OverlayScrollbars({ target: targetElm, content: true })); +// @ts-ignore +const msie11 = !!window.MSInputMethodContext && !!document.documentMode; +const firefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1; +const useContentElement = false; +const fixedDigits = msie11 ? 1 : 10; +const fixedDigitsOffset = firefox ? 3 : fixedDigits; // ff does roundign errors here only + +const startBtn: HTMLButtonElement | null = document.querySelector('#start'); const target: HTMLElement | null = document.querySelector('#target'); const comparison: HTMLElement | null = document.querySelector('#comparison'); -const targetRes: HTMLElement | null = document.querySelector('#target .resize'); -const comparisonRes: HTMLElement | null = document.querySelector('#comparison .resize'); +const targetResize: HTMLElement | null = document.querySelector('#target .resize'); +const comparisonResize: HTMLElement | null = document.querySelector('#comparison .resize'); +const targetPercent: HTMLElement | null = document.querySelector('#target .percent'); +const comparisonPercent: HTMLElement | null = document.querySelector('#comparison .percent'); +const targetEnd: HTMLElement | null = document.querySelector('#target .end'); +const comparisonEnd: HTMLElement | null = document.querySelector('#comparison .end'); const resizeElms = document.querySelectorAll('.resize'); const percentElms = document.querySelectorAll('.percent'); @@ -22,7 +35,7 @@ const containerElms = document.querySelectorAll('.container'); resize(target!).addResizeListener((width, height) => style(comparison, { width, height })); //resize(comparison!).addResizeListener((width, height) => style(target, { width, height })); -resize(targetRes!).addResizeListener((width, height) => style(comparisonRes, { width, height })); +resize(targetResize!).addResizeListener((width, height) => style(comparisonResize, { width, height })); //resize(comparisonRes!).addResizeListener((width, height) => style(targetRes, { width, height })); target!.querySelector('.os-viewport')?.addEventListener('scroll', (e) => { @@ -31,6 +44,14 @@ target!.querySelector('.os-viewport')?.addEventListener('scroll', (e) => { comparison!.scrollTop = viewport.scrollTop; }); +if (!useContentElement) { + envElms.forEach((elm) => { + addClass(elm, 'intrinsic-hack'); + }); +} + +const osInstance = (window.os = OverlayScrollbars({ target: target!, content: useContentElement })); + const selectCallbackEnv = generateClassChangeSelectCallback(from(envElms)); const envWidthSelect = document.querySelector('#envWidth'); const envHeightSelect = document.querySelector('#envHeight'); @@ -67,3 +88,170 @@ selectCallbackEnv(containerMarginSelect); selectCallbackEnv(containerBoxSizingSelect); selectCallbackEnv(containerDirectionSelect); selectCallbackEnv(containerMinMaxSelect); + +// 1. no overflow +// 2. 1px overflow (width) +// 3. 1px overflow (height) +// 4. lerge overflow (width & height) + +// tests for restricted measuring without content elm + +const iterate = async (select: HTMLSelectElement | null, afterEach?: () => any) => { + await iterateSelect(select, { + async check() { + const comparisonEnvBCR = getBoundingClientRect(parent(comparison!) as HTMLElement); + const comparisonBCR = getBoundingClientRect(comparison!); + const comparisonPercentBCR = getBoundingClientRect(comparisonPercent!); + const comparisonEndBCR = getBoundingClientRect(comparisonEnd!); + const comparisonMatrics = { + offset: { + left: (comparisonBCR.left - comparisonEnvBCR.left).toFixed(Math.min(fixedDigitsOffset, fixedDigits)), + top: (comparisonBCR.top - comparisonEnvBCR.top).toFixed(Math.min(fixedDigitsOffset, fixedDigits)), + }, + size: { + width: comparisonBCR.width.toFixed(fixedDigits), + height: comparisonBCR.height.toFixed(fixedDigits), + }, + scroll: { + width: comparison!.scrollWidth - comparison!.clientWidth, + height: comparison!.scrollHeight - comparison!.clientHeight, + }, + percentElm: { + width: comparisonPercentBCR.width.toFixed(fixedDigits), + height: comparisonPercentBCR.height.toFixed(fixedDigits), + }, + endElm: { + width: comparisonEndBCR.width.toFixed(fixedDigits), + height: comparisonEndBCR.height.toFixed(fixedDigits), + }, + }; + + await waitForOrFailTest(async () => { + const targetEnvBCR = getBoundingClientRect(parent(target!) as HTMLElement); + const targetBCR = getBoundingClientRect(target!); + const targetPercentBCR = getBoundingClientRect(targetPercent!); + const targetEndBCR = getBoundingClientRect(targetEnd!); + const targetViewport = target!.querySelector('.os-viewport'); + + const targetMetrics = { + offset: { + left: (targetBCR.left - targetEnvBCR.left).toFixed(Math.min(fixedDigitsOffset, fixedDigits)), + top: (targetBCR.top - targetEnvBCR.top).toFixed(Math.min(fixedDigitsOffset, fixedDigits)), + }, + size: { + width: targetBCR.width.toFixed(fixedDigits), + height: targetBCR.height.toFixed(fixedDigits), + }, + scroll: { + width: targetViewport!.scrollWidth - targetViewport!.clientWidth, + height: targetViewport!.scrollHeight - targetViewport!.clientHeight, + }, + percentElm: { + width: targetPercentBCR.width.toFixed(fixedDigits), + height: targetPercentBCR.height.toFixed(fixedDigits), + }, + endElm: { + width: targetEndBCR.width.toFixed(fixedDigits), + height: targetEndBCR.height.toFixed(fixedDigits), + }, + }; + + //console.log('t', targetMetrics); + //console.log('c', comparisonMatrics); + + should.equal(targetMetrics.offset.left, comparisonMatrics.offset.left, 'Offset left equality.'); + should.equal(targetMetrics.offset.top, comparisonMatrics.offset.top, 'Offset top equality.'); + + should.equal(targetMetrics.size.width, comparisonMatrics.size.width, 'Size width equality.'); + should.equal(targetMetrics.size.height, comparisonMatrics.size.height, 'Size height equality.'); + + should.equal(targetMetrics.scroll.width, comparisonMatrics.scroll.width, 'Scroll width equality.'); + should.equal(targetMetrics.scroll.height, comparisonMatrics.scroll.height, 'Scroll height equality.'); + + if (targetMetrics.scroll.width > 0) { + should.equal(style(targetViewport!, 'overflowX'), 'scroll', 'Overflow-X should result in scroll.'); + } else { + should.notEqual(style(targetViewport!, 'overflowX'), 'scroll', 'No Overflow-X shouldnt result in scroll.'); + } + + if (targetMetrics.scroll.height > 0) { + should.equal(style(targetViewport!, 'overflowY'), 'scroll', 'Overflow-Y should result in scroll.'); + } else { + should.notEqual(style(targetViewport!, 'overflowY'), 'scroll', 'No Overflow-Y shouldnt result in scroll.'); + } + + should.equal(targetMetrics.percentElm.width, comparisonMatrics.percentElm.width, 'Percent Elements width equality.'); + should.equal(targetMetrics.percentElm.height, comparisonMatrics.percentElm.height, 'Percent Elements height equality.'); + + should.equal(targetMetrics.endElm.width, comparisonMatrics.endElm.width, 'End Elements width equality.'); + should.equal(targetMetrics.endElm.height, comparisonMatrics.endElm.height, 'End Elements height equality.'); + + await timeout(1); + }); + }, + afterEach, + }); +}; + +const iterateEnvWidth = async (afterEach?: () => any) => { + await iterate(envWidthSelect, afterEach); +}; +const iterateEnvHeight = async (afterEach?: () => any) => { + await iterate(envHeightSelect, afterEach); +}; +const iterateHeight = async (afterEach?: () => any) => { + await iterate(containerHeightSelect, afterEach); +}; +const iterateWidth = async (afterEach?: () => any) => { + await iterate(containerWidthSelect, afterEach); +}; +const iterateFloat = async (afterEach?: () => any) => { + await iterate(containerFloatSelect, afterEach); +}; +const iteratePadding = async (afterEach?: () => any) => { + await iterate(containerPaddingSelect, afterEach); +}; +const iterateBorder = async (afterEach?: () => any) => { + await iterate(containerBorderSelect, afterEach); +}; +const iterateMargin = async (afterEach?: () => any) => { + await iterate(containerMarginSelect, afterEach); +}; +const iterateBoxSizing = async (afterEach?: () => any) => { + await iterate(containerBoxSizingSelect, afterEach); +}; +const iterateDirection = async (afterEach?: () => any) => { + await iterate(containerDirectionSelect, afterEach); +}; +const iterateMinMax = async (afterEach?: () => any) => { + await iterate(containerMinMaxSelect, afterEach); +}; + +const start = async () => { + setTestResult(null); + + target?.removeAttribute('style'); + await iterateMinMax(async () => { + await iterateBoxSizing(async () => { + await iterateHeight(async () => { + await iterateWidth(async () => { + await iterateBorder(async () => { + // assume this part isn't critical for IE11, to boost test speed + if (!msie11) { + await iterateFloat(async () => { + await iterateMargin(); + }); + } + + await iteratePadding(); + await iterateDirection(); + }); + }); + }); + }); + }); + + setTestResult(true); +}; + +startBtn?.addEventListener('click', start); diff --git a/packages/overlayscrollbars/tests/browser/lifecycles/structureLifecycle/index.html b/packages/overlayscrollbars/tests/browser/lifecycles/structureLifecycle/index.html index 509964f..a450e63 100644 --- a/packages/overlayscrollbars/tests/browser/lifecycles/structureLifecycle/index.html +++ b/packages/overlayscrollbars/tests/browser/lifecycles/structureLifecycle/index.html @@ -1,13 +1,13 @@