@splendidlabz/utils 1.11.2 → 1.11.4
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/dist/cjs/dom/accessibility.cjs +2 -0
- package/dist/cjs/dom/actions/index.cjs +392 -7
- package/dist/cjs/dom/actions/masonry.cjs +97 -6
- package/dist/cjs/dom/actions/prefer-horizontal-scroll.cjs +58 -14
- package/dist/cjs/dom/actions/sticky.cjs +126 -6
- package/dist/cjs/dom/bounding-box.cjs +2 -0
- package/dist/cjs/dom/clipboard.cjs +2 -0
- package/dist/cjs/dom/cookie.cjs +3 -1
- package/dist/cjs/dom/css-vars.cjs +13 -3
- package/dist/cjs/dom/events.cjs +2 -0
- package/dist/cjs/dom/focusable.cjs +2 -0
- package/dist/cjs/dom/font-size.cjs +15 -3
- package/dist/cjs/dom/get-element.cjs +3 -1
- package/dist/cjs/dom/hash.cjs +2 -0
- package/dist/cjs/dom/index.cjs +1249 -47
- package/dist/cjs/dom/keyboard.cjs +2 -0
- package/dist/cjs/dom/local-store.cjs +3 -1
- package/dist/cjs/dom/media.cjs +2 -0
- package/dist/cjs/dom/observers/index.cjs +235 -9
- package/dist/cjs/dom/observers/intersection-observer.cjs +38 -6
- package/dist/cjs/dom/observers/mutation-observer.cjs +38 -6
- package/dist/cjs/dom/observers/observer.cjs +12 -2
- package/dist/cjs/dom/observers/resize-observer.cjs +38 -6
- package/dist/cjs/dom/observers/scroll-observer.cjs +62 -3
- package/dist/cjs/dom/pkce.cjs +13 -3
- package/dist/cjs/dom/query-params.cjs +3 -1
- package/dist/cjs/dom/random-string.cjs +2 -0
- package/dist/cjs/dom/sanitize.cjs +27 -3
- package/dist/cjs/dom/session-store.cjs +3 -1
- package/dist/cjs/dom/spam-prevention.cjs +2 -0
- package/dist/cjs/dom/trap-focus.cjs +131 -9
- package/dist/cjs/dom/ui/aria-current.cjs +2 -0
- package/dist/cjs/dom/ui/inconsistent-button-fix.cjs +2 -0
- package/dist/cjs/dom/ui/index.cjs +87 -9
- package/dist/cjs/dom/ui/scroll-container.cjs +2 -0
- package/dist/cjs/dom/ui/traverse-and-scramble.cjs +2 -0
- package/dist/cjs/lib/arrays/index.cjs +121 -13
- package/dist/cjs/lib/arrays/item-position.cjs +2 -0
- package/dist/cjs/lib/arrays/join.cjs +10 -2
- package/dist/cjs/lib/arrays/shuffle.cjs +2 -0
- package/dist/cjs/lib/arrays/sort.cjs +11 -3
- package/dist/cjs/lib/arrays/split-array.cjs +2 -0
- package/dist/cjs/lib/arrays/unique.cjs +2 -0
- package/dist/cjs/lib/auth/index.cjs +35 -3
- package/dist/cjs/lib/auth/route-manager.cjs +2 -0
- package/dist/cjs/lib/checks.cjs +2 -0
- package/dist/cjs/lib/date/days.cjs +3 -1
- package/dist/cjs/lib/date/index.cjs +190 -7
- package/dist/cjs/lib/date/months.cjs +3 -1
- package/dist/cjs/lib/date/time.cjs +14 -2
- package/dist/cjs/lib/form/form-data.cjs +2 -0
- package/dist/cjs/lib/form/index.cjs +104 -5
- package/dist/cjs/lib/form/sanitize.cjs +2 -0
- package/dist/cjs/lib/functions/callback.cjs +2 -0
- package/dist/cjs/lib/functions/debounce.cjs +2 -0
- package/dist/cjs/lib/functions/env.cjs +2 -0
- package/dist/cjs/lib/functions/functional.cjs +2 -0
- package/dist/cjs/lib/functions/index.cjs +124 -11
- package/dist/cjs/lib/functions/throttle.cjs +2 -0
- package/dist/cjs/lib/functions/timeout.cjs +2 -0
- package/dist/cjs/lib/hash.cjs +2 -0
- package/dist/cjs/lib/index.cjs +1160 -29
- package/dist/cjs/lib/numbers/index.cjs +17 -4
- package/dist/cjs/lib/numbers/math.cjs +2 -0
- package/dist/cjs/lib/objects/camelcase-keys.cjs +45 -4
- package/dist/cjs/lib/objects/empty.cjs +2 -0
- package/dist/cjs/lib/objects/equal.cjs +2 -0
- package/dist/cjs/lib/objects/extend.cjs +117 -6
- package/dist/cjs/lib/objects/flatten.cjs +11 -3
- package/dist/cjs/lib/objects/index.cjs +360 -27
- package/dist/cjs/lib/objects/json.cjs +2 -0
- package/dist/cjs/lib/objects/loop.cjs +2 -0
- package/dist/cjs/lib/objects/mix/mix.cjs +5 -3
- package/dist/cjs/lib/objects/nested-property.cjs +2 -0
- package/dist/cjs/lib/objects/normalize-object.cjs +78 -4
- package/dist/cjs/lib/objects/omit-empty.cjs +2 -0
- package/dist/cjs/lib/objects/size.cjs +2 -0
- package/dist/cjs/lib/objects/split.cjs +2 -0
- package/dist/cjs/lib/promises/index.cjs +51 -3
- package/dist/cjs/lib/promises/reject.cjs +19 -2
- package/dist/cjs/lib/sse.cjs +29 -4
- package/dist/cjs/lib/strings/convert-case/convert-case.cjs +2 -0
- package/dist/cjs/lib/strings/index.cjs +179 -11
- package/dist/cjs/lib/strings/markdown.cjs +3 -1
- package/dist/cjs/lib/strings/name.cjs +2 -0
- package/dist/cjs/lib/strings/pluralize.cjs +3 -1
- package/dist/cjs/lib/strings/query-string.cjs +2 -0
- package/dist/cjs/lib/style/index.cjs +31 -2
- package/dist/cjs/lib/symbols/index.cjs +22 -3
- package/dist/cjs/lib/symbols/symbols.cjs +2 -0
- package/dist/cjs/node/context.cjs +0 -0
- package/dist/cjs/node/dirname.cjs +4 -2
- package/dist/cjs/node/file-cache.cjs +45 -3
- package/dist/cjs/node/file.cjs +4 -2
- package/dist/cjs/node/hash.cjs +3 -1
- package/dist/cjs/node/index.cjs +278 -15
- package/dist/cjs/node/pkce.cjs +20 -8
- package/dist/cjs/node/random-string.cjs +3 -1
- package/dist/cjs/node/sanitize.cjs +27 -3
- package/dist/cjs/node/uuid.cjs +2 -0
- package/dist/esm/node/context.js +0 -0
- package/dist/esm/node/file-cache.js +1 -1
- package/dist/esm/node/pkce.js +3 -2
- package/dist/types/node/context.d.cts +2 -0
- package/dist/types/node/index.d.cts +1 -1
- package/dist/types/node/pkce.d.cts +7 -1
- package/package.json +5 -2
- package/CHANGELOG.md +0 -231
- package/eslint.config.js +0 -2
- package/src/dom/accessibility.js +0 -58
- package/src/dom/actions/index.js +0 -3
- package/src/dom/actions/masonry.js +0 -43
- package/src/dom/actions/prefer-horizontal-scroll.js +0 -121
- package/src/dom/actions/sticky.js +0 -85
- package/src/dom/bounding-box.js +0 -82
- package/src/dom/clipboard.js +0 -14
- package/src/dom/cookie.js +0 -32
- package/src/dom/css-vars.js +0 -19
- package/src/dom/events.js +0 -71
- package/src/dom/focusable.js +0 -165
- package/src/dom/font-size.js +0 -40
- package/src/dom/get-element.js +0 -176
- package/src/dom/hash.js +0 -9
- package/src/dom/index.js +0 -23
- package/src/dom/keyboard.js +0 -44
- package/src/dom/local-store.js +0 -66
- package/src/dom/media.js +0 -30
- package/src/dom/observers/index.js +0 -4
- package/src/dom/observers/intersection-observer.js +0 -43
- package/src/dom/observers/mutation-observer.js +0 -51
- package/src/dom/observers/observer.js +0 -18
- package/src/dom/observers/resize-observer.js +0 -66
- package/src/dom/observers/scroll-observer.js +0 -146
- package/src/dom/pkce.js +0 -34
- package/src/dom/query-params.js +0 -14
- package/src/dom/random-string.js +0 -18
- package/src/dom/random-string.test.js +0 -13
- package/src/dom/sanitize.js +0 -16
- package/src/dom/session-store.js +0 -29
- package/src/dom/spam-prevention.js +0 -40
- package/src/dom/trap-focus.js +0 -70
- package/src/dom/ui/aria-current.js +0 -20
- package/src/dom/ui/inconsistent-button-fix.js +0 -20
- package/src/dom/ui/index.js +0 -4
- package/src/dom/ui/scroll-container.js +0 -25
- package/src/dom/ui/traverse-and-scramble.js +0 -61
- package/src/lib/arrays/index.js +0 -6
- package/src/lib/arrays/item-position.js +0 -23
- package/src/lib/arrays/join.js +0 -20
- package/src/lib/arrays/shuffle.js +0 -21
- package/src/lib/arrays/sort.js +0 -88
- package/src/lib/arrays/sort.spec.js +0 -123
- package/src/lib/arrays/split-array.js +0 -15
- package/src/lib/arrays/unique.js +0 -3
- package/src/lib/auth/index.js +0 -1
- package/src/lib/auth/route-manager.js +0 -45
- package/src/lib/checks.js +0 -57
- package/src/lib/date/days.js +0 -9
- package/src/lib/date/index.js +0 -3
- package/src/lib/date/months.js +0 -74
- package/src/lib/date/time.js +0 -83
- package/src/lib/form/form-data.js +0 -73
- package/src/lib/form/index.js +0 -2
- package/src/lib/form/sanitize.js +0 -80
- package/src/lib/form/sanitize.spec.js +0 -126
- package/src/lib/functions/callback.js +0 -8
- package/src/lib/functions/debounce.js +0 -18
- package/src/lib/functions/env.js +0 -5
- package/src/lib/functions/functional.js +0 -113
- package/src/lib/functions/functional.test.js +0 -196
- package/src/lib/functions/index.js +0 -5
- package/src/lib/functions/throttle.js +0 -11
- package/src/lib/functions/timeout.js +0 -15
- package/src/lib/hash.js +0 -18
- package/src/lib/hash.spec.js +0 -163
- package/src/lib/index.js +0 -14
- package/src/lib/numbers/index.js +0 -12
- package/src/lib/numbers/math.js +0 -9
- package/src/lib/numbers/split-unit.test.js +0 -45
- package/src/lib/objects/camelcase-keys.js +0 -17
- package/src/lib/objects/camelcase-keys.test.js +0 -72
- package/src/lib/objects/empty.js +0 -10
- package/src/lib/objects/equal.js +0 -9
- package/src/lib/objects/extend.js +0 -16
- package/src/lib/objects/extend.test.js +0 -35
- package/src/lib/objects/flatten.js +0 -19
- package/src/lib/objects/index.js +0 -13
- package/src/lib/objects/json.js +0 -7
- package/src/lib/objects/loop.js +0 -11
- package/src/lib/objects/loop.test.js +0 -31
- package/src/lib/objects/mix/mix.js +0 -191
- package/src/lib/objects/mix/mix.md +0 -78
- package/src/lib/objects/mix/mix.test.js +0 -445
- package/src/lib/objects/nested-property.js +0 -41
- package/src/lib/objects/normalize-object.js +0 -10
- package/src/lib/objects/omit-empty.js +0 -23
- package/src/lib/objects/omit-empty.test.js +0 -85
- package/src/lib/objects/size.js +0 -41
- package/src/lib/objects/split.js +0 -19
- package/src/lib/promises/index.js +0 -1
- package/src/lib/promises/reject.js +0 -17
- package/src/lib/sse.js +0 -61
- package/src/lib/strings/convert-case/convert-case.js +0 -86
- package/src/lib/strings/convert-case/convert-case.md +0 -44
- package/src/lib/strings/convert-case/convert-case.test.js +0 -50
- package/src/lib/strings/index.js +0 -5
- package/src/lib/strings/markdown.js +0 -71
- package/src/lib/strings/name.js +0 -24
- package/src/lib/strings/name.test.js +0 -67
- package/src/lib/strings/pluralize.js +0 -2
- package/src/lib/strings/query-string.js +0 -18
- package/src/lib/style/index.js +0 -13
- package/src/lib/symbols/index.js +0 -1
- package/src/lib/symbols/symbols.js +0 -9
- package/src/node/dirname.js +0 -17
- package/src/node/file-cache.js +0 -184
- package/src/node/file.js +0 -13
- package/src/node/hash.js +0 -5
- package/src/node/index.js +0 -7
- package/src/node/pkce.js +0 -33
- package/src/node/random-string.js +0 -10
- package/src/node/random-string.test.js +0 -15
- package/src/node/sanitize.js +0 -13
- package/src/node/sanitize.spec.js +0 -142
- package/src/node/uuid.js +0 -5
- package/tsconfig.json +0 -23
- package/tsup.config.js +0 -38
- package/tsup.config.mjs +0 -45
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
/* eslint-env browser */
|
|
2
|
-
import { dispatchEvent } from '../events.js'
|
|
3
|
-
import { useObserverMethodOnTarget } from './observer.js'
|
|
4
|
-
|
|
5
|
-
// https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver/observe
|
|
6
|
-
|
|
7
|
-
// These are simply options that will often be used. Check the docs for other options. Adding them here simply because it's easier to refer them here than reading the docs.
|
|
8
|
-
const defaultOptions = {
|
|
9
|
-
attributes: false,
|
|
10
|
-
childList: false,
|
|
11
|
-
subtree: false,
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function mutationObserver(target, options) {
|
|
15
|
-
options = { ...defaultOptions, ...options }
|
|
16
|
-
const { callback, ...opts } = options
|
|
17
|
-
const observer = new MutationObserver(observerFn)
|
|
18
|
-
|
|
19
|
-
if (target === window) target = document.body
|
|
20
|
-
useObserverMethodOnTarget(target, observer, 'observe', opts)
|
|
21
|
-
|
|
22
|
-
function observerFn(entries) {
|
|
23
|
-
for (const entry of entries) {
|
|
24
|
-
if (callback) callback({ entry, entries, observer })
|
|
25
|
-
else dispatchEvent(target, 'mutate', { entry, entries, observer })
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return {
|
|
30
|
-
observer,
|
|
31
|
-
observe(target) {
|
|
32
|
-
useObserverMethodOnTarget(target, observer, 'observe', options)
|
|
33
|
-
},
|
|
34
|
-
|
|
35
|
-
takeRecords() {
|
|
36
|
-
return observer.takeRecords()
|
|
37
|
-
},
|
|
38
|
-
|
|
39
|
-
disconnect() {
|
|
40
|
-
// Take records before disconnecting.
|
|
41
|
-
// https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver/takeRecords
|
|
42
|
-
const records = observer.takeRecords()
|
|
43
|
-
observer.disconnect()
|
|
44
|
-
if (records.length > 0) observerFn(records)
|
|
45
|
-
},
|
|
46
|
-
|
|
47
|
-
destroy() {
|
|
48
|
-
observer.disconnect()
|
|
49
|
-
},
|
|
50
|
-
}
|
|
51
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { getNodeType } from '../get-element.js'
|
|
2
|
-
|
|
3
|
-
export function useObserverMethodOnTarget(
|
|
4
|
-
target,
|
|
5
|
-
observer,
|
|
6
|
-
method = 'observe',
|
|
7
|
-
options = undefined
|
|
8
|
-
) {
|
|
9
|
-
const targetType = getNodeType(target)
|
|
10
|
-
if (targetType === 'element') observer[method](target, options)
|
|
11
|
-
if (targetType === 'nodelist') {
|
|
12
|
-
const elements = Array.from(target)
|
|
13
|
-
elements.forEach(element => observer[method](element, options))
|
|
14
|
-
}
|
|
15
|
-
if (targetType === 'array') {
|
|
16
|
-
target.forEach(element => observer[method](element, options))
|
|
17
|
-
}
|
|
18
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
/* eslint-env browser */
|
|
2
|
-
import { dispatchEvent } from '../events.js'
|
|
3
|
-
import { useObserverMethodOnTarget } from './observer.js'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Creates and manages a ResizeObserver instance to monitor size changes of a target element.
|
|
7
|
-
*
|
|
8
|
-
* @param {Element|Window|Document|NodeList|Element[]} target - The element(s) to observe.
|
|
9
|
-
* - If window or document is provided, document.documentElement will be observed instead.
|
|
10
|
-
* - If NodeList or Array of elements is provided, all elements will be observed.
|
|
11
|
-
* @param {Object} options - Configuration options for the resize observer
|
|
12
|
-
* @param {boolean} [options.observe=true] - Whether to start observing immediately. If false, the observer won't be created.
|
|
13
|
-
* @param {Function} [options.callback] - Optional callback function that will be called when resize changes are detected.
|
|
14
|
-
* If not provided, a 'resize-obs' event will be dispatched on the target.
|
|
15
|
-
* @param {Object} [options.observerOptions] - Additional options to pass to ResizeObserver.observe()
|
|
16
|
-
*
|
|
17
|
-
* @returns {Object} An object with methods to control the observer:
|
|
18
|
-
* - observe(target, options): Start observing a new target element
|
|
19
|
-
* - unobserve(target): Stop observing a target element
|
|
20
|
-
* - disconnect(): Disconnect the observer and stop all observations
|
|
21
|
-
* - destroy(): Alias for disconnect()
|
|
22
|
-
*
|
|
23
|
-
* @example
|
|
24
|
-
* // Basic usage with callback
|
|
25
|
-
* resizeObserver(element, {
|
|
26
|
-
* callback: ({ entry, entries, observer }) => {
|
|
27
|
-
* console.log('Element resized:', entry.contentRect);
|
|
28
|
-
* }
|
|
29
|
-
* });
|
|
30
|
-
*
|
|
31
|
-
* @example
|
|
32
|
-
* // Usage with event listener
|
|
33
|
-
* resizeObserver(element);
|
|
34
|
-
* element.addEventListener('resize-obs', ({ detail }) => {
|
|
35
|
-
* console.log('Element resized:', detail.entry.contentRect);
|
|
36
|
-
* });
|
|
37
|
-
*/
|
|
38
|
-
export function resizeObserver(target, options) {
|
|
39
|
-
const { callback, ...opts } = options
|
|
40
|
-
const observer = new ResizeObserver(observerFn)
|
|
41
|
-
|
|
42
|
-
if (target === window || target === document)
|
|
43
|
-
target = document.documentElement
|
|
44
|
-
useObserverMethodOnTarget(target, observer, 'observe', opts)
|
|
45
|
-
|
|
46
|
-
function observerFn(entries) {
|
|
47
|
-
for (const entry of entries) {
|
|
48
|
-
if (callback) callback({ entry, entries, observer })
|
|
49
|
-
else
|
|
50
|
-
dispatchEvent(entry.target, 'resize-obs', { entry, entries, observer })
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return {
|
|
55
|
-
observe(target) {
|
|
56
|
-
useObserverMethodOnTarget(target, observer, 'observe', options)
|
|
57
|
-
},
|
|
58
|
-
|
|
59
|
-
unobserve(target) {
|
|
60
|
-
useObserverMethodOnTarget(target, observer, 'unobserve')
|
|
61
|
-
},
|
|
62
|
-
|
|
63
|
-
disconnect: _ => observer.disconnect(),
|
|
64
|
-
destroy: _ => observer.disconnect(),
|
|
65
|
-
}
|
|
66
|
-
}
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
/* eslint-env browser */
|
|
2
|
-
import { resizeObserver } from './resize-observer.js'
|
|
3
|
-
|
|
4
|
-
const defaultOptions = {
|
|
5
|
-
threshold: 0, // Float between 0 to 1.
|
|
6
|
-
tolerance: 0.1, // Float between 0 to 1. Tolerance for event firing
|
|
7
|
-
once: false, // Only fire threshold callback once
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* @typedef {Object} ScrollObserverOptions
|
|
12
|
-
* @property {number} [threshold] - Float between 0 to 1. When to trigger callback (0.75 = 75% down page)
|
|
13
|
-
* @property {number} [tolerance] - Float between 0 to 1. Tolerance zone around threshold
|
|
14
|
-
* @property {boolean} [once] - Only fire threshold callback once (default: false)
|
|
15
|
-
* @property {Function} [callback] - Called on every scroll with scrollPercent
|
|
16
|
-
* @property {Function} [onScrollDown] - Called when scrolling down
|
|
17
|
-
* @property {Function} [onScrollUp] - Called when scrolling up
|
|
18
|
-
* @property {Function} [onEnterThreshold] - Called when entering threshold zone
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* @param {Element | Document | Window} node
|
|
23
|
-
* @returns {Element}
|
|
24
|
-
*/
|
|
25
|
-
function getScrollElement(node) {
|
|
26
|
-
if (
|
|
27
|
-
node === document ||
|
|
28
|
-
node === window ||
|
|
29
|
-
node === document.documentElement
|
|
30
|
-
) {
|
|
31
|
-
return document.documentElement
|
|
32
|
-
}
|
|
33
|
-
return /** @type {Element} */ (node)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Scroll Observer - Optimized for performance
|
|
38
|
-
* @param {Element | Document | Window} node - The element to observe scroll events on
|
|
39
|
-
* @param {ScrollObserverOptions} [options] - Configuration options
|
|
40
|
-
*/
|
|
41
|
-
export function scrollObserver(node, options = {}) {
|
|
42
|
-
const { callback, onScrollDown, onScrollUp, onEnterThreshold, ...userOpts } =
|
|
43
|
-
options
|
|
44
|
-
const opts = { ...defaultOptions, ...userOpts }
|
|
45
|
-
const { threshold, tolerance, once } = opts
|
|
46
|
-
|
|
47
|
-
let prevScrollDirection = null
|
|
48
|
-
let prevScrollTop = 0
|
|
49
|
-
let prevScrollPercent = 0
|
|
50
|
-
let thresholdFired = false
|
|
51
|
-
let rafId = null
|
|
52
|
-
|
|
53
|
-
// Determine scroll context once at initialization
|
|
54
|
-
const isDocumentScroll =
|
|
55
|
-
node === document || node === window || node === document.documentElement
|
|
56
|
-
const scrollElement = getScrollElement(node)
|
|
57
|
-
const eventTarget = isDocumentScroll ? window : node
|
|
58
|
-
|
|
59
|
-
// Cache DOM references and expensive calculations
|
|
60
|
-
let cachedScrollHeight = 0
|
|
61
|
-
let cachedClientHeight = 0
|
|
62
|
-
|
|
63
|
-
// Initialize cache and start observing for changes
|
|
64
|
-
updateCache()
|
|
65
|
-
const cacheObserver = resizeObserver(scrollElement, {
|
|
66
|
-
callback: updateCache,
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
eventTarget.addEventListener('scroll', throttledObserve, { passive: true })
|
|
70
|
-
|
|
71
|
-
function throttledObserve() {
|
|
72
|
-
rafId = requestAnimationFrame(observe)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function updateCache() {
|
|
76
|
-
cachedScrollHeight = scrollElement.scrollHeight
|
|
77
|
-
cachedClientHeight = scrollElement.clientHeight
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function observe() {
|
|
81
|
-
const scrollTop = scrollElement.scrollTop
|
|
82
|
-
|
|
83
|
-
// Skip if scroll position hasn't changed meaningfully
|
|
84
|
-
if (Math.abs(scrollTop - prevScrollTop) < 1) return
|
|
85
|
-
|
|
86
|
-
const scrollDirection = scrollTop > prevScrollTop ? 'down' : 'up'
|
|
87
|
-
const maxScroll = Math.max(1, cachedScrollHeight - cachedClientHeight)
|
|
88
|
-
const scrollPercent = Math.min(1, Math.max(0, scrollTop / maxScroll))
|
|
89
|
-
|
|
90
|
-
// Check threshold crossing (e.g., 75% ± tolerance)
|
|
91
|
-
const thresholdMin = threshold - tolerance / 2
|
|
92
|
-
const thresholdMax = threshold + tolerance / 2
|
|
93
|
-
const wasInThreshold =
|
|
94
|
-
prevScrollPercent >= thresholdMin && prevScrollPercent <= thresholdMax
|
|
95
|
-
const isInThreshold =
|
|
96
|
-
scrollPercent >= thresholdMin && scrollPercent <= thresholdMax
|
|
97
|
-
const hasEnteredThreshold =
|
|
98
|
-
!wasInThreshold && isInThreshold && (!once || !thresholdFired)
|
|
99
|
-
|
|
100
|
-
if (hasEnteredThreshold && once) {
|
|
101
|
-
thresholdFired = true
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Prepare common data for all callbacks
|
|
105
|
-
const callbackData = {
|
|
106
|
-
scrollTop,
|
|
107
|
-
scrollDirection,
|
|
108
|
-
scrollPercent,
|
|
109
|
-
directionChanged: scrollDirection !== prevScrollDirection,
|
|
110
|
-
hasEnteredThreshold,
|
|
111
|
-
isInThreshold,
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Call specific callbacks
|
|
115
|
-
if (typeof callback === 'function') {
|
|
116
|
-
callback(callbackData)
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Only fire direction callbacks when direction actually changes
|
|
120
|
-
if (scrollDirection !== prevScrollDirection) {
|
|
121
|
-
if (scrollDirection === 'down' && typeof onScrollDown === 'function') {
|
|
122
|
-
onScrollDown(callbackData)
|
|
123
|
-
}
|
|
124
|
-
if (scrollDirection === 'up' && typeof onScrollUp === 'function') {
|
|
125
|
-
onScrollUp(callbackData)
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Fire threshold callback when entering threshold zone
|
|
130
|
-
if (hasEnteredThreshold && typeof onEnterThreshold === 'function') {
|
|
131
|
-
onEnterThreshold(callbackData)
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
prevScrollTop = scrollTop
|
|
135
|
-
prevScrollPercent = scrollPercent
|
|
136
|
-
prevScrollDirection = scrollDirection
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
return {
|
|
140
|
-
destroy() {
|
|
141
|
-
eventTarget.removeEventListener('scroll', throttledObserve)
|
|
142
|
-
if (rafId) cancelAnimationFrame(rafId)
|
|
143
|
-
cacheObserver.destroy()
|
|
144
|
-
},
|
|
145
|
-
}
|
|
146
|
-
}
|
package/src/dom/pkce.js
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
// Browser only
|
|
2
|
-
// Check the node version for usage in servers
|
|
3
|
-
import { randomString } from './random-string.js'
|
|
4
|
-
|
|
5
|
-
export async function PKCE() {
|
|
6
|
-
const codeVerifier = await randomString()
|
|
7
|
-
const codeChallenge = await getCodeChallenge(codeVerifier)
|
|
8
|
-
|
|
9
|
-
return {
|
|
10
|
-
state: await randomString(),
|
|
11
|
-
code_verifier: codeVerifier,
|
|
12
|
-
code_challenge: codeChallenge,
|
|
13
|
-
code_challenge_method: 'S256',
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Generates code challenge in browser
|
|
19
|
-
* @param {string} verifier
|
|
20
|
-
* @returns promise
|
|
21
|
-
*/
|
|
22
|
-
async function getCodeChallenge(verifier) {
|
|
23
|
-
// Hash the code verifier with SHA-256
|
|
24
|
-
const encoder = new TextEncoder()
|
|
25
|
-
const data = encoder.encode(verifier)
|
|
26
|
-
const hashed = await window.crypto.subtle.digest('SHA-256', data)
|
|
27
|
-
|
|
28
|
-
// Base64url encode the hash
|
|
29
|
-
return window
|
|
30
|
-
.btoa(String.fromCharCode.apply(null, new Uint8Array(hashed)))
|
|
31
|
-
.replace(/\+/g, '-')
|
|
32
|
-
.replace(/\//g, '_')
|
|
33
|
-
.replace(/=+$/, '')
|
|
34
|
-
}
|
package/src/dom/query-params.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
// ========================
|
|
2
|
-
// Query Params
|
|
3
|
-
// ========================
|
|
4
|
-
export const queryParams = {
|
|
5
|
-
get(key) {
|
|
6
|
-
const searchParams = new URLSearchParams(location.search)
|
|
7
|
-
return searchParams.get(key)
|
|
8
|
-
},
|
|
9
|
-
|
|
10
|
-
stringify(params) {
|
|
11
|
-
const searchParams = new URLSearchParams(location.search)
|
|
12
|
-
return searchParams.toString()
|
|
13
|
-
},
|
|
14
|
-
}
|
package/src/dom/random-string.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Generates a random string.
|
|
3
|
-
* Ensures first character is a letter so it can be used in HTML ids and classes
|
|
4
|
-
* This can actually be used everywhere, not just in the browser.
|
|
5
|
-
*/
|
|
6
|
-
export function randomString(length = 10) {
|
|
7
|
-
const firstLetter = String.fromCharCode(65 + Math.floor(Math.random() * 26))
|
|
8
|
-
const others = Math.random()
|
|
9
|
-
.toString(36)
|
|
10
|
-
.substring(2, 2 + length - 1)
|
|
11
|
-
|
|
12
|
-
return firstLetter + others
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// For browsers only
|
|
16
|
-
export function uuid() {
|
|
17
|
-
return crypto.randomUUID()
|
|
18
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import { randomString } from './random-string.js'
|
|
3
|
-
|
|
4
|
-
describe('randomString', () => {
|
|
5
|
-
it('should generate a random string', () => {
|
|
6
|
-
const str = randomString()
|
|
7
|
-
expect(str).toBeDefined()
|
|
8
|
-
expect(str.length).toBe(10)
|
|
9
|
-
|
|
10
|
-
// Ensure the first character is a letter
|
|
11
|
-
expect(str[0]).toMatch(/[a-zA-Z]/)
|
|
12
|
-
})
|
|
13
|
-
})
|
package/src/dom/sanitize.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import DOMPurify from 'dompurify'
|
|
2
|
-
import { sanitize as sanitizeLib } from '../lib/form/sanitize.js'
|
|
3
|
-
|
|
4
|
-
// Need to test if possible.
|
|
5
|
-
// We can't test browser on node. So, unless we do like playwright or some form of browser testing, we can't actually verify this works every time.
|
|
6
|
-
// Node version works already.
|
|
7
|
-
/**
|
|
8
|
-
* Sanitizes values using DOMPurify
|
|
9
|
-
* @param {*} value - Value to sanitize (string, array, or object)
|
|
10
|
-
* @param {Object} [options={}] - DOMPurify options
|
|
11
|
-
* @returns {*} Sanitized value
|
|
12
|
-
* @throws {Error} If input is a string but DOMPurify fails
|
|
13
|
-
*/
|
|
14
|
-
export function sanitize(value, options = {}) {
|
|
15
|
-
return sanitizeLib(value, { sanitizer: DOMPurify.sanitize, ...options })
|
|
16
|
-
}
|
package/src/dom/session-store.js
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
// ========================
|
|
2
|
-
// Session Storage
|
|
3
|
-
// ========================
|
|
4
|
-
export const sessionStore = {
|
|
5
|
-
get(key) {
|
|
6
|
-
const value = sessionStorage.getItem(key)
|
|
7
|
-
if (!value) return
|
|
8
|
-
|
|
9
|
-
try {
|
|
10
|
-
// Returns an object if stored value is JSON
|
|
11
|
-
return JSON.parse(value)
|
|
12
|
-
} catch (error) {
|
|
13
|
-
// Returns plain string if stored value is not JSON
|
|
14
|
-
return value
|
|
15
|
-
}
|
|
16
|
-
},
|
|
17
|
-
|
|
18
|
-
set(key, value) {
|
|
19
|
-
if (typeof value === 'string') {
|
|
20
|
-
sessionStorage.setItem(key, value)
|
|
21
|
-
} else {
|
|
22
|
-
sessionStorage.setItem(key, JSON.stringify(value))
|
|
23
|
-
}
|
|
24
|
-
},
|
|
25
|
-
|
|
26
|
-
remove(key) {
|
|
27
|
-
sessionStorage.removeItem(key)
|
|
28
|
-
},
|
|
29
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Enhances a form element with spam detection capabilities
|
|
3
|
-
* @param {HTMLFormElement} form - The form element to enhance
|
|
4
|
-
* @param {Object} [options] - Configuration options
|
|
5
|
-
* @param {string} [options.honeypotField] - Name of the honeypot field (default: 'hp')
|
|
6
|
-
* @param {number} [options.honeypotDuration] - Minimum time in ms for legitimate form fill (default: 2000)
|
|
7
|
-
* @returns {void}
|
|
8
|
-
*/
|
|
9
|
-
export function preventSpam(
|
|
10
|
-
form,
|
|
11
|
-
{ honeypotField = 'hp', honeypotDuration = 2000 } = {},
|
|
12
|
-
) {
|
|
13
|
-
const startTime = Date.now()
|
|
14
|
-
let hasInteraction = false
|
|
15
|
-
|
|
16
|
-
function checkForInteraction() {
|
|
17
|
-
hasInteraction = true
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const events = ['keydown', 'mousemove', 'touchstart', 'click']
|
|
21
|
-
events.forEach(event => {
|
|
22
|
-
form.addEventListener(event, checkForInteraction, { once: true })
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
form.containsSpam = function () {
|
|
26
|
-
const fillTime = Date.now() - startTime
|
|
27
|
-
const isTooFast = fillTime < honeypotDuration
|
|
28
|
-
/** @type {HTMLInputElement | null} */
|
|
29
|
-
const honeypotInput = form.querySelector(`[name="${honeypotField}"]`)
|
|
30
|
-
const hasHoneypotValue = honeypotInput?.value?.trim()
|
|
31
|
-
const noInteraction = !hasInteraction
|
|
32
|
-
|
|
33
|
-
// Clean up event listeners after use
|
|
34
|
-
events.forEach(event =>
|
|
35
|
-
form.removeEventListener(event, checkForInteraction),
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
return isTooFast || !!hasHoneypotValue || noInteraction
|
|
39
|
-
}
|
|
40
|
-
}
|
package/src/dom/trap-focus.js
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { getFocusableElements } from './focusable.js'
|
|
2
|
-
import { isShiftTab, isTab } from './keyboard.js'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Traps focus within a set of focusable elements, creating a circular tab order
|
|
6
|
-
* @param {Object} params
|
|
7
|
-
* @param {KeyboardEvent} params.event - The keyboard event to handle
|
|
8
|
-
* @param {import('./focusable.js').Focusables} params.focusables - Object containing focusable elements
|
|
9
|
-
*/
|
|
10
|
-
export function trapFocus({ event, focusables }) {
|
|
11
|
-
if (event.key !== 'Tab') return
|
|
12
|
-
if (isTab(event) && document.activeElement === focusables.last) {
|
|
13
|
-
event.preventDefault()
|
|
14
|
-
focusables.first.focus()
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// User presses shift + tab from the first focusable element.
|
|
18
|
-
// Redirects to the last focusable element.
|
|
19
|
-
if (isShiftTab(event) && document.activeElement === focusables.first) {
|
|
20
|
-
event.preventDefault()
|
|
21
|
-
focusables.last.focus()
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Manages focus behavior when tabbing through content, with options to control focus on exit
|
|
27
|
-
* @param {Object} params
|
|
28
|
-
* @param {KeyboardEvent} params.event - The keyboard event to handle
|
|
29
|
-
* @param {HTMLElement} params.triggerNode - Element that triggered the focus trap
|
|
30
|
-
* @param {HTMLElement} params.contentNode - Container element that holds focusable content
|
|
31
|
-
* @param {import('./focusable.js').Focusables} params.focusables - Object containing focusable elements
|
|
32
|
-
* @param {'next'|'trigger'} [params.tabOutFocus='next'] - Where to focus when tabbing out: 'next' element or back to trigger
|
|
33
|
-
* @param {Function} [params.onLeave=()=>{}] - Callback when focus leaves content, receives 'forward' or 'back' direction
|
|
34
|
-
*/
|
|
35
|
-
export function manageFocus({
|
|
36
|
-
event,
|
|
37
|
-
triggerNode,
|
|
38
|
-
contentNode,
|
|
39
|
-
focusables,
|
|
40
|
-
tabOutFocus = 'next', // 'trigger'
|
|
41
|
-
onLeave = function () {},
|
|
42
|
-
}) {
|
|
43
|
-
const activeEl = document.activeElement
|
|
44
|
-
|
|
45
|
-
if (activeEl === contentNode && isShiftTab(event)) return backFocus()
|
|
46
|
-
if (activeEl === contentNode && focusables.length === 0 && isTab(event))
|
|
47
|
-
return nextFocus()
|
|
48
|
-
|
|
49
|
-
if (activeEl === focusables.last && isTab(event)) return nextFocus()
|
|
50
|
-
if (activeEl === focusables.first && isShiftTab(event)) return backFocus()
|
|
51
|
-
|
|
52
|
-
function backFocus() {
|
|
53
|
-
event.preventDefault()
|
|
54
|
-
triggerNode.focus()
|
|
55
|
-
onLeave('back')
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function nextFocus() {
|
|
59
|
-
if (tabOutFocus === 'trigger') triggerNode.focus()
|
|
60
|
-
if (tabOutFocus === 'next') {
|
|
61
|
-
event.preventDefault()
|
|
62
|
-
const DOMFocusables = getFocusableElements().keyboard
|
|
63
|
-
const index = DOMFocusables.findIndex(e => e === triggerNode)
|
|
64
|
-
const next = DOMFocusables[index + 1]
|
|
65
|
-
if (next) next.focus()
|
|
66
|
-
else triggerNode.focus()
|
|
67
|
-
}
|
|
68
|
-
onLeave('forward')
|
|
69
|
-
}
|
|
70
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export function markActiveAnchorIn(container, anchorType = 'page') {
|
|
2
|
-
if (!container) return
|
|
3
|
-
|
|
4
|
-
const { pathname } = window.location
|
|
5
|
-
const element = container.querySelector(`[href="${pathname}"]`)
|
|
6
|
-
if (!element) return
|
|
7
|
-
|
|
8
|
-
// Mark the active element
|
|
9
|
-
element.setAttribute('aria-current', 'page')
|
|
10
|
-
return element
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function scrollToElement(
|
|
14
|
-
element,
|
|
15
|
-
{ scrollBehaviour = 'instant', scrollMargin = '3rem' } = {},
|
|
16
|
-
) {
|
|
17
|
-
if (!element) return
|
|
18
|
-
element.style.scrollMarginTop = scrollMargin
|
|
19
|
-
element.scrollIntoView({ behavior: scrollBehaviour })
|
|
20
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Fixes inconsistent button focus behavior across browsers.
|
|
3
|
-
*
|
|
4
|
-
* In some browsers (like Firefox), clicking a button doesn't focus it.
|
|
5
|
-
* This function ensures buttons are focused when clicked, making behavior consistent.
|
|
6
|
-
*
|
|
7
|
-
* @see {@link https://zellwk.com/blog/inconsistent-button-behavior/|Source}
|
|
8
|
-
* @example
|
|
9
|
-
* // Add the fix to your app
|
|
10
|
-
* inconsistentButtonFix()
|
|
11
|
-
*/
|
|
12
|
-
export function inconsistentButtonFix() {
|
|
13
|
-
// https://zellwk.com/blog/inconsistent-button-behavior/
|
|
14
|
-
/** @param {MouseEvent} event */
|
|
15
|
-
document.addEventListener('click', event => {
|
|
16
|
-
if (event.target instanceof HTMLButtonElement) {
|
|
17
|
-
event.target.focus()
|
|
18
|
-
}
|
|
19
|
-
})
|
|
20
|
-
}
|
package/src/dom/ui/index.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
export function findScrollContainer(element, direction = 'both') {
|
|
2
|
-
if (!element) return
|
|
3
|
-
|
|
4
|
-
let parent = element.parentElement
|
|
5
|
-
while (parent) {
|
|
6
|
-
const { overflow, overflowX, overflowY } = getComputedStyle(parent)
|
|
7
|
-
|
|
8
|
-
if (direction === 'both') {
|
|
9
|
-
if (overflow.includes('auto') || overflow.includes('scroll'))
|
|
10
|
-
return parent
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
if (direction === 'horizontal') {
|
|
14
|
-
if (overflowX === 'auto' || overflowX === 'scroll') return parent
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
if (direction === 'vertical') {
|
|
18
|
-
if (overflowY === 'auto' || overflowY === 'scroll') return parent
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
parent = parent.parentElement
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return document.documentElement
|
|
25
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Traverses a DOM node and scrambles its text content, excluding specified elements.
|
|
3
|
-
* Useful for creating placeholder/loading states or privacy features.
|
|
4
|
-
*
|
|
5
|
-
* @param {Element|ChildNode} node - The root element or node to start traversal from
|
|
6
|
-
* @param {Object} [options] - Options for traversal
|
|
7
|
-
* @param {string[]} [options.excludes=[]] - Array of CSS selectors to exclude from scrambling
|
|
8
|
-
* @example
|
|
9
|
-
* // Scramble all text in a container
|
|
10
|
-
* traverseAndScramble(document.querySelector('.container'))
|
|
11
|
-
*
|
|
12
|
-
* // Scramble text but exclude certain elements
|
|
13
|
-
* traverseAndScramble(document.body, {
|
|
14
|
-
* excludes: ['.no-scramble', '[data-no-scramble]']
|
|
15
|
-
* })
|
|
16
|
-
*/
|
|
17
|
-
export function traverseAndScramble(node, { excludes = [] } = {}) {
|
|
18
|
-
// Check if the current node matches any of the exclude selectors
|
|
19
|
-
if (node instanceof Element && excludes.some(selector => node.matches(selector))) {
|
|
20
|
-
return // Skip this node and its children
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
node.childNodes.forEach(child => {
|
|
24
|
-
if (child.nodeType === 3) {
|
|
25
|
-
// Node.TEXT_NODE
|
|
26
|
-
child.nodeValue = scrambleText(child.nodeValue)
|
|
27
|
-
} else if (child.nodeType === 1 && child instanceof Element) {
|
|
28
|
-
// Node.ELEMENT_NODE
|
|
29
|
-
traverseAndScramble(child, { excludes }) // Recurse into child elements with excludes
|
|
30
|
-
}
|
|
31
|
-
})
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Scrambles text by replacing alphanumeric characters with random ones.
|
|
36
|
-
* Preserves case, punctuation, and other symbols.
|
|
37
|
-
*
|
|
38
|
-
* @param {string} text - The text to scramble
|
|
39
|
-
* @returns {string} The scrambled text
|
|
40
|
-
* @private
|
|
41
|
-
* @example
|
|
42
|
-
* scrambleText('Hello123!') // Returns something like 'Xk9mP2q!'
|
|
43
|
-
*/
|
|
44
|
-
function scrambleText(text) {
|
|
45
|
-
return text
|
|
46
|
-
.split('')
|
|
47
|
-
.map(char => {
|
|
48
|
-
if (/^[A-Za-z0-9]$/.test(char)) {
|
|
49
|
-
const randomCharCode =
|
|
50
|
-
Math.random() < 26 / 36
|
|
51
|
-
? Math.floor(Math.random() * 26) +
|
|
52
|
-
(char === char.toUpperCase() ? 65 : 97) // A-Z
|
|
53
|
-
: Math.floor(Math.random() * 10) + 48 // 0-9
|
|
54
|
-
return String.fromCharCode(randomCharCode)
|
|
55
|
-
} else {
|
|
56
|
-
// Keep punctuations and other symbols intact
|
|
57
|
-
return char
|
|
58
|
-
}
|
|
59
|
-
})
|
|
60
|
-
.join('')
|
|
61
|
-
}
|