312 lines
10 KiB
JavaScript
312 lines
10 KiB
JavaScript
define([
|
|
'jquery',
|
|
'mousewheel',
|
|
'customScrollbar'
|
|
], ($) => {
|
|
'use strict';
|
|
|
|
let config = {
|
|
autoScrollClass: 'auto-scroll',
|
|
autoScrollTopClass: 'auto-scroll-top',
|
|
autoScrollLeftClass: 'auto-scroll-left',
|
|
autoScrollBottomClass: 'auto-scroll-bottom',
|
|
autoScrollRightClass: 'auto-scroll-right',
|
|
};
|
|
|
|
let defaultConfig = {
|
|
axis: 'yx',
|
|
theme: 'light-3',
|
|
scrollInertia: 200,
|
|
autoExpandScrollbar: false,
|
|
scrollButtons: {
|
|
enable: true,
|
|
scrollAmount: 30,
|
|
scrollType: 'stepless'
|
|
},
|
|
callbacks: {
|
|
onTotalScrollOffset: 0,
|
|
onTotalScrollBackOffset: 0,
|
|
alwaysTriggerOffsets: true,
|
|
onScroll: function(){
|
|
if($(this).data('mCS').trigger === 'internal'){
|
|
autoScrollOff(this);
|
|
}
|
|
}
|
|
},
|
|
|
|
advanced: {
|
|
autoUpdateTimeout: 120, // auto-update timeout (default: 60)
|
|
updateOnContentResize: true,
|
|
autoExpandHorizontalScroll: false, // on resize css scale() scroll content should not change
|
|
//autoExpandHorizontalScroll: 2,
|
|
autoScrollOnFocus: 'div',
|
|
},
|
|
mouseWheel: {
|
|
enable: false, // scroll wheel currently disabled
|
|
scrollAmount: 'auto',
|
|
axis: 'x',
|
|
preventDefault: true
|
|
},
|
|
keyboard: {
|
|
enable: false, // not working with pathfinder "shortcuts"
|
|
scrollType: 'stepless',
|
|
scrollAmount: 'auto'
|
|
},
|
|
scrollbarPosition: 'inside',
|
|
autoDraggerLength: true,
|
|
autoHideScrollbar: false
|
|
};
|
|
|
|
let defaultScrollToOptions = {
|
|
scrollInertia: 2000,
|
|
scrollEasing: 'easeInOutSmooth',
|
|
timeout: 0
|
|
};
|
|
|
|
/**
|
|
* init map scrollbar
|
|
* @param scrollWrapper
|
|
* @param customConfig
|
|
*/
|
|
let initScrollbar = (scrollWrapper, customConfig) => {
|
|
customConfig = $.extend(true, {}, defaultConfig, customConfig);
|
|
// wrap callbacks -> callbacks from defaultConfig should run first
|
|
customConfig.callbacks = wrapObjectFunctions(customConfig.callbacks, defaultConfig.callbacks);
|
|
scrollWrapper.mCustomScrollbar(customConfig);
|
|
};
|
|
|
|
/**
|
|
* @param scrollWrapper
|
|
* @param position
|
|
* @param options
|
|
*/
|
|
let autoScroll = (scrollWrapper, position, options) => {
|
|
if(position.some(position => position !== null)){
|
|
// scroll position -> start auto scroll
|
|
autoScrollOn(scrollWrapper, position, options);
|
|
}else{
|
|
// no scroll position -> stop auto scroll
|
|
autoScrollOff(scrollWrapper);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @param scrollWrapper
|
|
* @param position
|
|
* @param options
|
|
*/
|
|
let autoScrollOn = (scrollWrapper, position, options) => {
|
|
let scrollToOptions = Object.assign({}, defaultScrollToOptions, options);
|
|
let scrollInertia = 0;
|
|
let autoScrollClasses = [];
|
|
['top', 'left', 'bottom', 'right'].forEach((direction, i) => {
|
|
if(position.includes(direction)){
|
|
autoScrollClasses.push(config['autoScroll' + direction.capitalize() + 'Class']);
|
|
if(i % 2){ // left || right
|
|
scrollInertia = scrollToOptions.scrollInertia * scrollWrapper.mcs.leftPct / 100;
|
|
}else{ // top || bottom
|
|
scrollInertia = scrollToOptions.scrollInertia * scrollWrapper.mcs.topPct / 100;
|
|
}
|
|
if(i === 2 || i === 3){ // bottom || right
|
|
scrollInertia = scrollToOptions.scrollInertia - scrollInertia;
|
|
}
|
|
}
|
|
});
|
|
|
|
if(autoScrollClasses.length){
|
|
// scroll position -> check if scroll direction changed
|
|
let compareClasses = getAutoScrollClasses();
|
|
let currentClasses = [...scrollWrapper.classList].filter(cls => compareClasses.includes(cls));
|
|
let newClasses = autoScrollClasses.diff(currentClasses);
|
|
let oldClasses = currentClasses.diff(autoScrollClasses);
|
|
|
|
if(newClasses.length || oldClasses.length){
|
|
// changed scroll direction (e.g. null -> y; y -> x; y -> xy, xy -> null)
|
|
// -> stop current autos scroll and start with new scroll direction
|
|
autoScrollOff(scrollWrapper, oldClasses);
|
|
|
|
scrollWrapper.classList.add(...newClasses);
|
|
scrollToOptions.scrollInertia = scrollInertia;
|
|
$(scrollWrapper).mCustomScrollbar('scrollTo', position, scrollToOptions);
|
|
}
|
|
}else{
|
|
// no scroll position -> stop auto scroll
|
|
autoScrollOff(scrollWrapper);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @param scrollWrapper
|
|
* @param classes
|
|
*/
|
|
let autoScrollOff = (scrollWrapper, classes) => {
|
|
classes = classes || getAutoScrollClasses();
|
|
scrollWrapper.classList.remove(...classes);
|
|
$(scrollWrapper).mCustomScrollbar('stop');
|
|
};
|
|
|
|
/**
|
|
* @returns {[string, string, string, string]}
|
|
*/
|
|
let getAutoScrollClasses = () => {
|
|
return [config.autoScrollTopClass, config.autoScrollLeftClass, config.autoScrollBottomClass, config.autoScrollRightClass];
|
|
};
|
|
|
|
/**
|
|
* get mCustomScrollbar container
|
|
* @param element
|
|
* @returns {*|[]}
|
|
*/
|
|
let getContainer = element => element.parents('.mCSB_container');
|
|
|
|
/**
|
|
*
|
|
* @param container
|
|
* @param element
|
|
* @returns {{x: number, y: number}}
|
|
*/
|
|
let getElementPos = (container, element) => {
|
|
return {
|
|
x: element.offset().left - container.offset().left,
|
|
y: element.offset().top - container.offset().top
|
|
};
|
|
};
|
|
|
|
/**
|
|
* @param element
|
|
* @returns {{x: number, y: number}}
|
|
*/
|
|
let getElementDim = element => {
|
|
return {
|
|
x: element.outerWidth(false),
|
|
y: element.outerHeight(false)
|
|
};
|
|
};
|
|
|
|
/**
|
|
* check if an element is 100% visible
|
|
* -> scrolled into viewport
|
|
* @param element
|
|
* @returns {boolean}
|
|
*/
|
|
let isInView = element => {
|
|
let container = getContainer(element);
|
|
let wrapper = container.parent();
|
|
let cPos = {x: container[0].offsetLeft, y: container[0].offsetTop};
|
|
let ePos = getElementPos(container, element);
|
|
let eDim = getElementDim(element);
|
|
|
|
return cPos.y + ePos.y >= 0 &&
|
|
cPos.y + ePos.y < wrapper.height() - eDim.y &&
|
|
cPos.x + ePos.x >= 0 &&
|
|
cPos.x + ePos.x < wrapper.width() - eDim.x;
|
|
};
|
|
|
|
/**
|
|
* get new scrollTo coordinates to center element in viewport
|
|
* @param element
|
|
* @returns {{x: number, y: number}}
|
|
*/
|
|
let getCenterScrollPosition = element => {
|
|
let container = getContainer(element);
|
|
let wrapper = container.parent();
|
|
let cDim = getElementDim(container);
|
|
let wDim = {x: wrapper.width(), y: wrapper.height()};
|
|
let eDim = getElementDim(element);
|
|
let ePos = getElementPos(container, element);
|
|
|
|
let eOff = {
|
|
x: (-wDim.x / 2) + (eDim.x / 2),
|
|
y: (-wDim.y / 2) + (eDim.y / 2)
|
|
};
|
|
|
|
return adjustPos(addOffset(ePos, eOff), cDim);
|
|
};
|
|
|
|
/**
|
|
* scroll to a specific position on map
|
|
* demo: http://manos.malihu.gr/repository/custom-scrollbar/demo/examples/scrollTo_demo.html
|
|
* @param scrollArea
|
|
* @param position
|
|
* @param options
|
|
*/
|
|
let scrollToPosition = (scrollArea, position, options) => {
|
|
$(scrollArea).mCustomScrollbar('scrollTo', position, options);
|
|
};
|
|
|
|
/**
|
|
* scroll to center an element
|
|
* -> subtract some offset for tooltips/connections
|
|
* @param scrollArea
|
|
* @param element
|
|
*/
|
|
let scrollToCenter = (scrollArea, element) => {
|
|
// no scroll if element is already FULL visible in scrollable viewport
|
|
if(!isInView(element)){
|
|
// get scrollTo position for centered element
|
|
scrollToPosition(scrollArea, getCenterScrollPosition(element));
|
|
}
|
|
};
|
|
|
|
/**
|
|
* add/subtract offset coordinates from position
|
|
* @param {{x: number, y: number}} position
|
|
* @param {{x: number, y: number}} offset
|
|
* @returns {{x: number, y: number}}
|
|
*/
|
|
let addOffset = (position, offset) => mapObject(position, (v, k) => v + offset[k]);
|
|
|
|
/**
|
|
* round position
|
|
* @param {{x: number, y: number}} position
|
|
* @returns {{x: number, y: number}}
|
|
*/
|
|
let roundPos = position => mapObject(position, Math.round);
|
|
|
|
/**
|
|
*
|
|
* @param {{x: number, y: number}} position
|
|
* @param {{x: number, y: number}} dimension
|
|
* @returns {{x: number, y: number}}
|
|
*/
|
|
let adjustPos = (position, dimension) => mapObject(roundPos(position), (v, k) => Math.max(1, Math.min(dimension[k], v)) );
|
|
|
|
/**
|
|
* wrap functions that exists in both objects (same key)
|
|
* -> o2 fkt run 1st (returned value ignored)
|
|
* -> o1 fkt run 2nd
|
|
* @param o1
|
|
* @param o2
|
|
* @returns {any}
|
|
*/
|
|
let wrapObjectFunctions = (o1 = {}, o2 = {}) => {
|
|
return mapObject(o1, function(v1, k1){
|
|
// check both obj has the same key and are functions
|
|
if([v1, o2[k1]].every(v => typeof v === 'function')){
|
|
return function(...args){
|
|
// run 'default' fkt first, then orig fkt
|
|
o2[k1].apply(this, ...args);
|
|
return v1.apply(this, ...args);
|
|
};
|
|
}
|
|
return v1;
|
|
});
|
|
};
|
|
|
|
/**
|
|
* like Array.map() for objects
|
|
* -> callback f is called for each property
|
|
* @see https://stackoverflow.com/a/38829074/4329969
|
|
* @param o
|
|
* @param f
|
|
* @returns {Object}
|
|
*/
|
|
let mapObject = (o, f) => Object.assign(...Object.entries(o).map(([k, v]) => ({[k]: f(v, k) })));
|
|
|
|
return {
|
|
initScrollbar: initScrollbar,
|
|
scrollToPosition: scrollToPosition,
|
|
scrollToCenter: scrollToCenter,
|
|
autoScroll: autoScroll
|
|
};
|
|
}); |