handy-scroll 1.1.4 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -93
- package/dist/handy-scroll.d.mts +14 -0
- package/dist/handy-scroll.mjs +124 -0
- package/package.json +16 -25
- package/src/handy-scroll.css +41 -0
- package/src/handy-scroll.mjs +223 -0
- package/dist/handy-scroll.css +0 -1
- package/dist/handy-scroll.d.ts +0 -10
- package/dist/handy-scroll.es6.js +0 -279
- package/dist/handy-scroll.es6.min.js +0 -6
- package/dist/handy-scroll.js +0 -282
- package/dist/handy-scroll.min.js +0 -6
- package/src/dom.js +0 -36
- package/src/handy-scroll-proto.js +0 -153
- package/src/handy-scroll.js +0 -84
- package/src/handy-scroll.less +0 -50
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
import dom from "./dom.js";
|
|
2
|
-
|
|
3
|
-
let handyScrollProto = {
|
|
4
|
-
init(container) {
|
|
5
|
-
let instance = this;
|
|
6
|
-
let scrollBodies = dom.$$(".handy-scroll-body")
|
|
7
|
-
.filter(node => node.contains(container));
|
|
8
|
-
if (scrollBodies.length) {
|
|
9
|
-
instance.scrollBody = scrollBodies[0];
|
|
10
|
-
}
|
|
11
|
-
instance.container = container;
|
|
12
|
-
instance.visible = true;
|
|
13
|
-
instance.initWidget();
|
|
14
|
-
instance.update(); // recalculate scrollbar parameters and set its visibility
|
|
15
|
-
instance.addEventHandlers();
|
|
16
|
-
// Set skipSync flags to their initial values (because update() above calls syncWidget())
|
|
17
|
-
instance.skipSyncContainer = instance.skipSyncWidget = false;
|
|
18
|
-
},
|
|
19
|
-
|
|
20
|
-
initWidget() {
|
|
21
|
-
let instance = this;
|
|
22
|
-
let widget = instance.widget = document.createElement("div");
|
|
23
|
-
widget.classList.add("handy-scroll");
|
|
24
|
-
let strut = document.createElement("div");
|
|
25
|
-
strut.style.width = `${instance.container.scrollWidth}px`;
|
|
26
|
-
widget.appendChild(strut);
|
|
27
|
-
instance.container.appendChild(widget);
|
|
28
|
-
},
|
|
29
|
-
|
|
30
|
-
addEventHandlers() {
|
|
31
|
-
let instance = this;
|
|
32
|
-
let eventHandlers = instance.eventHandlers = [
|
|
33
|
-
{
|
|
34
|
-
el: instance.scrollBody || window,
|
|
35
|
-
handlers: {
|
|
36
|
-
scroll() {
|
|
37
|
-
instance.checkVisibility();
|
|
38
|
-
},
|
|
39
|
-
resize() {
|
|
40
|
-
instance.update();
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
el: instance.widget,
|
|
46
|
-
handlers: {
|
|
47
|
-
scroll() {
|
|
48
|
-
if (instance.visible && !instance.skipSyncContainer) {
|
|
49
|
-
instance.syncContainer();
|
|
50
|
-
}
|
|
51
|
-
// Resume widget->container syncing after the widget scrolling has finished
|
|
52
|
-
// (it might be temporally disabled by the container while syncing the widget)
|
|
53
|
-
instance.skipSyncContainer = false;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
el: instance.container,
|
|
59
|
-
handlers: {
|
|
60
|
-
scroll() {
|
|
61
|
-
if (!instance.skipSyncWidget) {
|
|
62
|
-
instance.syncWidget();
|
|
63
|
-
}
|
|
64
|
-
// Resume container->widget syncing after the container scrolling has finished
|
|
65
|
-
// (it might be temporally disabled by the widget while syncing the container)
|
|
66
|
-
instance.skipSyncWidget = false;
|
|
67
|
-
},
|
|
68
|
-
focusin() {
|
|
69
|
-
setTimeout(() => {
|
|
70
|
-
// The widget might be destroyed before the timer is triggered (issue #14)
|
|
71
|
-
if (instance.widget) {
|
|
72
|
-
instance.syncWidget();
|
|
73
|
-
}
|
|
74
|
-
}, 0);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
];
|
|
79
|
-
eventHandlers.forEach(({el, handlers}) => {
|
|
80
|
-
Object.keys(handlers).forEach(event => el.addEventListener(event, handlers[event], false));
|
|
81
|
-
});
|
|
82
|
-
},
|
|
83
|
-
|
|
84
|
-
checkVisibility() {
|
|
85
|
-
let instance = this;
|
|
86
|
-
let {widget, container, scrollBody} = instance;
|
|
87
|
-
let mustHide = (widget.scrollWidth <= widget.offsetWidth);
|
|
88
|
-
if (!mustHide) {
|
|
89
|
-
let containerRect = container.getBoundingClientRect();
|
|
90
|
-
let maxVisibleY = scrollBody ?
|
|
91
|
-
scrollBody.getBoundingClientRect().bottom :
|
|
92
|
-
window.innerHeight || document.documentElement.clientHeight;
|
|
93
|
-
mustHide = ((containerRect.bottom <= maxVisibleY) || (containerRect.top > maxVisibleY));
|
|
94
|
-
}
|
|
95
|
-
if (instance.visible === mustHide) {
|
|
96
|
-
instance.visible = !mustHide;
|
|
97
|
-
// We cannot simply hide the scrollbar since its scrollLeft property will not update in that case
|
|
98
|
-
widget.classList.toggle("handy-scroll-hidden");
|
|
99
|
-
}
|
|
100
|
-
},
|
|
101
|
-
|
|
102
|
-
syncContainer() {
|
|
103
|
-
let instance = this;
|
|
104
|
-
let {scrollLeft} = instance.widget;
|
|
105
|
-
if (instance.container.scrollLeft !== scrollLeft) {
|
|
106
|
-
// Prevents container’s “scroll” event handler from syncing back again widget scroll position
|
|
107
|
-
instance.skipSyncWidget = true;
|
|
108
|
-
// Note that this makes container’s “scroll” event handlers execute
|
|
109
|
-
instance.container.scrollLeft = scrollLeft;
|
|
110
|
-
}
|
|
111
|
-
},
|
|
112
|
-
|
|
113
|
-
syncWidget() {
|
|
114
|
-
let instance = this;
|
|
115
|
-
let {scrollLeft} = instance.container;
|
|
116
|
-
if (instance.widget.scrollLeft !== scrollLeft) {
|
|
117
|
-
// Prevents widget’s “scroll” event handler from syncing back again container scroll position
|
|
118
|
-
instance.skipSyncContainer = true;
|
|
119
|
-
// Note that this makes widget’s “scroll” event handlers execute
|
|
120
|
-
instance.widget.scrollLeft = scrollLeft;
|
|
121
|
-
}
|
|
122
|
-
},
|
|
123
|
-
|
|
124
|
-
// Recalculate scroll width and container boundaries
|
|
125
|
-
update() {
|
|
126
|
-
let instance = this;
|
|
127
|
-
let {widget, container, scrollBody} = instance;
|
|
128
|
-
let {clientWidth, scrollWidth} = container;
|
|
129
|
-
widget.style.width = `${clientWidth}px`;
|
|
130
|
-
if (!scrollBody) {
|
|
131
|
-
widget.style.left = `${container.getBoundingClientRect().left}px`;
|
|
132
|
-
}
|
|
133
|
-
widget.firstElementChild.style.width = `${scrollWidth}px`;
|
|
134
|
-
// Fit widget height to the native scroll bar height if needed
|
|
135
|
-
if (scrollWidth > clientWidth) {
|
|
136
|
-
widget.style.height = `${widget.offsetHeight - widget.clientHeight + 1}px`; // +1px JIC
|
|
137
|
-
}
|
|
138
|
-
instance.syncWidget();
|
|
139
|
-
instance.checkVisibility(); // fixes issue Amphiluke/floating-scroll#2
|
|
140
|
-
},
|
|
141
|
-
|
|
142
|
-
// Remove a scrollbar and all related event handlers
|
|
143
|
-
destroy() {
|
|
144
|
-
let instance = this;
|
|
145
|
-
instance.eventHandlers.forEach(({el, handlers}) => {
|
|
146
|
-
Object.keys(handlers).forEach(event => el.removeEventListener(event, handlers[event], false));
|
|
147
|
-
});
|
|
148
|
-
instance.widget.parentNode.removeChild(instance.widget);
|
|
149
|
-
instance.eventHandlers = instance.widget = instance.container = instance.scrollBody = null;
|
|
150
|
-
}
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
export default handyScrollProto;
|
package/src/handy-scroll.js
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import dom from "./dom.js";
|
|
2
|
-
import handyScrollProto from "./handy-scroll-proto.js";
|
|
3
|
-
|
|
4
|
-
let instances = []; // if it were not for IE, it would be better to use Map (container -> instance)
|
|
5
|
-
|
|
6
|
-
let handyScroll = {
|
|
7
|
-
/**
|
|
8
|
-
* Mount widgets in the given containers
|
|
9
|
-
* @param {HTMLElement|NodeList|HTMLCollection|HTMLElement[]|String} containerRef - Widget container reference (either an element, or a list of elements, or a selector)
|
|
10
|
-
*/
|
|
11
|
-
mount(containerRef) {
|
|
12
|
-
dom.$$(containerRef).forEach(container => {
|
|
13
|
-
if (handyScroll.mounted(container)) {
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
let instance = Object.create(handyScrollProto);
|
|
17
|
-
instances.push(instance);
|
|
18
|
-
instance.init(container);
|
|
19
|
-
});
|
|
20
|
-
},
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Check if a widget is already mounted in the given container
|
|
24
|
-
* @param {HTMLElement|String} containerRef - Widget container reference (either an element, or a selector)
|
|
25
|
-
* @returns {Boolean}
|
|
26
|
-
*/
|
|
27
|
-
mounted(containerRef) {
|
|
28
|
-
let container = dom.$(containerRef);
|
|
29
|
-
return instances.some(instance => instance.container === container);
|
|
30
|
-
},
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Update widget parameters and position
|
|
34
|
-
* @param {HTMLElement|NodeList|HTMLCollection|HTMLElement[]|String} containerRef - Widget container reference (either an element, or a list of elements, or a selector)
|
|
35
|
-
*/
|
|
36
|
-
update(containerRef) {
|
|
37
|
-
dom.$$(containerRef).forEach(container => {
|
|
38
|
-
instances.some(instance => {
|
|
39
|
-
if (instance.container === container) {
|
|
40
|
-
instance.update();
|
|
41
|
-
return true;
|
|
42
|
-
}
|
|
43
|
-
return false;
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
},
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Destroy widgets mounted in the given containers
|
|
50
|
-
* @param {HTMLElement|NodeList|HTMLCollection|HTMLElement[]|String} containerRef - Widget container reference (either an element, or a list of elements, or a selector)
|
|
51
|
-
*/
|
|
52
|
-
destroy(containerRef) {
|
|
53
|
-
dom.$$(containerRef).forEach(container => {
|
|
54
|
-
instances.some((instance, index) => {
|
|
55
|
-
if (instance.container === container) {
|
|
56
|
-
instances.splice(index, 1)[0].destroy();
|
|
57
|
-
return true;
|
|
58
|
-
}
|
|
59
|
-
return false;
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
},
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Destroy handyScroll widgets whose containers are not in the document anymore
|
|
66
|
-
*/
|
|
67
|
-
destroyDetached() {
|
|
68
|
-
instances = instances.filter(instance => {
|
|
69
|
-
if (!document.body.contains(instance.container)) {
|
|
70
|
-
instance.destroy();
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
return true;
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
export default handyScroll;
|
|
79
|
-
|
|
80
|
-
if (dom.isDOMAvailable) {
|
|
81
|
-
dom.ready(() => {
|
|
82
|
-
handyScroll.mount("[data-handy-scroll]");
|
|
83
|
-
});
|
|
84
|
-
}
|
package/src/handy-scroll.less
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
.handy-scroll {
|
|
2
|
-
bottom:0;
|
|
3
|
-
min-height:17px; // based on https://codepen.io/sambible/post/browser-scrollbar-widths (fixes #3)
|
|
4
|
-
overflow:auto;
|
|
5
|
-
position:fixed;
|
|
6
|
-
|
|
7
|
-
div {
|
|
8
|
-
height:1px;
|
|
9
|
-
overflow:hidden;
|
|
10
|
-
pointer-events:none;
|
|
11
|
-
&:before {
|
|
12
|
-
content:"\A0"; // fixes Amphiluke/floating-scroll#6
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
&,
|
|
17
|
-
& div {
|
|
18
|
-
font-size:1px;
|
|
19
|
-
line-height:0;
|
|
20
|
-
margin:0;
|
|
21
|
-
padding:0;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
&-hidden {
|
|
25
|
-
bottom:9999px;
|
|
26
|
-
div:before {
|
|
27
|
-
content:"\A0\A0"; // changing content fixes eventual bug with widget re-rendering in Chrome
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
&-viewport {
|
|
32
|
-
// It can be any type of positioning except static. Redefine in your CSS as needed
|
|
33
|
-
position:relative;
|
|
34
|
-
}
|
|
35
|
-
&-body {
|
|
36
|
-
overflow:auto;
|
|
37
|
-
}
|
|
38
|
-
&-viewport & {
|
|
39
|
-
left:0;
|
|
40
|
-
position:absolute;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
&-hoverable & {
|
|
44
|
-
opacity:0;
|
|
45
|
-
transition:opacity 0.5s ease 0.3s;
|
|
46
|
-
}
|
|
47
|
-
&-hoverable:hover & {
|
|
48
|
-
opacity:1;
|
|
49
|
-
}
|
|
50
|
-
}
|