mark-3 0.0.6 → 0.0.9

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/CHANGELOG.md CHANGED
@@ -2,6 +2,40 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ### [0.0.9](https://github.com/ismailceylan/mark-3/compare/v0.0.8...v0.0.9) (2025-08-30)
6
+
7
+
8
+ ### Features
9
+
10
+ * **components:** add an option to ignore window size changes in virtual-scroll component ([2e27806](https://github.com/ismailceylan/mark-3/commit/2e2780680718824df973d050b109cf81ef657dc5))
11
+ * **components:** add support for two-way top scroll determination using the v-model method for virtual-scroll component ([123c89e](https://github.com/ismailceylan/mark-3/commit/123c89e6cc216305ac7d66e2c8d961d50cc7aaad))
12
+ * **helpers:** add new chunk method for arrays ([9150623](https://github.com/ismailceylan/mark-3/commit/9150623b9194830b5699c0863e8313fcde21c3d3))
13
+ * **helpers:** add new number/compact helper ([6d40850](https://github.com/ismailceylan/mark-3/commit/6d40850dcb174d32232e70e9139fe71f43a05d96))
14
+ * **helpers:** add new number/reduce helper ([1881c99](https://github.com/ismailceylan/mark-3/commit/1881c992547a42eb6b23aa60298d5abfc2ef0c56))
15
+
16
+
17
+ ### Bug Fixes
18
+
19
+ * **components:** add a watch getter to watch some ref elements in virtual-scroll component ([a74b0b7](https://github.com/ismailceylan/mark-3/commit/a74b0b7061a3fef9da8acc1177dda03f54edd9ae))
20
+ * **components:** add the immediate flag to process the already full item store immediately in virtual-scroll component ([a45a4da](https://github.com/ismailceylan/mark-3/commit/a45a4da63c3921714b0df8f86e33601956ad9a69))
21
+ * **components:** use a more secure value to ensure that the key value of the virtual scroll element is unique ([1db4081](https://github.com/ismailceylan/mark-3/commit/1db40812aadbbc186bcae6a7169a298060aa9ced))
22
+ * **components:** use the getter watch pattern to watch elements properly when they are sent as refs in virtual-scroll component ([6556ae8](https://github.com/ismailceylan/mark-3/commit/6556ae8abec1c2b5715fd914503d539bb1dd8e8a))
23
+ * **composables:** fix virtual-scroll artifact problems and add relative position to wrapper ([a7566c3](https://github.com/ismailceylan/mark-3/commit/a7566c352f0f83f8ab0df910d3e7d6517d0e5c3a))
24
+ * **composables:** virtual-scroll recalculate properly item heights when the screen size changed ([0749124](https://github.com/ismailceylan/mark-3/commit/07491247c28461c9cc65b49c98d992dfab07aeea))
25
+
26
+ ### [0.0.8](https://github.com/ismailceylan/mark-3/compare/v0.0.7...v0.0.8) (2025-08-13)
27
+
28
+ ### [0.0.7](https://github.com/ismailceylan/mark-3/compare/v0.0.6...v0.0.7) (2025-08-13)
29
+
30
+
31
+ ### Features
32
+
33
+ * **components:** add new virtual-scroll component ([c73a31d](https://github.com/ismailceylan/mark-3/commit/c73a31d6dc2ec37d8e70a15160bda495425712ca))
34
+ * **composables:** add new use-css-metrics composable ([8500859](https://github.com/ismailceylan/mark-3/commit/85008595788348956cd2f40cc0fff65d038672b0))
35
+ * **composables:** add new use-resize-observer composable ([6d110b5](https://github.com/ismailceylan/mark-3/commit/6d110b5c17facd4918a8a02a49d2f7644ada8f96))
36
+ * **composables:** add new use-scroll-position composable ([f847cb0](https://github.com/ismailceylan/mark-3/commit/f847cb0a9f19f09f82ab55d447227a7aa21516d5))
37
+ * **helpers:** add new time/throttle method ([60b8472](https://github.com/ismailceylan/mark-3/commit/60b8472fdb56119888c90446fe237b1fc9573f2b))
38
+
5
39
  ### [0.0.6](https://github.com/ismailceylan/mark-3/compare/v0.0.5...v0.0.6) (2025-08-09)
6
40
 
7
41
 
@@ -1 +1,2 @@
1
+ export { default as VirtualScroll } from "./virtual-scroll.vue";
1
2
  export { default as ScrollAwareContent } from "./scroll-aware-content.vue";
@@ -0,0 +1,255 @@
1
+ <template>
2
+ <!-- Mark-3/VirtualScroll -->
3
+ <div ref="container">
4
+ <div :style="{ transform: 'translateZ(0)', position: 'relative', minHeight: totalHeight + 'px' }">
5
+ <slot
6
+ v-for="{ item, index } of visibleItems"
7
+ :key="index"
8
+ :item
9
+ :index
10
+ :observe
11
+ :style="{ position: 'absolute', transform: 'translateY(' + offsets[ index ] + 'px)' }"
12
+ />
13
+ </div>
14
+
15
+ <slot name="bottom" />
16
+ </div>
17
+ </template>
18
+
19
+ <script lang="ts" setup>
20
+ import { ref, watch, computed, reactive, nextTick } from "vue";
21
+ import { useCssMetrics, useEventListener, useResizeObserver, useScrollPosition } from "../composables";
22
+ import { debounce } from "../helpers/time";
23
+ import { clamp } from "../helpers/number";
24
+
25
+ const emit = defineEmits([ "threshold-reached" ]);
26
+
27
+ const scrollTop = defineModel( "scrollTop", { default: 0 });
28
+
29
+ const props = defineProps(
30
+ {
31
+ pageMode: Boolean,
32
+ dontWatchResizing: Boolean,
33
+
34
+ items: {
35
+ type: Array,
36
+ required: true
37
+ },
38
+
39
+ buffer: {
40
+ type: Number,
41
+ default: 5
42
+ },
43
+
44
+ minHeight: {
45
+ type: Number,
46
+ default: 20
47
+ },
48
+
49
+ itemGapClasses: {
50
+ type: String
51
+ },
52
+
53
+ threshold: {
54
+ type: Number,
55
+ default: 3
56
+ }
57
+ });
58
+
59
+ const { max, floor } = Math;
60
+ const heights = ref<number[]>([]);
61
+ const identifiedItems = reactive<{ item: any, index: number }[]>([]);
62
+ const container = ref<HTMLDivElement>( null );
63
+ const metrics = useCssMetrics( props.itemGapClasses, [ "gap" ] as const, { throttle: 100 });
64
+ const isHeightsDirty = ref( false );
65
+ const dirtyItems = {}
66
+
67
+ const scrollableElement = computed(() =>
68
+ props.pageMode
69
+ ? window
70
+ : container.value
71
+ );
72
+
73
+ const scrollPos = useScrollPosition( scrollableElement, { throttle: 150 });
74
+
75
+ const offsets = computed<number[]>(() =>
76
+ {
77
+ const arr = new Array( heights.value.length );
78
+ let sum = 0;
79
+
80
+ for( let i = 0; i < heights.value.length; i++ )
81
+ {
82
+ arr[ i ] = sum;
83
+ sum += heights.value[ i ] || 0;
84
+ }
85
+
86
+ return arr;
87
+ });
88
+
89
+ const totalHeight = computed(() =>
90
+ {
91
+ if( identifiedItems.length === 0 )
92
+ {
93
+ return 0;
94
+ }
95
+
96
+ const latestOffset = offsets.value[ offsets.value.length - 1 ] || 0;
97
+ const latestHeight = heights.value[ heights.value.length - 1 ] || 0;
98
+ const h = latestOffset + latestHeight;
99
+
100
+ if( h === metrics.value.gap * 2 )
101
+ {
102
+ return 0;
103
+ }
104
+
105
+ return h + 1;
106
+ });
107
+
108
+ const startIndex = computed(() =>
109
+ {
110
+ let low = 0;
111
+ let high = offsets.value.length - 1;
112
+ let mid: number;
113
+
114
+ while( low <= high )
115
+ {
116
+ mid = floor(( low + high ) / 2 );
117
+
118
+ if( offsets.value[ mid ] === scrollPos.y.value )
119
+ {
120
+ return mid;
121
+ }
122
+
123
+ if( offsets.value[ mid ] < scrollPos.y.value )
124
+ {
125
+ low = mid + 1;
126
+ }
127
+ else
128
+ {
129
+ high = mid - 1;
130
+ }
131
+ }
132
+
133
+ return low - 1 < 0
134
+ ? 0
135
+ : low - 1;
136
+ });
137
+
138
+ const endIndex = computed(() =>
139
+ {
140
+ let total = 0;
141
+ let idx = startIndex.value;
142
+
143
+ while( idx < heights.value.length && total < window.innerHeight )
144
+ {
145
+ total += heights.value[ idx ];
146
+ idx++;
147
+ }
148
+
149
+ return max( 0, idx - 1 );
150
+ });
151
+
152
+ const startIndexWithMargin = computed(() =>
153
+ max( 0, startIndex.value - props.buffer )
154
+ );
155
+
156
+ const endIndexWithMargin = computed(() =>
157
+ clamp( endIndex.value + props.buffer, 0, heights.value.length )
158
+ );
159
+
160
+ const visibleItems = computed(() =>
161
+ identifiedItems.slice( startIndexWithMargin.value, endIndexWithMargin.value )
162
+ );
163
+
164
+ const { observe } = useResizeObserver(( entries ) =>
165
+ {
166
+ entries.forEach( entry =>
167
+ {
168
+ const index = parseInt(( entry.target as HTMLElement ).dataset.index );
169
+ const newHeight = entry.contentRect.height;
170
+ const oldHeight = heights.value[ index ] || 0;
171
+
172
+ heights.value[ index ] = index in dirtyItems
173
+ // replace old height
174
+ ? newHeight + metrics.value.gap
175
+ : max( newHeight + metrics.value.gap, oldHeight );
176
+
177
+ delete dirtyItems[ index ];
178
+ });
179
+ });
180
+
181
+ watch( scrollPos.y, () => scrollTop.value = scrollPos.y.value );
182
+
183
+ watch( scrollTop, async () =>
184
+ {
185
+ await nextTick();
186
+
187
+ if( scrollableElement.value instanceof HTMLElement )
188
+ {
189
+ scrollableElement.value.scrollTop = scrollTop.value;
190
+ }
191
+ else if( scrollableElement.value === window )
192
+ {
193
+ window.scrollTo( 0, scrollTop.value );
194
+ }
195
+ }, { immediate: true });
196
+
197
+
198
+ watch( endIndex, () =>
199
+ {
200
+ if(( identifiedItems.length - 1 ) - endIndex.value <= props.threshold )
201
+ {
202
+ emit( "threshold-reached" );
203
+ }
204
+ });
205
+
206
+ watch(() => props.items, items =>
207
+ {
208
+ const normalizedItems = items.map(( item, i ) =>
209
+ ({
210
+ item,
211
+ index: i
212
+ }));
213
+
214
+ identifiedItems.length = 0;
215
+ identifiedItems.push( ...normalizedItems );
216
+
217
+ heights.value = items.map(( _, i ) =>
218
+ heights.value[ i ] ?? props.minHeight
219
+ );
220
+ }, { immediate: true, deep: true });
221
+
222
+ watch( isHeightsDirty, () =>
223
+ {
224
+ if( isHeightsDirty.value === false )
225
+ {
226
+ return;
227
+ }
228
+
229
+ visibleItems.value.forEach(({ index }) =>
230
+ {
231
+ const itemEl = container.value.querySelector( "[data-index='" + index + "']" );
232
+
233
+ if( itemEl === null )
234
+ {
235
+ return dirtyItems[ index ] = true;
236
+ }
237
+
238
+ heights.value[ index ] = itemEl.getBoundingClientRect().height + metrics.value.gap;
239
+ });
240
+
241
+ isHeightsDirty.value = false;
242
+ });
243
+
244
+ if( props.dontWatchResizing === false )
245
+ {
246
+ useEventListener( window, "resize", debounce( resetHeights, 100 ), { passive: true });
247
+ }
248
+
249
+ function resetHeights()
250
+ {
251
+ heights.value = identifiedItems.map(() => props.minHeight );
252
+ isHeightsDirty.value = true;
253
+ }
254
+
255
+ </script>
@@ -1,8 +1,11 @@
1
1
  export { default as useMediaQuery } from "./use-media-query.js";
2
+ export { default as useCssMetrics } from "./use-css-metrics.ts";
2
3
  export { default as useScrollEvent } from "./use-scroll-event.js";
3
4
  export { default as usePointerSwipe } from "./use-pointer-swipe.js";
4
5
  export { default as useOutsideClicks } from "./use-outside-clicks.js";
5
6
  export { default as useEventListener } from "./use-event-listener.js";
6
7
  export { default as useResponsiveness } from "./use-responsiveness.js";
7
- export { default as useSwipeableDrawer } from "./use-swipeable-drawer.js";
8
+ export { default as useResizeObserver } from "./use-resize-observer.js";
9
+ export { default as useScrollPosition } from "./use-scroll-position.js";
10
+ export { default as useSwipeableDrawer } from "./use-swipeable-drawer.ts";
8
11
  export { default as useIntersectionObserver } from "./use-intersection-observer.js";
@@ -0,0 +1,115 @@
1
+ import { ref, onMounted, onUnmounted, Ref } from "vue";
2
+ import { useResizeObserver } from ".";
3
+
4
+ /**
5
+ * Creates a reactive reference containing numeric values of specified CSS properties
6
+ * for elements with given classes. It observes size changes and updates the metrics
7
+ * accordingly.
8
+ *
9
+ * @template P
10
+ * @param classes - CSS classes to apply to the ghost element used for measurement.
11
+ * @param properties - Array of CSS property names to track.
12
+ * @param options - Optional configuration object.
13
+ * @returns A reactive reference with the current values of the specified CSS properties.
14
+ */
15
+ export default function useCssMetrics<P extends readonly string[]>(
16
+ classes: string|string[],
17
+ properties: P, { throttle = 0 } = {}
18
+ ): Ref<{[ K in typeof properties[ number ]]: number }>
19
+ {
20
+ const metrics = ref({});
21
+ let ghostEl = null;
22
+ let widthEl = null;
23
+ let observer = null;
24
+ const { observe, unobserve, disconnect } = useResizeObserver( updateMetrics, { throttle });
25
+
26
+ metrics.value = Object.fromEntries( properties.map( prop =>
27
+ [ prop, 0 ]
28
+ ));
29
+
30
+ if( classes === undefined || ( Array.isArray( classes ) && classes.length === 0 ))
31
+ {
32
+ return metrics;
33
+ }
34
+
35
+ onMounted( init );
36
+ onUnmounted( destroy );
37
+
38
+ function init()
39
+ {
40
+ if( ghostEl )
41
+ {
42
+ return;
43
+ }
44
+
45
+ ghostEl = document.createElement( "div" );
46
+ widthEl = document.createElement( "div" );
47
+
48
+ const ghostStyle = ghostEl.style;
49
+ const widthStyle = widthEl.style;
50
+
51
+ ghostStyle.position = "absolute";
52
+ ghostStyle.top = "0";
53
+ ghostStyle.left = "0";
54
+ ghostStyle.width = "100%";
55
+ ghostStyle.height = "100%";
56
+ ghostStyle.visibility = "hidden";
57
+ ghostStyle.pointerEvents = "none";
58
+
59
+ widthStyle.position = "absolute";
60
+ widthStyle.visibility = "hidden";
61
+ widthStyle.pointerEvents = "none";
62
+
63
+ ghostEl.appendChild( widthEl );
64
+
65
+ const cls = Array.isArray( classes )
66
+ ? classes
67
+ : classes.split( " " );
68
+
69
+ ghostEl.classList.add( ...cls );
70
+ widthEl.classList.add( ...cls );
71
+
72
+ document.body.appendChild( ghostEl );
73
+
74
+ observe( ghostEl );
75
+ updateMetrics();
76
+ }
77
+
78
+ function updateMetrics()
79
+ {
80
+ if( ! ghostEl )
81
+ {
82
+ return;
83
+ }
84
+
85
+ const computed = getComputedStyle( ghostEl );
86
+ const computed2 = getComputedStyle( widthEl );
87
+
88
+ properties.forEach( prop =>
89
+ {
90
+ const source = [ "width", "height" ].includes( prop )
91
+ ? computed2
92
+ : computed;
93
+
94
+ metrics.value[ prop ] = parseFloat( source.getPropertyValue( prop )) || 0;
95
+ });
96
+ }
97
+
98
+ function destroy()
99
+ {
100
+ if( observer && ghostEl )
101
+ {
102
+ unobserve( ghostEl );
103
+ disconnect();
104
+ }
105
+
106
+ if( ghostEl && ghostEl.parentElement )
107
+ {
108
+ ghostEl.parentElement.removeChild( ghostEl );
109
+ }
110
+
111
+ ghostEl = null;
112
+ }
113
+
114
+ return metrics;
115
+ }
@@ -1,9 +1,5 @@
1
1
  import { isRef, unref, onMounted, onUnmounted, getCurrentInstance } from "vue";
2
2
 
3
- /**
4
- * @template T
5
- * @typedef {import("vue").Ref<T>} Ref
6
- */
7
3
  /**
8
4
  * A composition function that adds a DOM event listener to the given target with the
9
5
  * given event name and callback. The options object is optional. If the composition
@@ -15,7 +11,7 @@ import { isRef, unref, onMounted, onUnmounted, getCurrentInstance } from "vue";
15
11
  * @param {EventTarget|Ref<EventTarget>} maybeRefTarget - The target element to add the event listener to.
16
12
  * @param {string} eventName - The name of the event to add a listener for.
17
13
  * @param {function} callBack - The callback function to call when the event happens.
18
- * @param {object} [options] - The options object to pass to addEventListener.
14
+ * @param {EventListenerOptions} [options] - The options object to pass to addEventListener.
19
15
  * @returns {function} - A function that can be called to remove the event listener.
20
16
  */
21
17
  export default function useEventListener( maybeRefTarget, eventName, callBack, options )
@@ -52,3 +48,15 @@ export default function useEventListener( maybeRefTarget, eventName, callBack, o
52
48
 
53
49
  return stop;
54
50
  }
51
+
52
+ /**
53
+ * @template T
54
+ * @typedef {import("vue").Ref<T>} Ref
55
+ */
56
+ /**
57
+ * @typedef EventListenerOptions
58
+ * @type {object}
59
+ * @property {boolean} [capture=false] true for capturing, false for bubbling
60
+ * @property {boolean} [once=false] true for run the event once or false for keep it persistent
61
+ * @property {boolean} [passive=false] true for passive, false for not passive
62
+ */
@@ -1,4 +1,5 @@
1
1
  import { useEventListener } from ".";
2
+ import { isArray } from "../helpers/types";
2
3
 
3
4
  /**
4
5
  * Watches for clicks outside of the given element and calls the
@@ -16,7 +17,7 @@ import { useEventListener } from ".";
16
17
  * listener when the extraordinary circumstances arise.
17
18
  *
18
19
  * @typedef {import('vue').Ref<Element>} ElementRef
19
- * @param {ElementRef[]} elRefs - The elements to watch for clicks outside of.
20
+ * @param {ElementRef|ElementRef[]} elRefs - The elements to watch for clicks outside of.
20
21
  * @param {function} callback - The callback to call when a click is outside of the element.
21
22
  * @param {object} options - The options object.
22
23
  * @property {string} options.on - The event to listen for.
@@ -40,6 +41,11 @@ import { useEventListener } from ".";
40
41
  */
41
42
  export default function useOutsideClick( elRefs, callback, { on = "click" } = {})
42
43
  {
44
+ if( ! isArray( elRefs ))
45
+ {
46
+ elRefs = [ elRefs ];
47
+ }
48
+
43
49
  return useEventListener( document, on, e =>
44
50
  {
45
51
  if( elRefs.length === 0 )
@@ -0,0 +1,93 @@
1
+ import { onUnmounted } from "vue";
2
+ import { throttle as throttleHelper } from "../helpers/time";
3
+
4
+ /**
5
+ * A composition function that creates a ResizeObserver instance and provides
6
+ * methods to observe elements for their resize events.
7
+ *
8
+ * @param {ResizeObserverCallback} callback - The callback function
9
+ * to call when the observed elements are resized
10
+ * @returns {ResizeObserverReturnValue}
11
+ * An object with three properties. The `observe` property is a function that
12
+ * takes an element to observe and begins observing it for resize events.
13
+ * The `unobserve` property is a function that takes an element to no longer
14
+ * observe and stops observing it for resize events. The `disconnect` property
15
+ * is a function that disconnects the ResizeObserver instance and clears the
16
+ * set of observed elements.
17
+ */
18
+ export default function useResizeObserver( callback, { throttle = 0 } = {})
19
+ {
20
+ const observedElements = new Set;
21
+
22
+ const maybeThrottledCallback = throttle
23
+ ? throttleHelper( callback, throttle )
24
+ : callback;
25
+
26
+ const observer = new ResizeObserver( entries =>
27
+ {
28
+ maybeThrottledCallback( entries );
29
+ });
30
+
31
+ onUnmounted( disconnect );
32
+
33
+ function observe( el )
34
+ {
35
+ if( ! el || observedElements.has( el ))
36
+ {
37
+ return;
38
+ }
39
+
40
+ observer.observe( el );
41
+ observedElements.add( el );
42
+ }
43
+
44
+ function unobserve( el )
45
+ {
46
+ if( ! el )
47
+ {
48
+ return;
49
+ }
50
+
51
+ observer.unobserve( el );
52
+ observedElements.delete( el );
53
+ }
54
+
55
+ function disconnect()
56
+ {
57
+ observer.disconnect();
58
+ observedElements.clear();
59
+ }
60
+
61
+ return {
62
+ observe,
63
+ unobserve,
64
+ disconnect
65
+ }
66
+ }
67
+ /**
68
+ * Callback function to be invoked when observed elements are resized.
69
+ *
70
+ * @callback ResizeObserverCallback
71
+ * @param {ResizeObserverEntry[]} entries - An array of ResizeObserverEntry objects
72
+ * for each observed element that has changed size.
73
+ */
74
+ /**
75
+ * @callback ObserveMethod
76
+ * @param {EventTarget} el - The element to observe
77
+ * @returns {void}
78
+ */
79
+ /**
80
+ * @callback UnobserveMethod
81
+ * @param {EventTarget} el - The element to stop observing
82
+ * @returns {void}
83
+ */
84
+ /**
85
+ * @callback DisconnectMethod
86
+ * @returns {void}
87
+ */
88
+ /**
89
+ * @typedef {object} ResizeObserverReturnValue
90
+ * @property {ObserveMethod} observe - A function to observe an element for resize events
91
+ * @property {UnobserveMethod} unobserve - A function to stop observing an element for resize events
92
+ * @property {DisconnectMethod} disconnect - A function to disconnect the ResizeObserver instance
93
+ */
@@ -1,19 +1,3 @@
1
- /**
2
- * @typedef ScrollMetrics
3
- * @type {object}
4
- * @property {number} maxScrolled the furthest point reached by scrolling
5
- * @property {"vertical"|"horizontal"} direction scroll direction
6
- * @property {number} seen seen pixels
7
- * @property {number} unseen unseen pixels
8
- * @property {number} visible the area covered by the scrollable content
9
- * on the screen
10
- * @property {number} scrolled currently scrolled pixels
11
- * @property {number} scrollable total scrollable pixels
12
- * @property {"up"|"down"} vertical whether the vertical direction is
13
- * towards the up or the down
14
- * @property {"left"|"right"} horizontal whether the horizontal direction
15
- * is towards the left or the right
16
- */
17
1
  /**
18
2
  * This function takes a scroll event and calculates the scrolling
19
3
  * direction, along with metrics for visible, scrollable, and
@@ -23,11 +7,7 @@
23
7
  * along with relevant metrics.
24
8
  *
25
9
  * @param {Event} evt scroll event object
26
- * @param {object} metrics
27
- * @property {number} metrics.latestScrollTop latest vertically scrolled pixel
28
- * @property {number} metrics.latestScrollLeft latest horizontally scrolled pixel
29
- * @property {number} metrics.biggestScrollLeft biggest horizontally scrolled pixel
30
- * @property {number} metrics.biggestScrollTop biggest vertically scrolled pixel
10
+ * @param {LatestScrollMetrics} metrics latest scroll metrics
31
11
  * @return {ScrollMetrics}
32
12
  */
33
13
  export default function useScrollEvent(
@@ -95,3 +75,27 @@ export default function useScrollEvent(
95
75
  horizontal
96
76
  }
97
77
  }
78
+ /**
79
+ * @typedef ScrollMetrics
80
+ * @type {object}
81
+ * @property {number} maxScrolled the furthest point reached by scrolling
82
+ * @property {"vertical"|"horizontal"} direction scroll direction
83
+ * @property {number} seen seen pixels
84
+ * @property {number} unseen unseen pixels
85
+ * @property {number} visible the area covered by the scrollable content
86
+ * on the screen
87
+ * @property {number} scrolled currently scrolled pixels
88
+ * @property {number} scrollable total scrollable pixels
89
+ * @property {"up"|"down"} vertical whether the vertical direction is
90
+ * towards the up or the down
91
+ * @property {"left"|"right"} horizontal whether the horizontal direction
92
+ * is towards the left or the right
93
+ */
94
+ /**
95
+ * @typedef LatestScrollMetrics
96
+ * @type {object}
97
+ * @property {number} [latestScrollTop=0] latest vertically scrolled pixel
98
+ * @property {number} [latestScrollLeft=0] latest horizontally scrolled pixel
99
+ * @property {number} [biggestScrollLeft=0] biggest horizontally scrolled pixel
100
+ * @property {number} [biggestScrollTop=0] biggest vertically scrolled pixel
101
+ */