add box-sizing change detection to size observer

This commit is contained in:
Rene
2021-04-21 22:48:15 +02:00
parent 2f51abe081
commit 235cf73c1d
10 changed files with 299 additions and 103 deletions
@@ -19,5 +19,10 @@ export const createTrinsicLifecycle = (lifecycleHub: LifecycleHub): Lifecycle =>
height: heightIntrinsic ? 'auto' : '100%',
});
}
return {
_sizeChanged: heightIntrinsicChanged,
_contentMutation: heightIntrinsicChanged,
};
};
};
@@ -21,6 +21,7 @@ import {
ResizeObserverConstructor,
isArray,
isBoolean,
removeClass,
} from 'support';
import { getEnvironment } from 'environment';
import {
@@ -64,7 +65,7 @@ const directionIsRTL = (elm: HTMLElement): boolean => style(elm, 'direction') ==
const domRectHasDimensions = (rect?: DOMRectReadOnly) => rect && (rect.height || rect.width);
/**
* Creates a size observer which observes any size, padding, margin and border changes of the target element. Depending on the options also direction and appear can be observed.
* Creates a size observer which observes any size, padding, border, margin and box-sizing changes of the target element. Depending on the options also direction and appear can be observed.
* @param target The target element which shall be observed.
* @param onSizeChangedCallback The callback which gets called after a size change was detected.
* @param options The options for size detection, whether to observe also direction and appear.
@@ -193,10 +194,11 @@ export const createSizeObserver = (
const directionIsRTLCacheValues = updateDirectionIsRTLCache();
const { _value, _changed } = directionIsRTLCacheValues;
if (_changed) {
removeClass(listenerElement, 'ltr rtl');
if (_value) {
style(listenerElement, { left: 'auto', right: 0 });
addClass(listenerElement, 'rtl');
} else {
style(listenerElement, { left: 0, right: 'auto' });
addClass(listenerElement, 'ltr');
}
onSizeChangedCallbackProxy(directionIsRTLCacheValues);
}
@@ -2,14 +2,14 @@
@import './trinsicobserver.scss';
.os-environment {
--css-custom-prop: -1;
--os-css-custom-prop: -1;
position: fixed;
opacity: 0;
visibility: hidden;
overflow: scroll;
height: 200px;
width: 200px;
z-index: var(--css-custom-prop);
z-index: var(--os-css-custom-prop);
div {
width: 200%;
@@ -96,7 +96,7 @@
.os-padding,
.os-viewport {
box-sizing: border-box;
box-sizing: inherit;
position: relative;
flex: auto !important;
height: auto;
@@ -106,6 +106,7 @@
border: none;
overflow: visible;
max-width: 100%;
z-index: 0;
}
.os-viewport {
@@ -123,3 +124,7 @@
height: var(--viewport-arrange-height);
}
}
.os-content {
box-sizing: inherit;
}
@@ -1,11 +1,9 @@
$scrollbar-cushion: 100px;
$inflate-margin: 200px;
.os-size-observer,
.os-size-observer-listener {
direction: inherit;
padding: inherit;
border: inherit;
margin: 0;
pointer-events: none;
overflow: hidden;
visibility: hidden;
@@ -23,10 +21,34 @@ $scrollbar-cushion: 100px;
}
.os-size-observer {
height: 100%;
width: 100%;
height: 3%;
width: 3%;
z-index: -1;
contain: strict;
display: flex;
flex-direction: column;
flex-wrap: nowrap;
padding: inherit;
border: inherit;
box-sizing: inherit;
margin: 0;
&::before,
&::after {
content: '';
flex: none;
box-sizing: inherit;
}
&::before {
padding: 1px;
width: 1px;
height: 1px;
}
&::after {
content: '';
padding: inherit;
border: inherit;
}
}
.os-size-observer-appear {
@@ -35,9 +57,19 @@ $scrollbar-cushion: 100px;
}
.os-size-observer-listener {
display: block;
height: 500%;
width: 500%;
box-sizing: border-box;
position: relative;
flex: auto;
margin: -#{$inflate-margin};
&.ltr {
margin-right: -#{$inflate-margin * 2};
margin-left: 0;
}
&.rtl {
margin-left: -#{$inflate-margin * 2};
margin-right: 0;
}
// lets assume no scrollbar is 100px wide
& > .os-size-observer-listener-item {
@@ -2,17 +2,24 @@ import 'styles/overlayscrollbars.scss';
import './index.scss';
import { resize } from '@/testing-browser/Resize';
import { generateClassChangeSelectCallback, iterateSelect } from '@/testing-browser/Select';
import { OverlayScrollbars } from 'overlayscrollbars';
import { style } from 'support';
import { from, style } from 'support';
const targetElm = document.querySelector('#target') as HTMLElement;
const osInstance = (window.os = OverlayScrollbars({ target: targetElm, content: false }));
const osInstance = (window.os = OverlayScrollbars({ target: targetElm, content: true }));
const target: HTMLElement | null = document.querySelector('#target');
const comparison: HTMLElement | null = document.querySelector('#comparison');
const targetRes: HTMLElement | null = document.querySelector('#target .resize');
const comparisonRes: HTMLElement | null = document.querySelector('#comparison .resize');
const resizeElms = document.querySelectorAll('.resize');
const percentElms = document.querySelectorAll('.percent');
const endElms = document.querySelectorAll('.end');
const envElms = document.querySelectorAll<HTMLElement>('.env');
const containerElms = document.querySelectorAll<HTMLElement>('#target, #comparison');
resize(target!).addResizeListener((width, height) => style(comparison, { width, height }));
//resize(comparison!).addResizeListener((width, height) => style(target, { width, height }));
resize(targetRes!).addResizeListener((width, height) => style(comparisonRes, { width, height }));
@@ -23,3 +30,41 @@ target!.querySelector('.os-viewport')?.addEventListener('scroll', (e) => {
comparison!.scrollLeft = viewport.scrollLeft;
comparison!.scrollTop = viewport.scrollTop;
});
const envWidthSelect = document.querySelector<HTMLSelectElement>('#envWidth');
const envHeightSelect = document.querySelector<HTMLSelectElement>('#envHeight');
const selectCallbackEnv = generateClassChangeSelectCallback(from(envElms));
const containerWidthSelect = document.querySelector<HTMLSelectElement>('#width');
const containerHeightSelect = document.querySelector<HTMLSelectElement>('#height');
const containerFloatSelect = document.querySelector<HTMLSelectElement>('#float');
const containerPaddingSelect = document.querySelector<HTMLSelectElement>('#padding');
const containerBorderSelect = document.querySelector<HTMLSelectElement>('#border');
const containerMarginSelect = document.querySelector<HTMLSelectElement>('#margin');
const containerBoxSizingSelect = document.querySelector<HTMLSelectElement>('#boxSizing');
const containerDirectionSelect = document.querySelector<HTMLSelectElement>('#direction');
const containerMinMaxSelect = document.querySelector<HTMLSelectElement>('#minMax');
const selectCallbackContainer = generateClassChangeSelectCallback(from(containerElms));
envWidthSelect?.addEventListener('change', selectCallbackEnv);
envHeightSelect?.addEventListener('change', selectCallbackEnv);
containerWidthSelect?.addEventListener('change', selectCallbackContainer);
containerHeightSelect?.addEventListener('change', selectCallbackContainer);
containerFloatSelect?.addEventListener('change', selectCallbackContainer);
containerPaddingSelect?.addEventListener('change', selectCallbackContainer);
containerBorderSelect?.addEventListener('change', selectCallbackContainer);
containerMarginSelect?.addEventListener('change', selectCallbackContainer);
containerBoxSizingSelect?.addEventListener('change', selectCallbackContainer);
containerDirectionSelect?.addEventListener('change', selectCallbackContainer);
containerMinMaxSelect?.addEventListener('change', selectCallbackContainer);
selectCallbackEnv(envWidthSelect);
selectCallbackEnv(envHeightSelect);
selectCallbackContainer(containerWidthSelect);
selectCallbackContainer(containerHeightSelect);
selectCallbackContainer(containerFloatSelect);
selectCallbackContainer(containerPaddingSelect);
selectCallbackContainer(containerBorderSelect);
selectCallbackContainer(containerMarginSelect);
selectCallbackContainer(containerBoxSizingSelect);
selectCallbackContainer(containerDirectionSelect);
selectCallbackContainer(containerMinMaxSelect);
@@ -1,6 +1,6 @@
<div id="controls">
<label for="evnHeight">evnHeight</label>
<select name="evnHeight" id="evnHeight">
<label for="envHeight">envHeight</label>
<select name="envHeight" id="envHeight">
<option value="envHeightAuto">auto</option>
<option value="envHeightHundred">100%</option>
</select>
@@ -35,9 +35,9 @@
</select>
<label for="border">border</label>
<select name="border" id="border">
<option value="borderNone">none</option>
<option value="borderSmall">small</option>
<option value="borderLarge">large</option>
<option value="borderNone">none</option>
</select>
<label for="margin">margin</label>
<select name="margin" id="margin">
@@ -50,11 +50,6 @@
<option value="boxSizingBorderBox">border-box</option>
<option value="boxSizingContentBox">content-box</option>
</select>
<label for="display">display</label>
<select name="display" id="display">
<option value="displayBlock">block</option>
<option value="displayNone">none</option>
</select>
<label for="direction">direction</label>
<select name="direction" id="direction">
<option value="directionLTR">ltr</option>
@@ -36,21 +36,45 @@ body {
.column {
width: 100%;
height: 100%;
font-size: 0;
line-height: 0;
}
.env {
background: rgba(0, 0, 0, 0.1);
}
#target,
#comparison {
position: relative;
border: 2px solid red;
min-height: 100px;
min-width: 200px;
max-height: 300px;
max-width: 320px;
padding: 5px 50px 15px 20px;
.os-viewport::before,
&::before {
content: '';
display: block;
position: absolute;
border: 2px dotted red;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 2;
pointer-events: none;
}
* {
font-size: medium;
line-height: normal;
}
}
#target::before {
opacity: 0.3;
}
#comparison {
overflow: hidden;
z-index: 0;
}
.resize {
@@ -73,7 +97,6 @@ body {
border: 1px solid black;
padding: 10px;
margin: 10px;
display: none;
}
.end::before {
@@ -89,68 +112,6 @@ body {
opacity: 0.5;
}
.padding0 {
padding: 0;
}
.padding10 {
padding: 10px;
}
.padding50 {
padding: 50px;
}
.border2 {
border: 2px solid red;
}
.border10 {
border: 10px solid red;
}
.border0 {
border: none;
}
.heightAuto {
height: auto;
}
.height200 {
height: 200px;
}
.heightHundred {
height: 100%;
}
.widthAuto {
width: auto;
float: left;
}
.width200 {
width: 200px;
}
.widthHundred {
width: 100%;
}
.boxSizingBorderBox {
box-sizing: border-box;
}
.boxSizingContentBox {
box-sizing: content-box;
}
.displayNone {
display: none;
}
.displayBlock {
display: block;
}
.directionltr {
direction: ltr;
}
.directionRTL {
direction: rtl;
}
.resizer {
position: relative;
}
@@ -165,6 +126,117 @@ body {
opacity: 0.3;
}
.widthAuto,
.envWidthAuto {
width: auto;
display: inline-block;
}
#target.widthAuto {
display: inline-flex;
}
.widthHundred,
.envWidthHundred {
width: 100%;
}
.heightAuto,
.envHeightAuto {
height: auto;
}
.heightHundred,
.envHeightHundred {
height: 100%;
}
.width200 {
width: 200px;
}
.height200 {
height: 200px;
}
.floatNone {
float: none;
}
.floatLeft {
float: left;
}
.floatRight {
float: right;
}
.paddingNone {
padding: 0;
}
.paddingSmall {
padding: 5px 50px 15px 20px;
}
.paddingLarge {
padding: 12px 22px 53px 33px;
}
.borderNone {
border: none;
}
.borderSmall {
border-color: darkorange;
border-style: solid;
border-width: 2px 5px 3px 4px;
}
.borderLarge {
border-color: darkorange;
border-style: solid;
border-width: 9px 6px 7px 3px;
}
.marginNone {
margin: 0;
}
.marginSmall {
margin: 21px 16px 4px 33px;
}
.marginLarge {
margin: 33px 46px 69px 23px;
}
.boxSizingBorderBox {
box-sizing: border-box;
}
.boxSizingContentBox {
box-sizing: content-box;
}
.directionLTR {
direction: ltr;
}
.directionRTL {
direction: rtl;
}
.minMaxFixed {
min-height: 180px;
min-width: 180px;
max-height: 420px;
max-width: 420px;
}
.minMaxNone {
min-height: 0;
min-width: 0;
max-height: none;
max-width: none;
}
/*
.os-environment::-webkit-scrollbar,
.os-viewport::-webkit-scrollbar,
@@ -2,7 +2,7 @@ import 'styles/overlayscrollbars.scss';
import './index.scss';
import './handleEnvironment';
import should from 'should';
import { generateClassChangeSelectCallback, iterateSelect } from '@/testing-browser/Select';
import { generateClassChangeSelectCallback, iterateSelect, selectOption } from '@/testing-browser/Select';
import { setTestResult, waitForOrFailTest } from '@/testing-browser/TestResult';
import { timeout } from '@/testing-browser/timeout';
import { hasDimensions, offsetSize, WH, style } from 'support';
@@ -60,6 +60,7 @@ const iterate = async (select: HTMLSelectElement | null, afterEach?: () => any)
currOffsetSize: WH<number>;
currContentSize: WH<number>;
currDir: string;
currBoxSizing: string;
}
await iterateSelect<IterateSelect>(select, {
@@ -69,6 +70,7 @@ const iterate = async (select: HTMLSelectElement | null, afterEach?: () => any)
const currOffsetSize = offsetSize(targetElm as HTMLElement);
const currContentSize = contentBox(targetElm as HTMLElement);
const currDir = style(targetElm as HTMLElement, 'direction');
const currBoxSizing = style(targetElm as HTMLElement, 'box-sizing');
return {
currSizeIterations,
@@ -76,15 +78,18 @@ const iterate = async (select: HTMLSelectElement | null, afterEach?: () => any)
currOffsetSize,
currContentSize,
currDir,
currBoxSizing,
};
},
async check({ currSizeIterations, currDirectionIterations, currOffsetSize, currContentSize, currDir }) {
async check({ currSizeIterations, currDirectionIterations, currOffsetSize, currContentSize, currDir, currBoxSizing }) {
const newOffsetSize = offsetSize(targetElm as HTMLElement);
const newContentSize = contentBox(targetElm as HTMLElement);
const newDir = style(targetElm as HTMLElement, 'direction');
const newBoxSizing = style(targetElm as HTMLElement, 'box-sizing');
const offsetSizeChanged = currOffsetSize.w !== newOffsetSize.w || currOffsetSize.h !== newOffsetSize.h;
const contentSizeChanged = currContentSize.w !== newContentSize.w || currContentSize.h !== newContentSize.h;
const dirChanged = currDir !== newDir;
const boxSizingChanged = currBoxSizing !== newBoxSizing;
const dimensions = hasDimensions(targetElm as HTMLElement);
const observerElm = targetElm?.firstElementChild as HTMLElement;
@@ -102,9 +107,9 @@ const iterate = async (select: HTMLSelectElement | null, afterEach?: () => any)
);
}
if (dimensions && (offsetSizeChanged || contentSizeChanged || dirChanged)) {
if (dimensions && (offsetSizeChanged || contentSizeChanged || dirChanged || boxSizingChanged)) {
await waitForOrFailTest(() => {
if (offsetSizeChanged || contentSizeChanged) {
if (offsetSizeChanged || contentSizeChanged || boxSizingChanged) {
should.equal(sizeIterations, currSizeIterations + 1, 'Size change was detected correctly.');
}
if (dirChanged) {
@@ -159,6 +164,36 @@ const iterateDisplay = async (afterEach?: () => any) => {
const iterateDirection = async (afterEach?: () => any) => {
await iterate(directionSelect, afterEach);
};
const cleanBoxSizingChange = async () => {
selectOption(heightSelect as HTMLSelectElement, 'heightAuto');
selectOption(widthSelect as HTMLSelectElement, 'widthAuto');
selectOption(paddingSelect as HTMLSelectElement, 'padding0');
selectOption(borderSelect as HTMLSelectElement, 'border0');
await timeout(250);
await iterateDirection(async () => {
await iterateBoxSizing();
});
selectOption(heightSelect as HTMLSelectElement, 'height200');
selectOption(widthSelect as HTMLSelectElement, 'width200');
await timeout(250);
await iterateDirection(async () => {
await iterateBoxSizing();
});
selectOption(heightSelect as HTMLSelectElement, 'heightHundred');
selectOption(widthSelect as HTMLSelectElement, 'widthHundred');
await timeout(250);
await iterateDirection(async () => {
await iterateBoxSizing();
});
};
const start = async () => {
setTestResult(null);
@@ -180,6 +215,7 @@ const start = async () => {
});
});
});
await cleanBoxSizingChange();
sizeObserver._destroy();
should.equal(targetElm?.children.length, preInitChildren, 'Destruction removes all generated elements.');
@@ -34,6 +34,7 @@ body {
// prevent container from reaching 0x0 dimensions for testing purposes
min-width: 50px;
min-height: 50px;
background: rgba(0, 0, 0, 0.1);
}
.padding0 {
+9 -6
View File
@@ -10,22 +10,25 @@ const noop = <T>(): T => {
const getSelectOptions = (selectElement: HTMLSelectElement) => Array.from(selectElement.options).map((option) => option.value);
export const generateSelectCallback = (
targetElm: HTMLElement | null,
targetElms: HTMLElement[] | HTMLElement | null,
callback: (targetAffectedElm: HTMLElement, possibleValues: string[], selectedValue: string) => any
) => (event: Event | HTMLSelectElement | null) => {
const target: HTMLSelectElement | null = isEvent(event) ? (event.target as HTMLSelectElement) : event;
if (target) {
const selectedOption = target.value;
const selectOptions = getSelectOptions(target);
const elmsArr = Array.isArray(targetElms) ? targetElms : [targetElms];
if (targetElm) {
callback(targetElm, selectOptions, selectedOption);
}
elmsArr.forEach((elm) => {
if (elm) {
callback(elm, selectOptions, selectedOption);
}
});
}
};
export const generateClassChangeSelectCallback = (targetElm: HTMLElement | null) =>
generateSelectCallback(targetElm, (targetAffectedElm, possibleValues, selectedValue) => {
export const generateClassChangeSelectCallback = (targetElms: HTMLElement[] | HTMLElement | null) =>
generateSelectCallback(targetElms, (targetAffectedElm, possibleValues, selectedValue) => {
possibleValues.forEach((clazz) => targetAffectedElm.classList.remove(clazz));
targetAffectedElm.classList.add(selectedValue);
});