diff --git a/packages/overlayscrollbars/src/environment/environment.ts b/packages/overlayscrollbars/src/environment/environment.ts index 352cabf..0167a01 100644 --- a/packages/overlayscrollbars/src/environment/environment.ts +++ b/packages/overlayscrollbars/src/environment/environment.ts @@ -147,8 +147,6 @@ const createEnvironment = (): Environment => { }, }; - console.log(env); - removeAttr(envElm, 'style'); removeElements(envElm); diff --git a/packages/overlayscrollbars/src/lifecycles/lifecycleHub.ts b/packages/overlayscrollbars/src/lifecycles/lifecycleHub.ts index efd73ec..87b5382 100644 --- a/packages/overlayscrollbars/src/lifecycles/lifecycleHub.ts +++ b/packages/overlayscrollbars/src/lifecycles/lifecycleHub.ts @@ -1,4 +1,4 @@ -import { XY, TRBL, CacheValues, each, push, OptionsValidated, hasOwnProperty, isNumber, scrollLeft, scrollTop } from 'support'; +import { XY, TRBL, CacheValues, each, push, keys, OptionsValidated, hasOwnProperty, isNumber, scrollLeft, scrollTop } from 'support'; import { Options } from 'options'; import { getEnvironment } from 'environment'; import { StructureSetup } from 'setups/structureSetup'; @@ -47,10 +47,11 @@ export interface LifecycleHubInstance { export interface LifecycleHub { _options: Options; _structureSetup: StructureSetup; + _doViewportArrange: boolean; _getPaddingInfo(): PaddingInfo; _setPaddingInfo(newPadding?: PaddingInfo | null): void; - _getPaddingStyle(): StyleObject; - _setPaddingStyle(newPaddingStlye?: StyleObject | null): void; + _getViewportPaddingStyle(): StyleObject; + _setViewportPaddingStyle(newPaddingStlye?: StyleObject | null): void; _getViewportOverflowScroll(): XY; _setViewportOverflowScroll(newViewportOverflowScroll: XY): void; } @@ -58,6 +59,16 @@ export interface LifecycleHub { const getPropByPath = (obj: any, path: string): T => obj && path.split('.').reduce((o, prop) => (o && hasOwnProperty(o, prop) ? o[prop] : undefined), obj); +const emptyStylePropsToZero = (stlyeObj: StyleObject, baseStyle?: StyleObject) => + keys(stlyeObj).reduce( + (obj, key) => { + const value = stlyeObj[key]; + obj[key] = value === '' ? 0 : value; + return obj; + }, + { ...baseStyle } + ); + const attrs = ['id', 'class', 'style', 'open']; const paddingInfoFallback: PaddingInfo = { _absolute: false, @@ -73,6 +84,10 @@ const viewportPaddingStyleFallback: StyleObject = { marginRight: 0, marginBottom: 0, marginLeft: 0, + paddingTop: 0, + paddingRight: 0, + paddingBottom: 0, + paddingLeft: 0, }; const viewportOverflowScrollFallback: XY = { x: false, @@ -93,24 +108,27 @@ export const createLifecycleHub = (options: Options, structureSetup: StructureSe let paddingInfo = paddingInfoFallback; let viewportPaddingStyle = viewportPaddingStyleFallback; let viewportOverflowScroll = viewportOverflowScrollFallback; - const { _host, _viewport, _content, _contentArrange } = structureSetup._targetObj; + const { _host, _viewport, _content } = structureSetup._targetObj; const { _nativeScrollbarStyling, + _nativeScrollbarIsOverlaid, _flexboxGlue, _addListener: addEnvironmentListener, _removeListener: removeEnvironmentListener, } = getEnvironment(); + const doViewportArrange = !_nativeScrollbarStyling && (_nativeScrollbarIsOverlaid.x || _nativeScrollbarIsOverlaid.y); const lifecycles: Lifecycle[] = []; const instance: LifecycleHub = { _options: options, _structureSetup: structureSetup, + _doViewportArrange: doViewportArrange, _getPaddingInfo: () => paddingInfo, _setPaddingInfo(newPaddingInfo) { paddingInfo = newPaddingInfo || paddingInfoFallback; }, - _getPaddingStyle: () => viewportPaddingStyle, - _setPaddingStyle(newPaddingStlye) { - viewportPaddingStyle = newPaddingStlye || viewportPaddingStyleFallback; + _getViewportPaddingStyle: () => viewportPaddingStyle, + _setViewportPaddingStyle(newPaddingStlye) { + viewportPaddingStyle = newPaddingStlye ? emptyStylePropsToZero(newPaddingStlye, viewportPaddingStyleFallback) : viewportPaddingStyleFallback; }, _getViewportOverflowScroll: () => viewportOverflowScroll, _setViewportOverflowScroll(newViewportOverflowScroll) { @@ -143,7 +161,7 @@ export const createLifecycleHub = (options: Options, structureSetup: StructureSe _value: getPropByPath(options, path), _changed: force || getPropByPath(changedOptions, path) !== undefined, }); - const adjustScrollOffset = _contentArrange || !_flexboxGlue; + const adjustScrollOffset = doViewportArrange || !_flexboxGlue; const scrollOffsetX = adjustScrollOffset && scrollLeft(_viewport); const scrollOffsetY = adjustScrollOffset && scrollTop(_viewport); diff --git a/packages/overlayscrollbars/src/lifecycles/overflowLifecycle.ts b/packages/overlayscrollbars/src/lifecycles/overflowLifecycle.ts index 981b0d4..9a465c0 100644 --- a/packages/overlayscrollbars/src/lifecycles/overflowLifecycle.ts +++ b/packages/overlayscrollbars/src/lifecycles/overflowLifecycle.ts @@ -1,5 +1,6 @@ import { createCache, + keys, attr, WH, XY, @@ -13,12 +14,13 @@ import { clientSize, offsetSize, getBoundingClientRect, + noop, } from 'support'; import { LifecycleHub, Lifecycle } from 'lifecycles/lifecycleHub'; import { getEnvironment } from 'environment'; import { OverflowBehavior } from 'options'; import { StyleObject } from 'typings'; -import { classNameViewport, classNameViewportArrange, classNameViewportScrollbarStyling } from 'classnames'; +import { classNameViewportArrange, classNameViewportScrollbarStyling } from 'classnames'; interface ContentScrollSizeCacheContext { _viewportRect: DOMRect; @@ -41,20 +43,23 @@ interface OverflowOption { y: OverflowBehavior; } +interface ViewportArrangeCustomCssProps { + '--viewport-arrange-width': string; + '--viewport-arrange-height': string; +} + const overlaidScrollbarsHideOffset = 42; -const overlaidScrollbarsHideBorderStyle = `${overlaidScrollbarsHideOffset}px solid transparent`; export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle => { - const { _structureSetup, _getPaddingStyle, _getPaddingInfo } = lifecycleHub; - const { _host, _padding, _viewport, _content, _contentArrange } = _structureSetup._targetObj; + const { _structureSetup, _doViewportArrange, _getViewportPaddingStyle, _getPaddingInfo } = lifecycleHub; + const { _host, _padding, _viewport, _viewportArrange } = _structureSetup._targetObj; const { _update: updateContentScrollSizeCache, _current: getCurrentContentScrollSizeCache } = createCache< WH, ContentScrollSizeCacheContext >( (ctx) => { const { _viewportOffsetSize, _viewportScrollSize, _viewportRect } = ctx; - const contentViewportScrollSize = _content ? scrollSize(_content) : _viewportScrollSize; - return fixScrollSizeRounding(contentViewportScrollSize, _viewportOffsetSize, _viewportRect); + return fixScrollSizeRounding(_viewportScrollSize, _viewportOffsetSize, _viewportRect); }, { _equal: equalWH } ); @@ -67,8 +72,8 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle = ); const fixScrollSizeRounding = (scrollSize: WH, viewportOffsetSize: WH, viewportRect: DOMRect): WH => ({ - w: scrollSize.w - Math.ceil(Math.max(0, viewportRect.width - viewportOffsetSize.w)), - h: scrollSize.h - Math.ceil(Math.max(0, viewportRect.height - viewportOffsetSize.h)), + w: scrollSize.w - Math.round(Math.max(0, viewportRect.width - viewportOffsetSize.w)), + h: scrollSize.h - Math.round(Math.max(0, viewportRect.height - viewportOffsetSize.h)), }); const fixFlexboxGlue = (viewportOverflowState: ViewportOverflowState, heightIntrinsic: boolean) => { @@ -151,42 +156,54 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle = viewportOverflowState: ViewportOverflowState, contentScrollSize: WH, directionIsRTL: boolean, - contentStyleObj?: StyleObject + viewportStyleObj?: StyleObject ) => { const { _nativeScrollbarStyling, _nativeScrollbarIsOverlaid } = getEnvironment(); if ((_nativeScrollbarIsOverlaid.x || _nativeScrollbarIsOverlaid.y) && !_nativeScrollbarStyling) { const { _scrollbarsHideOffset } = viewportOverflowState; - const { _absolute: paddingAbsolute, _padding: padding } = _getPaddingInfo(); const { x: hideOffsetX, y: hideOffsetY } = _scrollbarsHideOffset; - const horizontalPaddingKey = directionIsRTL ? 'paddingLeft' : 'paddingRight'; - const horizontalPaddingValue = paddingAbsolute ? 0 : directionIsRTL ? padding.l : padding.r; - const verticalPaddingValue = paddingAbsolute ? 0 : padding.b; - - style(_viewport, { - [horizontalPaddingKey]: horizontalPaddingValue + hideOffsetY, - paddingBottom: verticalPaddingValue + hideOffsetX, - }); + const viewportPaddingStyle = _getViewportPaddingStyle(); + const viewportHorizontalPaddingKey = directionIsRTL ? 'paddingLeft' : 'paddingRight'; + const viewportHorizontalPaddingValue = viewportPaddingStyle[viewportHorizontalPaddingKey] as number; + const viewportVerticalPaddingValue = viewportPaddingStyle.paddingBottom as number; + const viewportArrangeHorizontalPaddingKey = directionIsRTL ? 'paddingRight' : 'paddingLeft'; + const viewportArrangeHorizontalPaddingValue = viewportPaddingStyle[viewportArrangeHorizontalPaddingKey] as number; + const viewportArrangeVerticalPaddingValue = viewportPaddingStyle.paddingTop as number; + const styleObj: StyleObject = viewportStyleObj || {}; + const arrangeSize = { + w: hideOffsetY ? `${hideOffsetY + contentScrollSize.w - viewportArrangeHorizontalPaddingValue}px` : '', + h: hideOffsetX ? `${hideOffsetX + contentScrollSize.h - viewportArrangeVerticalPaddingValue}px` : '', + }; // adjust content arrange / before element - if (_contentArrange) { - const { sheet } = _contentArrange; + if (_viewportArrange) { + const { sheet } = _viewportArrange; if (sheet) { const { cssRules } = sheet; if (cssRules) { if (!cssRules.length) { - sheet.insertRule(`#${attr(_contentArrange, 'id')} + .${classNameViewportArrange}::before {}`, 0); + sheet.insertRule(`#${attr(_viewportArrange, 'id')} + .${classNameViewportArrange}::before {}`, 0); } // @ts-ignore const ruleStyle = cssRules[0].style; - ruleStyle.width = hideOffsetY ? `${contentScrollSize.w}px` : '0px'; - ruleStyle.height = hideOffsetX ? `${contentScrollSize.h}px` : '0px'; - - addClass(_viewport, classNameViewportArrange); + ruleStyle.width = arrangeSize.w; + ruleStyle.height = arrangeSize.h; } } } else { + style(_viewport, { + '--viewport-arrange-width': arrangeSize.w, + '--viewport-arrange-height': arrangeSize.h, + }); + } + + styleObj[viewportHorizontalPaddingKey] = viewportHorizontalPaddingValue + hideOffsetY; + styleObj.paddingBottom = viewportVerticalPaddingValue + hideOffsetX; + + if (!viewportStyleObj) { + style(_viewport, styleObj); } } }; @@ -195,16 +212,17 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle = const { _nativeScrollbarStyling } = getEnvironment(); const { _overflowScroll, _scrollbarsHideOffset } = viewportOverflowState; const { x: scrollX, y: scrollY } = _overflowScroll; - const paddingStyle = _getPaddingStyle(); + const paddingStyle = _getViewportPaddingStyle(); const horizontalMarginKey = directionIsRTL ? 'marginLeft' : 'marginRight'; const horizontalPaddingValue = paddingStyle[horizontalMarginKey] as number; + const verticalPaddingValue = paddingStyle.marginBottom as number; // horizontal viewportStyleObj.maxWidth = `calc(100% + ${_scrollbarsHideOffset.y + horizontalPaddingValue * -1}px)`; viewportStyleObj[horizontalMarginKey] = -_scrollbarsHideOffset.y + horizontalPaddingValue; // vertical - viewportStyleObj.marginBottom = -_scrollbarsHideOffset.x + (paddingStyle.marginBottom as number); + viewportStyleObj.marginBottom = -_scrollbarsHideOffset.x + verticalPaddingValue; // hide overflowing scrollbars if there are any if (!_nativeScrollbarStyling) { @@ -214,6 +232,25 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle = } }; + const undoOverlaidScrollbarsHiding = () => { + const paddingStyle = _getViewportPaddingStyle(); + const viewportStyle = { + marginTop: '', + marginRight: '', + marginBottom: '', + marginLeft: '', + ...paddingStyle, + }; + const prevStyle = style(_viewport, keys(viewportStyle)); + removeClass(_viewport, classNameViewportArrange); + style(_viewport, viewportStyle); + + return () => { + style(_viewport, prevStyle); + addClass(_viewport, classNameViewportArrange); + }; + }; + return (updateHints, checkOption, force) => { const { _directionIsRTL, _heightIntrinsic, _sizeChanged, _hostMutation, _contentMutation, _paddingStyleChanged } = updateHints; const { _flexboxGlue, _nativeScrollbarStyling, _nativeScrollbarIsOverlaid } = getEnvironment(); @@ -222,9 +259,10 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle = const { _value: showNativeOverlaidScrollbarsOption, _changed: showNativeOverlaidScrollbarsChanged } = checkOption( 'nativeScrollbarsOverlaid.show' ); + const showNativeOverlaidScrollbars = showNativeOverlaidScrollbarsOption && _nativeScrollbarIsOverlaid.x && _nativeScrollbarIsOverlaid.y; + const viewportArrange = _doViewportArrange && !showNativeOverlaidScrollbars; const adjustFlexboxGlue = !_flexboxGlue && (_sizeChanged || _contentMutation || _hostMutation || showNativeOverlaidScrollbarsChanged || heightIntrinsicChanged); - const showNativeOverlaidScrollbars = showNativeOverlaidScrollbarsOption && _nativeScrollbarIsOverlaid.x && _nativeScrollbarIsOverlaid.y; let overflowAmuntCache: CacheValues> = getCurrentOverflowAmountCache(force); let contentScrollSizeCache: CacheValues> = getCurrentContentScrollSizeCache(force); let preMeasureViewportOverflowState: ViewportOverflowState | undefined; @@ -242,63 +280,43 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle = fixFlexboxGlue(preMeasureViewportOverflowState, !!heightIntrinsic); } - if (_sizeChanged || _contentMutation || directionChanged) { - removeClass(_viewport, classNameViewportArrange); - style(_viewport, { - paddingRight: _getPaddingInfo()._padding.r, - paddingBottom: _getPaddingInfo()._padding.b, - marginRight: -_getPaddingInfo()._padding.r - _getPaddingInfo()._padding.l, - marginBottom: -_getPaddingInfo()._padding.b - _getPaddingInfo()._padding.t, - }); - + if (_sizeChanged || _paddingStyleChanged || _contentMutation || directionChanged) { + const redoOverlaidScrollbarsHiding = viewportArrange ? undoOverlaidScrollbarsHiding() : noop; + const contentSize = clientSize(_viewport); const viewportRect = getBoundingClientRect(_viewport); const viewportOffsetSize = offsetSize(_viewport); - const contentClientSize = clientSize(_viewport); // needs to be client Size because applied border for content arrange on content - let viewportScrollSize = fixScrollSizeRounding(scrollSize(_viewport), viewportOffsetSize, viewportRect); - let viewportClientSize = clientSize(_viewport); - - let { _value: contentScrollSize, _changed: contentScrollSizeChanged } = (contentScrollSizeCache = updateContentScrollSizeCache(force, { + let viewportScrollSize = scrollSize(_viewport); + let viewportClientSize = contentSize; + const { _value: contentScrollSize, _changed: contentScrollSizeChanged } = (contentScrollSizeCache = updateContentScrollSizeCache(force, { _viewportRect: viewportRect, _viewportOffsetSize: viewportOffsetSize, _viewportScrollSize: viewportScrollSize, })); + + redoOverlaidScrollbarsHiding(); + // re measure is only required if we rely on content arrange to hide native scrollbars (no native scrollbar styling and overlaid scrollbars) - const reMeasureRequired = contentScrollSizeChanged && !showNativeOverlaidScrollbars; + const reMeasureRequired = viewportArrange && contentScrollSizeChanged && !showNativeOverlaidScrollbars; - if (true) { - const viewportStyle: StyleObject = { - overflowY: '', - overflowX: '', - marginTop: '', - marginRight: '', - marginBottom: '', - marginLeft: '', - maxWidth: '', - }; - setContentArrange(getViewportOverflowState(showNativeOverlaidScrollbars), contentScrollSize!, directionIsRTL!); - hideNativeScrollbars(getViewportOverflowState(showNativeOverlaidScrollbars), directionIsRTL!, viewportStyle); - - style(_viewport, viewportStyle); + if (reMeasureRequired) { + setContentArrange( + preMeasureViewportOverflowState || getViewportOverflowState(showNativeOverlaidScrollbars), + contentScrollSize!, + directionIsRTL! + ); viewportClientSize = clientSize(_viewport); viewportScrollSize = fixScrollSizeRounding(scrollSize(_viewport), offsetSize(_viewport), getBoundingClientRect(_viewport)); - - ({ _value: contentScrollSize, _changed: contentScrollSizeChanged } = contentScrollSizeCache = updateContentScrollSizeCache(force, { - _viewportRect: viewportRect, - _viewportOffsetSize: viewportOffsetSize, - _viewportScrollSize: viewportScrollSize, - })); } - //const contentArrangeOffsetSize = clientSize(_contentArrange); overflowAmuntCache = updateOverflowAmountCache(force, { _contentScrollSize: { w: Math.max(contentScrollSize!.w, viewportScrollSize.w), h: Math.max(contentScrollSize!.h, viewportScrollSize.h), }, _viewportSize: { - w: viewportClientSize.w + Math.max(0, contentClientSize.w - contentScrollSize!.w), - h: viewportClientSize.h + Math.max(0, contentClientSize.h - contentScrollSize!.h), + w: viewportClientSize.w + Math.max(0, contentSize.w - contentScrollSize!.w), + h: viewportClientSize.h + Math.max(0, contentSize.h - contentScrollSize!.h), }, }); } @@ -325,16 +343,13 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle = marginLeft: '', maxWidth: '', }; - const contentStyle: StyleObject = { - borderTop: '', - borderRight: '', - borderBottom: '', - borderLeft: '', - }; const viewportOverflowState = setViewportOverflowState(showNativeOverlaidScrollbars, overflowAmount!, overflow, viewportStyle); hideNativeScrollbars(viewportOverflowState, directionIsRTL!, viewportStyle); - setContentArrange(viewportOverflowState, contentScrollSize!, directionIsRTL!, contentStyle); + + if (_doViewportArrange) { + setContentArrange(viewportOverflowState, contentScrollSize!, directionIsRTL!, viewportStyle); + } if (adjustFlexboxGlue) { fixFlexboxGlue(viewportOverflowState, !!heightIntrinsic); @@ -349,7 +364,6 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle = // TODO: remove lifecycleHub get set padding if not needed style(_viewport, viewportStyle); - style(_content, contentStyle); } }; }; diff --git a/packages/overlayscrollbars/src/lifecycles/paddingLifecycle.ts b/packages/overlayscrollbars/src/lifecycles/paddingLifecycle.ts index 1217d01..f01e491 100644 --- a/packages/overlayscrollbars/src/lifecycles/paddingLifecycle.ts +++ b/packages/overlayscrollbars/src/lifecycles/paddingLifecycle.ts @@ -4,7 +4,8 @@ import { StyleObject } from 'typings'; import { getEnvironment } from 'environment'; export const createPaddingLifecycle = (lifecycleHub: LifecycleHub): Lifecycle => { - const { _host, _padding, _viewport } = lifecycleHub._structureSetup._targetObj; + const { _setPaddingInfo, _setViewportPaddingStyle, _structureSetup } = lifecycleHub; + const { _host, _padding, _viewport } = _structureSetup._targetObj; const { _update: updatePaddingCache, _current: currentPaddingCache } = createCache(() => topRightBottomLeft(_host, 'padding'), { _equal: equalTRBL, }); @@ -76,11 +77,18 @@ export const createPaddingLifecycle = (lifecycleHub: LifecycleHub): Lifecycle => style(_padding || _viewport, paddingStyle); style(_viewport, viewportStyle); - lifecycleHub._setPaddingInfo({ + _setPaddingInfo({ _absolute: !paddingRelative, _padding: padding!, }); - lifecycleHub._setPaddingStyle(!_padding ? paddingStyle : null); + _setViewportPaddingStyle( + _padding + ? viewportStyle + : { + ...paddingStyle, + ...viewportStyle, + } + ); } return { diff --git a/packages/overlayscrollbars/src/overlayscrollbars.scss b/packages/overlayscrollbars/src/overlayscrollbars.scss index 8a7479e..ba732b0 100644 --- a/packages/overlayscrollbars/src/overlayscrollbars.scss +++ b/packages/overlayscrollbars/src/overlayscrollbars.scss @@ -1,6 +1,5 @@ @import './sizeobserver.scss'; @import './trinsicobserver.scss'; -@import './structurelifecycle.scss'; .os-environment { --css-custom-prop: -1; @@ -70,7 +69,7 @@ } .os-environment, .os-viewport { - -ms-overflow-style: -ms-autohiding-scrollbar !important; + -ms-overflow-style: scrollbar !important; } .os-viewport-scrollbar-styled.os-environment, .os-viewport-scrollbar-styled.os-viewport { @@ -87,6 +86,34 @@ background: transparent !important; } +.os-host, +.os-padding { + position: relative; + display: flex; + flex-direction: row; + flex-wrap: nowrap; +} + +.os-padding, +.os-viewport { + box-sizing: border-box; + position: relative; + flex: auto; + height: auto; + width: 100%; + padding: 0; + margin: 0; + border: none; + overflow: visible; + max-width: 100%; + z-index: 0; +} + +.os-viewport { + --viewport-arrange-width: 0; + --viewport-arrange-height: 0; +} + .os-viewport-arrange::before { content: ''; position: absolute; @@ -94,8 +121,6 @@ z-index: -1; min-width: 1px; min-height: 1px; -} - -.os-host { - padding: 5px 50px 15px 20px; + width: var(--viewport-arrange-width); + height: var(--viewport-arrange-height); } diff --git a/packages/overlayscrollbars/src/setups/structureSetup.ts b/packages/overlayscrollbars/src/setups/structureSetup.ts index 5b019bd..313c3cb 100644 --- a/packages/overlayscrollbars/src/setups/structureSetup.ts +++ b/packages/overlayscrollbars/src/setups/structureSetup.ts @@ -37,7 +37,7 @@ export interface OSTargetContext { export interface PreparedOSTargetObject extends Required> { _host: HTMLElement; - _contentArrange: HTMLStyleElement | null; + _viewportArrange: HTMLStyleElement | null; } export interface StructureSetup { @@ -52,7 +52,7 @@ const unwrap = (elm: HTMLElement | null | undefined) => { }; let contentArrangeCounter = 0; -const createUniqueContentArrangeElement = () => { +const createUniqueViewportArrangeElement = () => { const elm = document.createElement('style'); attr(elm, 'id', `${classNameViewportArrange}-${contentArrangeCounter}`); @@ -174,15 +174,13 @@ export const createStructureSetup = (target: OSTarget | OSTargetObject): Structu const { _nativeScrollbarStyling, _nativeScrollbarIsOverlaid, _cssCustomProperties } = getEnvironment(); if (_nativeScrollbarStyling) { push(destroyFns, removeClass.bind(0, _viewport, classNameViewportScrollbarStyling)); - } else if (_nativeScrollbarIsOverlaid.x || _nativeScrollbarIsOverlaid.y) { - if (true) { - const contentArrangeElm = createUniqueContentArrangeElement(); + } else if (!_cssCustomProperties && (_nativeScrollbarIsOverlaid.x || _nativeScrollbarIsOverlaid.y)) { + const viewportArrangeElm = createUniqueViewportArrangeElement(); - insertBefore(_viewport, contentArrangeElm); - push(destroyFns, removeElements.bind(0, contentArrangeElm)); + insertBefore(_viewport, viewportArrangeElm); + push(destroyFns, removeElements.bind(0, viewportArrangeElm)); - obj._contentArrange = contentArrangeElm; - } + obj._viewportArrange = viewportArrangeElm; } return { diff --git a/packages/overlayscrollbars/src/structurelifecycle.scss b/packages/overlayscrollbars/src/structurelifecycle.scss deleted file mode 100644 index 42d3bec..0000000 --- a/packages/overlayscrollbars/src/structurelifecycle.scss +++ /dev/null @@ -1,27 +0,0 @@ -.os-host, -.os-padding { - position: relative; - display: flex; - flex-direction: row; - flex-wrap: nowrap; -} - -.os-padding, -.os-viewport { - box-sizing: border-box; - position: relative; - flex: auto; - height: auto; - width: 100%; - padding: 0; - margin: 0; - border: none; - overflow: visible; - max-width: 100%; - z-index: 0; -} - -.os-content { - position: relative; - z-index: 0; -} diff --git a/packages/overlayscrollbars/src/support/dom/style.ts b/packages/overlayscrollbars/src/support/dom/style.ts index bb89306..9a4655d 100644 --- a/packages/overlayscrollbars/src/support/dom/style.ts +++ b/packages/overlayscrollbars/src/support/dom/style.ts @@ -1,5 +1,5 @@ import { each, keys } from 'support/utils'; -import { isString, isNumber, isArray } from 'support/utils/types'; +import { isString, isNumber, isArray, isUndefined } from 'support/utils/types'; import { PlainObject, StyleObject } from 'typings'; export interface TRBL { @@ -37,8 +37,13 @@ const getCSSVal = (elm: HTMLElement, computedStyle: CSSStyleDeclaration, prop: s computedStyle != null ? computedStyle[prop] || computedStyle.getPropertyValue(prop) : elm.style[prop]; const setCSSVal = (elm: HTMLElement | null | undefined, prop: string, val: string | number): void => { try { - if (elm && elm.style[prop] !== undefined) { - elm.style[prop] = adaptCSSVal(prop, val); + if (elm) { + const { style } = elm; + if (!isUndefined(style[prop])) { + style[prop] = adaptCSSVal(prop, val); + } else { + style.setProperty(prop, val as string); + } } } catch (e) {} }; @@ -48,10 +53,13 @@ const setCSSVal = (elm: HTMLElement | null | undefined, prop: string, val: strin * @param elm The element to which the styles shall be applied to / be read from. * @param styles The styles which shall be set or read. */ -export function style(elm: HTMLElement | null | undefined, styles: StyleObject): void; -export function style(elm: HTMLElement | null | undefined, styles: string): string; -export function style(elm: HTMLElement | null | undefined, styles: Array | string): { [key: string]: string }; -export function style(elm: HTMLElement | null | undefined, styles: StyleObject | Array | string): { [key: string]: string } | string | void { +export function style(elm: HTMLElement | null | undefined, styles: StyleObject): void; +export function style(elm: HTMLElement | null | undefined, styles: string): string; +export function style(elm: HTMLElement | null | undefined, styles: Array | string): { [key: string]: string }; +export function style( + elm: HTMLElement | null | undefined, + styles: StyleObject | Array | string +): { [key: string]: string } | string | void { const getSingleStyle = isString(styles); const getStyles = isArray(styles) || getSingleStyle; diff --git a/packages/overlayscrollbars/src/typings.ts b/packages/overlayscrollbars/src/typings.ts index 262af87..06a1bd7 100644 --- a/packages/overlayscrollbars/src/typings.ts +++ b/packages/overlayscrollbars/src/typings.ts @@ -1,7 +1,7 @@ export type PlainObject = { [name: string]: T }; -export type StyleObject = { - [K in keyof CSSStyleDeclaration]?: string | number; +export type StyleObject = { + [K in keyof (CSSStyleDeclaration & CustomCssProps)]?: string | number; } export type InternalVersionOf = { diff --git a/packages/overlayscrollbars/tests/browser/lifecycles/structureLifecycle/index.browser.ts b/packages/overlayscrollbars/tests/browser/lifecycles/structureLifecycle/index.browser.ts index 6d670e2..761d423 100644 --- a/packages/overlayscrollbars/tests/browser/lifecycles/structureLifecycle/index.browser.ts +++ b/packages/overlayscrollbars/tests/browser/lifecycles/structureLifecycle/index.browser.ts @@ -4,7 +4,7 @@ import { createDiv, appendChildren, parent, style, on, off, addClass, WH, XY, cl import { OverlayScrollbars } from 'overlayscrollbars/OverlayScrollbars'; const targetElm = document.querySelector('#target') as HTMLElement; -window.os = OverlayScrollbars({ target: targetElm, padding: null, content: null }); +window.os = OverlayScrollbars({ target: targetElm, padding: null }); export const resize = (element: HTMLElement) => { const strMouseTouchDownEvent = 'mousedown touchstart'; diff --git a/packages/overlayscrollbars/tests/browser/lifecycles/structureLifecycle/index.scss b/packages/overlayscrollbars/tests/browser/lifecycles/structureLifecycle/index.scss index 57693f2..14b0d92 100644 --- a/packages/overlayscrollbars/tests/browser/lifecycles/structureLifecycle/index.scss +++ b/packages/overlayscrollbars/tests/browser/lifecycles/structureLifecycle/index.scss @@ -35,6 +35,7 @@ body { min-width: 200px; max-height: 300px; max-width: 320px; + //padding: 5px 50px 15px 20px; } #resize { diff --git a/packages/overlayscrollbars/tests/jsdom/lifecycles/lifecycleBase.test.ts b/packages/overlayscrollbars/tests/jsdom/lifecycles/lifecycleBase.test.ts deleted file mode 100644 index 8496053..0000000 --- a/packages/overlayscrollbars/tests/jsdom/lifecycles/lifecycleBase.test.ts +++ /dev/null @@ -1,245 +0,0 @@ -import { optionsTemplateTypes as oTypes } from 'support'; -import { createLifecycleBase } from 'lifecycles/lifecycleBase'; - -interface TestLifecycleOptions { - number?: number; - string?: string; - nested?: { - boolean?: boolean; - number?: number; - }; -} - -const createLifecycle = (initalOptions?: TestLifecycleOptions, updateFn?: (...args: any) => any) => - createLifecycleBase( - { - number: [0, oTypes.number], - string: ['hi', oTypes.string], - nested: { - boolean: [false, oTypes.boolean], - number: [0, oTypes.number], - }, - }, - initalOptions, - updateFn || (() => {}) - ); - -describe('lifecycleBase', () => { - describe('options', () => { - test('correct default options', () => { - const { _options } = createLifecycle(); - - const defaultOptions = _options(); - expect(defaultOptions.number).toBe(0); - expect(defaultOptions.string).toBe('hi'); - expect(defaultOptions.nested?.boolean).toBe(false); - expect(defaultOptions.nested?.number).toBe(0); - }); - - test('correct initial options', () => { - const { _options } = createLifecycle({ number: 1, nested: { boolean: true } }); - - const initOptions = _options(); - expect(initOptions.number).toBe(1); - expect(initOptions.string).toBe('hi'); - expect(initOptions.nested?.boolean).toBe(true); - expect(initOptions.nested?.number).toBe(0); - }); - - test('correct options change', () => { - const { _options } = createLifecycle(); - - const options = _options(); - expect(options.number).toBe(0); - expect(options.string).toBe('hi'); - expect(options.nested?.boolean).toBe(false); - expect(options.nested?.number).toBe(0); - - const changedOptions = _options({ number: 2, nested: { number: 3 } }); - expect(changedOptions.number).toBe(2); - expect(changedOptions.string).toBe('hi'); - expect(changedOptions.nested?.boolean).toBe(false); - expect(changedOptions.nested?.number).toBe(3); - }); - - test('correct options validation', () => { - const originalWarn = console.warn; - const mockWarn = jest.fn(); - console.warn = mockWarn; - - // @ts-ignore - const { _options } = createLifecycle({ string: 123 }); - expect(mockWarn).toBeCalledTimes(1); - - const options = _options(); - expect(options.string).toBe('hi'); - - // @ts-ignore - const changedOptions = _options({ number: 'string', nested: null }); - expect(mockWarn).toBeCalledTimes(2); - expect(changedOptions.number).toBe(0); - expect(changedOptions.string).toBe('hi'); - expect(changedOptions.nested?.boolean).toBe(false); - expect(changedOptions.nested?.number).toBe(0); - - console.warn = originalWarn; - }); - }); - - describe('update', () => { - test('initial call', () => { - const updateFn = jest.fn(); - createLifecycle({}, updateFn); - - expect(updateFn).toBeCalledTimes(1); - expect(updateFn).toHaveBeenLastCalledWith(true, expect.objectContaining({})); - }); - - test('updates correctly on options change', () => { - let checkOption = (...args: any): any => {}; // eslint-disable-line - const updateFn = jest.fn(); - const update = (force: any, check: any): void => { - updateFn(force, check); - checkOption = check; - }; - const { _options } = createLifecycle({}, update); - - _options({ number: 5 }); - expect(updateFn).toBeCalledTimes(2); - expect(updateFn).toHaveBeenLastCalledWith(false, expect.objectContaining({})); - let { _value, _changed } = checkOption('number'); - expect(_value).toBe(5); - expect(_changed).toBe(true); - ({ _value, _changed } = checkOption('string')); - expect(_value).toBe('hi'); - expect(_changed).toBe(false); - ({ _value, _changed } = checkOption('nested.boolean')); - expect(_value).toBe(false); - expect(_changed).toBe(false); - ({ _value, _changed } = checkOption('nested.number')); - expect(_value).toBe(0); - expect(_changed).toBe(false); - - _options({ number: 5, string: 'test', nested: { number: 3 } }); - expect(updateFn).toBeCalledTimes(3); - expect(updateFn).toHaveBeenLastCalledWith(false, expect.objectContaining({})); - ({ _value, _changed } = checkOption('number')); - expect(_value).toBe(5); - expect(_changed).toBe(false); - ({ _value, _changed } = checkOption('string')); - expect(_value).toBe('test'); - expect(_changed).toBe(true); - ({ _value, _changed } = checkOption('nested.boolean')); - expect(_value).toBe(false); - expect(_changed).toBe(false); - ({ _value, _changed } = checkOption('nested.number')); - expect(_value).toBe(3); - expect(_changed).toBe(true); - - _options({ string: 'test', nested: { number: 3 } }); - expect(updateFn).toBeCalledTimes(3); - }); - - test('updates correctly on update call', () => { - let checkOption = (...args: any): any => {}; // eslint-disable-line - const updateFn = jest.fn(); - const update = (force: any, check: any): void => { - updateFn(force, check); - checkOption = check; - }; - const { _update, _options } = createLifecycle({}, update); - - _update(); - expect(updateFn).toBeCalledTimes(2); - expect(updateFn).toHaveBeenLastCalledWith(false, expect.objectContaining({})); - let { _value, _changed } = checkOption('number'); - expect(_value).toBe(0); - expect(_changed).toBe(false); - ({ _value, _changed } = checkOption('string')); - expect(_value).toBe('hi'); - expect(_changed).toBe(false); - ({ _value, _changed } = checkOption('nested.boolean')); - expect(_value).toBe(false); - expect(_changed).toBe(false); - ({ _value, _changed } = checkOption('nested.number')); - expect(_value).toBe(0); - expect(_changed).toBe(false); - - _update(true); - expect(updateFn).toBeCalledTimes(3); - expect(updateFn).toHaveBeenLastCalledWith(true, expect.objectContaining({})); - ({ _value, _changed } = checkOption('number')); - expect(_value).toBe(0); - expect(_changed).toBe(true); - ({ _value, _changed } = checkOption('string')); - expect(_value).toBe('hi'); - expect(_changed).toBe(true); - ({ _value, _changed } = checkOption('nested.boolean')); - expect(_value).toBe(false); - expect(_changed).toBe(true); - ({ _value, _changed } = checkOption('nested.number')); - expect(_value).toBe(0); - expect(_changed).toBe(true); - - _options({ number: 3, nested: { boolean: true } }); - _update(true); - expect(updateFn).toBeCalledTimes(5); - expect(updateFn).toHaveBeenLastCalledWith(true, expect.objectContaining({})); - ({ _value, _changed } = checkOption('number')); - expect(_value).toBe(3); - expect(_changed).toBe(true); - ({ _value, _changed } = checkOption('string')); - expect(_value).toBe('hi'); - expect(_changed).toBe(true); - ({ _value, _changed } = checkOption('nested.boolean')); - expect(_value).toBe(true); - expect(_changed).toBe(true); - ({ _value, _changed } = checkOption('nested.number')); - expect(_value).toBe(0); - expect(_changed).toBe(true); - - _options({ number: 3, nested: { boolean: true } }); - _update(); - expect(updateFn).toBeCalledTimes(6); - expect(updateFn).toHaveBeenLastCalledWith(false, expect.objectContaining({})); - ({ _value, _changed } = checkOption('number')); - expect(_value).toBe(3); - expect(_changed).toBe(false); - ({ _value, _changed } = checkOption('string')); - expect(_value).toBe('hi'); - expect(_changed).toBe(false); - ({ _value, _changed } = checkOption('nested.boolean')); - expect(_value).toBe(true); - expect(_changed).toBe(false); - ({ _value, _changed } = checkOption('nested.number')); - expect(_value).toBe(0); - expect(_changed).toBe(false); - - _options({ number: 4, nested: { boolean: false }, string: 'hi' }); - expect(updateFn).toBeCalledTimes(7); - expect(updateFn).toHaveBeenLastCalledWith(false, expect.objectContaining({})); - ({ _value, _changed } = checkOption('number')); - expect(_value).toBe(4); - expect(_changed).toBe(true); - ({ _value, _changed } = checkOption('string')); - expect(_value).toBe('hi'); - expect(_changed).toBe(false); - ({ _value, _changed } = checkOption('nested.boolean')); - expect(_value).toBe(false); - expect(_changed).toBe(true); - ({ _value, _changed } = checkOption('nested.number')); - expect(_value).toBe(0); - expect(_changed).toBe(false); - - _update(); - expect(updateFn).toBeCalledTimes(8); - expect(updateFn).toHaveBeenLastCalledWith(false, expect.objectContaining({})); - - _options(); - expect(updateFn).toBeCalledTimes(8); - - _options({ number: 4, nested: { boolean: false }, string: 'hi' }); - expect(updateFn).toBeCalledTimes(8); - }); - }); -}); diff --git a/packages/overlayscrollbars/tests/jsdom/support/dom/style.test.ts b/packages/overlayscrollbars/tests/jsdom/support/dom/style.test.ts index 14f268c..e5347cf 100644 --- a/packages/overlayscrollbars/tests/jsdom/support/dom/style.test.ts +++ b/packages/overlayscrollbars/tests/jsdom/support/dom/style.test.ts @@ -1,6 +1,7 @@ import { isEmptyObject } from 'support/utils/object'; import { isString, isPlainObject } from 'support/utils/types'; import { style, hide, show, topRightBottomLeft } from 'support/dom/style'; +import { StyleObject } from 'typings'; describe('dom style', () => { afterEach(() => { @@ -34,6 +35,10 @@ describe('dom style', () => { expect(document.body.style.width).toBe(''); style(document.body, { width: '123px' }); expect(document.body.style.width).toBe('123px'); + + expect(document.body.style.getPropertyValue('--custom')).toBe(''); + style(document.body, { '--custom': '123px' }); + expect(document.body.style.getPropertyValue('--custom')).toBe('123px'); }); test('single add px', () => { @@ -54,11 +59,13 @@ describe('dom style', () => { expect(document.body.style.opacity).toBe(''); expect(document.body.style.zIndex).toBe(''); expect(document.body.style.lineHeight).toBe(''); - style(document.body, { width: '123px', height: 321, opacity: '0.5', zIndex: 1 }); + expect(document.body.style.getPropertyValue('--custom')).toBe(''); + style(document.body, { width: '123px', height: 321, opacity: '0.5', zIndex: 1, '--custom': '123px' }); expect(document.body.style.width).toBe('123px'); expect(document.body.style.height).toBe('321px'); expect(document.body.style.opacity).toBe('0.5'); expect(document.body.style.zIndex).toBe('1'); + expect(document.body.style.getPropertyValue('--custom')).toBe('123px'); }); test('null', () => {