mark-3 0.0.4 → 0.0.6
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 +25 -0
- package/components/index.js +1 -0
- package/components/scroll-aware-content.vue +162 -0
- package/composables/index.js +5 -0
- package/composables/use-event-listener.js +20 -6
- package/composables/use-intersection-observer.js +67 -0
- package/composables/use-outside-clicks.js +62 -0
- package/composables/use-pointer-swipe.js +138 -0
- package/composables/use-scroll-event.js +97 -0
- package/composables/use-swipeable-drawer.js +161 -0
- package/helpers/array/index.js +1 -0
- package/helpers/array/pick-random.js +24 -0
- package/helpers/array/tests/pick-random.test.js +39 -0
- package/helpers/date/index.js +1 -1
- package/helpers/date/{from-timestamp/index.test.js → tests/format-timestamp.test.js} +1 -1
- package/helpers/date/tests/format.test.js +97 -0
- package/helpers/date/{is-on-same-day/index.test.js → tests/is-on-same-day.test.js} +1 -1
- package/helpers/date/{is-on-same-year/index.test.js → tests/is-on-same-year.test.js} +1 -1
- package/helpers/number/clamp.js +17 -0
- package/helpers/number/index.js +2 -0
- package/helpers/number/random.js +24 -0
- package/helpers/number/tests/clamp.test.js +37 -0
- package/helpers/number/tests/random.test.js +51 -0
- package/helpers/string/{camel-to-dash/index.js → camel-to-dash.js} +2 -2
- package/helpers/string/encode-subscript.js +45 -0
- package/helpers/string/encode-superscript.js +46 -0
- package/helpers/string/{escape-regex/index.js → escape-regex.js} +1 -1
- package/helpers/string/index.js +2 -0
- package/helpers/string/{camel-to-dash/index.test.js → tests/camel-to-dash.test.js} +1 -1
- package/helpers/string/tests/encode-subscript.test.js +41 -0
- package/helpers/string/tests/encode-superscript.test.js +42 -0
- package/helpers/string/{escape-regex/index.test.js → tests/escape-regex.test.js} +1 -1
- package/helpers/string/{trim/index.test.js → tests/trim.test.js} +1 -1
- package/helpers/string/{trim/index.js → trim.js} +2 -2
- package/helpers/time/debounce.js +29 -0
- package/helpers/time/index.js +1 -0
- package/helpers/time/tests/debounce.test.js +58 -0
- package/helpers/types/index.js +1 -0
- package/helpers/types/{is-empty/index.js → is-empty.js} +1 -1
- package/helpers/types/{is-map/index.js → is-map.js} +1 -1
- package/helpers/types/is-numeric.js +22 -0
- package/helpers/types/{is-set/index.js → is-set.js} +1 -1
- package/helpers/types/{get-type-name/index.test.js → tests/get-type-name.test.js} +2 -2
- package/helpers/types/{is-array/index.test.js → tests/is-array.test.js} +1 -1
- package/helpers/types/{is-empty/index.test.js → tests/is-empty.test.js} +1 -1
- package/helpers/types/{is-map/index.test.js → tests/is-map.test.js} +1 -1
- package/helpers/types/tests/is-numeric.test.js +51 -0
- package/helpers/types/{is-plain-object/index.test.js → tests/is-plain-object.test.js} +1 -1
- package/helpers/types/{is-set/index.test.js → tests/is-set.test.js} +1 -1
- package/helpers/types/{is-string/index.test.js → tests/is-string.test.js} +1 -1
- package/package.json +4 -1
- package/helpers/date/format/README.md +0 -33
- package/helpers/date/format/index.test.js +0 -97
- package/helpers/date/from-timestamp/README.md +0 -14
- package/helpers/date/is-on-same-day/README.md +0 -12
- package/helpers/date/is-on-same-year/README.md +0 -13
- package/helpers/string/camel-to-dash/README.md +0 -24
- package/helpers/string/escape-regex/README.md +0 -12
- package/helpers/string/trim/README.md +0 -66
- package/helpers/types/get-type-name/README.md +0 -11
- package/helpers/types/is-array/README.md +0 -11
- package/helpers/types/is-empty/README.md +0 -35
- package/helpers/types/is-map/README.md +0 -15
- package/helpers/types/is-plain-object/README.md +0 -17
- package/helpers/types/is-set/README.md +0 -15
- package/helpers/types/is-string/README.md +0 -12
- /package/helpers/date/{from-timestamp/index.js → format-timestamp.js} +0 -0
- /package/helpers/date/{format/index.js → format.js} +0 -0
- /package/helpers/date/{is-on-same-day/index.js → is-on-same-day.js} +0 -0
- /package/helpers/date/{is-on-same-year/index.js → is-on-same-year.js} +0 -0
- /package/helpers/types/{get-type-name/index.js → get-type-name.js} +0 -0
- /package/helpers/types/{is-array/index.js → is-array.js} +0 -0
- /package/helpers/types/{is-plain-object/index.js → is-plain-object.js} +0 -0
- /package/helpers/types/{is-string/index.js → is-string.js} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
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.6](https://github.com/ismailceylan/mark-3/compare/v0.0.5...v0.0.6) (2025-08-09)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **composables:** add new use-outside-clicks composable ([6657b90](https://github.com/ismailceylan/mark-3/commit/6657b90c8330888d7f5d23b20cd85519bd44530b))
|
|
11
|
+
* **composables:** add new use-pointer-swipe composable ([c048b5c](https://github.com/ismailceylan/mark-3/commit/c048b5ce2aafe1f8d8a79aa7b1b37398a8e195e2))
|
|
12
|
+
* **composables:** add new use-swipeable-drawer composable ([f9e7c7a](https://github.com/ismailceylan/mark-3/commit/f9e7c7a0f757b04d63fef2ce078ed89d5e044dd8))
|
|
13
|
+
* **helpers:** add new string/encode-superscript method ([655fb59](https://github.com/ismailceylan/mark-3/commit/655fb59692486202e04da2f742c5ce0d432e3b71))
|
|
14
|
+
|
|
15
|
+
### [0.0.5](https://github.com/ismailceylan/mark-3/compare/v0.0.4...v0.0.5) (2025-08-03)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Features
|
|
19
|
+
|
|
20
|
+
* **component:** add new scroll-aware-content component ([5cb2645](https://github.com/ismailceylan/mark-3/commit/5cb2645a7f2ed778adc5f30a6e684b89b4ac360a))
|
|
21
|
+
* **composables:** add new use-intersection-observer composable ([5d71054](https://github.com/ismailceylan/mark-3/commit/5d710544a3067f29c8b5c4ce1db66c1ab3549a06))
|
|
22
|
+
* **composables:** add new use-scroll-event composable ([00e87cc](https://github.com/ismailceylan/mark-3/commit/00e87cc5bab981f53c4c61a7f3858ad4d0c45a9c))
|
|
23
|
+
* **helpers:** add new array/pick-random method ([1679cfa](https://github.com/ismailceylan/mark-3/commit/1679cfa821219fe85aef19afc17ed1b831d08ffd))
|
|
24
|
+
* **helpers:** add new number/clamp method ([3694de5](https://github.com/ismailceylan/mark-3/commit/3694de551c05ad8cab78e139f35225f92d7e50b1))
|
|
25
|
+
* **helpers:** add new number/random method ([bf718b3](https://github.com/ismailceylan/mark-3/commit/bf718b3ffca5147fb7985bde8d5c22e2763b3bfc))
|
|
26
|
+
* **helpers:** add new string/encode-subscript method ([3dce61a](https://github.com/ismailceylan/mark-3/commit/3dce61a8c9d927126ddb94118a33d0663bba5269))
|
|
27
|
+
* **helpers:** add new time/debounce helper method ([e9c4232](https://github.com/ismailceylan/mark-3/commit/e9c423233f7b946715f8624432af40f96aba9e90))
|
|
28
|
+
* **helpers:** add new types/is-numeric helper method ([ab84773](https://github.com/ismailceylan/mark-3/commit/ab847736ae695fce62e42155c3653a216f6e321b))
|
|
29
|
+
|
|
5
30
|
### [0.0.4](https://github.com/ismailceylan/mark-3/compare/v0.0.3...v0.0.4) (2025-08-02)
|
|
6
31
|
|
|
7
32
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as ScrollAwareContent } from "./scroll-aware-content.vue";
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<!-- mark-3/components/scroll-aware-content -->
|
|
3
|
+
<section ref="rootEl">
|
|
4
|
+
<slot
|
|
5
|
+
v-bind="
|
|
6
|
+
{
|
|
7
|
+
observe, biggestScrollTop, biggestScrollLeft,
|
|
8
|
+
latestScrollTop, latestScrollLeft
|
|
9
|
+
}"
|
|
10
|
+
/>
|
|
11
|
+
</section>
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<script setup>
|
|
15
|
+
import { ref, onMounted, onUnmounted } from "vue";
|
|
16
|
+
import { debounce } from "../helpers/time";
|
|
17
|
+
import { useIntersectionObserver, useScrollEvent } from "../composables";
|
|
18
|
+
|
|
19
|
+
const rootEl = ref( null );
|
|
20
|
+
const targetEl = ref( null );
|
|
21
|
+
const emit = defineEmits([ "scroll", "metrics" ]);
|
|
22
|
+
|
|
23
|
+
let biggestScrollTop = 0;
|
|
24
|
+
let latestScrollTop = 0;
|
|
25
|
+
let biggestScrollLeft = 0;
|
|
26
|
+
let latestScrollLeft = 0;
|
|
27
|
+
|
|
28
|
+
defineExpose({ scrollTo, reset });
|
|
29
|
+
|
|
30
|
+
const { factor, scrollable, delay, intersect } = defineProps(
|
|
31
|
+
{
|
|
32
|
+
scrollable: [ String, HTMLElement, Document ],
|
|
33
|
+
intersect: Function,
|
|
34
|
+
delay:
|
|
35
|
+
{
|
|
36
|
+
type: Number,
|
|
37
|
+
default: 50
|
|
38
|
+
},
|
|
39
|
+
factor:
|
|
40
|
+
{
|
|
41
|
+
type: Number,
|
|
42
|
+
default: 1.2
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const onScrollThrottled = debounce( onScroll, delay );
|
|
47
|
+
const { observe } = useIntersectionObserver( targetEl,
|
|
48
|
+
{
|
|
49
|
+
intersect,
|
|
50
|
+
rootMargin: "-0px",
|
|
51
|
+
threshold: [ 0, 1, .6 ]
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
onMounted(() =>
|
|
55
|
+
{
|
|
56
|
+
targetEl.value = (() =>
|
|
57
|
+
{
|
|
58
|
+
if( typeof scrollable == "string" )
|
|
59
|
+
{
|
|
60
|
+
if( scrollable == "html" )
|
|
61
|
+
{
|
|
62
|
+
return document;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if( scrollable == "body" )
|
|
66
|
+
{
|
|
67
|
+
return document.body;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if( scrollable[ 0 ] == "#" )
|
|
71
|
+
{
|
|
72
|
+
return document.querySelector( scrollable );
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return rootEl.value.querySelector( scrollable );
|
|
76
|
+
}
|
|
77
|
+
else
|
|
78
|
+
{
|
|
79
|
+
return scrollable || rootEl.value;
|
|
80
|
+
}
|
|
81
|
+
})();
|
|
82
|
+
|
|
83
|
+
targetEl.value.scrollTop = latestScrollTop;
|
|
84
|
+
targetEl.value.scrollLeft = latestScrollLeft;
|
|
85
|
+
|
|
86
|
+
targetEl.value.addEventListener( "scroll", onScrollThrottled,
|
|
87
|
+
{
|
|
88
|
+
passive: true
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
onUnmounted(() =>
|
|
94
|
+
targetEl.value.removeEventListener( "scroll", onScrollThrottled )
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
function scrollTo({ top = 0, left = 0 })
|
|
98
|
+
{
|
|
99
|
+
targetEl.value.scrollTo({ top, left });
|
|
100
|
+
latestScrollTop = biggestScrollTop = top;
|
|
101
|
+
latestScrollLeft = biggestScrollLeft = left;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function reset()
|
|
105
|
+
{
|
|
106
|
+
biggestScrollTop = 0;
|
|
107
|
+
latestScrollTop = 0;
|
|
108
|
+
biggestScrollLeft = 0;
|
|
109
|
+
latestScrollLeft = 0;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function onScroll( evt )
|
|
113
|
+
{
|
|
114
|
+
const metrics = useScrollEvent( evt,
|
|
115
|
+
{
|
|
116
|
+
latestScrollTop, latestScrollLeft,
|
|
117
|
+
biggestScrollLeft, biggestScrollTop
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const { direction, scrollable, visible, maxScrolled, scrolled } = metrics;
|
|
121
|
+
|
|
122
|
+
// if user scroll to one way and scroll back to starting point
|
|
123
|
+
// immediately, direction variable will be null which that means
|
|
124
|
+
// there was no scrolling at all
|
|
125
|
+
if( ! direction )
|
|
126
|
+
{
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if( direction == "vertical" )
|
|
131
|
+
{
|
|
132
|
+
latestScrollTop = scrolled;
|
|
133
|
+
|
|
134
|
+
if( scrolled > biggestScrollTop )
|
|
135
|
+
{
|
|
136
|
+
biggestScrollTop = scrolled;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
else if( direction == "horizontal" )
|
|
140
|
+
{
|
|
141
|
+
latestScrollLeft = scrolled;
|
|
142
|
+
|
|
143
|
+
if( scrolled > biggestScrollLeft )
|
|
144
|
+
{
|
|
145
|
+
biggestScrollLeft = scrolled;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
emit( "metrics",
|
|
150
|
+
{
|
|
151
|
+
biggestScrollLeft, biggestScrollTop,
|
|
152
|
+
latestScrollLeft, latestScrollTop,
|
|
153
|
+
...metrics
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
if( scrollable - visible - maxScrolled < visible * factor )
|
|
157
|
+
{
|
|
158
|
+
emit( "scroll", metrics );
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
</script>
|
package/composables/index.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
export { default as useMediaQuery } from "./use-media-query.js";
|
|
2
|
+
export { default as useScrollEvent } from "./use-scroll-event.js";
|
|
3
|
+
export { default as usePointerSwipe } from "./use-pointer-swipe.js";
|
|
4
|
+
export { default as useOutsideClicks } from "./use-outside-clicks.js";
|
|
2
5
|
export { default as useEventListener } from "./use-event-listener.js";
|
|
3
6
|
export { default as useResponsiveness } from "./use-responsiveness.js";
|
|
7
|
+
export { default as useSwipeableDrawer } from "./use-swipeable-drawer.js";
|
|
8
|
+
export { default as useIntersectionObserver } from "./use-intersection-observer.js";
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import { onMounted, onUnmounted, getCurrentInstance } from "vue";
|
|
1
|
+
import { isRef, unref, onMounted, onUnmounted, getCurrentInstance } from "vue";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* @template T
|
|
5
|
+
* @typedef {import("vue").Ref<T>} Ref
|
|
6
|
+
*/
|
|
3
7
|
/**
|
|
4
8
|
* A composition function that adds a DOM event listener to the given target with the
|
|
5
9
|
* given event name and callback. The options object is optional. If the composition
|
|
@@ -8,15 +12,15 @@ import { onMounted, onUnmounted, getCurrentInstance } from "vue";
|
|
|
8
12
|
* composition function is called outside of a component, then the event listener is
|
|
9
13
|
* added immediately and must be removed manually by calling the returned function.
|
|
10
14
|
*
|
|
11
|
-
* @param {
|
|
15
|
+
* @param {EventTarget|Ref<EventTarget>} maybeRefTarget - The target element to add the event listener to.
|
|
12
16
|
* @param {string} eventName - The name of the event to add a listener for.
|
|
13
17
|
* @param {function} callBack - The callback function to call when the event happens.
|
|
14
18
|
* @param {object} [options] - The options object to pass to addEventListener.
|
|
15
19
|
* @returns {function} - A function that can be called to remove the event listener.
|
|
16
20
|
*/
|
|
17
|
-
export default function useEventListener(
|
|
21
|
+
export default function useEventListener( maybeRefTarget, eventName, callBack, options )
|
|
18
22
|
{
|
|
19
|
-
if( getCurrentInstance())
|
|
23
|
+
if( getCurrentInstance() && isRef( maybeRefTarget ))
|
|
20
24
|
{
|
|
21
25
|
onMounted( listen );
|
|
22
26
|
onUnmounted( stop );
|
|
@@ -28,12 +32,22 @@ export default function useEventListener( target, eventName, callBack, options )
|
|
|
28
32
|
|
|
29
33
|
function listen()
|
|
30
34
|
{
|
|
31
|
-
|
|
35
|
+
const el = unref( maybeRefTarget );
|
|
36
|
+
|
|
37
|
+
if( el && el.addEventListener )
|
|
38
|
+
{
|
|
39
|
+
el.addEventListener( eventName, callBack, options );
|
|
40
|
+
}
|
|
32
41
|
}
|
|
33
42
|
|
|
34
43
|
function stop()
|
|
35
44
|
{
|
|
36
|
-
|
|
45
|
+
const el = unref( maybeRefTarget );
|
|
46
|
+
|
|
47
|
+
if( el && el.addEventListener )
|
|
48
|
+
{
|
|
49
|
+
el.removeEventListener( eventName, callBack );
|
|
50
|
+
}
|
|
37
51
|
}
|
|
38
52
|
|
|
39
53
|
return stop;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { unref, onMounted } from "vue";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A composition function that creates an IntersectionObserver instance and
|
|
5
|
+
* provides a method to observe the provided elements.
|
|
6
|
+
*
|
|
7
|
+
* @param {Ref<Element>|Element} maybeRefSourceElement - The source element
|
|
8
|
+
* that the observer will observe for intersection.
|
|
9
|
+
* @param {Object} [options] - The options to pass to the IntersectionObserver
|
|
10
|
+
* constructor.
|
|
11
|
+
* @returns {{ observe: (el: Element) => void, observer: IntersectionObserver }}
|
|
12
|
+
* A object with two properties. The `observe` property is a function that
|
|
13
|
+
* takes an element to observe and the `observer` property is the created
|
|
14
|
+
* IntersectionObserver instance.
|
|
15
|
+
*/
|
|
16
|
+
export default function useIntersectionObserver( maybeRefSourceElement, options = {})
|
|
17
|
+
{
|
|
18
|
+
let observer;
|
|
19
|
+
|
|
20
|
+
onMounted( init );
|
|
21
|
+
|
|
22
|
+
function init()
|
|
23
|
+
{
|
|
24
|
+
options.source = unref( maybeRefSourceElement );
|
|
25
|
+
observer = new IntersectionObserver( handleObservations, options );
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function handleObservations( entries )
|
|
29
|
+
{
|
|
30
|
+
requestAnimationFrame(() =>
|
|
31
|
+
{
|
|
32
|
+
for( const { target: { item }, isIntersecting } of entries )
|
|
33
|
+
{
|
|
34
|
+
if( options.intersect )
|
|
35
|
+
{
|
|
36
|
+
options.intersect( item, isIntersecting );
|
|
37
|
+
}
|
|
38
|
+
else
|
|
39
|
+
{
|
|
40
|
+
item.isIntersecting = isIntersecting;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function observe( item )
|
|
47
|
+
{
|
|
48
|
+
return el =>
|
|
49
|
+
{
|
|
50
|
+
if( ! el )
|
|
51
|
+
{
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
el.item = item;
|
|
56
|
+
|
|
57
|
+
if( ! observer )
|
|
58
|
+
{
|
|
59
|
+
init();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
observer && observer.observe( el );
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return { observe, observer }
|
|
67
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { useEventListener } from ".";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Watches for clicks outside of the given element and calls the
|
|
5
|
+
* callback when it happens.
|
|
6
|
+
*
|
|
7
|
+
* This is useful for things like dropdowns, where you want to close
|
|
8
|
+
* the dropdown when the user clicks outside of it.
|
|
9
|
+
*
|
|
10
|
+
* This is a composition function that uses useEventListener under the
|
|
11
|
+
* hood. So, it will automatically remove the event listener when the
|
|
12
|
+
* component is unmounted.
|
|
13
|
+
*
|
|
14
|
+
* It also returns a function that can be used to remove the event
|
|
15
|
+
* listener. This is useful for when you want to remove the event
|
|
16
|
+
* listener when the extraordinary circumstances arise.
|
|
17
|
+
*
|
|
18
|
+
* @typedef {import('vue').Ref<Element>} ElementRef
|
|
19
|
+
* @param {ElementRef[]} elRefs - The elements to watch for clicks outside of.
|
|
20
|
+
* @param {function} callback - The callback to call when a click is outside of the element.
|
|
21
|
+
* @param {object} options - The options object.
|
|
22
|
+
* @property {string} options.on - The event to listen for.
|
|
23
|
+
* @returns {function}
|
|
24
|
+
* @example
|
|
25
|
+
* const elRef = ref();
|
|
26
|
+
*
|
|
27
|
+
* useOutsideClick( elRef, () =>
|
|
28
|
+
* console.log( "Clicked outside!" )
|
|
29
|
+
* );
|
|
30
|
+
* @example
|
|
31
|
+
* const elRef = ref();
|
|
32
|
+
*
|
|
33
|
+
* useOutsideClick(
|
|
34
|
+
* elRef,
|
|
35
|
+
* () => console.log( "Clicked outside!" ),
|
|
36
|
+
* {
|
|
37
|
+
* on: "mousedown"
|
|
38
|
+
* }
|
|
39
|
+
* );
|
|
40
|
+
*/
|
|
41
|
+
export default function useOutsideClick( elRefs, callback, { on = "click" } = {})
|
|
42
|
+
{
|
|
43
|
+
return useEventListener( document, on, e =>
|
|
44
|
+
{
|
|
45
|
+
if( elRefs.length === 0 )
|
|
46
|
+
{
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
for( let el of elRefs )
|
|
51
|
+
{
|
|
52
|
+
el = el.value;
|
|
53
|
+
|
|
54
|
+
if( el?.contains( e.target ))
|
|
55
|
+
{
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
callback( e );
|
|
61
|
+
});
|
|
62
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { ref, reactive, computed, onMounted, onUnmounted } from "vue";
|
|
2
|
+
import { useEventListener } from ".";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A composition function that detects swipe gestures on a given element and provides
|
|
6
|
+
* reactive properties related to the swipe event.
|
|
7
|
+
*
|
|
8
|
+
* @param {Ref<EventTarget>|EventTarget} maybeRefEl - The element to detect swipe gestures on.
|
|
9
|
+
* @param {Object} [options] - Configuration options.
|
|
10
|
+
* @param {number} [options.threshold=50] - The minimum distance in pixels to consider a swipe.
|
|
11
|
+
* @param {boolean} [options.disableTextSelect=false] - Whether to disable text selection during swipe.
|
|
12
|
+
* @returns {PointerSwipeReturnValue} - An object containing reactive properties related to the swipe:
|
|
13
|
+
*/
|
|
14
|
+
export default function usePointerSwipe( maybeRefEl, { threshold = 50, disableTextSelect = false } = {})
|
|
15
|
+
{
|
|
16
|
+
const { abs, max } = Math;
|
|
17
|
+
const isSwiping = ref( false );
|
|
18
|
+
const posStart = reactive({ x: 0, y: 0 });
|
|
19
|
+
const posEnd = reactive({ x: 0, y: 0 });
|
|
20
|
+
const timeStart = ref( 0 );
|
|
21
|
+
const timeEnd = ref( 0 );
|
|
22
|
+
const distanceX = ref( 0 );
|
|
23
|
+
const distanceY = ref( 0 );
|
|
24
|
+
|
|
25
|
+
const timePassed = computed(() => timeEnd.value - timeStart.value );
|
|
26
|
+
|
|
27
|
+
const isThresholdExceeded = computed(() =>
|
|
28
|
+
max(abs( distanceX.value ), abs( distanceY.value )) >= threshold
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const direction = computed(() =>
|
|
32
|
+
{
|
|
33
|
+
if( ! isThresholdExceeded.value )
|
|
34
|
+
{
|
|
35
|
+
return "none";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if( abs( distanceX.value ) > abs( distanceY.value ))
|
|
39
|
+
{
|
|
40
|
+
return distanceX.value > 0
|
|
41
|
+
? "left"
|
|
42
|
+
: "right";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return distanceY.value > 0
|
|
46
|
+
? "up"
|
|
47
|
+
: "down";
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const velocity = computed(() =>
|
|
51
|
+
{
|
|
52
|
+
if( direction.value === "none" )
|
|
53
|
+
{
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const distance = [ "up", "down" ].includes( direction.value )
|
|
58
|
+
? distanceY.value
|
|
59
|
+
: distanceX.value;
|
|
60
|
+
|
|
61
|
+
return abs( distance / timePassed.value );
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
useEventListener( maybeRefEl, "pointerdown", e =>
|
|
65
|
+
{
|
|
66
|
+
isSwiping.value = true;
|
|
67
|
+
posStart.x = e.clientX;
|
|
68
|
+
posStart.y = e.clientY;
|
|
69
|
+
timeStart.value = performance.now();
|
|
70
|
+
|
|
71
|
+
const stopListeningMove = useEventListener( maybeRefEl, "pointermove", e =>
|
|
72
|
+
{
|
|
73
|
+
posEnd.x = e.clientX;
|
|
74
|
+
posEnd.y = e.clientY;
|
|
75
|
+
distanceX.value = posEnd.x - posStart.x;
|
|
76
|
+
distanceY.value = posEnd.y - posStart.y;
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
useEventListener( maybeRefEl, "pointerup", () =>
|
|
80
|
+
{
|
|
81
|
+
isSwiping.value = false;
|
|
82
|
+
timeEnd.value = performance.now();
|
|
83
|
+
|
|
84
|
+
stopListeningMove();
|
|
85
|
+
},{ once: true });
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
onMounted(() =>
|
|
89
|
+
{
|
|
90
|
+
if( disableTextSelect )
|
|
91
|
+
{
|
|
92
|
+
document.documentElement.style.setProperty( "user-select", "none" );
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
onUnmounted(() =>
|
|
97
|
+
{
|
|
98
|
+
if( disableTextSelect )
|
|
99
|
+
{
|
|
100
|
+
document.documentElement.style.removeProperty( "user-select" );
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
isSwiping,
|
|
106
|
+
posStart,
|
|
107
|
+
posEnd,
|
|
108
|
+
distanceX,
|
|
109
|
+
distanceY,
|
|
110
|
+
isThresholdExceeded,
|
|
111
|
+
direction,
|
|
112
|
+
velocity
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @template T
|
|
118
|
+
* @typedef {import("vue").Ref<T>} Ref
|
|
119
|
+
*/
|
|
120
|
+
/**
|
|
121
|
+
* @template T
|
|
122
|
+
* @typedef {import("vue").ComputedRef<T>} ComputedRef
|
|
123
|
+
*/
|
|
124
|
+
/**
|
|
125
|
+
* @template T
|
|
126
|
+
* @typedef {import("vue").Reactive<T>} Reactive
|
|
127
|
+
*/
|
|
128
|
+
/**
|
|
129
|
+
* @typedef {object} PointerSwipeReturnValue
|
|
130
|
+
* @property {Ref<boolean>} isSwiping - Indicates if a swipe is currently happening.
|
|
131
|
+
* @property {Reactive<{x: number, y: number}>} posStart - The starting position of the swipe.
|
|
132
|
+
* @property {Reactive<{x: number, y: number}>} posEnd - The ending position of the swipe.
|
|
133
|
+
* @property {Ref<number>} distanceX - The horizontal distance swiped.
|
|
134
|
+
* @property {Ref<number>} distanceY - The vertical distance swiped.
|
|
135
|
+
* @property {ComputedRef<boolean>} isThresholdExceeded - Whether the swipe exceeded the threshold.
|
|
136
|
+
* @property {ComputedRef<"left"|"right"|"up"|"down"|"none">} direction - The direction of the swipe.
|
|
137
|
+
* @property {ComputedRef<number>} velocity - The velocity of the swipe.
|
|
138
|
+
*/
|
|
@@ -0,0 +1,97 @@
|
|
|
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
|
+
/**
|
|
18
|
+
* This function takes a scroll event and calculates the scrolling
|
|
19
|
+
* direction, along with metrics for visible, scrollable, and
|
|
20
|
+
* unseen areas.
|
|
21
|
+
*
|
|
22
|
+
* The direction is determined as vertical or horizontal, and returned
|
|
23
|
+
* along with relevant metrics.
|
|
24
|
+
*
|
|
25
|
+
* @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
|
|
31
|
+
* @return {ScrollMetrics}
|
|
32
|
+
*/
|
|
33
|
+
export default function useScrollEvent(
|
|
34
|
+
{ target },
|
|
35
|
+
{ latestScrollTop = 0, latestScrollLeft = 0, biggestScrollLeft = 0, biggestScrollTop = 0 } = {}
|
|
36
|
+
)
|
|
37
|
+
{
|
|
38
|
+
target = target === document
|
|
39
|
+
? document.scrollingElement
|
|
40
|
+
: target;
|
|
41
|
+
|
|
42
|
+
const { clientHeight, clientWidth, scrollTop, scrollLeft, scrollHeight, scrollWidth } = target;
|
|
43
|
+
const vertical = calcDirection( "down", null, "up", [ scrollTop, latestScrollTop ]);
|
|
44
|
+
const horizontal = calcDirection( "right", null, "left", [ scrollLeft, latestScrollLeft ]);
|
|
45
|
+
const direction = ( vertical && "vertical" ) || ( horizontal && "horizontal" );
|
|
46
|
+
let visible = 0, scrollable = 0, scrolled = 0;
|
|
47
|
+
let seen = 0, unseen = 0, maxScrolled = 0;
|
|
48
|
+
|
|
49
|
+
if( direction == "vertical" )
|
|
50
|
+
{
|
|
51
|
+
visible = clientHeight;
|
|
52
|
+
scrollable = scrollHeight;
|
|
53
|
+
scrolled = scrollTop;
|
|
54
|
+
maxScrolled = biggestScrollTop;
|
|
55
|
+
}
|
|
56
|
+
else if( direction == "horizontal" )
|
|
57
|
+
{
|
|
58
|
+
visible = clientWidth;
|
|
59
|
+
scrollable = scrollWidth;
|
|
60
|
+
scrolled = scrollLeft;
|
|
61
|
+
maxScrolled = biggestScrollLeft;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
seen = scrolled + visible;
|
|
65
|
+
unseen = scrollable - seen;
|
|
66
|
+
maxScrolled = Math.max( maxScrolled, scrolled );
|
|
67
|
+
|
|
68
|
+
function calcDirection( a, middle, b, [ v1, v2 ])
|
|
69
|
+
{
|
|
70
|
+
if( v1 === v2 )
|
|
71
|
+
{
|
|
72
|
+
return middle;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if( v2 > v1 )
|
|
76
|
+
{
|
|
77
|
+
return b;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if( v2 < v1 )
|
|
81
|
+
{
|
|
82
|
+
return a;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
maxScrolled,
|
|
88
|
+
direction,
|
|
89
|
+
seen,
|
|
90
|
+
unseen,
|
|
91
|
+
visible,
|
|
92
|
+
scrolled,
|
|
93
|
+
scrollable,
|
|
94
|
+
vertical,
|
|
95
|
+
horizontal
|
|
96
|
+
}
|
|
97
|
+
}
|