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.
@@ -1,11 +1,11 @@
1
1
  /*!
2
- handy-scroll v2.0.2
2
+ handy-scroll v2.0.3
3
3
  https://amphiluke.github.io/handy-scroll/
4
4
  (c) 2024 Amphiluke
5
5
  */
6
- const o = ':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 h = (n) => `Attribute ‘${n}’ must reference a valid container ‘id’`;
8
- class r extends HTMLElement {
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 = /* @__PURE__ */ new Map();
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 = o, t.appendChild(e), this.#s = document.createElement("div"), this.#s.classList.add("strut"), t.appendChild(this.#s), this.#o = this.attachInternals();
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.#f(), this.update();
44
+ this.#a(), this.#c(), this.#u(), this.#p(), this.update();
45
45
  }
46
46
  disconnectedCallback() {
47
- this.#w(), this.#p(), this.#e = this.#t = null;
47
+ this.#w(), this.#f(), this.#e = this.#t = null;
48
48
  }
49
49
  attributeChangedCallback(t) {
50
- if (this.#i.size) {
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.#p(), this.#u(), this.#f(), this.update();
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(h("owner"));
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(h("viewport"));
70
+ throw new DOMException(n("viewport"));
71
71
  }
72
72
  #u() {
73
- this.#i.set(this.#t, {
74
- scroll: () => this.#v(),
75
- ...this.#t === window ? { resize: () => this.update() } : {}
76
- }), this.#i.set(this, {
77
- scroll: () => {
78
- this.#r && !this.#h && this.#b(), this.#r = !0;
79
- }
80
- }), this.#i.set(this.#e, {
81
- scroll: () => {
82
- this.#l && this.#d(), this.#l = !0;
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.forEach((t, e) => {
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
- #f() {
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
- #p() {
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", r);
117
+ customElements.define("handy-scroll", o);
128
118
  export {
129
- r as default
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.2",
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.10.0",
40
- "@stylistic/eslint-plugin-js": "^2.8.0",
41
- "eslint": "^9.10.0",
42
- "globals": "^15.9.0",
43
- "vite": "^5.4.5"
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
  }
@@ -13,7 +13,7 @@ class HandyScroll extends HTMLElement {
13
13
  #owner = null;
14
14
  #strut = null;
15
15
 
16
- #eventHandlers = new Map();
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.#eventHandlers.size) { // handle only dynamic changes when the element is completely connected
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.#eventHandlers.set(this.#viewport, {
116
- scroll: () => this.#recheckLatency(),
117
- ...(this.#viewport === window ? {resize: () => this.update()} : {}),
118
- });
119
- this.#eventHandlers.set(this, {
120
- scroll: () => {
121
- if (this.#syncingOwner && !this.#isLatent) {
122
- this.#syncOwner();
123
- }
124
- // Resume component->owner syncing after the component scrolling has finished
125
- // (it might be temporally disabled by the owner while syncing the component)
126
- this.#syncingOwner = true;
127
- },
128
- });
129
- this.#eventHandlers.set(this.#owner, {
130
- scroll: () => {
131
- if (this.#syncingComponent) {
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
- // Resume owner->component syncing after the owner scrolling has finished
135
- // (it might be temporally disabled by the component while syncing the owner)
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.#eventHandlers.forEach((handlers, el) => {
154
- Object.entries(handlers).forEach(([event, handler]) => el.removeEventListener(event, handler, false));
155
- });
156
- this.#eventHandlers.clear();
151
+ this.#eventController?.abort();
152
+ this.#eventController = null;
157
153
  }
158
154
 
159
155
  #addResizeObserver() {