handy-scroll 2.0.1 → 2.0.3
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 +6 -0
- package/dist/handy-scroll.mjs +34 -38
- package/package.json +6 -6
- package/src/handy-scroll.css +4 -1
- package/src/handy-scroll.mjs +42 -40
package/README.md
CHANGED
|
@@ -29,6 +29,12 @@ If you don’t use bundlers, just import the component as a module in your HTML
|
|
|
29
29
|
<script type="module" src="https://esm.run/handy-scroll"></script>
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
+
or in your ES modules:
|
|
33
|
+
|
|
34
|
+
```javascript
|
|
35
|
+
import "https://esm.run/handy-scroll";
|
|
36
|
+
```
|
|
37
|
+
|
|
32
38
|
## Standard usage
|
|
33
39
|
|
|
34
40
|
Drop the custom element `<handy-scroll>` where you need in your markup and link the component to the horizontally-scrollable target using the `owner` attribute:
|
package/dist/handy-scroll.mjs
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
handy-scroll v2.0.
|
|
2
|
+
handy-scroll v2.0.3
|
|
3
3
|
https://amphiluke.github.io/handy-scroll/
|
|
4
4
|
(c) 2024 Amphiluke
|
|
5
5
|
*/
|
|
6
|
-
const
|
|
7
|
-
let
|
|
8
|
-
class
|
|
6
|
+
const h = ':host{bottom:0;min-height:17px;overflow:auto;position:fixed}.strut{height:1px;overflow:hidden;pointer-events:none;&:before{content:" "}}:host,.strut{font-size:1px;line-height:0;margin:0;padding:0}:host(:state(latent)){bottom:110vh;.strut:before{content:" "}}:host([viewport]:not([hidden])){display:block}:host([viewport]){position:sticky}:host([viewport]:state(latent)){position:fixed}';
|
|
7
|
+
let n = (s) => `Attribute ‘${s}’ must reference a valid container ‘id’`;
|
|
8
|
+
class o extends HTMLElement {
|
|
9
9
|
static get observedAttributes() {
|
|
10
|
-
return ["owner", "viewport"];
|
|
10
|
+
return ["owner", "viewport", "hidden"];
|
|
11
11
|
}
|
|
12
12
|
#o = null;
|
|
13
13
|
#t = null;
|
|
14
14
|
#e = null;
|
|
15
15
|
#s = null;
|
|
16
|
-
#i =
|
|
16
|
+
#i = null;
|
|
17
17
|
#n = null;
|
|
18
18
|
#r = !0;
|
|
19
19
|
#l = !0;
|
|
@@ -38,21 +38,27 @@ class r extends HTMLElement {
|
|
|
38
38
|
constructor() {
|
|
39
39
|
super();
|
|
40
40
|
let t = this.attachShadow({ mode: "open" }), e = document.createElement("style");
|
|
41
|
-
e.textContent =
|
|
41
|
+
e.textContent = h, t.appendChild(e), this.#s = document.createElement("div"), this.#s.classList.add("strut"), t.appendChild(this.#s), this.#o = this.attachInternals();
|
|
42
42
|
}
|
|
43
43
|
connectedCallback() {
|
|
44
|
-
this.#a(), this.#c(), this.#u(), this.#
|
|
44
|
+
this.#a(), this.#c(), this.#u(), this.#p(), this.update();
|
|
45
45
|
}
|
|
46
46
|
disconnectedCallback() {
|
|
47
|
-
this.#w(), this.#
|
|
47
|
+
this.#w(), this.#f(), this.#e = this.#t = null;
|
|
48
48
|
}
|
|
49
49
|
attributeChangedCallback(t) {
|
|
50
|
-
|
|
50
|
+
if (this.#i) {
|
|
51
|
+
if (t === "hidden") {
|
|
52
|
+
this.hasAttribute("hidden") || this.update();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
t === "owner" ? this.#a() : t === "viewport" && this.#c(), this.#w(), this.#f(), this.#u(), this.#p(), this.update();
|
|
56
|
+
}
|
|
51
57
|
}
|
|
52
58
|
#a() {
|
|
53
59
|
let t = this.getAttribute("owner");
|
|
54
60
|
if (this.#e = document.getElementById(t), !this.#e)
|
|
55
|
-
throw new DOMException(
|
|
61
|
+
throw new DOMException(n("owner"));
|
|
56
62
|
}
|
|
57
63
|
#c() {
|
|
58
64
|
if (!this.hasAttribute("viewport")) {
|
|
@@ -61,40 +67,30 @@ class r extends HTMLElement {
|
|
|
61
67
|
}
|
|
62
68
|
let t = this.getAttribute("viewport");
|
|
63
69
|
if (this.#t = document.getElementById(t), !this.#t)
|
|
64
|
-
throw new DOMException(
|
|
70
|
+
throw new DOMException(n("viewport"));
|
|
65
71
|
}
|
|
66
72
|
#u() {
|
|
67
|
-
this.#i
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
focusin: () => {
|
|
79
|
-
setTimeout(() => {
|
|
80
|
-
this.isConnected && this.#d();
|
|
81
|
-
}, 0);
|
|
82
|
-
}
|
|
83
|
-
}), this.#i.forEach((t, e) => {
|
|
84
|
-
Object.entries(t).forEach(([i, s]) => e.addEventListener(i, s, !1));
|
|
85
|
-
});
|
|
73
|
+
this.#i = new AbortController();
|
|
74
|
+
let t = { signal: this.#i.signal };
|
|
75
|
+
this.#t.addEventListener("scroll", () => this.#v(), t), this.#t === window && this.#t.addEventListener("resize", () => this.update(), t), this.addEventListener("scroll", () => {
|
|
76
|
+
this.#r && !this.#h && this.#b(), this.#r = !0;
|
|
77
|
+
}, t), this.#e.addEventListener("scroll", () => {
|
|
78
|
+
this.#l && this.#d(), this.#l = !0;
|
|
79
|
+
}, t), this.#e.addEventListener("focusin", () => {
|
|
80
|
+
setTimeout(() => {
|
|
81
|
+
this.isConnected && this.#d();
|
|
82
|
+
}, 0);
|
|
83
|
+
}, t);
|
|
86
84
|
}
|
|
87
85
|
#w() {
|
|
88
|
-
this.#i
|
|
89
|
-
Object.entries(t).forEach(([i, s]) => e.removeEventListener(i, s, !1));
|
|
90
|
-
}), this.#i.clear();
|
|
86
|
+
this.#i?.abort(), this.#i = null;
|
|
91
87
|
}
|
|
92
|
-
#
|
|
88
|
+
#p() {
|
|
93
89
|
this.#t !== window && (this.#n = new ResizeObserver(([t]) => {
|
|
94
90
|
t.contentBoxSize?.[0]?.inlineSize && this.update();
|
|
95
91
|
}), this.#n.observe(this.#t));
|
|
96
92
|
}
|
|
97
|
-
#
|
|
93
|
+
#f() {
|
|
98
94
|
this.#n?.disconnect(), this.#n = null;
|
|
99
95
|
}
|
|
100
96
|
#b() {
|
|
@@ -118,7 +114,7 @@ class r extends HTMLElement {
|
|
|
118
114
|
i.width = `${t}px`, this.#t === window && (i.left = `${this.#e.getBoundingClientRect().left}px`), this.#s.style.width = `${e}px`, e > t && (i.height = `${this.offsetHeight - this.clientHeight + 1}px`), this.#d(), this.#v();
|
|
119
115
|
}
|
|
120
116
|
}
|
|
121
|
-
customElements.define("handy-scroll",
|
|
117
|
+
customElements.define("handy-scroll", o);
|
|
122
118
|
export {
|
|
123
|
-
|
|
119
|
+
o as default
|
|
124
120
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "handy-scroll",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
4
4
|
"description": "Handy dependency-free floating scrollbar web component",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -36,10 +36,10 @@
|
|
|
36
36
|
},
|
|
37
37
|
"homepage": "https://amphiluke.github.io/handy-scroll/",
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@eslint/js": "^9.
|
|
40
|
-
"@stylistic/eslint-plugin-js": "^2.
|
|
41
|
-
"eslint": "^9.
|
|
42
|
-
"globals": "^15.
|
|
43
|
-
"vite": "^5.4.
|
|
39
|
+
"@eslint/js": "^9.12.0",
|
|
40
|
+
"@stylistic/eslint-plugin-js": "^2.9.0",
|
|
41
|
+
"eslint": "^9.12.0",
|
|
42
|
+
"globals": "^15.10.0",
|
|
43
|
+
"vite": "^5.4.8"
|
|
44
44
|
}
|
|
45
45
|
}
|
package/src/handy-scroll.css
CHANGED
package/src/handy-scroll.mjs
CHANGED
|
@@ -4,7 +4,7 @@ let getAttributeErrorMessage = (attribute) => `Attribute ‘${attribute}’ must
|
|
|
4
4
|
|
|
5
5
|
class HandyScroll extends HTMLElement {
|
|
6
6
|
static get observedAttributes() {
|
|
7
|
-
return ["owner", "viewport"];
|
|
7
|
+
return ["owner", "viewport", "hidden"];
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
#internals = null;
|
|
@@ -13,7 +13,7 @@ class HandyScroll extends HTMLElement {
|
|
|
13
13
|
#owner = null;
|
|
14
14
|
#strut = null;
|
|
15
15
|
|
|
16
|
-
#
|
|
16
|
+
#eventController = null;
|
|
17
17
|
#resizeObserver = null;
|
|
18
18
|
|
|
19
19
|
#syncingOwner = true;
|
|
@@ -70,7 +70,13 @@ class HandyScroll extends HTMLElement {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
attributeChangedCallback(name) {
|
|
73
|
-
if (!this.#
|
|
73
|
+
if (!this.#eventController) { // handle only dynamic changes when the element is completely connected
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (name === "hidden") {
|
|
77
|
+
if (!this.hasAttribute("hidden")) {
|
|
78
|
+
this.update();
|
|
79
|
+
}
|
|
74
80
|
return;
|
|
75
81
|
}
|
|
76
82
|
if (name === "owner") {
|
|
@@ -106,48 +112,44 @@ class HandyScroll extends HTMLElement {
|
|
|
106
112
|
}
|
|
107
113
|
|
|
108
114
|
#addEventHandlers() {
|
|
109
|
-
this.#
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
this.#
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
115
|
+
this.#eventController = new AbortController();
|
|
116
|
+
let options = {signal: this.#eventController.signal};
|
|
117
|
+
|
|
118
|
+
this.#viewport.addEventListener("scroll", () => this.#recheckLatency(), options);
|
|
119
|
+
if (this.#viewport === window) {
|
|
120
|
+
this.#viewport.addEventListener("resize", () => this.update(), options);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
this.addEventListener("scroll", () => {
|
|
124
|
+
if (this.#syncingOwner && !this.#isLatent) {
|
|
125
|
+
this.#syncOwner();
|
|
126
|
+
}
|
|
127
|
+
// Resume component->owner syncing after the component scrolling has finished
|
|
128
|
+
// (it might be temporally disabled by the owner while syncing the component)
|
|
129
|
+
this.#syncingOwner = true;
|
|
130
|
+
}, options);
|
|
131
|
+
|
|
132
|
+
this.#owner.addEventListener("scroll", () => {
|
|
133
|
+
if (this.#syncingComponent) {
|
|
134
|
+
this.#syncComponent();
|
|
135
|
+
}
|
|
136
|
+
// Resume owner->component syncing after the owner scrolling has finished
|
|
137
|
+
// (it might be temporally disabled by the component while syncing the owner)
|
|
138
|
+
this.#syncingComponent = true;
|
|
139
|
+
}, options);
|
|
140
|
+
this.#owner.addEventListener("focusin", () => {
|
|
141
|
+
setTimeout(() => {
|
|
142
|
+
// The widget might be destroyed before the timer is triggered (issue #14)
|
|
143
|
+
if (this.isConnected) {
|
|
126
144
|
this.#syncComponent();
|
|
127
145
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
this.#syncingComponent = true;
|
|
131
|
-
},
|
|
132
|
-
focusin: () => {
|
|
133
|
-
setTimeout(() => {
|
|
134
|
-
// The widget might be destroyed before the timer is triggered (issue #14)
|
|
135
|
-
if (this.isConnected) {
|
|
136
|
-
this.#syncComponent();
|
|
137
|
-
}
|
|
138
|
-
}, 0);
|
|
139
|
-
},
|
|
140
|
-
});
|
|
141
|
-
this.#eventHandlers.forEach((handlers, el) => {
|
|
142
|
-
Object.entries(handlers).forEach(([event, handler]) => el.addEventListener(event, handler, false));
|
|
143
|
-
});
|
|
146
|
+
}, 0);
|
|
147
|
+
}, options);
|
|
144
148
|
}
|
|
145
149
|
|
|
146
150
|
#removeEventHandlers() {
|
|
147
|
-
this.#
|
|
148
|
-
|
|
149
|
-
});
|
|
150
|
-
this.#eventHandlers.clear();
|
|
151
|
+
this.#eventController?.abort();
|
|
152
|
+
this.#eventController = null;
|
|
151
153
|
}
|
|
152
154
|
|
|
153
155
|
#addResizeObserver() {
|