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');
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 { 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<boolean>;
_setViewportOverflowScroll(newViewportOverflowScroll: XY<boolean>): void;
}
@@ -58,6 +59,16 @@ export interface LifecycleHub {
const getPropByPath = <T>(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<boolean> = {
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);
@@ -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<number>,
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<number>, viewportOffsetSize: WH<number>, viewportRect: DOMRect): WH<number> => ({
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<number>,
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<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 { _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<boolean>(
'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<XY<number>> = getCurrentOverflowAmountCache(force);
let contentScrollSizeCache: CacheValues<WH<number>> = 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);
}
};
};
@@ -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 {
@@ -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);
}
@@ -37,7 +37,7 @@ export interface OSTargetContext {
export interface PreparedOSTargetObject extends Required<InternalVersionOf<OSTargetObject>> {
_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 {
@@ -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 { 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> | 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>): void;
export function style<CustomCssProps>(elm: HTMLElement | null | undefined, styles: string): string;
export function style<CustomCssProps>(elm: HTMLElement | null | undefined, styles: Array<string> | string): { [key: string]: string };
export function style<CustomCssProps>(
elm: HTMLElement | null | undefined,
styles: StyleObject<CustomCssProps> | Array<string> | string
): { [key: string]: string } | string | void {
const getSingleStyle = isString(styles);
const getStyles = isArray(styles) || getSingleStyle;
+2 -2
View File
@@ -1,7 +1,7 @@
export type PlainObject<T = any> = { [name: string]: T };
export type StyleObject = {
[K in keyof CSSStyleDeclaration]?: string | number;
export type StyleObject<CustomCssProps = {}> = {
[K in keyof (CSSStyleDeclaration & CustomCssProps)]?: string | number;
}
export type InternalVersionOf<T> = {
@@ -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';
@@ -35,6 +35,7 @@ body {
min-width: 200px;
max-height: 300px;
max-width: 320px;
//padding: 5px 50px 15px 20px;
}
#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 { 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', () => {