mark-3 0.0.4 → 0.0.5
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 +15 -0
- package/components/index.js +1 -0
- package/components/scroll-aware-content.vue +162 -0
- package/composables/index.js +2 -0
- package/composables/use-intersection-observer.js +67 -0
- package/composables/use-scroll-event.js +97 -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/{escape-regex/index.js → escape-regex.js} +1 -1
- package/helpers/string/index.js +1 -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/{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 +1 -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,21 @@
|
|
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.5](https://github.com/ismailceylan/mark-3/compare/v0.0.4...v0.0.5) (2025-08-03)
|
6
|
+
|
7
|
+
|
8
|
+
### Features
|
9
|
+
|
10
|
+
* **component:** add new scroll-aware-content component ([5cb2645](https://github.com/ismailceylan/mark-3/commit/5cb2645a7f2ed778adc5f30a6e684b89b4ac360a))
|
11
|
+
* **composables:** add new use-intersection-observer composable ([5d71054](https://github.com/ismailceylan/mark-3/commit/5d710544a3067f29c8b5c4ce1db66c1ab3549a06))
|
12
|
+
* **composables:** add new use-scroll-event composable ([00e87cc](https://github.com/ismailceylan/mark-3/commit/00e87cc5bab981f53c4c61a7f3858ad4d0c45a9c))
|
13
|
+
* **helpers:** add new array/pick-random method ([1679cfa](https://github.com/ismailceylan/mark-3/commit/1679cfa821219fe85aef19afc17ed1b831d08ffd))
|
14
|
+
* **helpers:** add new number/clamp method ([3694de5](https://github.com/ismailceylan/mark-3/commit/3694de551c05ad8cab78e139f35225f92d7e50b1))
|
15
|
+
* **helpers:** add new number/random method ([bf718b3](https://github.com/ismailceylan/mark-3/commit/bf718b3ffca5147fb7985bde8d5c22e2763b3bfc))
|
16
|
+
* **helpers:** add new string/encode-subscript method ([3dce61a](https://github.com/ismailceylan/mark-3/commit/3dce61a8c9d927126ddb94118a33d0663bba5269))
|
17
|
+
* **helpers:** add new time/debounce helper method ([e9c4232](https://github.com/ismailceylan/mark-3/commit/e9c423233f7b946715f8624432af40f96aba9e90))
|
18
|
+
* **helpers:** add new types/is-numeric helper method ([ab84773](https://github.com/ismailceylan/mark-3/commit/ab847736ae695fce62e42155c3653a216f6e321b))
|
19
|
+
|
5
20
|
### [0.0.4](https://github.com/ismailceylan/mark-3/compare/v0.0.3...v0.0.4) (2025-08-02)
|
6
21
|
|
7
22
|
|
@@ -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,5 @@
|
|
1
1
|
export { default as useMediaQuery } from "./use-media-query.js";
|
2
|
+
export { default as useScrollEvent } from "./use-scroll-event.js";
|
2
3
|
export { default as useEventListener } from "./use-event-listener.js";
|
3
4
|
export { default as useResponsiveness } from "./use-responsiveness.js";
|
5
|
+
export { default as useIntersectionObserver } from "./use-intersection-observer.js";
|
@@ -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,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
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
export { default as pickRandom } from "./pick-random";
|
@@ -0,0 +1,24 @@
|
|
1
|
+
import { random } from "../number";
|
2
|
+
import { isArray } from "../types";
|
3
|
+
|
4
|
+
/**
|
5
|
+
* Picks a random element from the given array.
|
6
|
+
*
|
7
|
+
* @template T
|
8
|
+
* @param {T[]} arr - The array to pick a random element from.
|
9
|
+
* @returns {T|undefined} - The random element from the array, or undefined if the array is empty.
|
10
|
+
*/
|
11
|
+
export default function pickRandom( arr )
|
12
|
+
{
|
13
|
+
if( ! isArray( arr ))
|
14
|
+
{
|
15
|
+
return undefined;
|
16
|
+
}
|
17
|
+
|
18
|
+
if( arr.length === 0 )
|
19
|
+
{
|
20
|
+
return undefined;
|
21
|
+
}
|
22
|
+
|
23
|
+
return arr[ random( 0, arr.length - 1 )];
|
24
|
+
}
|
@@ -0,0 +1,39 @@
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
2
|
+
import { pickRandom } from "..";
|
3
|
+
|
4
|
+
// örnek test verisi
|
5
|
+
const sampleArray = [10, 20, 30, 40, 50];
|
6
|
+
|
7
|
+
describe("pickRandom", () =>
|
8
|
+
{
|
9
|
+
it("should return undefined if input is not an array", () =>
|
10
|
+
{
|
11
|
+
expect(pickRandom(null)).toBeUndefined();
|
12
|
+
expect(pickRandom({})).toBeUndefined();
|
13
|
+
expect(pickRandom("not array")).toBeUndefined();
|
14
|
+
expect(pickRandom(123)).toBeUndefined();
|
15
|
+
});
|
16
|
+
|
17
|
+
it("should return undefined if array is empty", () =>
|
18
|
+
{
|
19
|
+
expect(pickRandom([])).toBeUndefined();
|
20
|
+
});
|
21
|
+
|
22
|
+
it("should return an element from the array", () =>
|
23
|
+
{
|
24
|
+
const value = pickRandom(sampleArray);
|
25
|
+
expect(sampleArray).toContain(value);
|
26
|
+
});
|
27
|
+
|
28
|
+
it("should return different results across multiple calls", () =>
|
29
|
+
{
|
30
|
+
const results = new Set();
|
31
|
+
for (let i = 0; i < 20; i++)
|
32
|
+
{
|
33
|
+
const val = pickRandom(sampleArray);
|
34
|
+
results.add(val);
|
35
|
+
}
|
36
|
+
// Rastgelelik garantisi yok ama çeşitlilik kontrolü yapılabilir
|
37
|
+
expect(results.size).toBeGreaterThan(1);
|
38
|
+
});
|
39
|
+
});
|
package/helpers/date/index.js
CHANGED
@@ -1,4 +1,4 @@
|
|
1
1
|
export { default as format } from "./format";
|
2
2
|
export { default as isOnSameDay } from "./is-on-same-day";
|
3
|
-
export { default as fromTimestamp } from "./
|
3
|
+
export { default as fromTimestamp } from "./format-timestamp";
|
4
4
|
export { default as isOnSameYear } from "./is-on-same-year";
|
@@ -0,0 +1,97 @@
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
2
|
+
import { format } from "..";
|
3
|
+
|
4
|
+
describe( "format", () =>
|
5
|
+
{
|
6
|
+
const date = new Date( "2025-12-21T22:10:05" );
|
7
|
+
|
8
|
+
it( "formats full year (Y)", () =>
|
9
|
+
{
|
10
|
+
expect( format( date, "Y" )).toBe( "2025" );
|
11
|
+
});
|
12
|
+
|
13
|
+
it( "formats short year (y)", () =>
|
14
|
+
{
|
15
|
+
expect( format( date, "y" )).toBe( "25" );
|
16
|
+
});
|
17
|
+
|
18
|
+
it( "formats zero-padded month (m)", () =>
|
19
|
+
{
|
20
|
+
expect( format( date, "m" )).toBe( "12" );
|
21
|
+
});
|
22
|
+
|
23
|
+
it( "formats non-padded month (n)", () =>
|
24
|
+
{
|
25
|
+
expect( format( date, "n" )).toBe( "12" );
|
26
|
+
});
|
27
|
+
|
28
|
+
it( "formats short month name (M)", () =>
|
29
|
+
{
|
30
|
+
expect( format( date, "M" )).toBe( "Dec" );
|
31
|
+
});
|
32
|
+
|
33
|
+
it( "formats full month name (MM)", () =>
|
34
|
+
{
|
35
|
+
expect( format( date, "MM" )).toBe( "December" );
|
36
|
+
});
|
37
|
+
|
38
|
+
it( "formats day padded (d)", () =>
|
39
|
+
{
|
40
|
+
expect( format( date, "d" )).toBe( "21" );
|
41
|
+
});
|
42
|
+
|
43
|
+
it( "formats day non-padded (j)", () =>
|
44
|
+
{
|
45
|
+
expect( format( date, "j" )).toBe( "21" );
|
46
|
+
});
|
47
|
+
|
48
|
+
it( "formats zero-padded hour (H)", () =>
|
49
|
+
{
|
50
|
+
expect( format( date, "H" )).toBe( "22" );
|
51
|
+
});
|
52
|
+
|
53
|
+
it( "formats non-padded hour (G)", () =>
|
54
|
+
{
|
55
|
+
expect( format( date, "G" )).toBe( "22" );
|
56
|
+
});
|
57
|
+
|
58
|
+
it( "formats minutes (i)", () =>
|
59
|
+
{
|
60
|
+
expect( format( date, "i" )).toBe( "10" );
|
61
|
+
});
|
62
|
+
|
63
|
+
it( "formats seconds (s)", () =>
|
64
|
+
{
|
65
|
+
expect( format( date, "s" )).toBe( "05" );
|
66
|
+
});
|
67
|
+
|
68
|
+
it( "formats a custom pattern", () =>
|
69
|
+
{
|
70
|
+
expect( format( date, "d.m.Y H:i" )).toBe( "21.12.2025 22:10" );
|
71
|
+
});
|
72
|
+
|
73
|
+
it( "supports localized short month (M) in TR", () =>
|
74
|
+
{
|
75
|
+
expect( format( date, "M", "tr" )).toBe( "Ara" ); // Aralık
|
76
|
+
});
|
77
|
+
|
78
|
+
it( "supports localized long month (MM) in TR", () =>
|
79
|
+
{
|
80
|
+
expect( format( date, "MM", "tr" )).toBe( "Aralık" );
|
81
|
+
});
|
82
|
+
|
83
|
+
it( "returns empty string for empty pattern", () =>
|
84
|
+
{
|
85
|
+
expect( format( date, "" )).toBe( "" );
|
86
|
+
});
|
87
|
+
|
88
|
+
it( "leaves unknown tokens as-is", () =>
|
89
|
+
{
|
90
|
+
expect( format( date, "[foo] Y" )).toBe( "[foo] 2025" );
|
91
|
+
});
|
92
|
+
|
93
|
+
it( "mixes plain text and tokens", () =>
|
94
|
+
{
|
95
|
+
expect( format( date, "d MM Y, H:i" )).toBe( "21 December 2025, 22:10" );
|
96
|
+
});
|
97
|
+
});
|
@@ -0,0 +1,17 @@
|
|
1
|
+
/**
|
2
|
+
* Clamps a value within a range.
|
3
|
+
*
|
4
|
+
* @param {number} value value to be clamped
|
5
|
+
* @param {number} min minimum value
|
6
|
+
* @param {number} max maximum value
|
7
|
+
* @return {number} clamped value
|
8
|
+
*/
|
9
|
+
export default function clamp( value, min, max )
|
10
|
+
{
|
11
|
+
if( min > max )
|
12
|
+
{
|
13
|
+
[ min, max ] = [ max, min ];
|
14
|
+
}
|
15
|
+
|
16
|
+
return Math.max( min, Math.min( max, value ));
|
17
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
import { clamp } from ".";
|
2
|
+
|
3
|
+
/**
|
4
|
+
* Produce a random number between two values. If no value is provided,
|
5
|
+
* a random number is produced.
|
6
|
+
*
|
7
|
+
* @param {number} min minimum value
|
8
|
+
* @param {number} max maximum value
|
9
|
+
* @param {boolean} shouldFloat should the result be a float
|
10
|
+
* @return {number}
|
11
|
+
*/
|
12
|
+
export default function random( min = 0, max = 1, shouldFloat = false )
|
13
|
+
{
|
14
|
+
if( min > max )
|
15
|
+
{
|
16
|
+
[ min, max ] = [ max, min ];
|
17
|
+
}
|
18
|
+
|
19
|
+
const rnd = clamp( Math.random() * max, min, max );
|
20
|
+
|
21
|
+
return shouldFloat
|
22
|
+
? parseFloat( rnd )
|
23
|
+
: Math.floor( rnd );
|
24
|
+
}
|
@@ -0,0 +1,37 @@
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
2
|
+
import { clamp } from "..";
|
3
|
+
|
4
|
+
describe("clamp()", () =>
|
5
|
+
{
|
6
|
+
it("returns the same value if it is within the min and max range", () =>
|
7
|
+
{
|
8
|
+
expect(clamp(5, 0, 10)).toBe(5);
|
9
|
+
expect(clamp(0, -10, 10)).toBe(0);
|
10
|
+
expect(clamp(9.5, 0, 10)).toBe(9.5);
|
11
|
+
});
|
12
|
+
|
13
|
+
it("clamps the value to the min if it is less than min", () =>
|
14
|
+
{
|
15
|
+
expect(clamp(-5, 0, 10)).toBe(0);
|
16
|
+
expect(clamp(-100, -10, 10)).toBe(-10);
|
17
|
+
});
|
18
|
+
|
19
|
+
it("clamps the value to the max if it is greater than max", () =>
|
20
|
+
{
|
21
|
+
expect(clamp(15, 0, 10)).toBe(10);
|
22
|
+
expect(clamp(99, -10, 10)).toBe(10);
|
23
|
+
});
|
24
|
+
|
25
|
+
it("returns the only possible value if min and max are equal", () =>
|
26
|
+
{
|
27
|
+
expect(clamp(5, 7, 7)).toBe(7);
|
28
|
+
expect(clamp(10, 10, 10)).toBe(10);
|
29
|
+
expect(clamp(-5, -3, -3)).toBe(-3);
|
30
|
+
});
|
31
|
+
|
32
|
+
it("swaps min and max if needed", () =>
|
33
|
+
{
|
34
|
+
expect(clamp(5, 10, 0)).toBe(5);
|
35
|
+
expect(clamp(-5, 3, -3)).toBe(-3);
|
36
|
+
});
|
37
|
+
});
|
@@ -0,0 +1,51 @@
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
2
|
+
import { random } from "..";
|
3
|
+
|
4
|
+
describe("random", () =>
|
5
|
+
{
|
6
|
+
it("should return a number between 0 and 1 by default", () =>
|
7
|
+
{
|
8
|
+
const result = random();
|
9
|
+
expect(result).toBeGreaterThanOrEqual(0);
|
10
|
+
expect(result).toBeLessThanOrEqual(1);
|
11
|
+
expect(Number.isInteger(result)).toBe(true);
|
12
|
+
});
|
13
|
+
|
14
|
+
it("should return float if shouldFloat is true", () =>
|
15
|
+
{
|
16
|
+
const result = random(0, 1, true);
|
17
|
+
expect(result).toBeGreaterThanOrEqual(0);
|
18
|
+
expect(result).toBeLessThanOrEqual(1);
|
19
|
+
expect(Number.isInteger(result)).toBe(false);
|
20
|
+
});
|
21
|
+
|
22
|
+
it("should respect min and max range with integers", () =>
|
23
|
+
{
|
24
|
+
const result = random(5, 10);
|
25
|
+
expect(result).toBeGreaterThanOrEqual(5);
|
26
|
+
expect(result).toBeLessThanOrEqual(10);
|
27
|
+
expect(Number.isInteger(result)).toBe(true);
|
28
|
+
});
|
29
|
+
|
30
|
+
it("should respect min and max range with floats", () =>
|
31
|
+
{
|
32
|
+
const result = random(5, 10, true);
|
33
|
+
expect(result).toBeGreaterThanOrEqual(5);
|
34
|
+
expect(result).toBeLessThanOrEqual(10);
|
35
|
+
});
|
36
|
+
|
37
|
+
it("should always return at least the min value", () =>
|
38
|
+
{
|
39
|
+
for (let i = 0; i < 100; i++)
|
40
|
+
{
|
41
|
+
const result = random(10, 20);
|
42
|
+
expect(result).toBeGreaterThanOrEqual(10);
|
43
|
+
}
|
44
|
+
});
|
45
|
+
|
46
|
+
it("should return exactly min if min and max are both 0", () =>
|
47
|
+
{
|
48
|
+
const result = random(0, 0);
|
49
|
+
expect(result).toBe(0);
|
50
|
+
});
|
51
|
+
});
|