handy-scroll 2.0.2 → 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/dist/handy-scroll.mjs +28 -38
- package/package.json +6 -6
- package/src/handy-scroll.mjs +35 -39
package/dist/handy-scroll.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
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
10
|
return ["owner", "viewport", "hidden"];
|
|
11
11
|
}
|
|
@@ -13,7 +13,7 @@ class r extends HTMLElement {
|
|
|
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,27 +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
|
-
if (this.#i
|
|
50
|
+
if (this.#i) {
|
|
51
51
|
if (t === "hidden") {
|
|
52
52
|
this.hasAttribute("hidden") || this.update();
|
|
53
53
|
return;
|
|
54
54
|
}
|
|
55
|
-
t === "owner" ? this.#a() : t === "viewport" && this.#c(), this.#w(), this.#
|
|
55
|
+
t === "owner" ? this.#a() : t === "viewport" && this.#c(), this.#w(), this.#f(), this.#u(), this.#p(), this.update();
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
#a() {
|
|
59
59
|
let t = this.getAttribute("owner");
|
|
60
60
|
if (this.#e = document.getElementById(t), !this.#e)
|
|
61
|
-
throw new DOMException(
|
|
61
|
+
throw new DOMException(n("owner"));
|
|
62
62
|
}
|
|
63
63
|
#c() {
|
|
64
64
|
if (!this.hasAttribute("viewport")) {
|
|
@@ -67,40 +67,30 @@ class r extends HTMLElement {
|
|
|
67
67
|
}
|
|
68
68
|
let t = this.getAttribute("viewport");
|
|
69
69
|
if (this.#t = document.getElementById(t), !this.#t)
|
|
70
|
-
throw new DOMException(
|
|
70
|
+
throw new DOMException(n("viewport"));
|
|
71
71
|
}
|
|
72
72
|
#u() {
|
|
73
|
-
this.#i
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
focusin: () => {
|
|
85
|
-
setTimeout(() => {
|
|
86
|
-
this.isConnected && this.#d();
|
|
87
|
-
}, 0);
|
|
88
|
-
}
|
|
89
|
-
}), this.#i.forEach((t, e) => {
|
|
90
|
-
Object.entries(t).forEach(([i, s]) => e.addEventListener(i, s, !1));
|
|
91
|
-
});
|
|
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);
|
|
92
84
|
}
|
|
93
85
|
#w() {
|
|
94
|
-
this.#i
|
|
95
|
-
Object.entries(t).forEach(([i, s]) => e.removeEventListener(i, s, !1));
|
|
96
|
-
}), this.#i.clear();
|
|
86
|
+
this.#i?.abort(), this.#i = null;
|
|
97
87
|
}
|
|
98
|
-
#
|
|
88
|
+
#p() {
|
|
99
89
|
this.#t !== window && (this.#n = new ResizeObserver(([t]) => {
|
|
100
90
|
t.contentBoxSize?.[0]?.inlineSize && this.update();
|
|
101
91
|
}), this.#n.observe(this.#t));
|
|
102
92
|
}
|
|
103
|
-
#
|
|
93
|
+
#f() {
|
|
104
94
|
this.#n?.disconnect(), this.#n = null;
|
|
105
95
|
}
|
|
106
96
|
#b() {
|
|
@@ -124,7 +114,7 @@ class r extends HTMLElement {
|
|
|
124
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();
|
|
125
115
|
}
|
|
126
116
|
}
|
|
127
|
-
customElements.define("handy-scroll",
|
|
117
|
+
customElements.define("handy-scroll", o);
|
|
128
118
|
export {
|
|
129
|
-
|
|
119
|
+
o as default
|
|
130
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.mjs
CHANGED
|
@@ -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,7 @@ 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
74
|
return;
|
|
75
75
|
}
|
|
76
76
|
if (name === "hidden") {
|
|
@@ -112,48 +112,44 @@ class HandyScroll extends HTMLElement {
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
#addEventHandlers() {
|
|
115
|
-
this.#
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
this.#
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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) {
|
|
132
144
|
this.#syncComponent();
|
|
133
145
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
this.#syncingComponent = true;
|
|
137
|
-
},
|
|
138
|
-
focusin: () => {
|
|
139
|
-
setTimeout(() => {
|
|
140
|
-
// The widget might be destroyed before the timer is triggered (issue #14)
|
|
141
|
-
if (this.isConnected) {
|
|
142
|
-
this.#syncComponent();
|
|
143
|
-
}
|
|
144
|
-
}, 0);
|
|
145
|
-
},
|
|
146
|
-
});
|
|
147
|
-
this.#eventHandlers.forEach((handlers, el) => {
|
|
148
|
-
Object.entries(handlers).forEach(([event, handler]) => el.addEventListener(event, handler, false));
|
|
149
|
-
});
|
|
146
|
+
}, 0);
|
|
147
|
+
}, options);
|
|
150
148
|
}
|
|
151
149
|
|
|
152
150
|
#removeEventHandlers() {
|
|
153
|
-
this.#
|
|
154
|
-
|
|
155
|
-
});
|
|
156
|
-
this.#eventHandlers.clear();
|
|
151
|
+
this.#eventController?.abort();
|
|
152
|
+
this.#eventController = null;
|
|
157
153
|
}
|
|
158
154
|
|
|
159
155
|
#addResizeObserver() {
|