initial commit
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
// Helper function to retrieve options from element attributes
|
||||
export const getOptions = function(obj) {
|
||||
const options = Array.prototype.reduce.call(
|
||||
obj,
|
||||
(acc, attribute) => {
|
||||
const option = attribute.name.match(/data-simplebar-(.+)/);
|
||||
if (option) {
|
||||
const key = option[1].replace(/\W+(.)/g, (x, chr) => chr.toUpperCase());
|
||||
switch (attribute.value) {
|
||||
case 'true':
|
||||
acc[key] = true;
|
||||
break;
|
||||
case 'false':
|
||||
acc[key] = false;
|
||||
break;
|
||||
case undefined:
|
||||
acc[key] = true;
|
||||
break;
|
||||
default:
|
||||
acc[key] = attribute.value;
|
||||
}
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
return options;
|
||||
};
|
||||
|
||||
export function getElementWindow(element) {
|
||||
if (
|
||||
!element ||
|
||||
!element.ownerDocument ||
|
||||
!element.ownerDocument.defaultView
|
||||
) {
|
||||
return window;
|
||||
}
|
||||
return element.ownerDocument.defaultView;
|
||||
}
|
||||
|
||||
export function getElementDocument(element) {
|
||||
if (!element || !element.ownerDocument) {
|
||||
return document;
|
||||
}
|
||||
return element.ownerDocument;
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import canUseDOM from 'can-use-dom';
|
||||
|
||||
import SimpleBar from './simplebar';
|
||||
import { getOptions } from './helpers';
|
||||
|
||||
SimpleBar.initDOMLoadedElements = function() {
|
||||
document.removeEventListener('DOMContentLoaded', this.initDOMLoadedElements);
|
||||
window.removeEventListener('load', this.initDOMLoadedElements);
|
||||
|
||||
Array.prototype.forEach.call(
|
||||
document.querySelectorAll('[data-simplebar]'),
|
||||
el => {
|
||||
if (
|
||||
el.getAttribute('data-simplebar') !== 'init' &&
|
||||
!SimpleBar.instances.has(el)
|
||||
)
|
||||
new SimpleBar(el, getOptions(el.attributes));
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
SimpleBar.removeObserver = function() {
|
||||
this.globalObserver.disconnect();
|
||||
};
|
||||
|
||||
SimpleBar.initHtmlApi = function() {
|
||||
this.initDOMLoadedElements = this.initDOMLoadedElements.bind(this);
|
||||
|
||||
// MutationObserver is IE11+
|
||||
if (typeof MutationObserver !== 'undefined') {
|
||||
// Mutation observer to observe dynamically added elements
|
||||
this.globalObserver = new MutationObserver(SimpleBar.handleMutations);
|
||||
|
||||
this.globalObserver.observe(document, { childList: true, subtree: true });
|
||||
}
|
||||
|
||||
// Taken from jQuery `ready` function
|
||||
// Instantiate elements already present on the page
|
||||
if (
|
||||
document.readyState === 'complete' ||
|
||||
(document.readyState !== 'loading' && !document.documentElement.doScroll)
|
||||
) {
|
||||
// Handle it asynchronously to allow scripts the opportunity to delay init
|
||||
window.setTimeout(this.initDOMLoadedElements);
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', this.initDOMLoadedElements);
|
||||
window.addEventListener('load', this.initDOMLoadedElements);
|
||||
}
|
||||
};
|
||||
|
||||
SimpleBar.handleMutations = mutations => {
|
||||
mutations.forEach(mutation => {
|
||||
Array.prototype.forEach.call(mutation.addedNodes, addedNode => {
|
||||
if (addedNode.nodeType === 1) {
|
||||
if (addedNode.hasAttribute('data-simplebar')) {
|
||||
!SimpleBar.instances.has(addedNode) &&
|
||||
document.documentElement.contains(addedNode) &&
|
||||
new SimpleBar(addedNode, getOptions(addedNode.attributes));
|
||||
} else {
|
||||
Array.prototype.forEach.call(
|
||||
addedNode.querySelectorAll('[data-simplebar]'),
|
||||
function(el) {
|
||||
if (
|
||||
el.getAttribute('data-simplebar') !== 'init' &&
|
||||
!SimpleBar.instances.has(el) &&
|
||||
document.documentElement.contains(el)
|
||||
)
|
||||
new SimpleBar(el, getOptions(el.attributes));
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Array.prototype.forEach.call(mutation.removedNodes, removedNode => {
|
||||
if (removedNode.nodeType === 1) {
|
||||
if (removedNode.getAttribute('data-simplebar') === 'init') {
|
||||
SimpleBar.instances.has(removedNode) &&
|
||||
!document.documentElement.contains(removedNode) &&
|
||||
SimpleBar.instances.get(removedNode).unMount();
|
||||
} else {
|
||||
Array.prototype.forEach.call(
|
||||
removedNode.querySelectorAll('[data-simplebar="init"]'),
|
||||
el => {
|
||||
SimpleBar.instances.has(el) &&
|
||||
!document.documentElement.contains(el) &&
|
||||
SimpleBar.instances.get(el).unMount();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
SimpleBar.getOptions = getOptions;
|
||||
|
||||
/**
|
||||
* HTML API
|
||||
* Called only in a browser env.
|
||||
*/
|
||||
if (canUseDOM) {
|
||||
SimpleBar.initHtmlApi();
|
||||
}
|
||||
|
||||
export default SimpleBar;
|
||||
@@ -0,0 +1,40 @@
|
||||
import canUseDOM from 'can-use-dom';
|
||||
import { getElementDocument } from "./helpers";
|
||||
|
||||
let cachedScrollbarWidth = null;
|
||||
let cachedDevicePixelRatio = null;
|
||||
|
||||
if (canUseDOM) {
|
||||
window.addEventListener('resize', () => {
|
||||
if (cachedDevicePixelRatio !== window.devicePixelRatio) {
|
||||
cachedDevicePixelRatio = window.devicePixelRatio;
|
||||
cachedScrollbarWidth = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default function scrollbarWidth(el) {
|
||||
if (cachedScrollbarWidth === null) {
|
||||
|
||||
const document = getElementDocument(el);
|
||||
|
||||
if (typeof document === 'undefined') {
|
||||
cachedScrollbarWidth = 0;
|
||||
return cachedScrollbarWidth;
|
||||
}
|
||||
const body = document.body;
|
||||
const box = document.createElement('div');
|
||||
|
||||
box.classList.add('simplebar-hide-scrollbar');
|
||||
|
||||
body.appendChild(box);
|
||||
|
||||
const width = box.getBoundingClientRect().right;
|
||||
|
||||
body.removeChild(box);
|
||||
|
||||
cachedScrollbarWidth = width;
|
||||
}
|
||||
|
||||
return cachedScrollbarWidth;
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
[data-simplebar] {
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
align-content: flex-start;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.simplebar-wrapper {
|
||||
overflow: hidden;
|
||||
width: inherit;
|
||||
height: inherit;
|
||||
max-width: inherit;
|
||||
max-height: inherit;
|
||||
}
|
||||
|
||||
.simplebar-mask {
|
||||
direction: inherit;
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: auto !important;
|
||||
height: auto !important;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.simplebar-offset {
|
||||
direction: inherit !important;
|
||||
box-sizing: inherit !important;
|
||||
resize: none !important;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.simplebar-content-wrapper {
|
||||
direction: inherit;
|
||||
box-sizing: border-box !important;
|
||||
position: relative;
|
||||
display: block;
|
||||
height: 100%; /* Required for horizontal native scrollbar to not appear if parent is taller than natural height */
|
||||
width: auto;
|
||||
max-width: 100%; /* Not required for horizontal scroll to trigger */
|
||||
max-height: 100%; /* Needed for vertical scroll to trigger */
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
.simplebar-content-wrapper::-webkit-scrollbar,
|
||||
.simplebar-hide-scrollbar::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.simplebar-content:before,
|
||||
.simplebar-content:after {
|
||||
content: ' ';
|
||||
display: table;
|
||||
}
|
||||
|
||||
.simplebar-placeholder {
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.simplebar-height-auto-observer-wrapper {
|
||||
box-sizing: inherit !important;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
max-width: 1px;
|
||||
position: relative;
|
||||
float: left;
|
||||
max-height: 1px;
|
||||
overflow: hidden;
|
||||
z-index: -1;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
pointer-events: none;
|
||||
flex-grow: inherit;
|
||||
flex-shrink: 0;
|
||||
flex-basis: 0;
|
||||
}
|
||||
|
||||
.simplebar-height-auto-observer {
|
||||
box-sizing: inherit;
|
||||
display: block;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 1000%;
|
||||
width: 1000%;
|
||||
min-height: 1px;
|
||||
min-width: 1px;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.simplebar-track {
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
[data-simplebar].simplebar-dragging .simplebar-content {
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
[data-simplebar].simplebar-dragging .simplebar-track {
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.simplebar-scrollbar {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
min-height: 10px;
|
||||
}
|
||||
|
||||
.simplebar-scrollbar:before {
|
||||
position: absolute;
|
||||
content: '';
|
||||
background: black;
|
||||
border-radius: 7px;
|
||||
left: 2px;
|
||||
right: 2px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s linear;
|
||||
}
|
||||
|
||||
.simplebar-scrollbar.simplebar-visible:before {
|
||||
/* When hovered, remove all transitions from drag handle */
|
||||
opacity: 0.5;
|
||||
transition: opacity 0s linear;
|
||||
}
|
||||
|
||||
.simplebar-track.simplebar-vertical {
|
||||
top: 0;
|
||||
width: 11px;
|
||||
}
|
||||
|
||||
.simplebar-track.simplebar-vertical .simplebar-scrollbar:before {
|
||||
top: 2px;
|
||||
bottom: 2px;
|
||||
}
|
||||
|
||||
.simplebar-track.simplebar-horizontal {
|
||||
left: 0;
|
||||
height: 11px;
|
||||
}
|
||||
|
||||
.simplebar-track.simplebar-horizontal .simplebar-scrollbar:before {
|
||||
height: 100%;
|
||||
left: 2px;
|
||||
right: 2px;
|
||||
}
|
||||
|
||||
.simplebar-track.simplebar-horizontal .simplebar-scrollbar {
|
||||
right: auto;
|
||||
left: 0;
|
||||
top: 2px;
|
||||
height: 7px;
|
||||
min-height: 0;
|
||||
min-width: 10px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
/* Rtl support */
|
||||
[data-simplebar-direction='rtl'] .simplebar-track.simplebar-vertical {
|
||||
right: auto;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.hs-dummy-scrollbar-size {
|
||||
direction: rtl;
|
||||
position: fixed;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
height: 500px;
|
||||
width: 500px;
|
||||
overflow-y: hidden;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
|
||||
.simplebar-hide-scrollbar {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
visibility: hidden;
|
||||
overflow-y: scroll;
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
@@ -0,0 +1,949 @@
|
||||
import throttle from 'lodash.throttle';
|
||||
import debounce from 'lodash.debounce';
|
||||
import memoize from 'lodash.memoize';
|
||||
import { ResizeObserver } from '@juggle/resize-observer';
|
||||
import canUseDOM from 'can-use-dom';
|
||||
import scrollbarWidth from './scrollbar-width';
|
||||
import { getElementWindow, getElementDocument } from './helpers';
|
||||
|
||||
export default class SimpleBar {
|
||||
constructor(element, options) {
|
||||
this.el = element;
|
||||
this.minScrollbarWidth = 20;
|
||||
this.options = { ...SimpleBar.defaultOptions, ...options };
|
||||
this.classNames = {
|
||||
...SimpleBar.defaultOptions.classNames,
|
||||
...this.options.classNames
|
||||
};
|
||||
this.axis = {
|
||||
x: {
|
||||
scrollOffsetAttr: 'scrollLeft',
|
||||
sizeAttr: 'width',
|
||||
scrollSizeAttr: 'scrollWidth',
|
||||
offsetSizeAttr: 'offsetWidth',
|
||||
offsetAttr: 'left',
|
||||
overflowAttr: 'overflowX',
|
||||
dragOffset: 0,
|
||||
isOverflowing: true,
|
||||
isVisible: false,
|
||||
forceVisible: false,
|
||||
track: {},
|
||||
scrollbar: {}
|
||||
},
|
||||
y: {
|
||||
scrollOffsetAttr: 'scrollTop',
|
||||
sizeAttr: 'height',
|
||||
scrollSizeAttr: 'scrollHeight',
|
||||
offsetSizeAttr: 'offsetHeight',
|
||||
offsetAttr: 'top',
|
||||
overflowAttr: 'overflowY',
|
||||
dragOffset: 0,
|
||||
isOverflowing: true,
|
||||
isVisible: false,
|
||||
forceVisible: false,
|
||||
track: {},
|
||||
scrollbar: {}
|
||||
}
|
||||
};
|
||||
this.removePreventClickId = null;
|
||||
|
||||
// Don't re-instantiate over an existing one
|
||||
if (SimpleBar.instances.has(this.el)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.recalculate = throttle(this.recalculate.bind(this), 64);
|
||||
this.onMouseMove = throttle(this.onMouseMove.bind(this), 64);
|
||||
this.hideScrollbars = debounce(
|
||||
this.hideScrollbars.bind(this),
|
||||
this.options.timeout
|
||||
);
|
||||
this.onWindowResize = debounce(this.onWindowResize.bind(this), 64, {
|
||||
leading: true
|
||||
});
|
||||
|
||||
SimpleBar.getRtlHelpers = memoize(SimpleBar.getRtlHelpers);
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Static properties
|
||||
*/
|
||||
|
||||
/**
|
||||
* Helper to fix browsers inconsistency on RTL:
|
||||
* - Firefox inverts the scrollbar initial position
|
||||
* - IE11 inverts both scrollbar position and scrolling offset
|
||||
* Directly inspired by @KingSora's OverlayScrollbars https://github.com/KingSora/OverlayScrollbars/blob/master/js/OverlayScrollbars.js#L1634
|
||||
*/
|
||||
static getRtlHelpers() {
|
||||
const dummyDiv = document.createElement('div');
|
||||
dummyDiv.innerHTML =
|
||||
'<div class="hs-dummy-scrollbar-size"><div style="height: 200%; width: 200%; margin: 10px 0;"></div></div>';
|
||||
const scrollbarDummyEl = dummyDiv.firstElementChild;
|
||||
document.body.appendChild(scrollbarDummyEl);
|
||||
const dummyContainerChild = scrollbarDummyEl.firstElementChild;
|
||||
scrollbarDummyEl.scrollLeft = 0;
|
||||
const dummyContainerOffset = SimpleBar.getOffset(scrollbarDummyEl);
|
||||
const dummyContainerChildOffset = SimpleBar.getOffset(dummyContainerChild);
|
||||
scrollbarDummyEl.scrollLeft = 999;
|
||||
const dummyContainerScrollOffsetAfterScroll = SimpleBar.getOffset(
|
||||
dummyContainerChild
|
||||
);
|
||||
|
||||
return {
|
||||
// determines if the scrolling is responding with negative values
|
||||
isRtlScrollingInverted:
|
||||
dummyContainerOffset.left !== dummyContainerChildOffset.left &&
|
||||
dummyContainerChildOffset.left -
|
||||
dummyContainerScrollOffsetAfterScroll.left !==
|
||||
0,
|
||||
// determines if the origin scrollbar position is inverted or not (positioned on left or right)
|
||||
isRtlScrollbarInverted:
|
||||
dummyContainerOffset.left !== dummyContainerChildOffset.left
|
||||
};
|
||||
}
|
||||
|
||||
static defaultOptions = {
|
||||
autoHide: true,
|
||||
forceVisible: false,
|
||||
clickOnTrack: true,
|
||||
clickOnTrackSpeed: 40,
|
||||
classNames: {
|
||||
contentEl: 'simplebar-content',
|
||||
contentWrapper: 'simplebar-content-wrapper',
|
||||
offset: 'simplebar-offset',
|
||||
mask: 'simplebar-mask',
|
||||
wrapper: 'simplebar-wrapper',
|
||||
placeholder: 'simplebar-placeholder',
|
||||
scrollbar: 'simplebar-scrollbar',
|
||||
track: 'simplebar-track',
|
||||
heightAutoObserverWrapperEl: 'simplebar-height-auto-observer-wrapper',
|
||||
heightAutoObserverEl: 'simplebar-height-auto-observer',
|
||||
visible: 'simplebar-visible',
|
||||
horizontal: 'simplebar-horizontal',
|
||||
vertical: 'simplebar-vertical',
|
||||
hover: 'simplebar-hover',
|
||||
dragging: 'simplebar-dragging'
|
||||
},
|
||||
scrollbarMinSize: 25,
|
||||
scrollbarMaxSize: 0,
|
||||
timeout: 1000
|
||||
};
|
||||
|
||||
static getOffset(el) {
|
||||
const rect = el.getBoundingClientRect();
|
||||
const elDocument = getElementDocument(el);
|
||||
const elWindow = getElementWindow(el);
|
||||
|
||||
return {
|
||||
top:
|
||||
rect.top +
|
||||
(elWindow.pageYOffset || elDocument.documentElement.scrollTop),
|
||||
left:
|
||||
rect.left +
|
||||
(elWindow.pageXOffset || elDocument.documentElement.scrollLeft)
|
||||
};
|
||||
}
|
||||
|
||||
static instances = new WeakMap();
|
||||
|
||||
init() {
|
||||
// Save a reference to the instance, so we know this DOM node has already been instancied
|
||||
SimpleBar.instances.set(this.el, this);
|
||||
|
||||
// We stop here on server-side
|
||||
if (canUseDOM) {
|
||||
this.initDOM();
|
||||
|
||||
this.setAccessibilityAttributes();
|
||||
|
||||
this.scrollbarWidth = this.getScrollbarWidth();
|
||||
|
||||
this.recalculate();
|
||||
|
||||
this.initListeners();
|
||||
}
|
||||
}
|
||||
|
||||
initDOM() {
|
||||
// make sure this element doesn't have the elements yet
|
||||
if (
|
||||
Array.prototype.filter.call(this.el.children, child =>
|
||||
child.classList.contains(this.classNames.wrapper)
|
||||
).length
|
||||
) {
|
||||
// assume that element has his DOM already initiated
|
||||
this.wrapperEl = this.el.querySelector(`.${this.classNames.wrapper}`);
|
||||
this.contentWrapperEl =
|
||||
this.options.scrollableNode ||
|
||||
this.el.querySelector(`.${this.classNames.contentWrapper}`);
|
||||
this.contentEl =
|
||||
this.options.contentNode ||
|
||||
this.el.querySelector(`.${this.classNames.contentEl}`);
|
||||
|
||||
this.offsetEl = this.el.querySelector(`.${this.classNames.offset}`);
|
||||
this.maskEl = this.el.querySelector(`.${this.classNames.mask}`);
|
||||
|
||||
this.placeholderEl = this.findChild(
|
||||
this.wrapperEl,
|
||||
`.${this.classNames.placeholder}`
|
||||
);
|
||||
this.heightAutoObserverWrapperEl = this.el.querySelector(
|
||||
`.${this.classNames.heightAutoObserverWrapperEl}`
|
||||
);
|
||||
this.heightAutoObserverEl = this.el.querySelector(
|
||||
`.${this.classNames.heightAutoObserverEl}`
|
||||
);
|
||||
this.axis.x.track.el = this.findChild(
|
||||
this.el,
|
||||
`.${this.classNames.track}.${this.classNames.horizontal}`
|
||||
);
|
||||
this.axis.y.track.el = this.findChild(
|
||||
this.el,
|
||||
`.${this.classNames.track}.${this.classNames.vertical}`
|
||||
);
|
||||
} else {
|
||||
// Prepare DOM
|
||||
this.wrapperEl = document.createElement('div');
|
||||
this.contentWrapperEl = document.createElement('div');
|
||||
this.offsetEl = document.createElement('div');
|
||||
this.maskEl = document.createElement('div');
|
||||
this.contentEl = document.createElement('div');
|
||||
this.placeholderEl = document.createElement('div');
|
||||
this.heightAutoObserverWrapperEl = document.createElement('div');
|
||||
this.heightAutoObserverEl = document.createElement('div');
|
||||
|
||||
this.wrapperEl.classList.add(this.classNames.wrapper);
|
||||
this.contentWrapperEl.classList.add(this.classNames.contentWrapper);
|
||||
this.offsetEl.classList.add(this.classNames.offset);
|
||||
this.maskEl.classList.add(this.classNames.mask);
|
||||
this.contentEl.classList.add(this.classNames.contentEl);
|
||||
this.placeholderEl.classList.add(this.classNames.placeholder);
|
||||
this.heightAutoObserverWrapperEl.classList.add(
|
||||
this.classNames.heightAutoObserverWrapperEl
|
||||
);
|
||||
this.heightAutoObserverEl.classList.add(
|
||||
this.classNames.heightAutoObserverEl
|
||||
);
|
||||
|
||||
while (this.el.firstChild) {
|
||||
this.contentEl.appendChild(this.el.firstChild);
|
||||
}
|
||||
|
||||
this.contentWrapperEl.appendChild(this.contentEl);
|
||||
this.offsetEl.appendChild(this.contentWrapperEl);
|
||||
this.maskEl.appendChild(this.offsetEl);
|
||||
this.heightAutoObserverWrapperEl.appendChild(this.heightAutoObserverEl);
|
||||
this.wrapperEl.appendChild(this.heightAutoObserverWrapperEl);
|
||||
this.wrapperEl.appendChild(this.maskEl);
|
||||
this.wrapperEl.appendChild(this.placeholderEl);
|
||||
this.el.appendChild(this.wrapperEl);
|
||||
}
|
||||
|
||||
if (!this.axis.x.track.el || !this.axis.y.track.el) {
|
||||
const track = document.createElement('div');
|
||||
const scrollbar = document.createElement('div');
|
||||
|
||||
track.classList.add(this.classNames.track);
|
||||
scrollbar.classList.add(this.classNames.scrollbar);
|
||||
|
||||
track.appendChild(scrollbar);
|
||||
|
||||
this.axis.x.track.el = track.cloneNode(true);
|
||||
this.axis.x.track.el.classList.add(this.classNames.horizontal);
|
||||
|
||||
this.axis.y.track.el = track.cloneNode(true);
|
||||
this.axis.y.track.el.classList.add(this.classNames.vertical);
|
||||
|
||||
this.el.appendChild(this.axis.x.track.el);
|
||||
this.el.appendChild(this.axis.y.track.el);
|
||||
}
|
||||
|
||||
this.axis.x.scrollbar.el = this.axis.x.track.el.querySelector(
|
||||
`.${this.classNames.scrollbar}`
|
||||
);
|
||||
this.axis.y.scrollbar.el = this.axis.y.track.el.querySelector(
|
||||
`.${this.classNames.scrollbar}`
|
||||
);
|
||||
|
||||
if (!this.options.autoHide) {
|
||||
this.axis.x.scrollbar.el.classList.add(this.classNames.visible);
|
||||
this.axis.y.scrollbar.el.classList.add(this.classNames.visible);
|
||||
}
|
||||
|
||||
this.el.setAttribute('data-simplebar', 'init');
|
||||
}
|
||||
|
||||
setAccessibilityAttributes() {
|
||||
const ariaLabel = this.options.ariaLabel || 'scrollable content';
|
||||
|
||||
this.contentWrapperEl.setAttribute('tabindex', '0');
|
||||
this.contentWrapperEl.setAttribute('role', 'region');
|
||||
this.contentWrapperEl.setAttribute('aria-label', ariaLabel);
|
||||
}
|
||||
|
||||
initListeners() {
|
||||
const elWindow = getElementWindow(this.el);
|
||||
// Event listeners
|
||||
if (this.options.autoHide) {
|
||||
this.el.addEventListener('mouseenter', this.onMouseEnter);
|
||||
}
|
||||
|
||||
['mousedown', 'click', 'dblclick'].forEach(e => {
|
||||
this.el.addEventListener(e, this.onPointerEvent, true);
|
||||
});
|
||||
|
||||
['touchstart', 'touchend', 'touchmove'].forEach(e => {
|
||||
this.el.addEventListener(e, this.onPointerEvent, {
|
||||
capture: true,
|
||||
passive: true
|
||||
});
|
||||
});
|
||||
|
||||
this.el.addEventListener('mousemove', this.onMouseMove);
|
||||
this.el.addEventListener('mouseleave', this.onMouseLeave);
|
||||
|
||||
this.contentWrapperEl.addEventListener('scroll', this.onScroll);
|
||||
|
||||
// Browser zoom triggers a window resize
|
||||
elWindow.addEventListener('resize', this.onWindowResize);
|
||||
|
||||
// Hack for https://github.com/WICG/ResizeObserver/issues/38
|
||||
let resizeObserverStarted = false;
|
||||
const resizeObserver = elWindow.ResizeObserver || ResizeObserver;
|
||||
this.resizeObserver = new resizeObserver(() => {
|
||||
if (!resizeObserverStarted) return;
|
||||
this.recalculate();
|
||||
});
|
||||
|
||||
this.resizeObserver.observe(this.el);
|
||||
this.resizeObserver.observe(this.contentEl);
|
||||
|
||||
elWindow.requestAnimationFrame(() => {
|
||||
resizeObserverStarted = true;
|
||||
});
|
||||
|
||||
// This is required to detect horizontal scroll. Vertical scroll only needs the resizeObserver.
|
||||
this.mutationObserver = new elWindow.MutationObserver(this.recalculate);
|
||||
|
||||
this.mutationObserver.observe(this.contentEl, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
characterData: true
|
||||
});
|
||||
}
|
||||
|
||||
recalculate() {
|
||||
const elWindow = getElementWindow(this.el);
|
||||
this.elStyles = elWindow.getComputedStyle(this.el);
|
||||
this.isRtl = this.elStyles.direction === 'rtl';
|
||||
|
||||
const isHeightAuto = this.heightAutoObserverEl.offsetHeight <= 1;
|
||||
const isWidthAuto = this.heightAutoObserverEl.offsetWidth <= 1;
|
||||
const contentElOffsetWidth = this.contentEl.offsetWidth;
|
||||
|
||||
const contentWrapperElOffsetWidth = this.contentWrapperEl.offsetWidth;
|
||||
|
||||
const elOverflowX = this.elStyles.overflowX;
|
||||
const elOverflowY = this.elStyles.overflowY;
|
||||
|
||||
this.contentEl.style.padding = `${this.elStyles.paddingTop} ${this.elStyles.paddingRight} ${this.elStyles.paddingBottom} ${this.elStyles.paddingLeft}`;
|
||||
this.wrapperEl.style.margin = `-${this.elStyles.paddingTop} -${this.elStyles.paddingRight} -${this.elStyles.paddingBottom} -${this.elStyles.paddingLeft}`;
|
||||
|
||||
const contentElScrollHeight = this.contentEl.scrollHeight;
|
||||
const contentElScrollWidth = this.contentEl.scrollWidth;
|
||||
|
||||
this.contentWrapperEl.style.height = isHeightAuto ? 'auto' : '100%';
|
||||
|
||||
// Determine placeholder size
|
||||
this.placeholderEl.style.width = isWidthAuto
|
||||
? `${contentElOffsetWidth}px`
|
||||
: 'auto';
|
||||
this.placeholderEl.style.height = `${contentElScrollHeight}px`;
|
||||
|
||||
const contentWrapperElOffsetHeight = this.contentWrapperEl.offsetHeight;
|
||||
|
||||
this.axis.x.isOverflowing = contentElScrollWidth > contentElOffsetWidth;
|
||||
this.axis.y.isOverflowing =
|
||||
contentElScrollHeight > contentWrapperElOffsetHeight;
|
||||
|
||||
// Set isOverflowing to false if user explicitely set hidden overflow
|
||||
this.axis.x.isOverflowing =
|
||||
elOverflowX === 'hidden' ? false : this.axis.x.isOverflowing;
|
||||
this.axis.y.isOverflowing =
|
||||
elOverflowY === 'hidden' ? false : this.axis.y.isOverflowing;
|
||||
|
||||
this.axis.x.forceVisible =
|
||||
this.options.forceVisible === 'x' || this.options.forceVisible === true;
|
||||
this.axis.y.forceVisible =
|
||||
this.options.forceVisible === 'y' || this.options.forceVisible === true;
|
||||
|
||||
this.hideNativeScrollbar();
|
||||
|
||||
// Set isOverflowing to false if scrollbar is not necessary (content is shorter than offset)
|
||||
let offsetForXScrollbar = this.axis.x.isOverflowing
|
||||
? this.scrollbarWidth
|
||||
: 0;
|
||||
let offsetForYScrollbar = this.axis.y.isOverflowing
|
||||
? this.scrollbarWidth
|
||||
: 0;
|
||||
|
||||
this.axis.x.isOverflowing =
|
||||
this.axis.x.isOverflowing &&
|
||||
contentElScrollWidth > contentWrapperElOffsetWidth - offsetForYScrollbar;
|
||||
this.axis.y.isOverflowing =
|
||||
this.axis.y.isOverflowing &&
|
||||
contentElScrollHeight >
|
||||
contentWrapperElOffsetHeight - offsetForXScrollbar;
|
||||
|
||||
this.axis.x.scrollbar.size = this.getScrollbarSize('x');
|
||||
this.axis.y.scrollbar.size = this.getScrollbarSize('y');
|
||||
|
||||
this.axis.x.scrollbar.el.style.width = `${this.axis.x.scrollbar.size}px`;
|
||||
this.axis.y.scrollbar.el.style.height = `${this.axis.y.scrollbar.size}px`;
|
||||
|
||||
this.positionScrollbar('x');
|
||||
this.positionScrollbar('y');
|
||||
|
||||
this.toggleTrackVisibility('x');
|
||||
this.toggleTrackVisibility('y');
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate scrollbar size
|
||||
*/
|
||||
getScrollbarSize(axis = 'y') {
|
||||
if (!this.axis[axis].isOverflowing) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const contentSize = this.contentEl[this.axis[axis].scrollSizeAttr];
|
||||
const trackSize = this.axis[axis].track.el[this.axis[axis].offsetSizeAttr];
|
||||
let scrollbarSize;
|
||||
|
||||
let scrollbarRatio = trackSize / contentSize;
|
||||
|
||||
// Calculate new height/position of drag handle.
|
||||
scrollbarSize = Math.max(
|
||||
~~(scrollbarRatio * trackSize),
|
||||
this.options.scrollbarMinSize
|
||||
);
|
||||
|
||||
if (this.options.scrollbarMaxSize) {
|
||||
scrollbarSize = Math.min(scrollbarSize, this.options.scrollbarMaxSize);
|
||||
}
|
||||
|
||||
return scrollbarSize;
|
||||
}
|
||||
|
||||
positionScrollbar(axis = 'y') {
|
||||
if (!this.axis[axis].isOverflowing) {
|
||||
return;
|
||||
}
|
||||
|
||||
const contentSize = this.contentWrapperEl[this.axis[axis].scrollSizeAttr];
|
||||
const trackSize = this.axis[axis].track.el[this.axis[axis].offsetSizeAttr];
|
||||
const hostSize = parseInt(this.elStyles[this.axis[axis].sizeAttr], 10);
|
||||
const scrollbar = this.axis[axis].scrollbar;
|
||||
|
||||
let scrollOffset = this.contentWrapperEl[this.axis[axis].scrollOffsetAttr];
|
||||
scrollOffset =
|
||||
axis === 'x' &&
|
||||
this.isRtl &&
|
||||
SimpleBar.getRtlHelpers().isRtlScrollingInverted
|
||||
? -scrollOffset
|
||||
: scrollOffset;
|
||||
let scrollPourcent = scrollOffset / (contentSize - hostSize);
|
||||
|
||||
let handleOffset = ~~((trackSize - scrollbar.size) * scrollPourcent);
|
||||
handleOffset =
|
||||
axis === 'x' &&
|
||||
this.isRtl &&
|
||||
SimpleBar.getRtlHelpers().isRtlScrollbarInverted
|
||||
? handleOffset + (trackSize - scrollbar.size)
|
||||
: handleOffset;
|
||||
|
||||
scrollbar.el.style.transform =
|
||||
axis === 'x'
|
||||
? `translate3d(${handleOffset}px, 0, 0)`
|
||||
: `translate3d(0, ${handleOffset}px, 0)`;
|
||||
}
|
||||
|
||||
toggleTrackVisibility(axis = 'y') {
|
||||
const track = this.axis[axis].track.el;
|
||||
const scrollbar = this.axis[axis].scrollbar.el;
|
||||
|
||||
if (this.axis[axis].isOverflowing || this.axis[axis].forceVisible) {
|
||||
track.style.visibility = 'visible';
|
||||
this.contentWrapperEl.style[this.axis[axis].overflowAttr] = 'scroll';
|
||||
} else {
|
||||
track.style.visibility = 'hidden';
|
||||
this.contentWrapperEl.style[this.axis[axis].overflowAttr] = 'hidden';
|
||||
}
|
||||
|
||||
// Even if forceVisible is enabled, scrollbar itself should be hidden
|
||||
if (this.axis[axis].isOverflowing) {
|
||||
scrollbar.style.display = 'block';
|
||||
} else {
|
||||
scrollbar.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
hideNativeScrollbar() {
|
||||
this.offsetEl.style[this.isRtl ? 'left' : 'right'] =
|
||||
this.axis.y.isOverflowing || this.axis.y.forceVisible
|
||||
? `-${this.scrollbarWidth}px`
|
||||
: 0;
|
||||
this.offsetEl.style.bottom =
|
||||
this.axis.x.isOverflowing || this.axis.x.forceVisible
|
||||
? `-${this.scrollbarWidth}px`
|
||||
: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* On scroll event handling
|
||||
*/
|
||||
onScroll = () => {
|
||||
const elWindow = getElementWindow(this.el);
|
||||
if (!this.scrollXTicking) {
|
||||
elWindow.requestAnimationFrame(this.scrollX);
|
||||
this.scrollXTicking = true;
|
||||
}
|
||||
|
||||
if (!this.scrollYTicking) {
|
||||
elWindow.requestAnimationFrame(this.scrollY);
|
||||
this.scrollYTicking = true;
|
||||
}
|
||||
};
|
||||
|
||||
scrollX = () => {
|
||||
if (this.axis.x.isOverflowing) {
|
||||
this.showScrollbar('x');
|
||||
this.positionScrollbar('x');
|
||||
}
|
||||
|
||||
this.scrollXTicking = false;
|
||||
};
|
||||
|
||||
scrollY = () => {
|
||||
if (this.axis.y.isOverflowing) {
|
||||
this.showScrollbar('y');
|
||||
this.positionScrollbar('y');
|
||||
}
|
||||
|
||||
this.scrollYTicking = false;
|
||||
};
|
||||
|
||||
onMouseEnter = () => {
|
||||
this.showScrollbar('x');
|
||||
this.showScrollbar('y');
|
||||
};
|
||||
|
||||
onMouseMove = e => {
|
||||
this.mouseX = e.clientX;
|
||||
this.mouseY = e.clientY;
|
||||
|
||||
if (this.axis.x.isOverflowing || this.axis.x.forceVisible) {
|
||||
this.onMouseMoveForAxis('x');
|
||||
}
|
||||
|
||||
if (this.axis.y.isOverflowing || this.axis.y.forceVisible) {
|
||||
this.onMouseMoveForAxis('y');
|
||||
}
|
||||
};
|
||||
|
||||
onMouseMoveForAxis(axis = 'y') {
|
||||
this.axis[axis].track.rect = this.axis[
|
||||
axis
|
||||
].track.el.getBoundingClientRect();
|
||||
this.axis[axis].scrollbar.rect = this.axis[
|
||||
axis
|
||||
].scrollbar.el.getBoundingClientRect();
|
||||
|
||||
const isWithinScrollbarBoundsX = this.isWithinBounds(
|
||||
this.axis[axis].scrollbar.rect
|
||||
);
|
||||
|
||||
if (isWithinScrollbarBoundsX) {
|
||||
this.axis[axis].scrollbar.el.classList.add(this.classNames.hover);
|
||||
} else {
|
||||
this.axis[axis].scrollbar.el.classList.remove(this.classNames.hover);
|
||||
}
|
||||
|
||||
if (this.isWithinBounds(this.axis[axis].track.rect)) {
|
||||
this.showScrollbar(axis);
|
||||
this.axis[axis].track.el.classList.add(this.classNames.hover);
|
||||
} else {
|
||||
this.axis[axis].track.el.classList.remove(this.classNames.hover);
|
||||
}
|
||||
}
|
||||
|
||||
onMouseLeave = () => {
|
||||
this.onMouseMove.cancel();
|
||||
|
||||
if (this.axis.x.isOverflowing || this.axis.x.forceVisible) {
|
||||
this.onMouseLeaveForAxis('x');
|
||||
}
|
||||
|
||||
if (this.axis.y.isOverflowing || this.axis.y.forceVisible) {
|
||||
this.onMouseLeaveForAxis('y');
|
||||
}
|
||||
|
||||
this.mouseX = -1;
|
||||
this.mouseY = -1;
|
||||
};
|
||||
|
||||
onMouseLeaveForAxis(axis = 'y') {
|
||||
this.axis[axis].track.el.classList.remove(this.classNames.hover);
|
||||
this.axis[axis].scrollbar.el.classList.remove(this.classNames.hover);
|
||||
}
|
||||
|
||||
onWindowResize = () => {
|
||||
// Recalculate scrollbarWidth in case it's a zoom
|
||||
this.scrollbarWidth = this.getScrollbarWidth();
|
||||
|
||||
this.hideNativeScrollbar();
|
||||
};
|
||||
|
||||
/**
|
||||
* Show scrollbar
|
||||
*/
|
||||
showScrollbar(axis = 'y') {
|
||||
let scrollbar = this.axis[axis].scrollbar.el;
|
||||
|
||||
if (!this.axis[axis].isVisible) {
|
||||
scrollbar.classList.add(this.classNames.visible);
|
||||
this.axis[axis].isVisible = true;
|
||||
}
|
||||
|
||||
if (this.options.autoHide) {
|
||||
this.hideScrollbars();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide Scrollbar
|
||||
*/
|
||||
hideScrollbars = () => {
|
||||
this.axis.x.track.rect = this.axis.x.track.el.getBoundingClientRect();
|
||||
this.axis.y.track.rect = this.axis.y.track.el.getBoundingClientRect();
|
||||
|
||||
if (!this.isWithinBounds(this.axis.y.track.rect)) {
|
||||
this.axis.y.scrollbar.el.classList.remove(this.classNames.visible);
|
||||
this.axis.y.isVisible = false;
|
||||
}
|
||||
|
||||
if (!this.isWithinBounds(this.axis.x.track.rect)) {
|
||||
this.axis.x.scrollbar.el.classList.remove(this.classNames.visible);
|
||||
this.axis.x.isVisible = false;
|
||||
}
|
||||
};
|
||||
|
||||
onPointerEvent = e => {
|
||||
let isWithinTrackXBounds, isWithinTrackYBounds;
|
||||
|
||||
this.axis.x.track.rect = this.axis.x.track.el.getBoundingClientRect();
|
||||
this.axis.y.track.rect = this.axis.y.track.el.getBoundingClientRect();
|
||||
|
||||
if (this.axis.x.isOverflowing || this.axis.x.forceVisible) {
|
||||
isWithinTrackXBounds = this.isWithinBounds(this.axis.x.track.rect);
|
||||
}
|
||||
|
||||
if (this.axis.y.isOverflowing || this.axis.y.forceVisible) {
|
||||
isWithinTrackYBounds = this.isWithinBounds(this.axis.y.track.rect);
|
||||
}
|
||||
|
||||
// If any pointer event is called on the scrollbar
|
||||
if (isWithinTrackXBounds || isWithinTrackYBounds) {
|
||||
// Preventing the event's default action stops text being
|
||||
// selectable during the drag.
|
||||
e.preventDefault();
|
||||
// Prevent event leaking
|
||||
e.stopPropagation();
|
||||
|
||||
if (e.type === 'mousedown') {
|
||||
if (isWithinTrackXBounds) {
|
||||
this.axis.x.scrollbar.rect = this.axis.x.scrollbar.el.getBoundingClientRect();
|
||||
|
||||
if (this.isWithinBounds(this.axis.x.scrollbar.rect)) {
|
||||
this.onDragStart(e, 'x');
|
||||
} else {
|
||||
this.onTrackClick(e, 'x');
|
||||
}
|
||||
}
|
||||
|
||||
if (isWithinTrackYBounds) {
|
||||
this.axis.y.scrollbar.rect = this.axis.y.scrollbar.el.getBoundingClientRect();
|
||||
|
||||
if (this.isWithinBounds(this.axis.y.scrollbar.rect)) {
|
||||
this.onDragStart(e, 'y');
|
||||
} else {
|
||||
this.onTrackClick(e, 'y');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* on scrollbar handle drag movement starts
|
||||
*/
|
||||
onDragStart(e, axis = 'y') {
|
||||
const elDocument = getElementDocument(this.el);
|
||||
const elWindow = getElementWindow(this.el);
|
||||
const scrollbar = this.axis[axis].scrollbar;
|
||||
|
||||
// Measure how far the user's mouse is from the top of the scrollbar drag handle.
|
||||
const eventOffset = axis === 'y' ? e.pageY : e.pageX;
|
||||
this.axis[axis].dragOffset =
|
||||
eventOffset - scrollbar.rect[this.axis[axis].offsetAttr];
|
||||
this.draggedAxis = axis;
|
||||
|
||||
this.el.classList.add(this.classNames.dragging);
|
||||
|
||||
elDocument.addEventListener('mousemove', this.drag, true);
|
||||
elDocument.addEventListener('mouseup', this.onEndDrag, true);
|
||||
if (this.removePreventClickId === null) {
|
||||
elDocument.addEventListener('click', this.preventClick, true);
|
||||
elDocument.addEventListener('dblclick', this.preventClick, true);
|
||||
} else {
|
||||
elWindow.clearTimeout(this.removePreventClickId);
|
||||
this.removePreventClickId = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Drag scrollbar handle
|
||||
*/
|
||||
drag = e => {
|
||||
let eventOffset;
|
||||
const track = this.axis[this.draggedAxis].track;
|
||||
const trackSize = track.rect[this.axis[this.draggedAxis].sizeAttr];
|
||||
const scrollbar = this.axis[this.draggedAxis].scrollbar;
|
||||
const contentSize = this.contentWrapperEl[
|
||||
this.axis[this.draggedAxis].scrollSizeAttr
|
||||
];
|
||||
const hostSize = parseInt(
|
||||
this.elStyles[this.axis[this.draggedAxis].sizeAttr],
|
||||
10
|
||||
);
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (this.draggedAxis === 'y') {
|
||||
eventOffset = e.pageY;
|
||||
} else {
|
||||
eventOffset = e.pageX;
|
||||
}
|
||||
|
||||
// Calculate how far the user's mouse is from the top/left of the scrollbar (minus the dragOffset).
|
||||
let dragPos =
|
||||
eventOffset -
|
||||
track.rect[this.axis[this.draggedAxis].offsetAttr] -
|
||||
this.axis[this.draggedAxis].dragOffset;
|
||||
// Convert the mouse position into a percentage of the scrollbar height/width.
|
||||
let dragPerc = dragPos / (trackSize - scrollbar.size);
|
||||
|
||||
// Scroll the content by the same percentage.
|
||||
let scrollPos = dragPerc * (contentSize - hostSize);
|
||||
|
||||
// Fix browsers inconsistency on RTL
|
||||
if (this.draggedAxis === 'x') {
|
||||
scrollPos =
|
||||
this.isRtl && SimpleBar.getRtlHelpers().isRtlScrollbarInverted
|
||||
? scrollPos - (trackSize + scrollbar.size)
|
||||
: scrollPos;
|
||||
scrollPos =
|
||||
this.isRtl && SimpleBar.getRtlHelpers().isRtlScrollingInverted
|
||||
? -scrollPos
|
||||
: scrollPos;
|
||||
}
|
||||
|
||||
this.contentWrapperEl[
|
||||
this.axis[this.draggedAxis].scrollOffsetAttr
|
||||
] = scrollPos;
|
||||
};
|
||||
|
||||
/**
|
||||
* End scroll handle drag
|
||||
*/
|
||||
onEndDrag = e => {
|
||||
const elDocument = getElementDocument(this.el);
|
||||
const elWindow = getElementWindow(this.el);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
this.el.classList.remove(this.classNames.dragging);
|
||||
|
||||
elDocument.removeEventListener('mousemove', this.drag, true);
|
||||
elDocument.removeEventListener('mouseup', this.onEndDrag, true);
|
||||
this.removePreventClickId = elWindow.setTimeout(() => {
|
||||
// Remove these asynchronously so we still suppress click events
|
||||
// generated simultaneously with mouseup.
|
||||
elDocument.removeEventListener('click', this.preventClick, true);
|
||||
elDocument.removeEventListener('dblclick', this.preventClick, true);
|
||||
this.removePreventClickId = null;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Handler to ignore click events during drag
|
||||
*/
|
||||
preventClick = e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
onTrackClick(e, axis = 'y') {
|
||||
if (!this.options.clickOnTrack) return;
|
||||
|
||||
const elWindow = getElementWindow(this.el);
|
||||
this.axis[axis].scrollbar.rect = this.axis[
|
||||
axis
|
||||
].scrollbar.el.getBoundingClientRect();
|
||||
const scrollbar = this.axis[axis].scrollbar;
|
||||
const scrollbarOffset = scrollbar.rect[this.axis[axis].offsetAttr];
|
||||
const hostSize = parseInt(this.elStyles[this.axis[axis].sizeAttr], 10);
|
||||
let scrolled = this.contentWrapperEl[this.axis[axis].scrollOffsetAttr];
|
||||
const t =
|
||||
axis === 'y'
|
||||
? this.mouseY - scrollbarOffset
|
||||
: this.mouseX - scrollbarOffset;
|
||||
const dir = t < 0 ? -1 : 1;
|
||||
const scrollSize = dir === -1 ? scrolled - hostSize : scrolled + hostSize;
|
||||
|
||||
const scrollTo = () => {
|
||||
if (dir === -1) {
|
||||
if (scrolled > scrollSize) {
|
||||
scrolled -= this.options.clickOnTrackSpeed;
|
||||
this.contentWrapperEl.scrollTo({
|
||||
[this.axis[axis].offsetAttr]: scrolled
|
||||
});
|
||||
elWindow.requestAnimationFrame(scrollTo);
|
||||
}
|
||||
} else {
|
||||
if (scrolled < scrollSize) {
|
||||
scrolled += this.options.clickOnTrackSpeed;
|
||||
this.contentWrapperEl.scrollTo({
|
||||
[this.axis[axis].offsetAttr]: scrolled
|
||||
});
|
||||
elWindow.requestAnimationFrame(scrollTo);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
scrollTo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for content element
|
||||
*/
|
||||
getContentElement() {
|
||||
return this.contentEl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for original scrolling element
|
||||
*/
|
||||
getScrollElement() {
|
||||
return this.contentWrapperEl;
|
||||
}
|
||||
|
||||
getScrollbarWidth() {
|
||||
// Try/catch for FF 56 throwing on undefined computedStyles
|
||||
try {
|
||||
// Detect browsers supporting CSS scrollbar styling and do not calculate
|
||||
if (
|
||||
getComputedStyle(this.contentWrapperEl, '::-webkit-scrollbar')
|
||||
.display === 'none' ||
|
||||
'scrollbarWidth' in document.documentElement.style ||
|
||||
'-ms-overflow-style' in document.documentElement.style
|
||||
) {
|
||||
return 0;
|
||||
} else {
|
||||
return scrollbarWidth(this.el);
|
||||
}
|
||||
} catch (e) {
|
||||
return scrollbarWidth(this.el);
|
||||
}
|
||||
}
|
||||
|
||||
removeListeners() {
|
||||
const elWindow = getElementWindow(this.el);
|
||||
// Event listeners
|
||||
if (this.options.autoHide) {
|
||||
this.el.removeEventListener('mouseenter', this.onMouseEnter);
|
||||
}
|
||||
|
||||
['mousedown', 'click', 'dblclick'].forEach(e => {
|
||||
this.el.removeEventListener(e, this.onPointerEvent, true);
|
||||
});
|
||||
|
||||
['touchstart', 'touchend', 'touchmove'].forEach(e => {
|
||||
this.el.removeEventListener(e, this.onPointerEvent, {
|
||||
capture: true,
|
||||
passive: true
|
||||
});
|
||||
});
|
||||
|
||||
this.el.removeEventListener('mousemove', this.onMouseMove);
|
||||
this.el.removeEventListener('mouseleave', this.onMouseLeave);
|
||||
|
||||
if (this.contentWrapperEl) {
|
||||
this.contentWrapperEl.removeEventListener('scroll', this.onScroll);
|
||||
}
|
||||
|
||||
elWindow.removeEventListener('resize', this.onWindowResize);
|
||||
|
||||
if (this.mutationObserver) {
|
||||
this.mutationObserver.disconnect();
|
||||
}
|
||||
|
||||
if (this.resizeObserver) {
|
||||
this.resizeObserver.disconnect();
|
||||
}
|
||||
|
||||
// Cancel all debounced functions
|
||||
this.recalculate.cancel();
|
||||
this.onMouseMove.cancel();
|
||||
this.hideScrollbars.cancel();
|
||||
this.onWindowResize.cancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* UnMount mutation observer and delete SimpleBar instance from DOM element
|
||||
*/
|
||||
unMount() {
|
||||
this.removeListeners();
|
||||
SimpleBar.instances.delete(this.el);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if mouse is within bounds
|
||||
*/
|
||||
isWithinBounds(bbox) {
|
||||
return (
|
||||
this.mouseX >= bbox.left &&
|
||||
this.mouseX <= bbox.left + bbox.width &&
|
||||
this.mouseY >= bbox.top &&
|
||||
this.mouseY <= bbox.top + bbox.height
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find element children matches query
|
||||
*/
|
||||
findChild(el, query) {
|
||||
const matches =
|
||||
el.matches ||
|
||||
el.webkitMatchesSelector ||
|
||||
el.mozMatchesSelector ||
|
||||
el.msMatchesSelector;
|
||||
return Array.prototype.filter.call(el.children, child =>
|
||||
matches.call(child, query)
|
||||
)[0];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user