WIP: make content element obsolve

This commit is contained in:
Rene Haas
2021-04-03 13:30:08 +02:00
parent ea6794eca8
commit 7db7fd551d
9 changed files with 136 additions and 53 deletions
+1 -1
View File
@@ -5,8 +5,8 @@ export const classNameEnvironmentFlexboxGlueMax = `${classNameEnvironmentFlexbox
export const classNameHost = 'os-host'; export const classNameHost = 'os-host';
export const classNamePadding = 'os-padding'; export const classNamePadding = 'os-padding';
export const classNameViewport = 'os-viewport'; export const classNameViewport = 'os-viewport';
export const classNameViewportArrange = `${classNameViewport}-arrange`;
export const classNameContent = 'os-content'; export const classNameContent = 'os-content';
export const classNameContentArrange = `${classNameContent}-arrange`;
export const classNameViewportScrollbarStyling = `${classNameViewport}-scrollbar-styled`; export const classNameViewportScrollbarStyling = `${classNameViewport}-scrollbar-styled`;
export const classNameSizeObserver = 'os-size-observer'; export const classNameSizeObserver = 'os-size-observer';
@@ -30,6 +30,7 @@ export interface Environment {
_nativeScrollbarStyling: boolean; _nativeScrollbarStyling: boolean;
_rtlScrollBehavior: { n: boolean; i: boolean }; _rtlScrollBehavior: { n: boolean; i: boolean };
_flexboxGlue: boolean; _flexboxGlue: boolean;
_cssCustomProperties: boolean;
_addListener(listener: OnEnvironmentChanged): void; _addListener(listener: OnEnvironmentChanged): void;
_removeListener(listener: OnEnvironmentChanged): void; _removeListener(listener: OnEnvironmentChanged): void;
} }
@@ -135,6 +136,7 @@ const createEnvironment = (): Environment => {
_nativeScrollbarSize: nativeScrollbarSize, _nativeScrollbarSize: nativeScrollbarSize,
_nativeScrollbarIsOverlaid: nativeScrollbarIsOverlaid, _nativeScrollbarIsOverlaid: nativeScrollbarIsOverlaid,
_nativeScrollbarStyling: nativeScrollbarStyling, _nativeScrollbarStyling: nativeScrollbarStyling,
_cssCustomProperties: style(envElm, 'zIndex') === '-1',
_rtlScrollBehavior: getRtlScrollBehavior(envElm, envChildElm), _rtlScrollBehavior: getRtlScrollBehavior(envElm, envChildElm),
_flexboxGlue: getFlexboxGlue(envElm, envChildElm), _flexboxGlue: getFlexboxGlue(envElm, envChildElm),
_addListener(listener: OnEnvironmentChanged): void { _addListener(listener: OnEnvironmentChanged): void {
@@ -145,6 +147,8 @@ const createEnvironment = (): Environment => {
}, },
}; };
console.log(env);
removeAttr(envElm, 'style'); removeAttr(envElm, 'style');
removeElements(envElm); removeElements(envElm);
@@ -11,6 +11,11 @@ import { StyleObject } from 'typings';
export type LifecycleCheckOption = <T>(path: string) => LifecycleOptionInfo<T>; export type LifecycleCheckOption = <T>(path: string) => LifecycleOptionInfo<T>;
export interface PaddingInfo {
_absolute: boolean;
_padding: TRBL;
}
export interface LifecycleOptionInfo<T> { export interface LifecycleOptionInfo<T> {
readonly _value: T; readonly _value: T;
_changed: boolean; _changed: boolean;
@@ -42,8 +47,8 @@ export interface LifecycleHubInstance {
export interface LifecycleHub { export interface LifecycleHub {
_options: Options; _options: Options;
_structureSetup: StructureSetup; _structureSetup: StructureSetup;
_getPadding(): TRBL; _getPaddingInfo(): PaddingInfo;
_setPadding(newPadding?: TRBL | null): void; _setPaddingInfo(newPadding?: PaddingInfo | null): void;
_getPaddingStyle(): StyleObject; _getPaddingStyle(): StyleObject;
_setPaddingStyle(newPaddingStlye?: StyleObject | null): void; _setPaddingStyle(newPaddingStlye?: StyleObject | null): void;
_getViewportOverflowScroll(): XY<boolean>; _getViewportOverflowScroll(): XY<boolean>;
@@ -54,7 +59,15 @@ 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 attrs = ['id', 'class', 'style', 'open']; const attrs = ['id', 'class', 'style', 'open'];
const paddingFallback: TRBL = { t: 0, r: 0, b: 0, l: 0 }; const paddingInfoFallback: PaddingInfo = {
_absolute: false,
_padding: {
t: 0,
r: 0,
b: 0,
l: 0,
},
};
const viewportPaddingStyleFallback: StyleObject = { const viewportPaddingStyleFallback: StyleObject = {
marginTop: 0, marginTop: 0,
marginRight: 0, marginRight: 0,
@@ -77,7 +90,7 @@ const heightIntrinsicCacheValuesFallback: CacheValues<boolean> = {
}; };
export const createLifecycleHub = (options: Options, structureSetup: StructureSetup): LifecycleHubInstance => { export const createLifecycleHub = (options: Options, structureSetup: StructureSetup): LifecycleHubInstance => {
let padding = paddingFallback; 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, _contentArrange } = structureSetup._targetObj;
@@ -91,9 +104,9 @@ export const createLifecycleHub = (options: Options, structureSetup: StructureSe
const instance: LifecycleHub = { const instance: LifecycleHub = {
_options: options, _options: options,
_structureSetup: structureSetup, _structureSetup: structureSetup,
_getPadding: () => padding, _getPaddingInfo: () => paddingInfo,
_setPadding(newPadding) { _setPaddingInfo(newPaddingInfo) {
padding = newPadding || paddingFallback; paddingInfo = newPaddingInfo || paddingInfoFallback;
}, },
_getPaddingStyle: () => viewportPaddingStyle, _getPaddingStyle: () => viewportPaddingStyle,
_setPaddingStyle(newPaddingStlye) { _setPaddingStyle(newPaddingStlye) {
@@ -1,5 +1,6 @@
import { import {
createCache, createCache,
attr,
WH, WH,
XY, XY,
equalXY, equalXY,
@@ -17,7 +18,7 @@ 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 { classNameViewportScrollbarStyling } from 'classnames'; import { classNameViewport, classNameViewportArrange, classNameViewportScrollbarStyling } from 'classnames';
interface ContentScrollSizeCacheContext { interface ContentScrollSizeCacheContext {
_viewportRect: DOMRect; _viewportRect: DOMRect;
@@ -44,7 +45,7 @@ const overlaidScrollbarsHideOffset = 42;
const overlaidScrollbarsHideBorderStyle = `${overlaidScrollbarsHideOffset}px solid transparent`; const overlaidScrollbarsHideBorderStyle = `${overlaidScrollbarsHideOffset}px solid transparent`;
export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle => { export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle => {
const { _structureSetup, _getPaddingStyle } = lifecycleHub; const { _structureSetup, _getPaddingStyle, _getPaddingInfo } = lifecycleHub;
const { _host, _padding, _viewport, _content, _contentArrange } = _structureSetup._targetObj; const { _host, _padding, _viewport, _content, _contentArrange } = _structureSetup._targetObj;
const { _update: updateContentScrollSizeCache, _current: getCurrentContentScrollSizeCache } = createCache< const { _update: updateContentScrollSizeCache, _current: getCurrentContentScrollSizeCache } = createCache<
WH<number>, WH<number>,
@@ -76,14 +77,16 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle =
}); });
if (heightIntrinsic) { if (heightIntrinsic) {
const { _absolute: paddingAbsolute, _padding: padding } = _getPaddingInfo();
const { _overflowScroll, _scrollbarsHideOffset } = viewportOverflowState; const { _overflowScroll, _scrollbarsHideOffset } = viewportOverflowState;
const hostBCR = getBoundingClientRect(_host); const hostBCR = getBoundingClientRect(_host);
const hostOffsetSize = offsetSize(_host); const hostOffsetSize = offsetSize(_host);
const hostClientSize = clientSize(_host); const hostClientSize = clientSize(_host);
const paddingAbsoluteVertical = paddingAbsolute ? padding.b + padding.t : 0;
const clientSizeWithoutRounding = hostClientSize.h + (hostBCR.height - hostOffsetSize.h); const clientSizeWithoutRounding = hostClientSize.h + (hostBCR.height - hostOffsetSize.h);
style(_viewport, { style(_viewport, {
maxHeight: clientSizeWithoutRounding + (_overflowScroll.x ? _scrollbarsHideOffset.x : 0), maxHeight: clientSizeWithoutRounding + (_overflowScroll.x ? _scrollbarsHideOffset.x : 0) - paddingAbsoluteVertical,
}); });
} }
}; };
@@ -92,7 +95,7 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle =
const { _nativeScrollbarSize, _nativeScrollbarIsOverlaid, _nativeScrollbarStyling } = getEnvironment(); const { _nativeScrollbarSize, _nativeScrollbarIsOverlaid, _nativeScrollbarStyling } = getEnvironment();
const { x: overlaidX, y: overlaidY } = _nativeScrollbarIsOverlaid; const { x: overlaidX, y: overlaidY } = _nativeScrollbarIsOverlaid;
const determineOverflow = !viewportStyleObj; const determineOverflow = !viewportStyleObj;
const arrangeHideOffset = _content && !_nativeScrollbarStyling && !showNativeOverlaidScrollbars ? overlaidScrollbarsHideOffset : 0; const arrangeHideOffset = !_nativeScrollbarStyling && !showNativeOverlaidScrollbars ? overlaidScrollbarsHideOffset : 0;
const styleObj = determineOverflow ? style(_viewport, ['overflowX', 'overflowY']) : viewportStyleObj; const styleObj = determineOverflow ? style(_viewport, ['overflowX', 'overflowY']) : viewportStyleObj;
const scroll = { const scroll = {
x: styleObj!.overflowX === 'scroll', x: styleObj!.overflowX === 'scroll',
@@ -150,23 +153,42 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle =
directionIsRTL: boolean, directionIsRTL: boolean,
contentStyleObj?: StyleObject contentStyleObj?: StyleObject
) => { ) => {
const { _scrollbarsHideOffset } = viewportOverflowState; const { _nativeScrollbarStyling, _nativeScrollbarIsOverlaid } = getEnvironment();
const { x: hideOffsetX, y: hideOffsetY } = _scrollbarsHideOffset; if ((_nativeScrollbarIsOverlaid.x || _nativeScrollbarIsOverlaid.y) && !_nativeScrollbarStyling) {
const horizontalBorderKey = directionIsRTL ? 'borderLeft' : 'borderRight'; 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;
if (_contentArrange && contentStyleObj) { style(_viewport, {
// horizontal [horizontalPaddingKey]: horizontalPaddingValue + hideOffsetY,
contentStyleObj[horizontalBorderKey] = hideOffsetY ? overlaidScrollbarsHideBorderStyle : ''; paddingBottom: verticalPaddingValue + hideOffsetX,
});
// vertical // adjust content arrange / before element
contentStyleObj.borderBottom = hideOffsetX ? overlaidScrollbarsHideBorderStyle : ''; if (_contentArrange) {
const { sheet } = _contentArrange;
if (sheet) {
const { cssRules } = sheet;
if (cssRules) {
if (!cssRules.length) {
sheet.insertRule(`#${attr(_contentArrange, '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);
}
}
} else {
}
} }
// adjust content arrange (content arrange doesn't exist if its not needed)
style(_contentArrange, {
width: hideOffsetY ? hideOffsetY + contentScrollSize.w : '',
height: hideOffsetX ? hideOffsetX + contentScrollSize.h : '',
});
}; };
const hideNativeScrollbars = (viewportOverflowState: ViewportOverflowState, directionIsRTL: boolean, viewportStyleObj: StyleObject) => { const hideNativeScrollbars = (viewportOverflowState: ViewportOverflowState, directionIsRTL: boolean, viewportStyleObj: StyleObject) => {
@@ -220,37 +242,59 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle =
fixFlexboxGlue(preMeasureViewportOverflowState, !!heightIntrinsic); fixFlexboxGlue(preMeasureViewportOverflowState, !!heightIntrinsic);
} }
if (_sizeChanged || _contentMutation) { 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,
});
const viewportRect = getBoundingClientRect(_viewport); const viewportRect = getBoundingClientRect(_viewport);
const viewportOffsetSize = offsetSize(_viewport); const viewportOffsetSize = offsetSize(_viewport);
const contentClientSize = clientSize(_content || _viewport); // needs to be client Size because applied border for content arrange on content 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 viewportScrollSize = fixScrollSizeRounding(scrollSize(_viewport), viewportOffsetSize, viewportRect);
let viewportClientSize = clientSize(_viewport); 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,
})); }));
// 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 && _contentArrange; const reMeasureRequired = contentScrollSizeChanged && !showNativeOverlaidScrollbars;
if (reMeasureRequired) { if (true) {
setContentArrange( const viewportStyle: StyleObject = {
preMeasureViewportOverflowState || getViewportOverflowState(showNativeOverlaidScrollbars), overflowY: '',
contentScrollSize!, overflowX: '',
directionIsRTL! marginTop: '',
); 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); //const contentArrangeOffsetSize = clientSize(_contentArrange);
overflowAmuntCache = updateOverflowAmountCache(force, { overflowAmuntCache = updateOverflowAmountCache(force, {
_contentScrollSize: { _contentScrollSize: {
w: Math.max(contentScrollSize!.w, viewportScrollSize.w, contentArrangeOffsetSize.w), w: Math.max(contentScrollSize!.w, viewportScrollSize.w),
h: Math.max(contentScrollSize!.h, viewportScrollSize.h, contentArrangeOffsetSize.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, contentClientSize.w - contentScrollSize!.w),
@@ -76,7 +76,10 @@ export const createPaddingLifecycle = (lifecycleHub: LifecycleHub): Lifecycle =>
style(_padding || _viewport, paddingStyle); style(_padding || _viewport, paddingStyle);
style(_viewport, viewportStyle); style(_viewport, viewportStyle);
lifecycleHub._setPadding(padding); lifecycleHub._setPaddingInfo({
_absolute: !paddingRelative,
_padding: padding!,
});
lifecycleHub._setPaddingStyle(!_padding ? paddingStyle : null); lifecycleHub._setPaddingStyle(!_padding ? paddingStyle : null);
} }
@@ -3,12 +3,14 @@
@import './structurelifecycle.scss'; @import './structurelifecycle.scss';
.os-environment { .os-environment {
--css-custom-prop: -1;
position: fixed; position: fixed;
opacity: 0; opacity: 0;
visibility: hidden; visibility: hidden;
overflow: scroll; overflow: scroll;
height: 200px; height: 200px;
width: 200px; width: 200px;
z-index: var(--css-custom-prop);
div { div {
width: 200%; width: 200%;
@@ -68,7 +70,7 @@
} }
.os-environment, .os-environment,
.os-viewport { .os-viewport {
-ms-overflow-style: scrollbar !important; -ms-overflow-style: -ms-autohiding-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 {
@@ -85,10 +87,15 @@
background: transparent !important; background: transparent !important;
} }
.os-content-arrange { .os-viewport-arrange::before {
min-width: 1px; content: '';
min-height: 1px;
z-index: -1;
position: absolute; position: absolute;
pointer-events: none; pointer-events: none;
z-index: -1;
min-width: 1px;
min-height: 1px;
}
.os-host {
padding: 5px 50px 15px 20px;
} }
@@ -12,14 +12,15 @@ import {
removeClass, removeClass,
push, push,
runEach, runEach,
prependChildren, insertBefore,
attr,
} from 'support'; } from 'support';
import { import {
classNameHost, classNameHost,
classNamePadding, classNamePadding,
classNameViewport, classNameViewport,
classNameViewportArrange,
classNameContent, classNameContent,
classNameContentArrange,
classNameViewportScrollbarStyling, classNameViewportScrollbarStyling,
} from 'classnames'; } from 'classnames';
import { getEnvironment } from 'environment'; import { getEnvironment } from 'environment';
@@ -36,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: HTMLElement | null; _contentArrange: HTMLStyleElement | null;
} }
export interface StructureSetup { export interface StructureSetup {
@@ -50,6 +51,16 @@ const unwrap = (elm: HTMLElement | null | undefined) => {
removeElements(elm); removeElements(elm);
}; };
let contentArrangeCounter = 0;
const createUniqueContentArrangeElement = () => {
const elm = document.createElement('style');
attr(elm, 'id', `${classNameViewportArrange}-${contentArrangeCounter}`);
contentArrangeCounter++;
return elm;
};
export const createStructureSetup = (target: OSTarget | OSTargetObject): StructureSetup => { export const createStructureSetup = (target: OSTarget | OSTargetObject): StructureSetup => {
const targetIsElm = isHTMLElement(target); const targetIsElm = isHTMLElement(target);
const osTargetObj: InternalVersionOf<OSTargetObject> = targetIsElm const osTargetObj: InternalVersionOf<OSTargetObject> = targetIsElm
@@ -160,14 +171,14 @@ export const createStructureSetup = (target: OSTarget | OSTargetObject): Structu
_host, _host,
}; };
const { _nativeScrollbarStyling, _nativeScrollbarIsOverlaid } = 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 (_nativeScrollbarIsOverlaid.x || _nativeScrollbarIsOverlaid.y) {
if (obj._content) { if (true) {
const contentArrangeElm = createDiv(classNameContentArrange); const contentArrangeElm = createUniqueContentArrangeElement();
prependChildren(_viewport, contentArrangeElm); insertBefore(_viewport, contentArrangeElm);
push(destroyFns, removeElements.bind(0, contentArrangeElm)); push(destroyFns, removeElements.bind(0, contentArrangeElm));
obj._contentArrange = contentArrangeElm; obj._contentArrange = contentArrangeElm;
@@ -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 }); window.os = OverlayScrollbars({ target: targetElm, padding: null, content: null });
export const resize = (element: HTMLElement) => { export const resize = (element: HTMLElement) => {
const strMouseTouchDownEvent = 'mousedown touchstart'; const strMouseTouchDownEvent = 'mousedown touchstart';
@@ -57,6 +57,7 @@ body {
border: 1px solid black; border: 1px solid black;
padding: 10px; padding: 10px;
margin: 10px; margin: 10px;
display: none;
} }
#end::before { #end::before {