@tobedone-de/ameva-scrollbar 0.4.2

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/src/index.js ADDED
@@ -0,0 +1,183 @@
1
+ import './styles.css';
2
+ import { AmevaScrollbar } from './AmevaScrollbar.js';
3
+
4
+ const DEFAULT_ELEMENT_SCROLLBAR_SELECTOR = '[data-amevascrollbar]';
5
+ const elementScrollbarInstanceMap = new WeakMap();
6
+ let viewportScrollbarInstance = null;
7
+ let viewportScrollbarHost = null;
8
+
9
+ export const getScopedElements = (container = document, selector = DEFAULT_ELEMENT_SCROLLBAR_SELECTOR) => {
10
+ if (!container || typeof container.querySelectorAll !== 'function') {
11
+ return [];
12
+ }
13
+
14
+ if (container instanceof Element && container.matches(selector)) {
15
+ return [container, ...container.querySelectorAll(selector)];
16
+ }
17
+
18
+ return [...container.querySelectorAll(selector)];
19
+ };
20
+
21
+ export const initializeElementScrollbar = (container = document, options = {}) => {
22
+ const selector = options.selector ?? DEFAULT_ELEMENT_SCROLLBAR_SELECTOR;
23
+ const instances = [];
24
+
25
+ getScopedElements(container, selector).forEach((element) => {
26
+ const existingInstance = elementScrollbarInstanceMap.get(element);
27
+
28
+ if (existingInstance) {
29
+ existingInstance.refresh();
30
+ instances.push(existingInstance);
31
+
32
+ return;
33
+ }
34
+
35
+ const horizontal = element.dataset.amevascrollbarX === 'true';
36
+ const vertical = element.dataset.amevascrollbarY !== 'false';
37
+ const scrollbar = new AmevaScrollbar({
38
+ host: element,
39
+ scrollElement: element,
40
+ horizontal,
41
+ vertical,
42
+ });
43
+
44
+ scrollbar.initialize();
45
+ elementScrollbarInstanceMap.set(element, scrollbar);
46
+ instances.push(scrollbar);
47
+ });
48
+
49
+ return instances;
50
+ };
51
+
52
+ export const destroyElementScrollbar = (element) => {
53
+ if (!(element instanceof Element)) {
54
+ return false;
55
+ }
56
+
57
+ const scrollbar = elementScrollbarInstanceMap.get(element);
58
+
59
+ if (!scrollbar) {
60
+ return false;
61
+ }
62
+
63
+ scrollbar.destroy();
64
+ elementScrollbarInstanceMap.delete(element);
65
+
66
+ return true;
67
+ };
68
+
69
+ export const destroyScrollbar = (container = document, options = {}) => {
70
+ const selector = options.selector ?? DEFAULT_ELEMENT_SCROLLBAR_SELECTOR;
71
+ let elements = 0;
72
+
73
+ getScopedElements(container, selector).forEach((element) => {
74
+ if (destroyElementScrollbar(element)) {
75
+ elements += 1;
76
+ }
77
+ });
78
+
79
+ const viewport = options.viewport
80
+ ? destroyViewportScrollbar()
81
+ : false;
82
+
83
+ return { elements, viewport };
84
+ };
85
+
86
+ export const initializeViewportScrollbar = (options = {}) => {
87
+ const {
88
+ enabled = true,
89
+ host = document.body,
90
+ scrollElement = document.scrollingElement || document.documentElement,
91
+ horizontal = false,
92
+ vertical = true,
93
+ } = options;
94
+
95
+ if (!enabled) {
96
+ destroyViewportScrollbar();
97
+
98
+ return null;
99
+ }
100
+
101
+ if (!(host instanceof HTMLElement)) {
102
+ return null;
103
+ }
104
+
105
+ if (viewportScrollbarInstance) {
106
+ if (
107
+ viewportScrollbarHost === host
108
+ && viewportScrollbarInstance.scrollElement === scrollElement
109
+ && viewportScrollbarInstance.horizontalEnabled === horizontal
110
+ && viewportScrollbarInstance.verticalEnabled === vertical
111
+ ) {
112
+ viewportScrollbarInstance.refresh();
113
+
114
+ return viewportScrollbarInstance;
115
+ }
116
+
117
+ destroyViewportScrollbar();
118
+ }
119
+
120
+ viewportScrollbarInstance = new AmevaScrollbar({
121
+ host,
122
+ scrollElement,
123
+ viewport: true,
124
+ horizontal,
125
+ vertical,
126
+ });
127
+
128
+ viewportScrollbarInstance.initialize();
129
+ viewportScrollbarHost = host;
130
+ host.dataset.amevascrollbarViewportInitialized = 'true';
131
+
132
+ return viewportScrollbarInstance;
133
+ };
134
+
135
+ export const destroyViewportScrollbar = () => {
136
+ if (!viewportScrollbarInstance) {
137
+ return false;
138
+ }
139
+
140
+ viewportScrollbarInstance.destroy();
141
+
142
+ if (viewportScrollbarHost) {
143
+ delete viewportScrollbarHost.dataset.amevascrollbarViewportInitialized;
144
+ }
145
+
146
+ viewportScrollbarInstance = null;
147
+ viewportScrollbarHost = null;
148
+
149
+ return true;
150
+ };
151
+
152
+ export const initializeScrollbar = (options = {}) => {
153
+ const {
154
+ container = document,
155
+ elements = true,
156
+ viewport = false,
157
+ ...elementOptions
158
+ } = options;
159
+
160
+ const elementInstances = elements
161
+ ? initializeElementScrollbar(container, elementOptions)
162
+ : [];
163
+ const viewportInstance = viewport
164
+ ? initializeViewportScrollbar(viewport === true ? {} : viewport)
165
+ : null;
166
+
167
+ return {
168
+ elements: elementInstances,
169
+ viewport: viewportInstance,
170
+ };
171
+ };
172
+
173
+ export const refreshScrollbar = (container = document, options = {}) => {
174
+ const elements = initializeElementScrollbar(container, options);
175
+ viewportScrollbarInstance?.refresh();
176
+
177
+ return {
178
+ elements,
179
+ viewport: viewportScrollbarInstance,
180
+ };
181
+ };
182
+
183
+ export { AmevaScrollbar };
package/src/styles.css ADDED
@@ -0,0 +1,279 @@
1
+ .ameva-scrollbar,
2
+ .ameva-scrollbar *,
3
+ .ameva-scrollbar *::before,
4
+ .ameva-scrollbar *::after {
5
+ box-sizing: border-box;
6
+ }
7
+
8
+ .ameva-scrollbar-host {
9
+ scrollbar-width: none;
10
+ -ms-overflow-style: none;
11
+ }
12
+
13
+ .ameva-scrollbar-host::-webkit-scrollbar {
14
+ height: 0;
15
+ width: 0;
16
+ }
17
+
18
+ .ameva-scrollbar {
19
+ opacity: 0;
20
+ pointer-events: none;
21
+ transition:
22
+ opacity 220ms ease,
23
+ transform 220ms ease;
24
+ }
25
+
26
+ .ameva-scrollbar--active {
27
+ opacity: 1;
28
+ }
29
+
30
+ .ameva-scrollbar--hidden,
31
+ .ameva-scrollbar--initializing {
32
+ opacity: 0;
33
+ pointer-events: none;
34
+ }
35
+
36
+ .ameva-scrollbar--viewport {
37
+ position: fixed;
38
+ right: var(--ameva-scrollbar-viewport-right, 1px);
39
+ top: var(--ameva-scrollbar-viewport-top, 92px);
40
+ bottom: var(--ameva-scrollbar-viewport-bottom, 16px);
41
+ width: var(--ameva-scrollbar-hit-size, 16px);
42
+ z-index: var(--ameva-scrollbar-viewport-z-index, 60);
43
+ }
44
+
45
+ .ameva-scrollbar--element {
46
+ position: fixed;
47
+ z-index: var(--ameva-scrollbar-element-z-index, 4);
48
+ }
49
+
50
+ .ameva-scrollbar--element .ameva-scrollbar__track--vertical {
51
+ bottom: var(--ameva-scrollbar-element-vertical-inset, 0px);
52
+ right: var(--ameva-scrollbar-element-inline-end-inset, 0px);
53
+ top: var(--ameva-scrollbar-element-vertical-inset, 0px);
54
+ }
55
+
56
+ .ameva-scrollbar--element .ameva-scrollbar__track--horizontal {
57
+ bottom: 0;
58
+ left: var(--ameva-scrollbar-element-horizontal-inset, 0px);
59
+ right: var(--ameva-scrollbar-element-horizontal-inset, 0px);
60
+ }
61
+
62
+ .ameva-scrollbar__track {
63
+ appearance: none;
64
+ background: transparent;
65
+ border: 0;
66
+ border-radius: var(--ameva-scrollbar-radius, 999px);
67
+ box-shadow: none;
68
+ cursor: pointer;
69
+ display: block;
70
+ font: inherit;
71
+ opacity: 0;
72
+ padding: 0;
73
+ margin: 0;
74
+ pointer-events: auto;
75
+ position: absolute;
76
+ touch-action: none;
77
+ transition:
78
+ opacity 220ms ease,
79
+ transform 180ms ease;
80
+ user-select: none;
81
+ }
82
+
83
+ .ameva-scrollbar--active .ameva-scrollbar__track {
84
+ opacity: 1;
85
+ }
86
+
87
+ .ameva-scrollbar--active .ameva-scrollbar__track--hidden,
88
+ .ameva-scrollbar__track--hidden {
89
+ opacity: 0;
90
+ pointer-events: none;
91
+ }
92
+
93
+ .ameva-scrollbar__track::before {
94
+ background: linear-gradient(180deg, rgba(8, 16, 29, 0.82), rgba(12, 20, 37, 0.92));
95
+ border: 1px solid rgba(116, 241, 255, 0.14);
96
+ border-radius: var(--ameva-scrollbar-radius, 999px);
97
+ box-shadow:
98
+ inset 0 0 0 1px rgba(255, 255, 255, 0.02),
99
+ 0 0 20px rgba(92, 242, 255, 0.08);
100
+ content: '';
101
+ opacity: 0;
102
+ position: absolute;
103
+ transition:
104
+ border-color 180ms ease,
105
+ box-shadow 180ms ease,
106
+ transform 180ms ease,
107
+ opacity 180ms ease,
108
+ width 180ms ease,
109
+ height 180ms ease;
110
+ }
111
+
112
+ .ameva-scrollbar__track--vertical {
113
+ bottom: 0;
114
+ right: 0;
115
+ top: 0;
116
+ width: var(--ameva-scrollbar-track-size, 12px);
117
+ }
118
+
119
+ .ameva-scrollbar__track--vertical::before {
120
+ bottom: 0;
121
+ left: 50%;
122
+ top: 0;
123
+ transform: translateX(-50%);
124
+ width: 20px;
125
+ }
126
+
127
+ .ameva-scrollbar__track--horizontal {
128
+ bottom: 0;
129
+ height: var(--ameva-scrollbar-track-size, 12px);
130
+ left: 0;
131
+ right: 0;
132
+ }
133
+
134
+ .ameva-scrollbar__track--horizontal::before {
135
+ height: 1px;
136
+ left: 0;
137
+ right: 0;
138
+ top: 50%;
139
+ transform: translateY(-50%);
140
+ }
141
+
142
+ .ameva-scrollbar__thumb {
143
+ background: rgba(132, 246, 255, 0.96);
144
+ border-radius: var(--ameva-scrollbar-radius, 999px);
145
+ box-shadow: 0 0 6px rgba(92, 242, 255, 0.14);
146
+ display: block;
147
+ pointer-events: auto;
148
+ position: absolute;
149
+ touch-action: none;
150
+ transition:
151
+ box-shadow 180ms ease,
152
+ opacity 180ms ease;
153
+ user-select: none;
154
+ will-change: transform, width, height;
155
+ }
156
+
157
+ .ameva-scrollbar__thumb::after {
158
+ background: transparent;
159
+ border-radius: inherit;
160
+ box-shadow:
161
+ 0 0 8px rgba(92, 242, 255, 0.12),
162
+ 0 0 16px rgba(92, 242, 255, 0.08);
163
+ content: '';
164
+ inset: -2px;
165
+ opacity: var(--ameva-scrollbar-thumb-glow-opacity, 0.16);
166
+ pointer-events: none;
167
+ position: absolute;
168
+ transition:
169
+ opacity 220ms ease,
170
+ box-shadow 220ms ease,
171
+ transform 220ms ease;
172
+ }
173
+
174
+ .ameva-scrollbar__thumb--vertical {
175
+ left: 50%;
176
+ min-height: var(--ameva-scrollbar-thumb-min-size, 56px);
177
+ top: 0;
178
+ transform: translateX(-50%) translateY(var(--ameva-scrollbar-thumb-offset-y, 0));
179
+ transition:
180
+ box-shadow 220ms cubic-bezier(0.4, 0, 0.2, 1),
181
+ width 220ms cubic-bezier(0.4, 0, 0.2, 1),
182
+ opacity 180ms ease;
183
+ width: 1px;
184
+ }
185
+
186
+ .ameva-scrollbar__thumb--horizontal {
187
+ height: 1px;
188
+ left: 0;
189
+ min-width: var(--ameva-scrollbar-thumb-min-size, 56px);
190
+ top: 50%;
191
+ transform: translateX(var(--ameva-scrollbar-thumb-offset-x, 0)) translateY(-50%);
192
+ transition:
193
+ box-shadow 220ms cubic-bezier(0.4, 0, 0.2, 1),
194
+ height 220ms cubic-bezier(0.4, 0, 0.2, 1),
195
+ opacity 180ms ease;
196
+ }
197
+
198
+ .ameva-scrollbar__track--hovering,
199
+ .ameva-scrollbar__track--dragging {
200
+ z-index: 1;
201
+ }
202
+
203
+ .ameva-scrollbar__track--hovering::before,
204
+ .ameva-scrollbar__track--dragging::before,
205
+ .ameva-scrollbar__track--pressing::before {
206
+ border-color: rgba(144, 247, 255, 0.24);
207
+ box-shadow:
208
+ inset 0 0 0 1px rgba(255, 255, 255, 0.03),
209
+ 0 0 24px rgba(92, 242, 255, 0.12);
210
+ opacity: 0.9;
211
+ }
212
+
213
+ .ameva-scrollbar__track--vertical.ameva-scrollbar__track--hovering::before,
214
+ .ameva-scrollbar__track--vertical.ameva-scrollbar__track--dragging::before,
215
+ .ameva-scrollbar__track--vertical.ameva-scrollbar__track--pressing::before {
216
+ width: 6px;
217
+ }
218
+
219
+ .ameva-scrollbar__track--horizontal.ameva-scrollbar__track--hovering::before,
220
+ .ameva-scrollbar__track--horizontal.ameva-scrollbar__track--dragging::before,
221
+ .ameva-scrollbar__track--horizontal.ameva-scrollbar__track--pressing::before {
222
+ height: 6px;
223
+ }
224
+
225
+ .ameva-scrollbar__track--hovering .ameva-scrollbar__thumb,
226
+ .ameva-scrollbar__track--dragging .ameva-scrollbar__thumb,
227
+ .ameva-scrollbar__track--pressing .ameva-scrollbar__thumb {
228
+ box-shadow:
229
+ 0 0 8px rgba(92, 242, 255, 0.2),
230
+ 0 0 14px rgba(92, 242, 255, 0.12);
231
+ }
232
+
233
+ .ameva-scrollbar__track--hovering .ameva-scrollbar__thumb::after,
234
+ .ameva-scrollbar__track--dragging .ameva-scrollbar__thumb::after,
235
+ .ameva-scrollbar__track--pressing .ameva-scrollbar__thumb::after {
236
+ --ameva-scrollbar-thumb-glow-opacity: 0.34;
237
+ box-shadow:
238
+ 0 0 10px rgba(92, 242, 255, 0.18),
239
+ 0 0 20px rgba(92, 242, 255, 0.1);
240
+ }
241
+
242
+ .ameva-scrollbar__track--vertical.ameva-scrollbar__track--hovering .ameva-scrollbar__thumb,
243
+ .ameva-scrollbar__track--vertical.ameva-scrollbar__track--dragging .ameva-scrollbar__thumb,
244
+ .ameva-scrollbar__track--vertical.ameva-scrollbar__track--pressing .ameva-scrollbar__thumb {
245
+ width: 4px;
246
+ }
247
+
248
+ .ameva-scrollbar__track--horizontal.ameva-scrollbar__track--hovering .ameva-scrollbar__thumb,
249
+ .ameva-scrollbar__track--horizontal.ameva-scrollbar__track--dragging .ameva-scrollbar__thumb,
250
+ .ameva-scrollbar__track--horizontal.ameva-scrollbar__track--pressing .ameva-scrollbar__thumb {
251
+ height: 4px;
252
+ }
253
+
254
+ @media (max-width: 860px),
255
+ (pointer: coarse) {
256
+ .ameva-scrollbar-host--viewport {
257
+ scrollbar-width: auto;
258
+ -ms-overflow-style: auto;
259
+ }
260
+
261
+ .ameva-scrollbar-host--viewport::-webkit-scrollbar {
262
+ height: initial;
263
+ width: initial;
264
+ }
265
+
266
+ .ameva-scrollbar--viewport {
267
+ display: none;
268
+ }
269
+ }
270
+
271
+ @media (prefers-reduced-motion: reduce) {
272
+ .ameva-scrollbar,
273
+ .ameva-scrollbar *,
274
+ .ameva-scrollbar *::before,
275
+ .ameva-scrollbar *::after {
276
+ animation: none;
277
+ transition: none;
278
+ }
279
+ }