@inizioevoke/astro-core 1.1.1 → 1.3.0

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.
@@ -2,7 +2,7 @@
2
2
  import type { HTMLAttributes } from 'astro/types';
3
3
  import './ScrollContainer.css';
4
4
 
5
- interface Props extends HTMLAttributes<'div'> {
5
+ export interface Props extends HTMLAttributes<'div'> {
6
6
  outerWidth?: string | number;
7
7
  innerWidth?: string | number;
8
8
  height?: string | number;
@@ -1,97 +1,225 @@
1
+ export class ScrollContainer {
2
+ #container: HTMLElement;
3
+ #content: HTMLElement;
4
+ #track: HTMLElement;
5
+ #thumb: HTMLElement;
6
+ #isDragging: boolean;
7
+ #startY: number;
8
+ #startScrollTop: number;
9
+ // #enabled: boolean;
10
+
11
+ #mouseupHandler: any = undefined; // [] = [];
12
+ #mousemoveHandler: any = undefined; // [] = [];
1
13
 
2
- export interface IScrollContainer {
3
- container: HTMLElement;
4
- content: HTMLElement;
5
- track: HTMLElement;
6
- thumb: HTMLElement;
7
- isDragging: boolean;
8
- startY: number;
9
- startScrollTop: number;
14
+ constructor(container: HTMLElement) { // , { enabled }: { enabled?: boolean } = {}) {
15
+ const _this = this;
16
+ this.#container = container;
17
+ this.#content = container.querySelector('.evo-scroll-content') as HTMLElement;
18
+ this.#track = container.querySelector('.evo-scroll-track') as HTMLElement;
19
+ this.#thumb = container.querySelector('.evo-scroll-thumb') as HTMLElement;
20
+ this.#isDragging = false;
21
+ this.#startY = 0;
22
+ this.#startScrollTop = 0;
23
+ // this.#enabled = enabled ?? true;
24
+
25
+ this.#thumb.addEventListener('mousedown', (e) => {
26
+ _this.#thumbMouseDown(e);
27
+ }, { passive: true });
28
+
29
+ // this.#thumb.addEventListener('mouseup', (e) => {
30
+ // _this.#thumbMouseUp(e);
31
+ // }, { passive: true });
32
+
33
+ this.#content.addEventListener('scroll', () => {
34
+ _this.#scrollContent();
35
+ }, { passive: true });
36
+ }
37
+
38
+ // enable(flag?: boolean) {
39
+ // this.#enabled = flag ?? true;
40
+ // }
41
+ // get isEnabled() {
42
+ // return this.#enabled === true;
43
+ // }
44
+
45
+ scroll({ left, top, behavior = 'auto' }: { left?: number | string, top?: number | string, behavior?: 'auto' | 'instant' | 'smooth' } = {}) {
46
+ const current = this.getScroll();
47
+ if (typeof left == 'string') {
48
+ if (/^[\+\-]=?\d+/.test(left)) {
49
+ left = current.left + parseInt(left.replace(/=/,''), 10);
50
+ } else {
51
+ left = parseInt(left, 10);
52
+ }
53
+ if (isNaN(left)) {
54
+ left = undefined;
55
+ }
56
+ }
57
+ if (typeof top == 'string') {
58
+ if (/^[\+\-]=?\d+/.test(top)) {
59
+ top = current.top + parseInt(top.replace(/=/,''), 10);
60
+ } else {
61
+ top = parseInt(top, 10);
62
+ }
63
+ if (isNaN(top)) {
64
+ top = undefined;
65
+ }
66
+ }
67
+ left = left ?? current.left;
68
+ top = top ?? current.top;
69
+ if (left < 0) {
70
+ left = 0;
71
+ }
72
+ if (top < 0) {
73
+ top = 0;
74
+ }
75
+ this.#content.scroll({ left: left, top: top, behavior });
76
+ }
77
+
78
+ getScroll() {
79
+ return {
80
+ left: this.#content.scrollLeft,
81
+ top: this.#content.scrollTop
82
+ };
83
+ }
84
+
85
+ resize({ height }: { height?: number }) {
86
+ if (typeof height == 'number') {
87
+ this.#container.style.height = `${height}px`;
88
+ refresh();
89
+ }
90
+ }
91
+
92
+ refresh() {
93
+ this.#updateThumbSize();
94
+ }
95
+
96
+ #updateThumbSize() {
97
+ const scrollHeight = this.#content.scrollHeight - getPaddingY(this.#content);
98
+ const containerHeight = this.#container.clientHeight - getPaddingY(this.#container);
99
+ const visibleRatio = containerHeight / scrollHeight;
100
+ const thumbHeight = visibleRatio * containerHeight;
101
+ this.#thumb.style.height = `${thumbHeight > 20 ? thumbHeight : 20}px`;
102
+ this.#track.style.display = visibleRatio < 1 ? 'block' : 'none';
103
+ }
104
+
105
+ #thumbMouseDown(e: MouseEvent) {
106
+ this.#mouseupHandler = this.#mouseDetach.bind(this); // .push(this.#mouseDetach.bind(this));
107
+ this.#mousemoveHandler = this.#thumbMouseMove.bind(this); // .push(this.#thumbMouseMove.bind(this));
108
+ document.addEventListener('mouseup', this.#mouseupHandler, { passive: true }); // this.#mouseupHandler[this.#mouseupHandler.length - 1]);
109
+ document.addEventListener('dragend', this.#mouseupHandler, { passive: true }); // this.#mouseupHandler[this.#mouseupHandler.length - 1]);
110
+ document.addEventListener('mousemove', this.#mousemoveHandler, { passive: true }); // this.#mousemoveHandler[this.#mousemoveHandler.length - 1]);
111
+ this.#isDragging = true;
112
+ this.#startY = e.clientY;
113
+ this.#startScrollTop = this.#content.scrollTop;
114
+ this.#thumb.style.cursor = 'grabbing';
115
+ }
116
+
117
+ #thumbMouseMove(e: MouseEvent) {
118
+ // console.log(performance.now());
119
+ if (this.#isDragging) {
120
+ const deltaY = e.clientY - this.#startY;
121
+ const scrollRatio =
122
+ (this.#content.scrollHeight - this.#container.clientHeight) /
123
+ (this.#container.clientHeight - this.#thumb.clientHeight);
124
+ this.#content.scrollTop = this.#startScrollTop + deltaY * scrollRatio;
125
+ } else {
126
+ this.#mouseDetach();
127
+ }
128
+ }
129
+
130
+ // #thumbMouseUp(e: MouseEvent) {
131
+ // this.#isDragging = false;
132
+ // this.#thumb.style.cursor = 'grab';
133
+ // }
134
+
135
+ #mouseDetach() {
136
+ this.#isDragging = false;
137
+ this.#thumb.style.cursor = 'grab';
138
+ document.removeEventListener('mouseup', this.#mouseupHandler);
139
+ document.removeEventListener('dragend', this.#mouseupHandler);
140
+ document.removeEventListener('mousemove', this.#mousemoveHandler);
141
+ // for (const m of this.#mouseupHandler) {
142
+ // document.removeEventListener('mouseup', m);
143
+ // document.removeEventListener('dragend', m);
144
+ // }
145
+ // for (const m of this.#mousemoveHandler) {
146
+ // document.removeEventListener('mousemove', m);
147
+ // }
148
+ // this.#mouseupHandler = [];
149
+ // this.#mousemoveHandler = [];
150
+ }
151
+
152
+ #scrollContent() {
153
+ const scrollHeight = this.#content.scrollHeight - getPaddingY(this.#content);
154
+ const containerHeight = this.#container.clientHeight - getPaddingY(this.#container);
155
+ const scrollRatio = this.#content.scrollTop / (scrollHeight - containerHeight);
156
+ const top = scrollRatio * (this.#track.clientHeight - this.#thumb.clientHeight);
157
+ this.#thumb.style.top = `${top}px`;
158
+ }
159
+
160
+ }
161
+
162
+ export function resize(scrollContainer: string | HTMLElement | ScrollContainer, { height }: { height?: number }) {
163
+ if (typeof height == 'number') {
164
+ const sc = getScrollContainer(scrollContainer);
165
+ if (sc) {
166
+ sc.resize({ height })
167
+ }
168
+ }
10
169
  }
11
170
 
12
- export function refresh(scrollContainer?: IScrollContainer) {
171
+ export function refresh(scrollContainer?: string | HTMLElement | ScrollContainer) {
13
172
  if (scrollContainer) {
14
- updateThumbSize(scrollContainer);
173
+ const sc = getScrollContainer(scrollContainer);
174
+ if (sc) {
175
+ sc.refresh()
176
+ }
15
177
  } else {
16
178
  [...scrollContainers.values()].forEach((scrollContainer) => {
17
- updateThumbSize(scrollContainer);
179
+ scrollContainer.refresh();
18
180
  });
19
181
  }
20
182
  }
21
183
 
22
- export function getScroll(scrollContainer: IScrollContainer): { left: number, top: number } {
23
- return {
24
- left: scrollContainer.content.scrollLeft,
25
- top: scrollContainer.content.scrollTop
26
- };
184
+ export function getScroll(scrollContainer: string | HTMLElement | ScrollContainer): { left: number, top: number } | undefined {
185
+ const sc = getScrollContainer(scrollContainer);
186
+ if (sc) {
187
+ return sc.getScroll();
188
+ } else {
189
+ return undefined;
190
+ }
27
191
  }
28
- export function scroll(scrollContainer: IScrollContainer, { left, top, behavior = 'auto' }: { left?: number, top?: number, behavior?: 'auto' | 'instant' | 'smooth' } = {}) {
29
- const current = getScroll(scrollContainer);
30
- scrollContainer.content.scroll({ left: left ?? current.left, top: top ?? current.top, behavior });
192
+
193
+ export function scroll(scrollContainer: string | HTMLElement | ScrollContainer, { left, top, behavior = 'auto' }: { left?: number, top?: number, behavior?: 'auto' | 'instant' | 'smooth' } = {}) {
194
+ const sc = getScrollContainer(scrollContainer);
195
+ if (sc) {
196
+ const current = sc.getScroll();
197
+ sc.scroll({ left: left ?? current.left, top: top ?? current.top, behavior });
198
+ }
31
199
  }
32
200
 
33
- export function getScrollContainer(selector: string | HTMLElement): IScrollContainer | undefined {
201
+ export function getScrollContainer(selector: string | HTMLElement | ScrollContainer): ScrollContainer | undefined {
202
+ if (selector instanceof ScrollContainer) {
203
+ return selector;
204
+ }
34
205
  const key = typeof selector == 'string' ? document.querySelector<HTMLElement>(selector) : selector;
35
206
  return key ? scrollContainers.get(key) : undefined;
36
207
  }
37
208
 
38
- function thumbMouseDown(scrollContainer: IScrollContainer, e: MouseEvent) {
39
- scrollContainer.isDragging = true;
40
- scrollContainer.startY = e.clientY;
41
- scrollContainer.startScrollTop = scrollContainer.content.scrollTop;
42
- scrollContainer.thumb.style.cursor = 'grabbing';
43
- }
44
-
45
- function updateThumbSize(scrollContainer: IScrollContainer) {
46
- const { container, content, thumb, track } = scrollContainer;
47
- const scrollHeight = content.scrollHeight - getPaddingY(content);
48
- const containerHeight = container.clientHeight - getPaddingY(container);
49
- const visibleRatio = containerHeight / scrollHeight;
50
- const thumbHeight = visibleRatio * containerHeight;
51
- thumb.style.height = `${thumbHeight > 20 ? thumbHeight : 20}px`;
52
- track.style.display = visibleRatio < 1 ? 'block' : 'none';
53
- }
54
-
55
- function scrollContent(scrollContainer: IScrollContainer) {
56
- const { container, content, thumb, track } = scrollContainer;
57
- const scrollHeight = content.scrollHeight - getPaddingY(content);
58
- const containerHeight = container.clientHeight - getPaddingY(container);
59
- const scrollRatio = content.scrollTop / (scrollHeight - containerHeight);
60
- const top = scrollRatio * (track.clientHeight - thumb.clientHeight);
61
- thumb.style.top = `${top}px`;
62
- }
63
-
64
209
  function getPaddingY(content: HTMLElement): number {
65
210
  const style = getComputedStyle(content);
66
211
  return parseFloat(style.paddingTop) + parseFloat(style.paddingBottom);
67
212
  }
68
213
 
69
- function getScrollContainerElement(element: HTMLElement) {
70
- let container = element;
71
- while (container && !container.classList.contains('scroll-container')) {
72
- container = container.parentElement as HTMLElement;
73
- }
74
- return container;
75
- }
76
-
77
- function createScrollContainer(container: HTMLElement): IScrollContainer {
78
- return {
79
- container: container,
80
- content: container.querySelector('.evo-scroll-content') as HTMLElement,
81
- track: container.querySelector('.evo-scroll-track') as HTMLElement,
82
- thumb: container.querySelector('.evo-scroll-thumb') as HTMLElement,
83
- isDragging: false,
84
- startY: 0,
85
- startScrollTop: 0
86
- };
87
- }
88
-
89
- const scrollContainers = new Map<HTMLElement, IScrollContainer>();
214
+ const scrollContainers = new Map<HTMLElement, ScrollContainer>();
90
215
 
91
216
  const intersectionObserver = new IntersectionObserver((entries) => {
92
217
  entries.forEach((entry) => {
93
218
  if (entry.isIntersecting) {
94
- updateThumbSize(scrollContainers.get(entry.target as HTMLElement) as IScrollContainer);
219
+ const sc = getScrollContainer(entry.target as HTMLElement);
220
+ if (sc) {
221
+ sc.refresh();
222
+ }
95
223
  }
96
224
  });
97
225
  },
@@ -99,45 +227,28 @@ const intersectionObserver = new IntersectionObserver((entries) => {
99
227
  );
100
228
 
101
229
  const mutationObserver = new MutationObserver((mutations) => {
102
- const containers: IScrollContainer[] = [];
230
+ const containers = new Set<ScrollContainer>();
103
231
  mutations.forEach((mutation) => {
104
- const sc = scrollContainers.get(getScrollContainerElement(mutation.target as HTMLElement));
105
- if (sc && !containers.includes(sc)) {
106
- containers.push(sc);
232
+ const sc = getScrollContainer(mutation.target as HTMLElement);
233
+ if (sc) {
234
+ containers.add(sc);
107
235
  }
108
236
  });
109
- containers.forEach((container) => {
110
- updateThumbSize(container);
237
+ [...containers].forEach((container) => {
238
+ container.refresh();
111
239
  });
112
240
  });
113
241
 
114
242
  document.querySelectorAll<HTMLElement>('.evo-scroll-container').forEach((container) => {
115
- const scrollContainer = createScrollContainer(container);
243
+ const scrollContainer = new ScrollContainer(container);
116
244
  scrollContainers.set(container, scrollContainer);
117
- scrollContainer.thumb.addEventListener('mousedown', (e) => { thumbMouseDown(scrollContainer, e); }, { passive: true });
118
- scrollContainer.content.addEventListener('scroll', (e) => { scrollContent(scrollContainer); }, { passive: true });
245
+
119
246
  intersectionObserver.observe(container);
120
- mutationObserver.observe(container, { childList: true, subtree: true });
121
- });
122
- document.addEventListener('mousemove', (e) => {
123
- [...scrollContainers.values()].forEach((scrollContainer) => {
124
- if (!scrollContainer.isDragging) return;
125
- const { container, content, thumb } = scrollContainer;
126
- const deltaY = e.clientY - scrollContainer.startY;
127
- const scrollRatio =
128
- (content.scrollHeight - container.clientHeight) /
129
- (container.clientHeight - thumb.clientHeight);
130
- scrollContainer.content.scrollTop = scrollContainer.startScrollTop + deltaY * scrollRatio;
131
- });
132
- });
133
- document.addEventListener('mouseup', () => {
134
- [...scrollContainers.values()].forEach((scrollContainer) => {
135
- scrollContainer.isDragging = false;
136
- scrollContainer.thumb.style.cursor = 'grab';
137
- });
247
+ mutationObserver.observe(container, { attributes: true, childList: true, subtree: true });
138
248
  });
249
+
139
250
  window.addEventListener('resize', () => {
140
251
  [...scrollContainers.values()].forEach((scrollContainer) => {
141
- updateThumbSize(scrollContainer);
252
+ scrollContainer.refresh();
142
253
  });
143
254
  });
@@ -0,0 +1,150 @@
1
+
2
+ export interface IScrollContainer {
3
+ container: HTMLElement;
4
+ content: HTMLElement;
5
+ track: HTMLElement;
6
+ thumb: HTMLElement;
7
+ isDragging: boolean;
8
+ startY: number;
9
+ startScrollTop: number;
10
+ }
11
+
12
+ export function resize(scrollContainer: IScrollContainer, { height }: { height?: number }) {
13
+ if (typeof height == 'number') {
14
+ scrollContainer.container.style.height = `${height}px`;
15
+ refresh(scrollContainer);
16
+ }
17
+ }
18
+
19
+ export function refresh(scrollContainer?: IScrollContainer) {
20
+ if (scrollContainer) {
21
+ updateThumbSize(scrollContainer);
22
+ } else {
23
+ [...scrollContainers.values()].forEach((scrollContainer) => {
24
+ updateThumbSize(scrollContainer);
25
+ });
26
+ }
27
+ }
28
+
29
+ export function getScroll(scrollContainer: IScrollContainer): { left: number, top: number } {
30
+ return {
31
+ left: scrollContainer.content.scrollLeft,
32
+ top: scrollContainer.content.scrollTop
33
+ };
34
+ }
35
+ export function scroll(scrollContainer: IScrollContainer, { left, top, behavior = 'auto' }: { left?: number, top?: number, behavior?: 'auto' | 'instant' | 'smooth' } = {}) {
36
+ const current = getScroll(scrollContainer);
37
+ scrollContainer.content.scroll({ left: left ?? current.left, top: top ?? current.top, behavior });
38
+ }
39
+
40
+ export function getScrollContainer(selector: string | HTMLElement): IScrollContainer | undefined {
41
+ const key = typeof selector == 'string' ? document.querySelector<HTMLElement>(selector) : selector;
42
+ return key ? scrollContainers.get(key) : undefined;
43
+ }
44
+
45
+ function thumbMouseDown(scrollContainer: IScrollContainer, e: MouseEvent) {
46
+ scrollContainer.isDragging = true;
47
+ scrollContainer.startY = e.clientY;
48
+ scrollContainer.startScrollTop = scrollContainer.content.scrollTop;
49
+ scrollContainer.thumb.style.cursor = 'grabbing';
50
+ }
51
+
52
+ function updateThumbSize(scrollContainer: IScrollContainer) {
53
+ const { container, content, thumb, track } = scrollContainer;
54
+ const scrollHeight = content.scrollHeight - getPaddingY(content);
55
+ const containerHeight = container.clientHeight - getPaddingY(container);
56
+ const visibleRatio = containerHeight / scrollHeight;
57
+ const thumbHeight = visibleRatio * containerHeight;
58
+ thumb.style.height = `${thumbHeight > 20 ? thumbHeight : 20}px`;
59
+ track.style.display = visibleRatio < 1 ? 'block' : 'none';
60
+ }
61
+
62
+ function scrollContent(scrollContainer: IScrollContainer) {
63
+ const { container, content, thumb, track } = scrollContainer;
64
+ const scrollHeight = content.scrollHeight - getPaddingY(content);
65
+ const containerHeight = container.clientHeight - getPaddingY(container);
66
+ const scrollRatio = content.scrollTop / (scrollHeight - containerHeight);
67
+ const top = scrollRatio * (track.clientHeight - thumb.clientHeight);
68
+ thumb.style.top = `${top}px`;
69
+ }
70
+
71
+ function getPaddingY(content: HTMLElement): number {
72
+ const style = getComputedStyle(content);
73
+ return parseFloat(style.paddingTop) + parseFloat(style.paddingBottom);
74
+ }
75
+
76
+ function getScrollContainerElement(element: HTMLElement) {
77
+ let container = element;
78
+ while (container && !container.classList.contains('scroll-container')) {
79
+ container = container.parentElement as HTMLElement;
80
+ }
81
+ return container;
82
+ }
83
+
84
+ function createScrollContainer(container: HTMLElement): IScrollContainer {
85
+ return {
86
+ container: container,
87
+ content: container.querySelector('.evo-scroll-content') as HTMLElement,
88
+ track: container.querySelector('.evo-scroll-track') as HTMLElement,
89
+ thumb: container.querySelector('.evo-scroll-thumb') as HTMLElement,
90
+ isDragging: false,
91
+ startY: 0,
92
+ startScrollTop: 0
93
+ };
94
+ }
95
+
96
+ const scrollContainers = new Map<HTMLElement, IScrollContainer>();
97
+
98
+ const intersectionObserver = new IntersectionObserver((entries) => {
99
+ entries.forEach((entry) => {
100
+ if (entry.isIntersecting) {
101
+ updateThumbSize(scrollContainers.get(entry.target as HTMLElement) as IScrollContainer);
102
+ }
103
+ });
104
+ },
105
+ { threshold: 0.1 }
106
+ );
107
+
108
+ const mutationObserver = new MutationObserver((mutations) => {
109
+ const containers: IScrollContainer[] = [];
110
+ mutations.forEach((mutation) => {
111
+ const sc = scrollContainers.get(getScrollContainerElement(mutation.target as HTMLElement));
112
+ if (sc && !containers.includes(sc)) {
113
+ containers.push(sc);
114
+ }
115
+ });
116
+ containers.forEach((container) => {
117
+ updateThumbSize(container);
118
+ });
119
+ });
120
+
121
+ document.querySelectorAll<HTMLElement>('.evo-scroll-container').forEach((container) => {
122
+ const scrollContainer = createScrollContainer(container);
123
+ scrollContainers.set(container, scrollContainer);
124
+ scrollContainer.thumb.addEventListener('mousedown', (e) => { thumbMouseDown(scrollContainer, e); }, { passive: true });
125
+ scrollContainer.content.addEventListener('scroll', (e) => { scrollContent(scrollContainer); }, { passive: true });
126
+ intersectionObserver.observe(container);
127
+ mutationObserver.observe(container, { childList: true, subtree: true });
128
+ });
129
+ document.addEventListener('mousemove', (e) => {
130
+ [...scrollContainers.values()].forEach((scrollContainer) => {
131
+ if (!scrollContainer.isDragging) return;
132
+ const { container, content, thumb } = scrollContainer;
133
+ const deltaY = e.clientY - scrollContainer.startY;
134
+ const scrollRatio =
135
+ (content.scrollHeight - container.clientHeight) /
136
+ (container.clientHeight - thumb.clientHeight);
137
+ scrollContainer.content.scrollTop = scrollContainer.startScrollTop + deltaY * scrollRatio;
138
+ });
139
+ });
140
+ document.addEventListener('mouseup', () => {
141
+ [...scrollContainers.values()].forEach((scrollContainer) => {
142
+ scrollContainer.isDragging = false;
143
+ scrollContainer.thumb.style.cursor = 'grab';
144
+ });
145
+ });
146
+ window.addEventListener('resize', () => {
147
+ [...scrollContainers.values()].forEach((scrollContainer) => {
148
+ updateThumbSize(scrollContainer);
149
+ });
150
+ });
@@ -0,0 +1,2 @@
1
+ export { default as ScrollContainer } from './ScrollContainer.astro';
2
+ export * from './ScrollContainer';