handy-scroll 1.0.6 → 1.1.1
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 +16 -6
- package/dist/handy-scroll.d.ts +10 -0
- package/dist/handy-scroll.es6.js +27 -9
- package/dist/handy-scroll.es6.min.js +3 -3
- package/dist/handy-scroll.js +47 -53
- package/dist/handy-scroll.min.js +3 -3
- package/package.json +21 -15
- package/src/handy-scroll-proto.js +6 -1
- package/src/handy-scroll.js +17 -4
- package/doc/angular-integration.md +0 -45
package/README.md
CHANGED
|
@@ -30,7 +30,8 @@ The module exports a single object `handyScroll` which provides the following me
|
|
|
30
30
|
* [`mount`](#mounting-the-widget) — initializes and “mounts” the widgets in the specified containers;
|
|
31
31
|
* [`mounted`](#checking-widget-existence) — checks if the widget is already mounted in the given container;
|
|
32
32
|
* [`update`](#updating-scrollbar) — updates the widget parameters and position;
|
|
33
|
-
* [`destroy`](#destroying-the-widget) — destroys the widgets mounted in the specified containers and removes all related event handlers
|
|
33
|
+
* [`destroy`](#destroying-the-widget) — destroys the widgets mounted in the specified containers and removes all related event handlers;
|
|
34
|
+
* [`destroyDetached`](#destroying-detached-widgets) — destroys handy-scroll widget instances whose containers were removed from the document.
|
|
34
35
|
|
|
35
36
|
### Mounting the widget
|
|
36
37
|
|
|
@@ -69,7 +70,7 @@ console.log(handyScroll.mounted("#spacious-container")); // true
|
|
|
69
70
|
|
|
70
71
|
### Updating scrollbar
|
|
71
72
|
|
|
72
|
-
If you mount the widget in a container whose size and/or content may dynamically change,
|
|
73
|
+
If you mount the widget in a container whose size and/or content may dynamically change, you need a way to update the scrollbar each time the container’s sizes change. This can be done by invoking the method `handyScroll.update()` as in the example below.
|
|
73
74
|
|
|
74
75
|
```javascript
|
|
75
76
|
handyScroll.mount(".spacious-container");
|
|
@@ -89,6 +90,19 @@ handyScroll.destroy(".spacious-container");
|
|
|
89
90
|
|
|
90
91
|
The method expects a single argument, the target containers reference, which can be either an element, or a list of elements, or a selector.
|
|
91
92
|
|
|
93
|
+
|
|
94
|
+
### Destroying detached widgets
|
|
95
|
+
|
|
96
|
+
If your app completely re-renders a large portion of DOM where handy-scroll widgets were mounted, actual container references are lost, and therefore you cannot unmount the widgets and perform related cleanup using the `destroy` method. In this case, you may just call the `handyScroll.destroyDetached()` method, and the module will find all “zombie” instances and will destroy them for you.
|
|
97
|
+
|
|
98
|
+
```javascript
|
|
99
|
+
handyScroll.mount(".main-view .spacious-container");
|
|
100
|
+
// ... the app re-renders the main view ...
|
|
101
|
+
document.querySelector(".main-view").innerHTML = "...";
|
|
102
|
+
// destroy handy-scroll widgets whose containers are not in the document anymore
|
|
103
|
+
handyScroll.destroyDetached();
|
|
104
|
+
```
|
|
105
|
+
|
|
92
106
|
### Special cases
|
|
93
107
|
|
|
94
108
|
If you want to attach the widget to a container living in a positioned box (e.g. a modal popup with `position: fixed`) then you need to apply two special indicating class names in the markup. The module detects these indicating class names (they are prefixed with `handy-scroll-`) and switches to a special functioning mode.
|
|
@@ -115,10 +129,6 @@ The [handy-scroll.css](dist/handy-scroll.css) file provides some basic styles fo
|
|
|
115
129
|
|
|
116
130
|
You can make the widget more “unobtrusive” so that it will appear only when the mouse pointer hovers over the scrollable container. To do so just apply the class `handy-scroll-hoverable` to the desired scrollable container owning the widget.
|
|
117
131
|
|
|
118
|
-
## Integration with Angular
|
|
119
|
-
|
|
120
|
-
If you have problems with the widget integration into your Angular app, please consult [this instruction](doc/angular-integration.md) first.
|
|
121
|
-
|
|
122
132
|
## Live demos
|
|
123
133
|
|
|
124
134
|
Check out some usage demos [here](https://amphiluke.github.io/handy-scroll/).
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
declare namespace handyScroll {
|
|
2
|
+
export function mount(containerRef: HTMLElement | NodeList | HTMLCollection | HTMLElement[] | string): void;
|
|
3
|
+
export function mounted(containerRef: HTMLElement | string): boolean;
|
|
4
|
+
export function update(containerRef: HTMLElement | NodeList | HTMLCollection | HTMLElement[] | string): void;
|
|
5
|
+
export function destroy(containerRef: HTMLElement | NodeList | HTMLCollection | HTMLElement[] | string): void;
|
|
6
|
+
export function destroyDetached(): void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default handyScroll;
|
|
10
|
+
export as namespace handyScroll;
|
package/dist/handy-scroll.es6.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
handy-scroll v1.
|
|
2
|
+
handy-scroll v1.1.1
|
|
3
3
|
https://amphiluke.github.io/handy-scroll/
|
|
4
|
-
(c)
|
|
4
|
+
(c) 2022 Amphiluke
|
|
5
5
|
*/
|
|
6
6
|
(function (global, factory) {
|
|
7
7
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
|
8
8
|
typeof define === 'function' && define.amd ? define(factory) :
|
|
9
9
|
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.handyScroll = factory());
|
|
10
|
-
}(this, (function () { 'use strict';
|
|
10
|
+
})(this, (function () { 'use strict';
|
|
11
11
|
|
|
12
12
|
let slice = Array.prototype.slice;
|
|
13
13
|
|
|
@@ -116,7 +116,12 @@ https://amphiluke.github.io/handy-scroll/
|
|
|
116
116
|
instance.skipSyncWidget = false;
|
|
117
117
|
},
|
|
118
118
|
focusin() {
|
|
119
|
-
setTimeout(() =>
|
|
119
|
+
setTimeout(() => {
|
|
120
|
+
// The widget might be destroyed before the timer is triggered (issue #14)
|
|
121
|
+
if (instance.widget) {
|
|
122
|
+
instance.syncWidget();
|
|
123
|
+
}
|
|
124
|
+
}, 0);
|
|
120
125
|
}
|
|
121
126
|
}
|
|
122
127
|
}
|
|
@@ -195,12 +200,12 @@ https://amphiluke.github.io/handy-scroll/
|
|
|
195
200
|
}
|
|
196
201
|
};
|
|
197
202
|
|
|
198
|
-
let instances = []; // if it were not for IE it would be better to use
|
|
203
|
+
let instances = []; // if it were not for IE, it would be better to use Map (container -> instance)
|
|
199
204
|
|
|
200
205
|
let handyScroll = {
|
|
201
206
|
/**
|
|
202
207
|
* Mount widgets in the given containers
|
|
203
|
-
* @param {HTMLElement|NodeList|HTMLCollection|
|
|
208
|
+
* @param {HTMLElement|NodeList|HTMLCollection|HTMLElement[]|String} containerRef - Widget container reference (either an element, or a list of elements, or a selector)
|
|
204
209
|
*/
|
|
205
210
|
mount(containerRef) {
|
|
206
211
|
dom.$$(containerRef).forEach(container => {
|
|
@@ -225,7 +230,7 @@ https://amphiluke.github.io/handy-scroll/
|
|
|
225
230
|
|
|
226
231
|
/**
|
|
227
232
|
* Update widget parameters and position
|
|
228
|
-
* @param {HTMLElement|NodeList|HTMLCollection|
|
|
233
|
+
* @param {HTMLElement|NodeList|HTMLCollection|HTMLElement[]|String} containerRef - Widget container reference (either an element, or a list of elements, or a selector)
|
|
229
234
|
*/
|
|
230
235
|
update(containerRef) {
|
|
231
236
|
dom.$$(containerRef).forEach(container => {
|
|
@@ -241,7 +246,7 @@ https://amphiluke.github.io/handy-scroll/
|
|
|
241
246
|
|
|
242
247
|
/**
|
|
243
248
|
* Destroy widgets mounted in the given containers
|
|
244
|
-
* @param {HTMLElement|NodeList|HTMLCollection|
|
|
249
|
+
* @param {HTMLElement|NodeList|HTMLCollection|HTMLElement[]|String} containerRef - Widget container reference (either an element, or a list of elements, or a selector)
|
|
245
250
|
*/
|
|
246
251
|
destroy(containerRef) {
|
|
247
252
|
dom.$$(containerRef).forEach(container => {
|
|
@@ -253,6 +258,19 @@ https://amphiluke.github.io/handy-scroll/
|
|
|
253
258
|
return false;
|
|
254
259
|
});
|
|
255
260
|
});
|
|
261
|
+
},
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Destroy handyScroll widgets whose containers are not in the document anymore
|
|
265
|
+
*/
|
|
266
|
+
destroyDetached() {
|
|
267
|
+
instances = instances.filter(instance => {
|
|
268
|
+
if (!dom.body.contains(instance.container)) {
|
|
269
|
+
instance.destroy();
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
return true;
|
|
273
|
+
});
|
|
256
274
|
}
|
|
257
275
|
};
|
|
258
276
|
|
|
@@ -264,4 +282,4 @@ https://amphiluke.github.io/handy-scroll/
|
|
|
264
282
|
|
|
265
283
|
return handyScroll;
|
|
266
284
|
|
|
267
|
-
}))
|
|
285
|
+
}));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
handy-scroll v1.
|
|
2
|
+
handy-scroll v1.1.1
|
|
3
3
|
https://amphiluke.github.io/handy-scroll/
|
|
4
|
-
(c)
|
|
4
|
+
(c) 2022 Amphiluke
|
|
5
5
|
*/
|
|
6
|
-
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).handyScroll=t()}(this,(function(){"use strict";let e=Array.prototype.slice,t="object"==typeof document&&!!document.documentElement,i={isDOMAvailable:t,doc:t?document:null,html:t?document.documentElement:null,body:t?document.body:null,ready(e){"loading"===i.doc.readyState?i.doc.addEventListener("DOMContentLoaded",(()=>{e()}),{once:!0}):e()},$:e=>"string"==typeof e?i.body.querySelector(e):e,$$:t=>Array.isArray(t)?t:t.nodeType===Node.ELEMENT_NODE?[t]:"string"==typeof t?e.call(i.body.querySelectorAll(t)):e.call(t)},n={init(e){let t=this,n=i.$$(".handy-scroll-body").filter((t=>t.contains(e)));n.length&&(t.scrollBody=n[0]),t.container=e,t.visible=!0,t.initWidget(),t.update(),t.addEventHandlers(),t.skipSyncContainer=t.skipSyncWidget=!1},initWidget(){let e=this,t=e.widget=i.doc.createElement("div");t.classList.add("handy-scroll");let n=i.doc.createElement("div");n.style.width=`${e.container.scrollWidth}px`,t.appendChild(n),e.container.appendChild(t)},addEventHandlers(){let e=this;(e.eventHandlers=[{el:e.scrollBody||window,handlers:{scroll(){e.checkVisibility()},resize(){e.update()}}},{el:e.widget,handlers:{scroll(){e.visible&&!e.skipSyncContainer&&e.syncContainer(),e.skipSyncContainer=!1}}},{el:e.container,handlers:{scroll(){e.skipSyncWidget||e.syncWidget(),e.skipSyncWidget=!1},focusin(){setTimeout((()=>e.syncWidget()),0)}}}]).forEach((({el:e,handlers:t})=>{Object.keys(t).forEach((i=>e.addEventListener(i,t[i],!1)))}))},checkVisibility(){let e=this,{widget:t,container:n,scrollBody:l}=e,o=t.scrollWidth<=t.offsetWidth;if(!o){let e=n.getBoundingClientRect(),t=l?l.getBoundingClientRect().bottom:window.innerHeight||i.html.clientHeight;o=e.bottom<=t||e.top>t}e.visible===o&&(e.visible=!o,t.classList.toggle("handy-scroll-hidden"))},syncContainer(){let e=this,{scrollLeft:t}=e.widget;e.container.scrollLeft!==t&&(e.skipSyncWidget=!0,e.container.scrollLeft=t)},syncWidget(){let e=this,{scrollLeft:t}=e.container;e.widget.scrollLeft!==t&&(e.skipSyncContainer=!0,e.widget.scrollLeft=t)},update(){let e=this,{widget:t,container:i,scrollBody:n}=e,{clientWidth:l,scrollWidth:o}=i;t.style.width=`${l}px`,n||(t.style.left=`${i.getBoundingClientRect().left}px`),t.firstElementChild.style.width=`${o}px`,o>l&&(t.style.height=t.offsetHeight-t.clientHeight+1+"px"),e.syncWidget(),e.checkVisibility()},destroy(){let e=this;e.eventHandlers.forEach((({el:e,handlers:t})=>{Object.keys(t).forEach((i=>e.removeEventListener(i,t[i],!1)))})),e.widget.parentNode.removeChild(e.widget),e.eventHandlers=e.widget=e.container=e.scrollBody=null}},l=[],o={mount(e){i.$$(e).forEach((e=>{if(o.mounted(e))return;let t=Object.create(n);l.push(t),t.init(e)}))},mounted(e){let t=i.$(e);return l.some((e=>e.container===t))},update(e){i.$$(e).forEach((e=>{l.some((t=>t.container===e&&(t.update(),!0)))}))},destroy(e){i.$$(e).forEach((e=>{l.some(((t,i)=>t.container===e&&(l.splice(i,1)[0].destroy(),!0)))}))}};return i.isDOMAvailable&&i.ready((()=>{o.mount("[data-handy-scroll]")})),o}));
|
|
6
|
+
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).handyScroll=t()}(this,(function(){"use strict";let e=Array.prototype.slice,t="object"==typeof document&&!!document.documentElement,i={isDOMAvailable:t,doc:t?document:null,html:t?document.documentElement:null,body:t?document.body:null,ready(e){"loading"===i.doc.readyState?i.doc.addEventListener("DOMContentLoaded",(()=>{e()}),{once:!0}):e()},$:e=>"string"==typeof e?i.body.querySelector(e):e,$$:t=>Array.isArray(t)?t:t.nodeType===Node.ELEMENT_NODE?[t]:"string"==typeof t?e.call(i.body.querySelectorAll(t)):e.call(t)},n={init(e){let t=this,n=i.$$(".handy-scroll-body").filter((t=>t.contains(e)));n.length&&(t.scrollBody=n[0]),t.container=e,t.visible=!0,t.initWidget(),t.update(),t.addEventHandlers(),t.skipSyncContainer=t.skipSyncWidget=!1},initWidget(){let e=this,t=e.widget=i.doc.createElement("div");t.classList.add("handy-scroll");let n=i.doc.createElement("div");n.style.width=`${e.container.scrollWidth}px`,t.appendChild(n),e.container.appendChild(t)},addEventHandlers(){let e=this;(e.eventHandlers=[{el:e.scrollBody||window,handlers:{scroll(){e.checkVisibility()},resize(){e.update()}}},{el:e.widget,handlers:{scroll(){e.visible&&!e.skipSyncContainer&&e.syncContainer(),e.skipSyncContainer=!1}}},{el:e.container,handlers:{scroll(){e.skipSyncWidget||e.syncWidget(),e.skipSyncWidget=!1},focusin(){setTimeout((()=>{e.widget&&e.syncWidget()}),0)}}}]).forEach((({el:e,handlers:t})=>{Object.keys(t).forEach((i=>e.addEventListener(i,t[i],!1)))}))},checkVisibility(){let e=this,{widget:t,container:n,scrollBody:l}=e,o=t.scrollWidth<=t.offsetWidth;if(!o){let e=n.getBoundingClientRect(),t=l?l.getBoundingClientRect().bottom:window.innerHeight||i.html.clientHeight;o=e.bottom<=t||e.top>t}e.visible===o&&(e.visible=!o,t.classList.toggle("handy-scroll-hidden"))},syncContainer(){let e=this,{scrollLeft:t}=e.widget;e.container.scrollLeft!==t&&(e.skipSyncWidget=!0,e.container.scrollLeft=t)},syncWidget(){let e=this,{scrollLeft:t}=e.container;e.widget.scrollLeft!==t&&(e.skipSyncContainer=!0,e.widget.scrollLeft=t)},update(){let e=this,{widget:t,container:i,scrollBody:n}=e,{clientWidth:l,scrollWidth:o}=i;t.style.width=`${l}px`,n||(t.style.left=`${i.getBoundingClientRect().left}px`),t.firstElementChild.style.width=`${o}px`,o>l&&(t.style.height=t.offsetHeight-t.clientHeight+1+"px"),e.syncWidget(),e.checkVisibility()},destroy(){let e=this;e.eventHandlers.forEach((({el:e,handlers:t})=>{Object.keys(t).forEach((i=>e.removeEventListener(i,t[i],!1)))})),e.widget.parentNode.removeChild(e.widget),e.eventHandlers=e.widget=e.container=e.scrollBody=null}},l=[],o={mount(e){i.$$(e).forEach((e=>{if(o.mounted(e))return;let t=Object.create(n);l.push(t),t.init(e)}))},mounted(e){let t=i.$(e);return l.some((e=>e.container===t))},update(e){i.$$(e).forEach((e=>{l.some((t=>t.container===e&&(t.update(),!0)))}))},destroy(e){i.$$(e).forEach((e=>{l.some(((t,i)=>t.container===e&&(l.splice(i,1)[0].destroy(),!0)))}))},destroyDetached(){l=l.filter((e=>!!i.body.contains(e.container)||(e.destroy(),!1)))}};return i.isDOMAvailable&&i.ready((()=>{o.mount("[data-handy-scroll]")})),o}));
|
package/dist/handy-scroll.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
handy-scroll v1.
|
|
2
|
+
handy-scroll v1.1.1
|
|
3
3
|
https://amphiluke.github.io/handy-scroll/
|
|
4
|
-
(c)
|
|
4
|
+
(c) 2022 Amphiluke
|
|
5
5
|
*/
|
|
6
6
|
(function (global, factory) {
|
|
7
7
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
|
8
8
|
typeof define === 'function' && define.amd ? define(factory) :
|
|
9
9
|
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.handyScroll = factory());
|
|
10
|
-
}(this, (function () { 'use strict';
|
|
10
|
+
})(this, (function () { 'use strict';
|
|
11
11
|
|
|
12
|
-
var slice = Array.prototype.slice;
|
|
12
|
+
var slice = Array.prototype.slice;
|
|
13
13
|
|
|
14
|
+
// Precaution to avoid reference errors when imported for SSR (issue #13)
|
|
14
15
|
var isDOMAvailable = typeof document === "object" && !!document.documentElement;
|
|
15
16
|
var dom = {
|
|
16
17
|
isDOMAvailable: isDOMAvailable,
|
|
@@ -33,7 +34,6 @@ https://amphiluke.github.io/handy-scroll/
|
|
|
33
34
|
// ref is a selector
|
|
34
35
|
return dom.body.querySelector(ref);
|
|
35
36
|
}
|
|
36
|
-
|
|
37
37
|
return ref; // ref is already an element
|
|
38
38
|
},
|
|
39
39
|
$$: function $$(ref) {
|
|
@@ -41,17 +41,14 @@ https://amphiluke.github.io/handy-scroll/
|
|
|
41
41
|
// ref is an array of elements
|
|
42
42
|
return ref;
|
|
43
43
|
}
|
|
44
|
-
|
|
45
44
|
if (ref.nodeType === Node.ELEMENT_NODE) {
|
|
46
45
|
// ref is an element
|
|
47
46
|
return [ref];
|
|
48
47
|
}
|
|
49
|
-
|
|
50
48
|
if (typeof ref === "string") {
|
|
51
49
|
// ref is a selector
|
|
52
50
|
return slice.call(dom.body.querySelectorAll(ref));
|
|
53
51
|
}
|
|
54
|
-
|
|
55
52
|
return slice.call(ref); // ref is an array-like object (NodeList or HTMLCollection)
|
|
56
53
|
}
|
|
57
54
|
};
|
|
@@ -62,18 +59,15 @@ https://amphiluke.github.io/handy-scroll/
|
|
|
62
59
|
var scrollBodies = dom.$$(".handy-scroll-body").filter(function (node) {
|
|
63
60
|
return node.contains(container);
|
|
64
61
|
});
|
|
65
|
-
|
|
66
62
|
if (scrollBodies.length) {
|
|
67
63
|
instance.scrollBody = scrollBodies[0];
|
|
68
64
|
}
|
|
69
|
-
|
|
70
65
|
instance.container = container;
|
|
71
66
|
instance.visible = true;
|
|
72
67
|
instance.initWidget();
|
|
73
68
|
instance.update(); // recalculate scrollbar parameters and set its visibility
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
69
|
+
instance.addEventHandlers();
|
|
70
|
+
// Set skipSync flags to their initial values (because update() above calls syncWidget())
|
|
77
71
|
instance.skipSyncContainer = instance.skipSyncWidget = false;
|
|
78
72
|
},
|
|
79
73
|
initWidget: function initWidget() {
|
|
@@ -103,10 +97,9 @@ https://amphiluke.github.io/handy-scroll/
|
|
|
103
97
|
scroll: function scroll() {
|
|
104
98
|
if (instance.visible && !instance.skipSyncContainer) {
|
|
105
99
|
instance.syncContainer();
|
|
106
|
-
}
|
|
100
|
+
}
|
|
101
|
+
// Resume widget->container syncing after the widget scrolling has finished
|
|
107
102
|
// (it might be temporally disabled by the container while syncing the widget)
|
|
108
|
-
|
|
109
|
-
|
|
110
103
|
instance.skipSyncContainer = false;
|
|
111
104
|
}
|
|
112
105
|
}
|
|
@@ -116,22 +109,24 @@ https://amphiluke.github.io/handy-scroll/
|
|
|
116
109
|
scroll: function scroll() {
|
|
117
110
|
if (!instance.skipSyncWidget) {
|
|
118
111
|
instance.syncWidget();
|
|
119
|
-
}
|
|
112
|
+
}
|
|
113
|
+
// Resume container->widget syncing after the container scrolling has finished
|
|
120
114
|
// (it might be temporally disabled by the widget while syncing the container)
|
|
121
|
-
|
|
122
|
-
|
|
123
115
|
instance.skipSyncWidget = false;
|
|
124
116
|
},
|
|
125
117
|
focusin: function focusin() {
|
|
126
118
|
setTimeout(function () {
|
|
127
|
-
|
|
119
|
+
// The widget might be destroyed before the timer is triggered (issue #14)
|
|
120
|
+
if (instance.widget) {
|
|
121
|
+
instance.syncWidget();
|
|
122
|
+
}
|
|
128
123
|
}, 0);
|
|
129
124
|
}
|
|
130
125
|
}
|
|
131
126
|
}];
|
|
132
127
|
eventHandlers.forEach(function (_ref) {
|
|
133
128
|
var el = _ref.el,
|
|
134
|
-
|
|
129
|
+
handlers = _ref.handlers;
|
|
135
130
|
Object.keys(handlers).forEach(function (event) {
|
|
136
131
|
return el.addEventListener(event, handlers[event], false);
|
|
137
132
|
});
|
|
@@ -140,41 +135,37 @@ https://amphiluke.github.io/handy-scroll/
|
|
|
140
135
|
checkVisibility: function checkVisibility() {
|
|
141
136
|
var instance = this;
|
|
142
137
|
var widget = instance.widget,
|
|
143
|
-
|
|
144
|
-
|
|
138
|
+
container = instance.container,
|
|
139
|
+
scrollBody = instance.scrollBody;
|
|
145
140
|
var mustHide = widget.scrollWidth <= widget.offsetWidth;
|
|
146
|
-
|
|
147
141
|
if (!mustHide) {
|
|
148
142
|
var containerRect = container.getBoundingClientRect();
|
|
149
143
|
var maxVisibleY = scrollBody ? scrollBody.getBoundingClientRect().bottom : window.innerHeight || dom.html.clientHeight;
|
|
150
144
|
mustHide = containerRect.bottom <= maxVisibleY || containerRect.top > maxVisibleY;
|
|
151
145
|
}
|
|
152
|
-
|
|
153
146
|
if (instance.visible === mustHide) {
|
|
154
|
-
instance.visible = !mustHide;
|
|
155
|
-
|
|
147
|
+
instance.visible = !mustHide;
|
|
148
|
+
// We cannot simply hide the scrollbar since its scrollLeft property will not update in that case
|
|
156
149
|
widget.classList.toggle("handy-scroll-hidden");
|
|
157
150
|
}
|
|
158
151
|
},
|
|
159
152
|
syncContainer: function syncContainer() {
|
|
160
153
|
var instance = this;
|
|
161
154
|
var scrollLeft = instance.widget.scrollLeft;
|
|
162
|
-
|
|
163
155
|
if (instance.container.scrollLeft !== scrollLeft) {
|
|
164
156
|
// Prevents container’s “scroll” event handler from syncing back again widget scroll position
|
|
165
|
-
instance.skipSyncWidget = true;
|
|
166
|
-
|
|
157
|
+
instance.skipSyncWidget = true;
|
|
158
|
+
// Note that this makes container’s “scroll” event handlers execute
|
|
167
159
|
instance.container.scrollLeft = scrollLeft;
|
|
168
160
|
}
|
|
169
161
|
},
|
|
170
162
|
syncWidget: function syncWidget() {
|
|
171
163
|
var instance = this;
|
|
172
164
|
var scrollLeft = instance.container.scrollLeft;
|
|
173
|
-
|
|
174
165
|
if (instance.widget.scrollLeft !== scrollLeft) {
|
|
175
166
|
// Prevents widget’s “scroll” event handler from syncing back again container scroll position
|
|
176
|
-
instance.skipSyncContainer = true;
|
|
177
|
-
|
|
167
|
+
instance.skipSyncContainer = true;
|
|
168
|
+
// Note that this makes widget’s “scroll” event handlers execute
|
|
178
169
|
instance.widget.scrollLeft = scrollLeft;
|
|
179
170
|
}
|
|
180
171
|
},
|
|
@@ -182,18 +173,16 @@ https://amphiluke.github.io/handy-scroll/
|
|
|
182
173
|
update: function update() {
|
|
183
174
|
var instance = this;
|
|
184
175
|
var widget = instance.widget,
|
|
185
|
-
|
|
186
|
-
|
|
176
|
+
container = instance.container,
|
|
177
|
+
scrollBody = instance.scrollBody;
|
|
187
178
|
var clientWidth = container.clientWidth,
|
|
188
|
-
|
|
179
|
+
scrollWidth = container.scrollWidth;
|
|
189
180
|
widget.style.width = clientWidth + "px";
|
|
190
|
-
|
|
191
181
|
if (!scrollBody) {
|
|
192
182
|
widget.style.left = container.getBoundingClientRect().left + "px";
|
|
193
183
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
184
|
+
widget.firstElementChild.style.width = scrollWidth + "px";
|
|
185
|
+
// Fit widget height to the native scroll bar height if needed
|
|
197
186
|
if (scrollWidth > clientWidth) {
|
|
198
187
|
widget.style.height = widget.offsetHeight - widget.clientHeight + 1 + "px"; // +1px JIC
|
|
199
188
|
}
|
|
@@ -206,7 +195,7 @@ https://amphiluke.github.io/handy-scroll/
|
|
|
206
195
|
var instance = this;
|
|
207
196
|
instance.eventHandlers.forEach(function (_ref2) {
|
|
208
197
|
var el = _ref2.el,
|
|
209
|
-
|
|
198
|
+
handlers = _ref2.handlers;
|
|
210
199
|
Object.keys(handlers).forEach(function (event) {
|
|
211
200
|
return el.removeEventListener(event, handlers[event], false);
|
|
212
201
|
});
|
|
@@ -216,25 +205,23 @@ https://amphiluke.github.io/handy-scroll/
|
|
|
216
205
|
}
|
|
217
206
|
};
|
|
218
207
|
|
|
219
|
-
var instances = []; // if it were not for IE it would be better to use
|
|
208
|
+
var instances = []; // if it were not for IE, it would be better to use Map (container -> instance)
|
|
220
209
|
|
|
221
210
|
var handyScroll = {
|
|
222
211
|
/**
|
|
223
212
|
* Mount widgets in the given containers
|
|
224
|
-
* @param {HTMLElement|NodeList|HTMLCollection|
|
|
213
|
+
* @param {HTMLElement|NodeList|HTMLCollection|HTMLElement[]|String} containerRef - Widget container reference (either an element, or a list of elements, or a selector)
|
|
225
214
|
*/
|
|
226
215
|
mount: function mount(containerRef) {
|
|
227
216
|
dom.$$(containerRef).forEach(function (container) {
|
|
228
217
|
if (handyScroll.mounted(container)) {
|
|
229
218
|
return;
|
|
230
219
|
}
|
|
231
|
-
|
|
232
220
|
var instance = Object.create(handyScrollProto);
|
|
233
221
|
instances.push(instance);
|
|
234
222
|
instance.init(container);
|
|
235
223
|
});
|
|
236
224
|
},
|
|
237
|
-
|
|
238
225
|
/**
|
|
239
226
|
* Check if a widget is already mounted in the given container
|
|
240
227
|
* @param {HTMLElement|String} containerRef - Widget container reference (either an element, or a selector)
|
|
@@ -246,10 +233,9 @@ https://amphiluke.github.io/handy-scroll/
|
|
|
246
233
|
return instance.container === container;
|
|
247
234
|
});
|
|
248
235
|
},
|
|
249
|
-
|
|
250
236
|
/**
|
|
251
237
|
* Update widget parameters and position
|
|
252
|
-
* @param {HTMLElement|NodeList|HTMLCollection|
|
|
238
|
+
* @param {HTMLElement|NodeList|HTMLCollection|HTMLElement[]|String} containerRef - Widget container reference (either an element, or a list of elements, or a selector)
|
|
253
239
|
*/
|
|
254
240
|
update: function update(containerRef) {
|
|
255
241
|
dom.$$(containerRef).forEach(function (container) {
|
|
@@ -258,15 +244,13 @@ https://amphiluke.github.io/handy-scroll/
|
|
|
258
244
|
instance.update();
|
|
259
245
|
return true;
|
|
260
246
|
}
|
|
261
|
-
|
|
262
247
|
return false;
|
|
263
248
|
});
|
|
264
249
|
});
|
|
265
250
|
},
|
|
266
|
-
|
|
267
251
|
/**
|
|
268
252
|
* Destroy widgets mounted in the given containers
|
|
269
|
-
* @param {HTMLElement|NodeList|HTMLCollection|
|
|
253
|
+
* @param {HTMLElement|NodeList|HTMLCollection|HTMLElement[]|String} containerRef - Widget container reference (either an element, or a list of elements, or a selector)
|
|
270
254
|
*/
|
|
271
255
|
destroy: function destroy(containerRef) {
|
|
272
256
|
dom.$$(containerRef).forEach(function (container) {
|
|
@@ -275,13 +259,23 @@ https://amphiluke.github.io/handy-scroll/
|
|
|
275
259
|
instances.splice(index, 1)[0].destroy();
|
|
276
260
|
return true;
|
|
277
261
|
}
|
|
278
|
-
|
|
279
262
|
return false;
|
|
280
263
|
});
|
|
281
264
|
});
|
|
265
|
+
},
|
|
266
|
+
/**
|
|
267
|
+
* Destroy handyScroll widgets whose containers are not in the document anymore
|
|
268
|
+
*/
|
|
269
|
+
destroyDetached: function destroyDetached() {
|
|
270
|
+
instances = instances.filter(function (instance) {
|
|
271
|
+
if (!dom.body.contains(instance.container)) {
|
|
272
|
+
instance.destroy();
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
return true;
|
|
276
|
+
});
|
|
282
277
|
}
|
|
283
278
|
};
|
|
284
|
-
|
|
285
279
|
if (dom.isDOMAvailable) {
|
|
286
280
|
dom.ready(function () {
|
|
287
281
|
handyScroll.mount("[data-handy-scroll]");
|
|
@@ -290,4 +284,4 @@ https://amphiluke.github.io/handy-scroll/
|
|
|
290
284
|
|
|
291
285
|
return handyScroll;
|
|
292
286
|
|
|
293
|
-
}))
|
|
287
|
+
}));
|
package/dist/handy-scroll.min.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
handy-scroll v1.
|
|
2
|
+
handy-scroll v1.1.1
|
|
3
3
|
https://amphiluke.github.io/handy-scroll/
|
|
4
|
-
(c)
|
|
4
|
+
(c) 2022 Amphiluke
|
|
5
5
|
*/
|
|
6
|
-
!function(
|
|
6
|
+
!function(t,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):(t="undefined"!=typeof globalThis?globalThis:t||self).handyScroll=n()}(this,(function(){"use strict";var t=Array.prototype.slice,n="object"==typeof document&&!!document.documentElement,e={isDOMAvailable:n,doc:n?document:null,html:n?document.documentElement:null,body:n?document.body:null,ready:function(t){"loading"===e.doc.readyState?e.doc.addEventListener("DOMContentLoaded",(function(){t()}),{once:!0}):t()},$:function(t){return"string"==typeof t?e.body.querySelector(t):t},$$:function(n){return Array.isArray(n)?n:n.nodeType===Node.ELEMENT_NODE?[n]:"string"==typeof n?t.call(e.body.querySelectorAll(n)):t.call(n)}},i={init:function(t){var n=this,i=e.$$(".handy-scroll-body").filter((function(n){return n.contains(t)}));i.length&&(n.scrollBody=i[0]),n.container=t,n.visible=!0,n.initWidget(),n.update(),n.addEventHandlers(),n.skipSyncContainer=n.skipSyncWidget=!1},initWidget:function(){var t=this,n=t.widget=e.doc.createElement("div");n.classList.add("handy-scroll");var i=e.doc.createElement("div");i.style.width=t.container.scrollWidth+"px",n.appendChild(i),t.container.appendChild(n)},addEventHandlers:function(){var t=this;(t.eventHandlers=[{el:t.scrollBody||window,handlers:{scroll:function(){t.checkVisibility()},resize:function(){t.update()}}},{el:t.widget,handlers:{scroll:function(){t.visible&&!t.skipSyncContainer&&t.syncContainer(),t.skipSyncContainer=!1}}},{el:t.container,handlers:{scroll:function(){t.skipSyncWidget||t.syncWidget(),t.skipSyncWidget=!1},focusin:function(){setTimeout((function(){t.widget&&t.syncWidget()}),0)}}}]).forEach((function(t){var n=t.el,e=t.handlers;Object.keys(e).forEach((function(t){return n.addEventListener(t,e[t],!1)}))}))},checkVisibility:function(){var t=this,n=t.widget,i=t.container,o=t.scrollBody,c=n.scrollWidth<=n.offsetWidth;if(!c){var r=i.getBoundingClientRect(),l=o?o.getBoundingClientRect().bottom:window.innerHeight||e.html.clientHeight;c=r.bottom<=l||r.top>l}t.visible===c&&(t.visible=!c,n.classList.toggle("handy-scroll-hidden"))},syncContainer:function(){var t=this,n=t.widget.scrollLeft;t.container.scrollLeft!==n&&(t.skipSyncWidget=!0,t.container.scrollLeft=n)},syncWidget:function(){var t=this,n=t.container.scrollLeft;t.widget.scrollLeft!==n&&(t.skipSyncContainer=!0,t.widget.scrollLeft=n)},update:function(){var t=this,n=t.widget,e=t.container,i=t.scrollBody,o=e.clientWidth,c=e.scrollWidth;n.style.width=o+"px",i||(n.style.left=e.getBoundingClientRect().left+"px"),n.firstElementChild.style.width=c+"px",c>o&&(n.style.height=n.offsetHeight-n.clientHeight+1+"px"),t.syncWidget(),t.checkVisibility()},destroy:function(){var t=this;t.eventHandlers.forEach((function(t){var n=t.el,e=t.handlers;Object.keys(e).forEach((function(t){return n.removeEventListener(t,e[t],!1)}))})),t.widget.parentNode.removeChild(t.widget),t.eventHandlers=t.widget=t.container=t.scrollBody=null}},o=[],c={mount:function(t){e.$$(t).forEach((function(t){if(!c.mounted(t)){var n=Object.create(i);o.push(n),n.init(t)}}))},mounted:function(t){var n=e.$(t);return o.some((function(t){return t.container===n}))},update:function(t){e.$$(t).forEach((function(t){o.some((function(n){return n.container===t&&(n.update(),!0)}))}))},destroy:function(t){e.$$(t).forEach((function(t){o.some((function(n,e){return n.container===t&&(o.splice(e,1)[0].destroy(),!0)}))}))},destroyDetached:function(){o=o.filter((function(t){return!!e.body.contains(t.container)||(t.destroy(),!1)}))}};return e.isDOMAvailable&&e.ready((function(){c.mount("[data-handy-scroll]")})),c}));
|
package/package.json
CHANGED
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "handy-scroll",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Handy dependency-free floating scrollbar widget",
|
|
5
|
-
"main": "dist/handy-scroll.min.js",
|
|
6
|
-
"module": "src/handy-scroll.js",
|
|
7
|
-
"style": "dist/handy-scroll.css",
|
|
5
|
+
"main": "./dist/handy-scroll.min.js",
|
|
6
|
+
"module": "./src/handy-scroll.js",
|
|
7
|
+
"style": "./dist/handy-scroll.css",
|
|
8
|
+
"types": "./dist/handy-scroll.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
"types": "./dist/handy-scroll.d.ts",
|
|
11
|
+
"import": "./src/handy-scroll.js",
|
|
12
|
+
"require": "./dist/handy-scroll.js"
|
|
13
|
+
},
|
|
14
|
+
"type": "module",
|
|
8
15
|
"files": [
|
|
9
16
|
"dist",
|
|
10
|
-
"doc",
|
|
11
17
|
"src"
|
|
12
18
|
],
|
|
13
19
|
"scripts": {
|
|
14
20
|
"prepare": "husky install",
|
|
15
21
|
"lint": "eslint src/*.js",
|
|
16
22
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
17
|
-
"build": "rollup -c && lessc src/handy-scroll.less | cleancss -O2 -o dist/handy-scroll.css"
|
|
23
|
+
"build": "rollup -c --bundleConfigAsCjs && lessc src/handy-scroll.less | cleancss -O2 -o dist/handy-scroll.css"
|
|
18
24
|
},
|
|
19
25
|
"repository": {
|
|
20
26
|
"type": "git",
|
|
@@ -32,14 +38,14 @@
|
|
|
32
38
|
},
|
|
33
39
|
"homepage": "https://amphiluke.github.io/handy-scroll/",
|
|
34
40
|
"devDependencies": {
|
|
35
|
-
"@babel/core": "^7.
|
|
36
|
-
"@babel/preset-env": "^7.
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"rollup
|
|
41
|
+
"@babel/core": "^7.20.5",
|
|
42
|
+
"@babel/preset-env": "^7.20.2",
|
|
43
|
+
"@rollup/plugin-babel": "^6.0.3",
|
|
44
|
+
"@rollup/plugin-terser": "^0.2.0",
|
|
45
|
+
"clean-css-cli": "^5.6.1",
|
|
46
|
+
"eslint": "^8.29.0",
|
|
47
|
+
"husky": "8.0.2",
|
|
48
|
+
"less": "^4.1.3",
|
|
49
|
+
"rollup": "^3.6.0"
|
|
44
50
|
}
|
|
45
51
|
}
|
|
@@ -66,7 +66,12 @@ let handyScrollProto = {
|
|
|
66
66
|
instance.skipSyncWidget = false;
|
|
67
67
|
},
|
|
68
68
|
focusin() {
|
|
69
|
-
setTimeout(() =>
|
|
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);
|
|
70
75
|
}
|
|
71
76
|
}
|
|
72
77
|
}
|
package/src/handy-scroll.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import dom from "./dom.js";
|
|
2
2
|
import handyScrollProto from "./handy-scroll-proto";
|
|
3
3
|
|
|
4
|
-
let instances = []; // if it were not for IE it would be better to use
|
|
4
|
+
let instances = []; // if it were not for IE, it would be better to use Map (container -> instance)
|
|
5
5
|
|
|
6
6
|
let handyScroll = {
|
|
7
7
|
/**
|
|
8
8
|
* Mount widgets in the given containers
|
|
9
|
-
* @param {HTMLElement|NodeList|HTMLCollection|
|
|
9
|
+
* @param {HTMLElement|NodeList|HTMLCollection|HTMLElement[]|String} containerRef - Widget container reference (either an element, or a list of elements, or a selector)
|
|
10
10
|
*/
|
|
11
11
|
mount(containerRef) {
|
|
12
12
|
dom.$$(containerRef).forEach(container => {
|
|
@@ -31,7 +31,7 @@ let handyScroll = {
|
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* Update widget parameters and position
|
|
34
|
-
* @param {HTMLElement|NodeList|HTMLCollection|
|
|
34
|
+
* @param {HTMLElement|NodeList|HTMLCollection|HTMLElement[]|String} containerRef - Widget container reference (either an element, or a list of elements, or a selector)
|
|
35
35
|
*/
|
|
36
36
|
update(containerRef) {
|
|
37
37
|
dom.$$(containerRef).forEach(container => {
|
|
@@ -47,7 +47,7 @@ let handyScroll = {
|
|
|
47
47
|
|
|
48
48
|
/**
|
|
49
49
|
* Destroy widgets mounted in the given containers
|
|
50
|
-
* @param {HTMLElement|NodeList|HTMLCollection|
|
|
50
|
+
* @param {HTMLElement|NodeList|HTMLCollection|HTMLElement[]|String} containerRef - Widget container reference (either an element, or a list of elements, or a selector)
|
|
51
51
|
*/
|
|
52
52
|
destroy(containerRef) {
|
|
53
53
|
dom.$$(containerRef).forEach(container => {
|
|
@@ -59,6 +59,19 @@ let handyScroll = {
|
|
|
59
59
|
return false;
|
|
60
60
|
});
|
|
61
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 (!dom.body.contains(instance.container)) {
|
|
70
|
+
instance.destroy();
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
return true;
|
|
74
|
+
});
|
|
62
75
|
}
|
|
63
76
|
};
|
|
64
77
|
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
# Using handy-scroll in an Angular app
|
|
2
|
-
|
|
3
|
-
handy-scroll is a “VanillaJS” module, which means that you may need to perform a few additional steps to make it ready for use with your favorite framework. Here are the steps to install and use the handy-scroll widget in a sample Angular app. This instruction is partly adopted from the [“official” Angular CLI guide](https://github.com/angular/angular-cli/wiki/stories-third-party-lib) on installing a third party library in an Angular project.
|
|
4
|
-
|
|
5
|
-
1. Install the handy-scroll package locally as your project’s dependency:
|
|
6
|
-
```
|
|
7
|
-
npm install --save handy-scroll
|
|
8
|
-
```
|
|
9
|
-
1. Include widget styles into your project’s [global styles](https://github.com/angular/angular-cli/wiki/stories-global-styles). You may import widget styles in your global stylesheet file `styles.css`:
|
|
10
|
-
```css
|
|
11
|
-
@import "../node_modules/handy-scroll/dist/handy-scroll.css";
|
|
12
|
-
```
|
|
13
|
-
1. Create a `typings.d.ts` file in your `src/` folder. This file will be automatically included as global type definition.
|
|
14
|
-
1. In `src/typings.d.ts`, add the following code:
|
|
15
|
-
```typescript
|
|
16
|
-
declare module 'handy-scroll';
|
|
17
|
-
```
|
|
18
|
-
1. In the component or file that uses the handy-scroll widget, add the following code:
|
|
19
|
-
```typescript
|
|
20
|
-
import handyScroll from 'handy-scroll';
|
|
21
|
-
```
|
|
22
|
-
1. You may now use the widget API in your code as usual. E.g. in a component:
|
|
23
|
-
```typescript
|
|
24
|
-
import {AfterViewInit, Component, ElementRef, ViewChild} from '@angular/core';
|
|
25
|
-
import handyScroll from 'handy-scroll'; // 1) import
|
|
26
|
-
|
|
27
|
-
@Component({
|
|
28
|
-
selector: 'app-root',
|
|
29
|
-
template: `
|
|
30
|
-
<div #mycontainer class="my-container">
|
|
31
|
-
<div>{{ "Long contents. ".repeat(2000) }}</div>
|
|
32
|
-
</div>
|
|
33
|
-
`,
|
|
34
|
-
styles: ['.my-container {overflow:auto} .my-container div {min-width:150vw}']
|
|
35
|
-
})
|
|
36
|
-
export class AppComponent implements AfterViewInit {
|
|
37
|
-
@ViewChild('mycontainer') myContainer: ElementRef;
|
|
38
|
-
|
|
39
|
-
ngAfterViewInit() {
|
|
40
|
-
handyScroll.mount(this.myContainer.nativeElement); // 2) use
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
You may also play with a [live demo](https://stackblitz.com/edit/angular-handy-scroll-integration).
|