content element independency

This commit is contained in:
Rene
2021-04-03 18:27:02 +02:00
parent 7db7fd551d
commit e3e0614a7b
13 changed files with 190 additions and 385 deletions
@@ -147,8 +147,6 @@ const createEnvironment = (): Environment => {
}, },
}; };
console.log(env);
removeAttr(envElm, 'style'); removeAttr(envElm, 'style');
removeElements(envElm); removeElements(envElm);
@@ -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 { Options } from 'options';
import { getEnvironment } from 'environment'; import { getEnvironment } from 'environment';
import { StructureSetup } from 'setups/structureSetup'; import { StructureSetup } from 'setups/structureSetup';
@@ -47,10 +47,11 @@ export interface LifecycleHubInstance {
export interface LifecycleHub { export interface LifecycleHub {
_options: Options; _options: Options;
_structureSetup: StructureSetup; _structureSetup: StructureSetup;
_doViewportArrange: boolean;
_getPaddingInfo(): PaddingInfo; _getPaddingInfo(): PaddingInfo;
_setPaddingInfo(newPadding?: PaddingInfo | null): void; _setPaddingInfo(newPadding?: PaddingInfo | null): void;
_getPaddingStyle(): StyleObject; _getViewportPaddingStyle(): StyleObject;
_setPaddingStyle(newPaddingStlye?: StyleObject | null): void; _setViewportPaddingStyle(newPaddingStlye?: StyleObject | null): void;
_getViewportOverflowScroll(): XY<boolean>; _getViewportOverflowScroll(): XY<boolean>;
_setViewportOverflowScroll(newViewportOverflowScroll: XY<boolean>): void; _setViewportOverflowScroll(newViewportOverflowScroll: XY<boolean>): void;
} }
@@ -58,6 +59,16 @@ export interface LifecycleHub {
const getPropByPath = <T>(obj: any, path: string): T => const getPropByPath = <T>(obj: any, path: string): T =>
obj && path.split('.').reduce((o, prop) => (o && hasOwnProperty(o, prop) ? o[prop] : undefined), obj); 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 attrs = ['id', 'class', 'style', 'open'];
const paddingInfoFallback: PaddingInfo = { const paddingInfoFallback: PaddingInfo = {
_absolute: false, _absolute: false,
@@ -73,6 +84,10 @@ const viewportPaddingStyleFallback: StyleObject = {
marginRight: 0, marginRight: 0,
marginBottom: 0, marginBottom: 0,
marginLeft: 0, marginLeft: 0,
paddingTop: 0,
paddingRight: 0,
paddingBottom: 0,
paddingLeft: 0,
}; };
const viewportOverflowScrollFallback: XY<boolean> = { const viewportOverflowScrollFallback: XY<boolean> = {
x: false, x: false,
@@ -93,24 +108,27 @@ export const createLifecycleHub = (options: Options, structureSetup: StructureSe
let paddingInfo = paddingInfoFallback; let paddingInfo = paddingInfoFallback;
let viewportPaddingStyle = viewportPaddingStyleFallback; let viewportPaddingStyle = viewportPaddingStyleFallback;
let viewportOverflowScroll = viewportOverflowScrollFallback; let viewportOverflowScroll = viewportOverflowScrollFallback;
const { _host, _viewport, _content, _contentArrange } = structureSetup._targetObj; const { _host, _viewport, _content } = structureSetup._targetObj;
const { const {
_nativeScrollbarStyling, _nativeScrollbarStyling,
_nativeScrollbarIsOverlaid,
_flexboxGlue, _flexboxGlue,
_addListener: addEnvironmentListener, _addListener: addEnvironmentListener,
_removeListener: removeEnvironmentListener, _removeListener: removeEnvironmentListener,
} = getEnvironment(); } = getEnvironment();
const doViewportArrange = !_nativeScrollbarStyling && (_nativeScrollbarIsOverlaid.x || _nativeScrollbarIsOverlaid.y);
const lifecycles: Lifecycle[] = []; const lifecycles: Lifecycle[] = [];
const instance: LifecycleHub = { const instance: LifecycleHub = {
_options: options, _options: options,
_structureSetup: structureSetup, _structureSetup: structureSetup,
_doViewportArrange: doViewportArrange,
_getPaddingInfo: () => paddingInfo, _getPaddingInfo: () => paddingInfo,
_setPaddingInfo(newPaddingInfo) { _setPaddingInfo(newPaddingInfo) {
paddingInfo = newPaddingInfo || paddingInfoFallback; paddingInfo = newPaddingInfo || paddingInfoFallback;
}, },
_getPaddingStyle: () => viewportPaddingStyle, _getViewportPaddingStyle: () => viewportPaddingStyle,
_setPaddingStyle(newPaddingStlye) { _setViewportPaddingStyle(newPaddingStlye) {
viewportPaddingStyle = newPaddingStlye || viewportPaddingStyleFallback; viewportPaddingStyle = newPaddingStlye ? emptyStylePropsToZero(newPaddingStlye, viewportPaddingStyleFallback) : viewportPaddingStyleFallback;
}, },
_getViewportOverflowScroll: () => viewportOverflowScroll, _getViewportOverflowScroll: () => viewportOverflowScroll,
_setViewportOverflowScroll(newViewportOverflowScroll) { _setViewportOverflowScroll(newViewportOverflowScroll) {
@@ -143,7 +161,7 @@ export const createLifecycleHub = (options: Options, structureSetup: StructureSe
_value: getPropByPath(options, path), _value: getPropByPath(options, path),
_changed: force || getPropByPath(changedOptions, path) !== undefined, _changed: force || getPropByPath(changedOptions, path) !== undefined,
}); });
const adjustScrollOffset = _contentArrange || !_flexboxGlue; const adjustScrollOffset = doViewportArrange || !_flexboxGlue;
const scrollOffsetX = adjustScrollOffset && scrollLeft(_viewport); const scrollOffsetX = adjustScrollOffset && scrollLeft(_viewport);
const scrollOffsetY = adjustScrollOffset && scrollTop(_viewport); const scrollOffsetY = adjustScrollOffset && scrollTop(_viewport);
@@ -1,5 +1,6 @@
import { import {
createCache, createCache,
keys,
attr, attr,
WH, WH,
XY, XY,
@@ -13,12 +14,13 @@ import {
clientSize, clientSize,
offsetSize, offsetSize,
getBoundingClientRect, getBoundingClientRect,
noop,
} from 'support'; } from 'support';
import { LifecycleHub, Lifecycle } from 'lifecycles/lifecycleHub'; import { LifecycleHub, Lifecycle } from 'lifecycles/lifecycleHub';
import { getEnvironment } from 'environment'; import { getEnvironment } from 'environment';
import { OverflowBehavior } from 'options'; import { OverflowBehavior } from 'options';
import { StyleObject } from 'typings'; import { StyleObject } from 'typings';
import { classNameViewport, classNameViewportArrange, classNameViewportScrollbarStyling } from 'classnames'; import { classNameViewportArrange, classNameViewportScrollbarStyling } from 'classnames';
interface ContentScrollSizeCacheContext { interface ContentScrollSizeCacheContext {
_viewportRect: DOMRect; _viewportRect: DOMRect;
@@ -41,20 +43,23 @@ interface OverflowOption {
y: OverflowBehavior; y: OverflowBehavior;
} }
interface ViewportArrangeCustomCssProps {
'--viewport-arrange-width': string;
'--viewport-arrange-height': string;
}
const overlaidScrollbarsHideOffset = 42; const overlaidScrollbarsHideOffset = 42;
const overlaidScrollbarsHideBorderStyle = `${overlaidScrollbarsHideOffset}px solid transparent`;
export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle => { export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle => {
const { _structureSetup, _getPaddingStyle, _getPaddingInfo } = lifecycleHub; const { _structureSetup, _doViewportArrange, _getViewportPaddingStyle, _getPaddingInfo } = lifecycleHub;
const { _host, _padding, _viewport, _content, _contentArrange } = _structureSetup._targetObj; const { _host, _padding, _viewport, _viewportArrange } = _structureSetup._targetObj;
const { _update: updateContentScrollSizeCache, _current: getCurrentContentScrollSizeCache } = createCache< const { _update: updateContentScrollSizeCache, _current: getCurrentContentScrollSizeCache } = createCache<
WH<number>, WH<number>,
ContentScrollSizeCacheContext ContentScrollSizeCacheContext
>( >(
(ctx) => { (ctx) => {
const { _viewportOffsetSize, _viewportScrollSize, _viewportRect } = ctx; const { _viewportOffsetSize, _viewportScrollSize, _viewportRect } = ctx;
const contentViewportScrollSize = _content ? scrollSize(_content) : _viewportScrollSize; return fixScrollSizeRounding(_viewportScrollSize, _viewportOffsetSize, _viewportRect);
return fixScrollSizeRounding(contentViewportScrollSize, _viewportOffsetSize, _viewportRect);
}, },
{ _equal: equalWH } { _equal: equalWH }
); );
@@ -67,8 +72,8 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle =
); );
const fixScrollSizeRounding = (scrollSize: WH<number>, viewportOffsetSize: WH<number>, viewportRect: DOMRect): WH<number> => ({ const fixScrollSizeRounding = (scrollSize: WH<number>, viewportOffsetSize: WH<number>, viewportRect: DOMRect): WH<number> => ({
w: scrollSize.w - Math.ceil(Math.max(0, viewportRect.width - viewportOffsetSize.w)), w: scrollSize.w - Math.round(Math.max(0, viewportRect.width - viewportOffsetSize.w)),
h: scrollSize.h - Math.ceil(Math.max(0, viewportRect.height - viewportOffsetSize.h)), h: scrollSize.h - Math.round(Math.max(0, viewportRect.height - viewportOffsetSize.h)),
}); });
const fixFlexboxGlue = (viewportOverflowState: ViewportOverflowState, heightIntrinsic: boolean) => { const fixFlexboxGlue = (viewportOverflowState: ViewportOverflowState, heightIntrinsic: boolean) => {
@@ -151,42 +156,54 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle =
viewportOverflowState: ViewportOverflowState, viewportOverflowState: ViewportOverflowState,
contentScrollSize: WH<number>, contentScrollSize: WH<number>,
directionIsRTL: boolean, directionIsRTL: boolean,
contentStyleObj?: StyleObject viewportStyleObj?: StyleObject
) => { ) => {
const { _nativeScrollbarStyling, _nativeScrollbarIsOverlaid } = getEnvironment(); const { _nativeScrollbarStyling, _nativeScrollbarIsOverlaid } = getEnvironment();
if ((_nativeScrollbarIsOverlaid.x || _nativeScrollbarIsOverlaid.y) && !_nativeScrollbarStyling) { if ((_nativeScrollbarIsOverlaid.x || _nativeScrollbarIsOverlaid.y) && !_nativeScrollbarStyling) {
const { _scrollbarsHideOffset } = viewportOverflowState; const { _scrollbarsHideOffset } = viewportOverflowState;
const { _absolute: paddingAbsolute, _padding: padding } = _getPaddingInfo();
const { x: hideOffsetX, y: hideOffsetY } = _scrollbarsHideOffset; const { x: hideOffsetX, y: hideOffsetY } = _scrollbarsHideOffset;
const horizontalPaddingKey = directionIsRTL ? 'paddingLeft' : 'paddingRight'; const viewportPaddingStyle = _getViewportPaddingStyle();
const horizontalPaddingValue = paddingAbsolute ? 0 : directionIsRTL ? padding.l : padding.r; const viewportHorizontalPaddingKey = directionIsRTL ? 'paddingLeft' : 'paddingRight';
const verticalPaddingValue = paddingAbsolute ? 0 : padding.b; const viewportHorizontalPaddingValue = viewportPaddingStyle[viewportHorizontalPaddingKey] as number;
const viewportVerticalPaddingValue = viewportPaddingStyle.paddingBottom as number;
style(_viewport, { const viewportArrangeHorizontalPaddingKey = directionIsRTL ? 'paddingRight' : 'paddingLeft';
[horizontalPaddingKey]: horizontalPaddingValue + hideOffsetY, const viewportArrangeHorizontalPaddingValue = viewportPaddingStyle[viewportArrangeHorizontalPaddingKey] as number;
paddingBottom: verticalPaddingValue + hideOffsetX, 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 // adjust content arrange / before element
if (_contentArrange) { if (_viewportArrange) {
const { sheet } = _contentArrange; const { sheet } = _viewportArrange;
if (sheet) { if (sheet) {
const { cssRules } = sheet; const { cssRules } = sheet;
if (cssRules) { if (cssRules) {
if (!cssRules.length) { if (!cssRules.length) {
sheet.insertRule(`#${attr(_contentArrange, 'id')} + .${classNameViewportArrange}::before {}`, 0); sheet.insertRule(`#${attr(_viewportArrange, 'id')} + .${classNameViewportArrange}::before {}`, 0);
} }
// @ts-ignore // @ts-ignore
const ruleStyle = cssRules[0].style; const ruleStyle = cssRules[0].style;
ruleStyle.width = hideOffsetY ? `${contentScrollSize.w}px` : '0px'; ruleStyle.width = arrangeSize.w;
ruleStyle.height = hideOffsetX ? `${contentScrollSize.h}px` : '0px'; ruleStyle.height = arrangeSize.h;
addClass(_viewport, classNameViewportArrange);
} }
} }
} else { } else {
style<ViewportArrangeCustomCssProps>(_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 { _nativeScrollbarStyling } = getEnvironment();
const { _overflowScroll, _scrollbarsHideOffset } = viewportOverflowState; const { _overflowScroll, _scrollbarsHideOffset } = viewportOverflowState;
const { x: scrollX, y: scrollY } = _overflowScroll; const { x: scrollX, y: scrollY } = _overflowScroll;
const paddingStyle = _getPaddingStyle(); const paddingStyle = _getViewportPaddingStyle();
const horizontalMarginKey = directionIsRTL ? 'marginLeft' : 'marginRight'; const horizontalMarginKey = directionIsRTL ? 'marginLeft' : 'marginRight';
const horizontalPaddingValue = paddingStyle[horizontalMarginKey] as number; const horizontalPaddingValue = paddingStyle[horizontalMarginKey] as number;
const verticalPaddingValue = paddingStyle.marginBottom as number;
// horizontal // horizontal
viewportStyleObj.maxWidth = `calc(100% + ${_scrollbarsHideOffset.y + horizontalPaddingValue * -1}px)`; viewportStyleObj.maxWidth = `calc(100% + ${_scrollbarsHideOffset.y + horizontalPaddingValue * -1}px)`;
viewportStyleObj[horizontalMarginKey] = -_scrollbarsHideOffset.y + horizontalPaddingValue; viewportStyleObj[horizontalMarginKey] = -_scrollbarsHideOffset.y + horizontalPaddingValue;
// vertical // vertical
viewportStyleObj.marginBottom = -_scrollbarsHideOffset.x + (paddingStyle.marginBottom as number); viewportStyleObj.marginBottom = -_scrollbarsHideOffset.x + verticalPaddingValue;
// hide overflowing scrollbars if there are any // hide overflowing scrollbars if there are any
if (!_nativeScrollbarStyling) { 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) => { return (updateHints, checkOption, force) => {
const { _directionIsRTL, _heightIntrinsic, _sizeChanged, _hostMutation, _contentMutation, _paddingStyleChanged } = updateHints; const { _directionIsRTL, _heightIntrinsic, _sizeChanged, _hostMutation, _contentMutation, _paddingStyleChanged } = updateHints;
const { _flexboxGlue, _nativeScrollbarStyling, _nativeScrollbarIsOverlaid } = getEnvironment(); const { _flexboxGlue, _nativeScrollbarStyling, _nativeScrollbarIsOverlaid } = getEnvironment();
@@ -222,9 +259,10 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle =
const { _value: showNativeOverlaidScrollbarsOption, _changed: showNativeOverlaidScrollbarsChanged } = checkOption<boolean>( const { _value: showNativeOverlaidScrollbarsOption, _changed: showNativeOverlaidScrollbarsChanged } = checkOption<boolean>(
'nativeScrollbarsOverlaid.show' 'nativeScrollbarsOverlaid.show'
); );
const showNativeOverlaidScrollbars = showNativeOverlaidScrollbarsOption && _nativeScrollbarIsOverlaid.x && _nativeScrollbarIsOverlaid.y;
const viewportArrange = _doViewportArrange && !showNativeOverlaidScrollbars;
const adjustFlexboxGlue = const adjustFlexboxGlue =
!_flexboxGlue && (_sizeChanged || _contentMutation || _hostMutation || showNativeOverlaidScrollbarsChanged || heightIntrinsicChanged); !_flexboxGlue && (_sizeChanged || _contentMutation || _hostMutation || showNativeOverlaidScrollbarsChanged || heightIntrinsicChanged);
const showNativeOverlaidScrollbars = showNativeOverlaidScrollbarsOption && _nativeScrollbarIsOverlaid.x && _nativeScrollbarIsOverlaid.y;
let overflowAmuntCache: CacheValues<XY<number>> = getCurrentOverflowAmountCache(force); let overflowAmuntCache: CacheValues<XY<number>> = getCurrentOverflowAmountCache(force);
let contentScrollSizeCache: CacheValues<WH<number>> = getCurrentContentScrollSizeCache(force); let contentScrollSizeCache: CacheValues<WH<number>> = getCurrentContentScrollSizeCache(force);
let preMeasureViewportOverflowState: ViewportOverflowState | undefined; let preMeasureViewportOverflowState: ViewportOverflowState | undefined;
@@ -242,63 +280,43 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle =
fixFlexboxGlue(preMeasureViewportOverflowState, !!heightIntrinsic); fixFlexboxGlue(preMeasureViewportOverflowState, !!heightIntrinsic);
} }
if (_sizeChanged || _contentMutation || directionChanged) { if (_sizeChanged || _paddingStyleChanged || _contentMutation || directionChanged) {
removeClass(_viewport, classNameViewportArrange); const redoOverlaidScrollbarsHiding = viewportArrange ? undoOverlaidScrollbarsHiding() : noop;
style(_viewport, { const contentSize = clientSize(_viewport);
paddingRight: _getPaddingInfo()._padding.r,
paddingBottom: _getPaddingInfo()._padding.b,
marginRight: -_getPaddingInfo()._padding.r - _getPaddingInfo()._padding.l,
marginBottom: -_getPaddingInfo()._padding.b - _getPaddingInfo()._padding.t,
});
const viewportRect = getBoundingClientRect(_viewport); const viewportRect = getBoundingClientRect(_viewport);
const viewportOffsetSize = offsetSize(_viewport); const viewportOffsetSize = offsetSize(_viewport);
const contentClientSize = clientSize(_viewport); // needs to be client Size because applied border for content arrange on content let viewportScrollSize = scrollSize(_viewport);
let viewportScrollSize = fixScrollSizeRounding(scrollSize(_viewport), viewportOffsetSize, viewportRect); let viewportClientSize = contentSize;
let viewportClientSize = clientSize(_viewport); const { _value: contentScrollSize, _changed: contentScrollSizeChanged } = (contentScrollSizeCache = updateContentScrollSizeCache(force, {
let { _value: contentScrollSize, _changed: contentScrollSizeChanged } = (contentScrollSizeCache = updateContentScrollSizeCache(force, {
_viewportRect: viewportRect, _viewportRect: viewportRect,
_viewportOffsetSize: viewportOffsetSize, _viewportOffsetSize: viewportOffsetSize,
_viewportScrollSize: viewportScrollSize, _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) // 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) { if (reMeasureRequired) {
const viewportStyle: StyleObject = { setContentArrange(
overflowY: '', preMeasureViewportOverflowState || getViewportOverflowState(showNativeOverlaidScrollbars),
overflowX: '', contentScrollSize!,
marginTop: '', directionIsRTL!
marginRight: '', );
marginBottom: '',
marginLeft: '',
maxWidth: '',
};
setContentArrange(getViewportOverflowState(showNativeOverlaidScrollbars), contentScrollSize!, directionIsRTL!);
hideNativeScrollbars(getViewportOverflowState(showNativeOverlaidScrollbars), directionIsRTL!, viewportStyle);
style(_viewport, viewportStyle);
viewportClientSize = clientSize(_viewport); viewportClientSize = clientSize(_viewport);
viewportScrollSize = fixScrollSizeRounding(scrollSize(_viewport), offsetSize(_viewport), getBoundingClientRect(_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, { overflowAmuntCache = updateOverflowAmountCache(force, {
_contentScrollSize: { _contentScrollSize: {
w: Math.max(contentScrollSize!.w, viewportScrollSize.w), w: Math.max(contentScrollSize!.w, viewportScrollSize.w),
h: Math.max(contentScrollSize!.h, viewportScrollSize.h), h: Math.max(contentScrollSize!.h, viewportScrollSize.h),
}, },
_viewportSize: { _viewportSize: {
w: viewportClientSize.w + Math.max(0, contentClientSize.w - contentScrollSize!.w), w: viewportClientSize.w + Math.max(0, contentSize.w - contentScrollSize!.w),
h: viewportClientSize.h + Math.max(0, contentClientSize.h - contentScrollSize!.h), h: viewportClientSize.h + Math.max(0, contentSize.h - contentScrollSize!.h),
}, },
}); });
} }
@@ -325,16 +343,13 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle =
marginLeft: '', marginLeft: '',
maxWidth: '', maxWidth: '',
}; };
const contentStyle: StyleObject = {
borderTop: '',
borderRight: '',
borderBottom: '',
borderLeft: '',
};
const viewportOverflowState = setViewportOverflowState(showNativeOverlaidScrollbars, overflowAmount!, overflow, viewportStyle); const viewportOverflowState = setViewportOverflowState(showNativeOverlaidScrollbars, overflowAmount!, overflow, viewportStyle);
hideNativeScrollbars(viewportOverflowState, directionIsRTL!, viewportStyle); hideNativeScrollbars(viewportOverflowState, directionIsRTL!, viewportStyle);
setContentArrange(viewportOverflowState, contentScrollSize!, directionIsRTL!, contentStyle);
if (_doViewportArrange) {
setContentArrange(viewportOverflowState, contentScrollSize!, directionIsRTL!, viewportStyle);
}
if (adjustFlexboxGlue) { if (adjustFlexboxGlue) {
fixFlexboxGlue(viewportOverflowState, !!heightIntrinsic); fixFlexboxGlue(viewportOverflowState, !!heightIntrinsic);
@@ -349,7 +364,6 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle =
// TODO: remove lifecycleHub get set padding if not needed // TODO: remove lifecycleHub get set padding if not needed
style(_viewport, viewportStyle); style(_viewport, viewportStyle);
style(_content, contentStyle);
} }
}; };
}; };
@@ -4,7 +4,8 @@ import { StyleObject } from 'typings';
import { getEnvironment } from 'environment'; import { getEnvironment } from 'environment';
export const createPaddingLifecycle = (lifecycleHub: LifecycleHub): Lifecycle => { 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'), { const { _update: updatePaddingCache, _current: currentPaddingCache } = createCache(() => topRightBottomLeft(_host, 'padding'), {
_equal: equalTRBL, _equal: equalTRBL,
}); });
@@ -76,11 +77,18 @@ export const createPaddingLifecycle = (lifecycleHub: LifecycleHub): Lifecycle =>
style(_padding || _viewport, paddingStyle); style(_padding || _viewport, paddingStyle);
style(_viewport, viewportStyle); style(_viewport, viewportStyle);
lifecycleHub._setPaddingInfo({ _setPaddingInfo({
_absolute: !paddingRelative, _absolute: !paddingRelative,
_padding: padding!, _padding: padding!,
}); });
lifecycleHub._setPaddingStyle(!_padding ? paddingStyle : null); _setViewportPaddingStyle(
_padding
? viewportStyle
: {
...paddingStyle,
...viewportStyle,
}
);
} }
return { return {
@@ -1,6 +1,5 @@
@import './sizeobserver.scss'; @import './sizeobserver.scss';
@import './trinsicobserver.scss'; @import './trinsicobserver.scss';
@import './structurelifecycle.scss';
.os-environment { .os-environment {
--css-custom-prop: -1; --css-custom-prop: -1;
@@ -70,7 +69,7 @@
} }
.os-environment, .os-environment,
.os-viewport { .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-environment,
.os-viewport-scrollbar-styled.os-viewport { .os-viewport-scrollbar-styled.os-viewport {
@@ -87,6 +86,34 @@
background: transparent !important; 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 { .os-viewport-arrange::before {
content: ''; content: '';
position: absolute; position: absolute;
@@ -94,8 +121,6 @@
z-index: -1; z-index: -1;
min-width: 1px; min-width: 1px;
min-height: 1px; min-height: 1px;
} width: var(--viewport-arrange-width);
height: var(--viewport-arrange-height);
.os-host {
padding: 5px 50px 15px 20px;
} }
@@ -37,7 +37,7 @@ export interface OSTargetContext {
export interface PreparedOSTargetObject extends Required<InternalVersionOf<OSTargetObject>> { export interface PreparedOSTargetObject extends Required<InternalVersionOf<OSTargetObject>> {
_host: HTMLElement; _host: HTMLElement;
_contentArrange: HTMLStyleElement | null; _viewportArrange: HTMLStyleElement | null;
} }
export interface StructureSetup { export interface StructureSetup {
@@ -52,7 +52,7 @@ const unwrap = (elm: HTMLElement | null | undefined) => {
}; };
let contentArrangeCounter = 0; let contentArrangeCounter = 0;
const createUniqueContentArrangeElement = () => { const createUniqueViewportArrangeElement = () => {
const elm = document.createElement('style'); const elm = document.createElement('style');
attr(elm, 'id', `${classNameViewportArrange}-${contentArrangeCounter}`); attr(elm, 'id', `${classNameViewportArrange}-${contentArrangeCounter}`);
@@ -174,15 +174,13 @@ export const createStructureSetup = (target: OSTarget | OSTargetObject): Structu
const { _nativeScrollbarStyling, _nativeScrollbarIsOverlaid, _cssCustomProperties } = getEnvironment(); const { _nativeScrollbarStyling, _nativeScrollbarIsOverlaid, _cssCustomProperties } = getEnvironment();
if (_nativeScrollbarStyling) { if (_nativeScrollbarStyling) {
push(destroyFns, removeClass.bind(0, _viewport, classNameViewportScrollbarStyling)); push(destroyFns, removeClass.bind(0, _viewport, classNameViewportScrollbarStyling));
} else if (_nativeScrollbarIsOverlaid.x || _nativeScrollbarIsOverlaid.y) { } else if (!_cssCustomProperties && (_nativeScrollbarIsOverlaid.x || _nativeScrollbarIsOverlaid.y)) {
if (true) { const viewportArrangeElm = createUniqueViewportArrangeElement();
const contentArrangeElm = createUniqueContentArrangeElement();
insertBefore(_viewport, contentArrangeElm); insertBefore(_viewport, viewportArrangeElm);
push(destroyFns, removeElements.bind(0, contentArrangeElm)); push(destroyFns, removeElements.bind(0, viewportArrangeElm));
obj._contentArrange = contentArrangeElm; obj._viewportArrange = viewportArrangeElm;
}
} }
return { return {
@@ -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;
}
@@ -1,5 +1,5 @@
import { each, keys } from 'support/utils'; 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'; import { PlainObject, StyleObject } from 'typings';
export interface TRBL { 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]; computedStyle != null ? computedStyle[prop] || computedStyle.getPropertyValue(prop) : elm.style[prop];
const setCSSVal = (elm: HTMLElement | null | undefined, prop: string, val: string | number): void => { const setCSSVal = (elm: HTMLElement | null | undefined, prop: string, val: string | number): void => {
try { try {
if (elm && elm.style[prop] !== undefined) { if (elm) {
elm.style[prop] = adaptCSSVal(prop, val); const { style } = elm;
if (!isUndefined(style[prop])) {
style[prop] = adaptCSSVal(prop, val);
} else {
style.setProperty(prop, val as string);
}
} }
} catch (e) {} } 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 elm The element to which the styles shall be applied to / be read from.
* @param styles The styles which shall be set or read. * @param styles The styles which shall be set or read.
*/ */
export function style(elm: HTMLElement | null | undefined, styles: StyleObject): void; export function style<CustomCssProps>(elm: HTMLElement | null | undefined, styles: StyleObject<CustomCssProps>): void;
export function style(elm: HTMLElement | null | undefined, styles: string): string; export function style<CustomCssProps>(elm: HTMLElement | null | undefined, styles: string): string;
export function style(elm: HTMLElement | null | undefined, styles: Array<string> | string): { [key: string]: string }; export function style<CustomCssProps>(elm: HTMLElement | null | undefined, styles: Array<string> | string): { [key: string]: string };
export function style(elm: HTMLElement | null | undefined, styles: StyleObject | Array<string> | string): { [key: string]: string } | string | void { export function style<CustomCssProps>(
elm: HTMLElement | null | undefined,
styles: StyleObject<CustomCssProps> | Array<string> | string
): { [key: string]: string } | string | void {
const getSingleStyle = isString(styles); const getSingleStyle = isString(styles);
const getStyles = isArray(styles) || getSingleStyle; const getStyles = isArray(styles) || getSingleStyle;
+2 -2
View File
@@ -1,7 +1,7 @@
export type PlainObject<T = any> = { [name: string]: T }; export type PlainObject<T = any> = { [name: string]: T };
export type StyleObject = { export type StyleObject<CustomCssProps = {}> = {
[K in keyof CSSStyleDeclaration]?: string | number; [K in keyof (CSSStyleDeclaration & CustomCssProps)]?: string | number;
} }
export type InternalVersionOf<T> = { export type InternalVersionOf<T> = {
@@ -4,7 +4,7 @@ import { createDiv, appendChildren, parent, style, on, off, addClass, WH, XY, cl
import { OverlayScrollbars } from 'overlayscrollbars/OverlayScrollbars'; import { OverlayScrollbars } from 'overlayscrollbars/OverlayScrollbars';
const targetElm = document.querySelector('#target') as HTMLElement; 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) => { export const resize = (element: HTMLElement) => {
const strMouseTouchDownEvent = 'mousedown touchstart'; const strMouseTouchDownEvent = 'mousedown touchstart';
@@ -35,6 +35,7 @@ body {
min-width: 200px; min-width: 200px;
max-height: 300px; max-height: 300px;
max-width: 320px; max-width: 320px;
//padding: 5px 50px 15px 20px;
} }
#resize { #resize {
@@ -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<TestLifecycleOptions>(
{
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);
});
});
});
@@ -1,6 +1,7 @@
import { isEmptyObject } from 'support/utils/object'; import { isEmptyObject } from 'support/utils/object';
import { isString, isPlainObject } from 'support/utils/types'; import { isString, isPlainObject } from 'support/utils/types';
import { style, hide, show, topRightBottomLeft } from 'support/dom/style'; import { style, hide, show, topRightBottomLeft } from 'support/dom/style';
import { StyleObject } from 'typings';
describe('dom style', () => { describe('dom style', () => {
afterEach(() => { afterEach(() => {
@@ -34,6 +35,10 @@ describe('dom style', () => {
expect(document.body.style.width).toBe(''); expect(document.body.style.width).toBe('');
style(document.body, { width: '123px' }); style(document.body, { width: '123px' });
expect(document.body.style.width).toBe('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', () => { test('single add px', () => {
@@ -54,11 +59,13 @@ describe('dom style', () => {
expect(document.body.style.opacity).toBe(''); expect(document.body.style.opacity).toBe('');
expect(document.body.style.zIndex).toBe(''); expect(document.body.style.zIndex).toBe('');
expect(document.body.style.lineHeight).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.width).toBe('123px');
expect(document.body.style.height).toBe('321px'); expect(document.body.style.height).toBe('321px');
expect(document.body.style.opacity).toBe('0.5'); expect(document.body.style.opacity).toBe('0.5');
expect(document.body.style.zIndex).toBe('1'); expect(document.body.style.zIndex).toBe('1');
expect(document.body.style.getPropertyValue('--custom')).toBe('123px');
}); });
test('null', () => { test('null', () => {