import React, { useEffect, useCallback } from 'react';
import PropTypes from 'prop-types';
import { debounce } from 'throttle-debounce';
import StickyEvents from 'sticky-events';
import loggerFactory from '~/utils/logger';
const logger = loggerFactory('growi:cli:StickyStretchableScroller');
/**
* USAGE:
*
const calcViewHeight = useCallback(() => {
const containerElem = document.querySelector('#sticky-elem');
const containerTop = containerElem.getBoundingClientRect().top;
// stretch to the bottom of window
return window.innerHeight - containerTop;
});
return (
...
);
or
return (
);
*/
const StickyStretchableScroller = (props) => {
let { scrollTargetSelector } = props;
const {
children, contentsElemSelector, stickyElemSelector,
calcViewHeightFunc, calcContentsHeightFunc,
resetKey,
} = props;
if (scrollTargetSelector == null && children == null) {
throw new Error('Either of scrollTargetSelector or children is required');
}
if (scrollTargetSelector == null) {
scrollTargetSelector = `#${children.props.id}`;
}
/**
* Reset scrollbar
*/
const resetScrollbar = useCallback(() => {
const contentsElem = document.querySelector(contentsElemSelector);
if (contentsElem == null) {
return;
}
const viewHeight = calcViewHeightFunc != null
? calcViewHeightFunc()
: 'auto';
const contentsHeight = calcContentsHeightFunc != null
? calcContentsHeightFunc(contentsElem)
: contentsElem.getBoundingClientRect().height;
logger.debug(`[${scrollTargetSelector}] viewHeight`, viewHeight);
logger.debug(`[${scrollTargetSelector}] contentsHeight`, contentsHeight);
const isScrollEnabled = viewHeight === 'auto' || (viewHeight < contentsHeight);
$(scrollTargetSelector).slimScroll({
color: '#666',
railColor: '#999',
railVisible: true,
position: 'right',
height: isScrollEnabled ? viewHeight : contentsHeight,
wheelStep: 10,
allowPageScroll: true,
});
// destroy
if (!isScrollEnabled) {
$(scrollTargetSelector).slimScroll({ destroy: true });
}
}, [contentsElemSelector, calcViewHeightFunc, calcContentsHeightFunc, scrollTargetSelector]);
const resetScrollbarDebounced = debounce(100, resetScrollbar);
const stickyChangeHandler = useCallback((event) => {
logger.debug('StickyEvents.CHANGE detected');
setTimeout(resetScrollbar, 100);
}, [resetScrollbar]);
// setup effect by sticky event
useEffect(() => {
if (stickyElemSelector == null) {
return;
}
// sticky
// See: https://github.com/ryanwalters/sticky-events
const stickyEvents = new StickyEvents({ stickySelector: stickyElemSelector });
const { stickySelector } = stickyEvents;
const elem = document.querySelector(stickySelector);
elem.addEventListener(StickyEvents.CHANGE, stickyChangeHandler);
// return clean up handler
return () => {
elem.removeEventListener(StickyEvents.CHANGE, stickyChangeHandler);
};
}, [stickyElemSelector, stickyChangeHandler]);
// setup effect by resizing event
useEffect(() => {
const resizeHandler = (event) => {
resetScrollbarDebounced();
};
window.addEventListener('resize', resizeHandler);
// return clean up handler
return () => {
window.removeEventListener('resize', resizeHandler);
};
}, [resetScrollbarDebounced]);
// setup effect on init
useEffect(() => {
if (resetKey != null) {
resetScrollbarDebounced();
}
}, [resetKey, resetScrollbarDebounced]);
return (
<>
{ children }
>
);
};
StickyStretchableScroller.propTypes = {
contentsElemSelector: PropTypes.string.isRequired,
children: PropTypes.node,
scrollTargetSelector: PropTypes.string,
stickyElemSelector: PropTypes.string,
resetKey: PropTypes.any,
calcViewHeightFunc: PropTypes.func,
calcContentsHeightFunc: PropTypes.func,
};
export default StickyStretchableScroller;