@splendidlabz/utils 1.5.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +77 -1
- package/dist/dom/accessibility.d.ts +28 -0
- package/dist/dom/actions/index.d.ts +3 -0
- package/dist/dom/actions/masonry.d.ts +3 -0
- package/dist/dom/actions/prefer-horizontal-scroll.d.ts +3 -0
- package/dist/dom/actions/sticky.d.ts +3 -0
- package/dist/dom/bounding-box.d.ts +39 -0
- package/dist/dom/clipboard.d.ts +5 -0
- package/dist/dom/cookie.d.ts +5 -0
- package/dist/dom/css-vars.d.ts +4 -0
- package/dist/dom/events.d.ts +18 -0
- package/dist/dom/focusable.d.ts +76 -0
- package/dist/dom/font-size.d.ts +5 -0
- package/dist/dom/get-element.d.ts +100 -0
- package/dist/dom/hash.d.ts +1 -0
- package/dist/dom/index.d.ts +22 -0
- package/dist/dom/keyboard.d.ts +30 -0
- package/dist/dom/local-store.d.ts +29 -0
- package/dist/dom/media.d.ts +3 -0
- package/dist/dom/observers/index.d.ts +3 -0
- package/dist/dom/observers/intersection-observer.d.ts +8 -0
- package/dist/dom/observers/mutation-observer.d.ts +7 -0
- package/dist/dom/observers/observer.d.ts +1 -0
- package/dist/dom/observers/resize-observer.d.ts +38 -0
- package/dist/dom/pkce.d.ts +6 -0
- package/dist/dom/query-params.d.ts +4 -0
- package/dist/dom/random-string.d.ts +7 -0
- package/dist/dom/sanitize.d.ts +8 -0
- package/dist/dom/session-store.d.ts +5 -0
- package/dist/dom/trap-focus.d.ts +28 -0
- package/dist/dom/ui/aria-current.d.ts +5 -0
- package/dist/dom/ui/inconsistent-button-fix.d.ts +12 -0
- package/dist/dom/ui/index.d.ts +4 -0
- package/dist/dom/ui/scroll-container.d.ts +1 -0
- package/dist/dom/ui/traverse-and-scramble.d.ts +19 -0
- package/dist/lib/arrays/index.d.ts +5 -0
- package/dist/lib/arrays/item-position.d.ts +14 -0
- package/dist/lib/arrays/join.d.ts +13 -0
- package/dist/lib/arrays/shuffle.d.ts +1 -0
- package/dist/lib/arrays/sort.d.ts +30 -0
- package/dist/lib/arrays/unique.d.ts +1 -0
- package/dist/lib/auth/index.d.ts +1 -0
- package/dist/lib/auth/route-manager.d.ts +25 -0
- package/dist/lib/checks.d.ts +30 -0
- package/dist/lib/date/days.d.ts +5 -0
- package/dist/lib/date/index.d.ts +3 -0
- package/dist/lib/date/months.d.ts +9 -0
- package/dist/lib/date/time.d.ts +8 -0
- package/dist/lib/form/form-data.d.ts +7 -0
- package/dist/lib/form/index.d.ts +2 -0
- package/dist/lib/form/sanitize.d.ts +58 -0
- package/dist/lib/functions/debounce.d.ts +1 -0
- package/dist/lib/functions/env.d.ts +1 -0
- package/dist/lib/functions/functional.d.ts +4 -0
- package/dist/lib/functions/index.d.ts +5 -0
- package/dist/lib/functions/throttle.d.ts +1 -0
- package/dist/lib/functions/timeout.d.ts +3 -0
- package/dist/lib/hash.d.ts +1 -0
- package/dist/lib/index.d.ts +14 -0
- package/dist/lib/numbers/index.d.ts +2 -0
- package/dist/lib/numbers/math.d.ts +2 -0
- package/dist/lib/objects/camelcase-keys.d.ts +1 -0
- package/dist/lib/objects/empty.d.ts +2 -0
- package/dist/lib/objects/equal.d.ts +2 -0
- package/dist/lib/objects/extend.d.ts +4 -0
- package/dist/lib/objects/flatten.d.ts +4 -0
- package/dist/lib/objects/index.d.ts +13 -0
- package/dist/lib/objects/json.d.ts +1 -0
- package/dist/lib/objects/loop.d.ts +2 -0
- package/dist/lib/objects/mix/mix.d.ts +1 -0
- package/dist/lib/objects/nested-property.d.ts +22 -0
- package/dist/lib/objects/normalize-object.d.ts +1 -0
- package/dist/lib/objects/omit-empty.d.ts +1 -0
- package/dist/lib/objects/size.d.ts +1 -0
- package/dist/lib/objects/split.d.ts +12 -0
- package/dist/lib/promises/index.d.ts +1 -0
- package/dist/lib/promises/reject.d.ts +3 -0
- package/dist/lib/sse.d.ts +16 -0
- package/dist/lib/strings/convert-case/convert-case.d.ts +8 -0
- package/dist/lib/strings/index.d.ts +5 -0
- package/dist/lib/strings/markdown.d.ts +23 -0
- package/dist/lib/strings/name.d.ts +5 -0
- package/dist/lib/strings/pluralize.d.ts +1 -0
- package/dist/lib/strings/query-string.d.ts +8 -0
- package/dist/lib/style/index.d.ts +1 -0
- package/dist/lib/symbols/index.d.ts +1 -0
- package/dist/lib/symbols/symbols.d.ts +2 -0
- package/dist/node/common.d.ts +1 -0
- package/dist/node/dirname.d.ts +7 -0
- package/dist/node/file-cache.d.ts +54 -0
- package/dist/node/file.d.ts +2 -0
- package/dist/node/hash.d.ts +1 -0
- package/dist/node/index.d.ts +7 -0
- package/dist/node/pkce.d.ts +6 -0
- package/dist/node/random-string.d.ts +1 -0
- package/dist/node/sanitize.d.ts +8 -0
- package/dom/accessibility.js +46 -13
- package/{actions → dom/actions}/index.js +0 -2
- package/dom/actions/masonry.js +42 -0
- package/{actions → dom/actions}/prefer-horizontal-scroll.js +8 -8
- package/{actions → dom/actions}/sticky.js +3 -6
- package/dom/bounding-box.js +35 -7
- package/dom/cookie.js +8 -7
- package/dom/css-vars.js +8 -0
- package/dom/events.js +2 -1
- package/dom/focusable.js +119 -10
- package/dom/font-size.js +21 -10
- package/dom/get-element.js +112 -5
- package/dom/index.js +3 -0
- package/dom/keyboard.js +13 -8
- package/dom/local-store.js +37 -0
- package/dom/media.js +1 -0
- package/dom/observers/index.js +3 -0
- package/dom/observers/intersection-observer.js +43 -0
- package/dom/observers/mutation-observer.js +51 -0
- package/dom/observers/observer.js +18 -0
- package/dom/observers/resize-observer.js +64 -0
- package/dom/random-string.js +1 -1
- package/dom/random-string.test.js +13 -0
- package/dom/sanitize.js +16 -0
- package/dom/trap-focus.js +18 -3
- package/dom/ui/inconsistent-button-fix.js +14 -2
- package/dom/ui/traverse-and-scramble.js +29 -8
- package/eslint.config.js +2 -0
- package/lib/arrays/index.js +5 -62
- package/lib/arrays/item-position.js +23 -0
- package/lib/arrays/join.js +20 -0
- package/lib/arrays/shuffle.js +21 -0
- package/lib/arrays/sort.js +88 -0
- package/lib/arrays/sort.spec.js +123 -0
- package/lib/arrays/unique.js +3 -0
- package/lib/checks.js +42 -0
- package/lib/date/index.js +2 -1
- package/lib/date/time.js +50 -0
- package/lib/form/sanitize.js +68 -35
- package/lib/form/sanitize.spec.js +126 -0
- package/lib/hash.js +18 -0
- package/lib/hash.spec.js +163 -0
- package/lib/index.js +2 -0
- package/lib/numbers/index.js +2 -0
- package/lib/numbers/math.js +9 -0
- package/lib/objects/equal.js +9 -0
- package/lib/objects/index.js +1 -0
- package/lib/objects/mix/mix.js +2 -0
- package/lib/objects/mix/mix.test.js +24 -0
- package/lib/objects/nested-property.js +5 -0
- package/lib/objects/split.js +1 -1
- package/lib/sse.js +61 -0
- package/lib/strings/index.js +1 -0
- package/lib/strings/markdown.js +29 -10
- package/lib/strings/name.js +24 -0
- package/lib/strings/name.test.js +67 -0
- package/node/dirname.js +1 -1
- package/node/file-cache.js +46 -7
- package/node/index.js +1 -0
- package/node/random-string.js +6 -6
- package/node/random-string.test.js +15 -0
- package/node/sanitize.js +13 -0
- package/node/sanitize.spec.js +142 -0
- package/package.json +38 -13
- package/tsconfig.json +23 -0
- package/.eslintrc.cjs +0 -3
- package/.turbo/turbo-lint.log +0 -10
- package/.turbo/turbo-test.log +0 -10
- package/actions/intersection-observer.js +0 -37
- package/actions/masonry.js +0 -75
- package/actions/mutation-observer.js +0 -42
- package/actions/resize-observer.js +0 -33
- package/lib/index.test.js +0 -9
package/dom/keyboard.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Event is Tab (without shift)
|
|
3
|
-
* @param {
|
|
4
|
-
* @returns {
|
|
3
|
+
* @param {KeyboardEvent} event - The keyboard event object
|
|
4
|
+
* @returns {boolean}
|
|
5
5
|
*/
|
|
6
6
|
export function isTab(event) {
|
|
7
7
|
return !event.shiftKey && event.key === 'Tab'
|
|
@@ -9,8 +9,8 @@ export function isTab(event) {
|
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Event is shift + Tab
|
|
12
|
-
* @param {
|
|
13
|
-
* @returns {
|
|
12
|
+
* @param {KeyboardEvent} event - The keyboard event object
|
|
13
|
+
* @returns {boolean}
|
|
14
14
|
*/
|
|
15
15
|
export function isShiftTab(event) {
|
|
16
16
|
return event.shiftKey && event.key === 'Tab'
|
|
@@ -18,8 +18,8 @@ export function isShiftTab(event) {
|
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Matches both Tab and Shift+Tab
|
|
21
|
-
* @param {
|
|
22
|
-
* @returns
|
|
21
|
+
* @param {string} key - The keyboard key to check
|
|
22
|
+
* @returns {boolean} Whether the key is Tab
|
|
23
23
|
*/
|
|
24
24
|
export function isTabKey(key) {
|
|
25
25
|
return key === 'Tab'
|
|
@@ -27,13 +27,18 @@ export function isTabKey(key) {
|
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
29
|
* Event is an arrow key
|
|
30
|
-
* @param {
|
|
31
|
-
* @returns {
|
|
30
|
+
* @param {string} key - The keyboard key to check
|
|
31
|
+
* @returns {boolean} Whether the key is an arrow key (Up, Down, Left, Right)
|
|
32
32
|
*/
|
|
33
33
|
export function isArrowKey(key) {
|
|
34
34
|
return ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(key)
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Event is Escape key
|
|
39
|
+
* @param {string} key - The keyboard key to check
|
|
40
|
+
* @returns {boolean} Whether the key is Escape
|
|
41
|
+
*/
|
|
37
42
|
export function isEscapeKey(key) {
|
|
38
43
|
return key === 'Escape'
|
|
39
44
|
}
|
package/dom/local-store.js
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
// ========================
|
|
2
2
|
// Local Storage
|
|
3
3
|
// ========================
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Utility object for managing localStorage operations with JSON support
|
|
7
|
+
* @namespace localStore
|
|
8
|
+
*/
|
|
4
9
|
export const localStore = {
|
|
10
|
+
/**
|
|
11
|
+
* Retrieves a value from localStorage
|
|
12
|
+
* @param {string} key - The key to retrieve from localStorage
|
|
13
|
+
* @returns {any} The stored value. Returns parsed JSON if the value was stored as JSON. Returns the original string if not JSON, or undefined if key doesn't exist
|
|
14
|
+
*/
|
|
5
15
|
get(key) {
|
|
6
16
|
const value = localStorage.getItem(key)
|
|
7
17
|
if (!value) return
|
|
@@ -15,6 +25,11 @@ export const localStore = {
|
|
|
15
25
|
}
|
|
16
26
|
},
|
|
17
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Stores a value in localStorage
|
|
30
|
+
* @param {string} key - The key to store the value under
|
|
31
|
+
* @param {any} value - The value to store. Objects will be stringified to JSON
|
|
32
|
+
*/
|
|
18
33
|
set(key, value) {
|
|
19
34
|
if (typeof value === 'string') {
|
|
20
35
|
localStorage.setItem(key, value)
|
|
@@ -23,7 +38,29 @@ export const localStore = {
|
|
|
23
38
|
}
|
|
24
39
|
},
|
|
25
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Stores multiple key-value pairs in localStorage
|
|
43
|
+
* @param {Object|Map} data - An object or Map containing key-value pairs to store
|
|
44
|
+
*/
|
|
45
|
+
setMultiple(data) {
|
|
46
|
+
for (const [key, value] of Object.entries(data)) {
|
|
47
|
+
this.set(key, value)
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Removes a value from localStorage
|
|
53
|
+
* @param {string} key - The key to remove from localStorage
|
|
54
|
+
*/
|
|
26
55
|
remove(key) {
|
|
27
56
|
localStorage.removeItem(key)
|
|
28
57
|
},
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Removes multiple keys from localStorage
|
|
61
|
+
* @param {string[]} keys - Array of keys to remove from localStorage
|
|
62
|
+
*/
|
|
63
|
+
removeMultiple(keys) {
|
|
64
|
+
keys.forEach(key => this.remove(key))
|
|
65
|
+
},
|
|
29
66
|
}
|
package/dom/media.js
CHANGED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { dispatchEvent } from '../events.js'
|
|
2
|
+
import { useObserverMethodOnTarget } from './observer.js'
|
|
3
|
+
|
|
4
|
+
// See https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/IntersectionObserver#options for options
|
|
5
|
+
export function intersectionObserver(target, options = {}) {
|
|
6
|
+
const { callback, ...opts } = options
|
|
7
|
+
const observer = new IntersectionObserver(observerFn, opts)
|
|
8
|
+
|
|
9
|
+
useObserverMethodOnTarget(target, observer, 'observe')
|
|
10
|
+
|
|
11
|
+
function observerFn(entries) {
|
|
12
|
+
for (const entry of entries) {
|
|
13
|
+
if (callback) callback({ entry, entries, observer })
|
|
14
|
+
else dispatchEvent(target, 'intersect', { entry, entries, observer })
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
observer,
|
|
20
|
+
observe(target) {
|
|
21
|
+
useObserverMethodOnTarget(target, observer, 'observe', options)
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
unobserve(target) {
|
|
25
|
+
useObserverMethodOnTarget(target, observer, 'unobserve')
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
takeRecords() {
|
|
29
|
+
return observer.takeRecords()
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
disconnect() {
|
|
33
|
+
// Take records before disconnecting.
|
|
34
|
+
const records = observer.takeRecords()
|
|
35
|
+
observer.disconnect()
|
|
36
|
+
if (records.length > 0) observerFn(records)
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
destroy() {
|
|
40
|
+
observer.disconnect()
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
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|NodeList|Element[]} target - The element(s) to observe.
|
|
9
|
+
* - If window is provided, document.body 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.body
|
|
43
|
+
useObserverMethodOnTarget(target, observer, 'observe', opts)
|
|
44
|
+
|
|
45
|
+
function observerFn(entries) {
|
|
46
|
+
for (const entry of entries) {
|
|
47
|
+
if (callback) callback({ entry, entries, observer })
|
|
48
|
+
else dispatchEvent(target, 'resize-obs', { entry, entries, observer })
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
observe(target) {
|
|
54
|
+
useObserverMethodOnTarget(target, observer, 'observe', options)
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
unobserve(target) {
|
|
58
|
+
useObserverMethodOnTarget(target, observer, 'unobserve')
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
disconnect: _ => observer.disconnect(),
|
|
62
|
+
destroy: _ => observer.disconnect(),
|
|
63
|
+
}
|
|
64
|
+
}
|
package/dom/random-string.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Ensures first character is a letter so it can be used in HTML ids and classes
|
|
4
4
|
* This can actually be used everywhere, not just in the browser.
|
|
5
5
|
*/
|
|
6
|
-
export function randomString(length) {
|
|
6
|
+
export function randomString(length = 10) {
|
|
7
7
|
const firstLetter = String.fromCharCode(65 + Math.floor(Math.random() * 26))
|
|
8
8
|
const others = Math.random()
|
|
9
9
|
.toString(36)
|
|
@@ -0,0 +1,13 @@
|
|
|
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/dom/sanitize.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
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/dom/trap-focus.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getFocusableElements } from './focusable.js'
|
|
2
2
|
import { isShiftTab, isTab } from './keyboard.js'
|
|
3
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
|
+
*/
|
|
4
10
|
export function trapFocus({ event, focusables }) {
|
|
5
11
|
if (event.key !== 'Tab') return
|
|
6
12
|
if (isTab(event) && document.activeElement === focusables.last) {
|
|
@@ -16,7 +22,16 @@ export function trapFocus({ event, focusables }) {
|
|
|
16
22
|
}
|
|
17
23
|
}
|
|
18
24
|
|
|
19
|
-
|
|
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
|
+
*/
|
|
20
35
|
export function manageFocus({
|
|
21
36
|
event,
|
|
22
37
|
triggerNode,
|
|
@@ -44,7 +59,7 @@ export function manageFocus({
|
|
|
44
59
|
if (tabOutFocus === 'trigger') triggerNode.focus()
|
|
45
60
|
if (tabOutFocus === 'next') {
|
|
46
61
|
event.preventDefault()
|
|
47
|
-
const DOMFocusables =
|
|
62
|
+
const DOMFocusables = getFocusableElements().keyboard
|
|
48
63
|
const index = DOMFocusables.findIndex(e => e === triggerNode)
|
|
49
64
|
const next = DOMFocusables[index + 1]
|
|
50
65
|
if (next) next.focus()
|
|
@@ -1,7 +1,19 @@
|
|
|
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
|
+
*/
|
|
1
12
|
export function inconsistentButtonFix() {
|
|
2
13
|
// https://zellwk.com/blog/inconsistent-button-behavior/
|
|
3
|
-
|
|
4
|
-
|
|
14
|
+
/** @param {MouseEvent} event */
|
|
15
|
+
document.addEventListener('click', event => {
|
|
16
|
+
if (event.target instanceof HTMLButtonElement) {
|
|
5
17
|
event.target.focus()
|
|
6
18
|
}
|
|
7
19
|
})
|
|
@@ -1,10 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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 = [] } = {}) {
|
|
3
18
|
// Check if the current node matches any of the exclude selectors
|
|
4
|
-
if (
|
|
5
|
-
excludes &&
|
|
6
|
-
excludes.some(selector => node.matches && node.matches(selector))
|
|
7
|
-
) {
|
|
19
|
+
if (node instanceof Element && excludes.some(selector => node.matches(selector))) {
|
|
8
20
|
return // Skip this node and its children
|
|
9
21
|
}
|
|
10
22
|
|
|
@@ -12,14 +24,23 @@ export function traverseAndScramble(node, { excludes } = {}) {
|
|
|
12
24
|
if (child.nodeType === 3) {
|
|
13
25
|
// Node.TEXT_NODE
|
|
14
26
|
child.nodeValue = scrambleText(child.nodeValue)
|
|
15
|
-
} else if (child.nodeType === 1) {
|
|
27
|
+
} else if (child.nodeType === 1 && child instanceof Element) {
|
|
16
28
|
// Node.ELEMENT_NODE
|
|
17
29
|
traverseAndScramble(child, { excludes }) // Recurse into child elements with excludes
|
|
18
30
|
}
|
|
19
31
|
})
|
|
20
32
|
}
|
|
21
33
|
|
|
22
|
-
|
|
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
|
+
*/
|
|
23
44
|
function scrambleText(text) {
|
|
24
45
|
return text
|
|
25
46
|
.split('')
|
package/eslint.config.js
ADDED
package/lib/arrays/index.js
CHANGED
|
@@ -1,62 +1,5 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
export function shuffle(array) {
|
|
7
|
-
const clone = array.slice()
|
|
8
|
-
let currentIndex = array.length
|
|
9
|
-
let temporaryValue
|
|
10
|
-
let randomIndex
|
|
11
|
-
|
|
12
|
-
// While there remain elements to shuffle...
|
|
13
|
-
while (currentIndex !== 0) {
|
|
14
|
-
// Pick a remaining element...
|
|
15
|
-
randomIndex = Math.floor(Math.random() * currentIndex)
|
|
16
|
-
currentIndex -= 1
|
|
17
|
-
|
|
18
|
-
// And swap it with the current element.
|
|
19
|
-
temporaryValue = clone[currentIndex]
|
|
20
|
-
clone[currentIndex] = clone[randomIndex]
|
|
21
|
-
clone[randomIndex] = temporaryValue
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return clone
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Sorts an array of objects or strings without mutating the array
|
|
29
|
-
*/
|
|
30
|
-
export function sort(
|
|
31
|
-
array,
|
|
32
|
-
{
|
|
33
|
-
property, // Property to sort by. Used when sorting array of objects.
|
|
34
|
-
order = 'asc', // 'asc' or 'desc'
|
|
35
|
-
},
|
|
36
|
-
) {
|
|
37
|
-
const clone = array.slice()
|
|
38
|
-
const sorted = clone.sort((a, b) => {
|
|
39
|
-
let one = a
|
|
40
|
-
let two = b
|
|
41
|
-
|
|
42
|
-
// Use the `sortBy` property if sorting by objects
|
|
43
|
-
if (property) {
|
|
44
|
-
one = a[property]
|
|
45
|
-
two = b[property]
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (order === 'asc') {
|
|
49
|
-
if (one < two) return -1
|
|
50
|
-
if (one > two) return 1
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (order === 'desc') {
|
|
54
|
-
if (one < two) return 1
|
|
55
|
-
if (one > two) return -1
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return 0
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
return sorted
|
|
62
|
-
}
|
|
1
|
+
export * from './item-position.js'
|
|
2
|
+
export * from './join.js'
|
|
3
|
+
export * from './shuffle.js'
|
|
4
|
+
export * from './sort.js'
|
|
5
|
+
export * from './unique.js'
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function last(array, index) {
|
|
2
|
+
console.warn('last is deprecated. Use isLastItem instead.')
|
|
3
|
+
return index === array.length - 1
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Checks if the given index is the last item in the array
|
|
8
|
+
* @param {Array} array - The array to check
|
|
9
|
+
* @param {number} index - The index to check
|
|
10
|
+
* @returns {boolean} True if the index is the last item, false otherwise
|
|
11
|
+
*/
|
|
12
|
+
export function isLastItem(array, index) {
|
|
13
|
+
return index === array.length - 1
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Returns the last item in the array
|
|
18
|
+
* @param {Array} array - The array to get the last item from
|
|
19
|
+
* @returns {*} The last item in the array
|
|
20
|
+
*/
|
|
21
|
+
export function lastArrayItem(array) {
|
|
22
|
+
return array[array.length - 1]
|
|
23
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { lastArrayItem } from './item-position.js'
|
|
2
|
+
/**
|
|
3
|
+
* Joins an array with a conjunction (and/or)
|
|
4
|
+
* @param {Array<string>} array - Array of strings to join
|
|
5
|
+
* @param {Object} [options] - Options for joining
|
|
6
|
+
* @param {string} [options.conjunction='and'] - Conjunction to use ('and' or 'or')
|
|
7
|
+
* @returns {string} Joined string
|
|
8
|
+
* @example
|
|
9
|
+
* joinWithConjunction(['a', 'b', 'c']) // 'a, b, and c'
|
|
10
|
+
* joinWithConjunction(['a', 'b'], { conjunction: 'or' }) // 'a or b'
|
|
11
|
+
*/
|
|
12
|
+
export function joinWithConjunction(array, { conjunction = 'and' } = {}) {
|
|
13
|
+
if (!array?.length) return ''
|
|
14
|
+
if (array.length === 1) return array[0]
|
|
15
|
+
if (array.length === 2) return `${array[0]} ${conjunction} ${array[1]}`
|
|
16
|
+
|
|
17
|
+
const lastItem = lastArrayItem(array)
|
|
18
|
+
const rest = array.slice(0, -1)
|
|
19
|
+
return `${rest.join(', ')} ${conjunction} ${lastItem}`
|
|
20
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Fisher Yates Shuffle without mutating the original array
|
|
2
|
+
export function shuffle(array) {
|
|
3
|
+
const clone = array.slice()
|
|
4
|
+
let currentIndex = array.length
|
|
5
|
+
let temporaryValue
|
|
6
|
+
let randomIndex
|
|
7
|
+
|
|
8
|
+
// While there remain elements to shuffle...
|
|
9
|
+
while (currentIndex !== 0) {
|
|
10
|
+
// Pick a remaining element...
|
|
11
|
+
randomIndex = Math.floor(Math.random() * currentIndex)
|
|
12
|
+
currentIndex -= 1
|
|
13
|
+
|
|
14
|
+
// And swap it with the current element.
|
|
15
|
+
temporaryValue = clone[currentIndex]
|
|
16
|
+
clone[currentIndex] = clone[randomIndex]
|
|
17
|
+
clone[randomIndex] = temporaryValue
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return clone
|
|
21
|
+
}
|