@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.
Files changed (228) hide show
  1. package/dist/cjs/dom/accessibility.cjs +2 -0
  2. package/dist/cjs/dom/actions/index.cjs +392 -7
  3. package/dist/cjs/dom/actions/masonry.cjs +97 -6
  4. package/dist/cjs/dom/actions/prefer-horizontal-scroll.cjs +58 -14
  5. package/dist/cjs/dom/actions/sticky.cjs +126 -6
  6. package/dist/cjs/dom/bounding-box.cjs +2 -0
  7. package/dist/cjs/dom/clipboard.cjs +2 -0
  8. package/dist/cjs/dom/cookie.cjs +3 -1
  9. package/dist/cjs/dom/css-vars.cjs +13 -3
  10. package/dist/cjs/dom/events.cjs +2 -0
  11. package/dist/cjs/dom/focusable.cjs +2 -0
  12. package/dist/cjs/dom/font-size.cjs +15 -3
  13. package/dist/cjs/dom/get-element.cjs +3 -1
  14. package/dist/cjs/dom/hash.cjs +2 -0
  15. package/dist/cjs/dom/index.cjs +1249 -47
  16. package/dist/cjs/dom/keyboard.cjs +2 -0
  17. package/dist/cjs/dom/local-store.cjs +3 -1
  18. package/dist/cjs/dom/media.cjs +2 -0
  19. package/dist/cjs/dom/observers/index.cjs +235 -9
  20. package/dist/cjs/dom/observers/intersection-observer.cjs +38 -6
  21. package/dist/cjs/dom/observers/mutation-observer.cjs +38 -6
  22. package/dist/cjs/dom/observers/observer.cjs +12 -2
  23. package/dist/cjs/dom/observers/resize-observer.cjs +38 -6
  24. package/dist/cjs/dom/observers/scroll-observer.cjs +62 -3
  25. package/dist/cjs/dom/pkce.cjs +13 -3
  26. package/dist/cjs/dom/query-params.cjs +3 -1
  27. package/dist/cjs/dom/random-string.cjs +2 -0
  28. package/dist/cjs/dom/sanitize.cjs +27 -3
  29. package/dist/cjs/dom/session-store.cjs +3 -1
  30. package/dist/cjs/dom/spam-prevention.cjs +2 -0
  31. package/dist/cjs/dom/trap-focus.cjs +131 -9
  32. package/dist/cjs/dom/ui/aria-current.cjs +2 -0
  33. package/dist/cjs/dom/ui/inconsistent-button-fix.cjs +2 -0
  34. package/dist/cjs/dom/ui/index.cjs +87 -9
  35. package/dist/cjs/dom/ui/scroll-container.cjs +2 -0
  36. package/dist/cjs/dom/ui/traverse-and-scramble.cjs +2 -0
  37. package/dist/cjs/lib/arrays/index.cjs +121 -13
  38. package/dist/cjs/lib/arrays/item-position.cjs +2 -0
  39. package/dist/cjs/lib/arrays/join.cjs +10 -2
  40. package/dist/cjs/lib/arrays/shuffle.cjs +2 -0
  41. package/dist/cjs/lib/arrays/sort.cjs +11 -3
  42. package/dist/cjs/lib/arrays/split-array.cjs +2 -0
  43. package/dist/cjs/lib/arrays/unique.cjs +2 -0
  44. package/dist/cjs/lib/auth/index.cjs +35 -3
  45. package/dist/cjs/lib/auth/route-manager.cjs +2 -0
  46. package/dist/cjs/lib/checks.cjs +2 -0
  47. package/dist/cjs/lib/date/days.cjs +3 -1
  48. package/dist/cjs/lib/date/index.cjs +190 -7
  49. package/dist/cjs/lib/date/months.cjs +3 -1
  50. package/dist/cjs/lib/date/time.cjs +14 -2
  51. package/dist/cjs/lib/form/form-data.cjs +2 -0
  52. package/dist/cjs/lib/form/index.cjs +104 -5
  53. package/dist/cjs/lib/form/sanitize.cjs +2 -0
  54. package/dist/cjs/lib/functions/callback.cjs +2 -0
  55. package/dist/cjs/lib/functions/debounce.cjs +2 -0
  56. package/dist/cjs/lib/functions/env.cjs +2 -0
  57. package/dist/cjs/lib/functions/functional.cjs +2 -0
  58. package/dist/cjs/lib/functions/index.cjs +124 -11
  59. package/dist/cjs/lib/functions/throttle.cjs +2 -0
  60. package/dist/cjs/lib/functions/timeout.cjs +2 -0
  61. package/dist/cjs/lib/hash.cjs +2 -0
  62. package/dist/cjs/lib/index.cjs +1160 -29
  63. package/dist/cjs/lib/numbers/index.cjs +17 -4
  64. package/dist/cjs/lib/numbers/math.cjs +2 -0
  65. package/dist/cjs/lib/objects/camelcase-keys.cjs +45 -4
  66. package/dist/cjs/lib/objects/empty.cjs +2 -0
  67. package/dist/cjs/lib/objects/equal.cjs +2 -0
  68. package/dist/cjs/lib/objects/extend.cjs +117 -6
  69. package/dist/cjs/lib/objects/flatten.cjs +11 -3
  70. package/dist/cjs/lib/objects/index.cjs +360 -27
  71. package/dist/cjs/lib/objects/json.cjs +2 -0
  72. package/dist/cjs/lib/objects/loop.cjs +2 -0
  73. package/dist/cjs/lib/objects/mix/mix.cjs +5 -3
  74. package/dist/cjs/lib/objects/nested-property.cjs +2 -0
  75. package/dist/cjs/lib/objects/normalize-object.cjs +78 -4
  76. package/dist/cjs/lib/objects/omit-empty.cjs +2 -0
  77. package/dist/cjs/lib/objects/size.cjs +2 -0
  78. package/dist/cjs/lib/objects/split.cjs +2 -0
  79. package/dist/cjs/lib/promises/index.cjs +51 -3
  80. package/dist/cjs/lib/promises/reject.cjs +19 -2
  81. package/dist/cjs/lib/sse.cjs +29 -4
  82. package/dist/cjs/lib/strings/convert-case/convert-case.cjs +2 -0
  83. package/dist/cjs/lib/strings/index.cjs +179 -11
  84. package/dist/cjs/lib/strings/markdown.cjs +3 -1
  85. package/dist/cjs/lib/strings/name.cjs +2 -0
  86. package/dist/cjs/lib/strings/pluralize.cjs +3 -1
  87. package/dist/cjs/lib/strings/query-string.cjs +2 -0
  88. package/dist/cjs/lib/style/index.cjs +31 -2
  89. package/dist/cjs/lib/symbols/index.cjs +22 -3
  90. package/dist/cjs/lib/symbols/symbols.cjs +2 -0
  91. package/dist/cjs/node/context.cjs +0 -0
  92. package/dist/cjs/node/dirname.cjs +4 -2
  93. package/dist/cjs/node/file-cache.cjs +45 -3
  94. package/dist/cjs/node/file.cjs +4 -2
  95. package/dist/cjs/node/hash.cjs +3 -1
  96. package/dist/cjs/node/index.cjs +278 -15
  97. package/dist/cjs/node/pkce.cjs +20 -8
  98. package/dist/cjs/node/random-string.cjs +3 -1
  99. package/dist/cjs/node/sanitize.cjs +27 -3
  100. package/dist/cjs/node/uuid.cjs +2 -0
  101. package/dist/esm/node/context.js +0 -0
  102. package/dist/esm/node/file-cache.js +1 -1
  103. package/dist/esm/node/pkce.js +3 -2
  104. package/dist/types/node/context.d.cts +2 -0
  105. package/dist/types/node/index.d.cts +1 -1
  106. package/dist/types/node/pkce.d.cts +7 -1
  107. package/package.json +5 -2
  108. package/CHANGELOG.md +0 -231
  109. package/eslint.config.js +0 -2
  110. package/src/dom/accessibility.js +0 -58
  111. package/src/dom/actions/index.js +0 -3
  112. package/src/dom/actions/masonry.js +0 -43
  113. package/src/dom/actions/prefer-horizontal-scroll.js +0 -121
  114. package/src/dom/actions/sticky.js +0 -85
  115. package/src/dom/bounding-box.js +0 -82
  116. package/src/dom/clipboard.js +0 -14
  117. package/src/dom/cookie.js +0 -32
  118. package/src/dom/css-vars.js +0 -19
  119. package/src/dom/events.js +0 -71
  120. package/src/dom/focusable.js +0 -165
  121. package/src/dom/font-size.js +0 -40
  122. package/src/dom/get-element.js +0 -176
  123. package/src/dom/hash.js +0 -9
  124. package/src/dom/index.js +0 -23
  125. package/src/dom/keyboard.js +0 -44
  126. package/src/dom/local-store.js +0 -66
  127. package/src/dom/media.js +0 -30
  128. package/src/dom/observers/index.js +0 -4
  129. package/src/dom/observers/intersection-observer.js +0 -43
  130. package/src/dom/observers/mutation-observer.js +0 -51
  131. package/src/dom/observers/observer.js +0 -18
  132. package/src/dom/observers/resize-observer.js +0 -66
  133. package/src/dom/observers/scroll-observer.js +0 -146
  134. package/src/dom/pkce.js +0 -34
  135. package/src/dom/query-params.js +0 -14
  136. package/src/dom/random-string.js +0 -18
  137. package/src/dom/random-string.test.js +0 -13
  138. package/src/dom/sanitize.js +0 -16
  139. package/src/dom/session-store.js +0 -29
  140. package/src/dom/spam-prevention.js +0 -40
  141. package/src/dom/trap-focus.js +0 -70
  142. package/src/dom/ui/aria-current.js +0 -20
  143. package/src/dom/ui/inconsistent-button-fix.js +0 -20
  144. package/src/dom/ui/index.js +0 -4
  145. package/src/dom/ui/scroll-container.js +0 -25
  146. package/src/dom/ui/traverse-and-scramble.js +0 -61
  147. package/src/lib/arrays/index.js +0 -6
  148. package/src/lib/arrays/item-position.js +0 -23
  149. package/src/lib/arrays/join.js +0 -20
  150. package/src/lib/arrays/shuffle.js +0 -21
  151. package/src/lib/arrays/sort.js +0 -88
  152. package/src/lib/arrays/sort.spec.js +0 -123
  153. package/src/lib/arrays/split-array.js +0 -15
  154. package/src/lib/arrays/unique.js +0 -3
  155. package/src/lib/auth/index.js +0 -1
  156. package/src/lib/auth/route-manager.js +0 -45
  157. package/src/lib/checks.js +0 -57
  158. package/src/lib/date/days.js +0 -9
  159. package/src/lib/date/index.js +0 -3
  160. package/src/lib/date/months.js +0 -74
  161. package/src/lib/date/time.js +0 -83
  162. package/src/lib/form/form-data.js +0 -73
  163. package/src/lib/form/index.js +0 -2
  164. package/src/lib/form/sanitize.js +0 -80
  165. package/src/lib/form/sanitize.spec.js +0 -126
  166. package/src/lib/functions/callback.js +0 -8
  167. package/src/lib/functions/debounce.js +0 -18
  168. package/src/lib/functions/env.js +0 -5
  169. package/src/lib/functions/functional.js +0 -113
  170. package/src/lib/functions/functional.test.js +0 -196
  171. package/src/lib/functions/index.js +0 -5
  172. package/src/lib/functions/throttle.js +0 -11
  173. package/src/lib/functions/timeout.js +0 -15
  174. package/src/lib/hash.js +0 -18
  175. package/src/lib/hash.spec.js +0 -163
  176. package/src/lib/index.js +0 -14
  177. package/src/lib/numbers/index.js +0 -12
  178. package/src/lib/numbers/math.js +0 -9
  179. package/src/lib/numbers/split-unit.test.js +0 -45
  180. package/src/lib/objects/camelcase-keys.js +0 -17
  181. package/src/lib/objects/camelcase-keys.test.js +0 -72
  182. package/src/lib/objects/empty.js +0 -10
  183. package/src/lib/objects/equal.js +0 -9
  184. package/src/lib/objects/extend.js +0 -16
  185. package/src/lib/objects/extend.test.js +0 -35
  186. package/src/lib/objects/flatten.js +0 -19
  187. package/src/lib/objects/index.js +0 -13
  188. package/src/lib/objects/json.js +0 -7
  189. package/src/lib/objects/loop.js +0 -11
  190. package/src/lib/objects/loop.test.js +0 -31
  191. package/src/lib/objects/mix/mix.js +0 -191
  192. package/src/lib/objects/mix/mix.md +0 -78
  193. package/src/lib/objects/mix/mix.test.js +0 -445
  194. package/src/lib/objects/nested-property.js +0 -41
  195. package/src/lib/objects/normalize-object.js +0 -10
  196. package/src/lib/objects/omit-empty.js +0 -23
  197. package/src/lib/objects/omit-empty.test.js +0 -85
  198. package/src/lib/objects/size.js +0 -41
  199. package/src/lib/objects/split.js +0 -19
  200. package/src/lib/promises/index.js +0 -1
  201. package/src/lib/promises/reject.js +0 -17
  202. package/src/lib/sse.js +0 -61
  203. package/src/lib/strings/convert-case/convert-case.js +0 -86
  204. package/src/lib/strings/convert-case/convert-case.md +0 -44
  205. package/src/lib/strings/convert-case/convert-case.test.js +0 -50
  206. package/src/lib/strings/index.js +0 -5
  207. package/src/lib/strings/markdown.js +0 -71
  208. package/src/lib/strings/name.js +0 -24
  209. package/src/lib/strings/name.test.js +0 -67
  210. package/src/lib/strings/pluralize.js +0 -2
  211. package/src/lib/strings/query-string.js +0 -18
  212. package/src/lib/style/index.js +0 -13
  213. package/src/lib/symbols/index.js +0 -1
  214. package/src/lib/symbols/symbols.js +0 -9
  215. package/src/node/dirname.js +0 -17
  216. package/src/node/file-cache.js +0 -184
  217. package/src/node/file.js +0 -13
  218. package/src/node/hash.js +0 -5
  219. package/src/node/index.js +0 -7
  220. package/src/node/pkce.js +0 -33
  221. package/src/node/random-string.js +0 -10
  222. package/src/node/random-string.test.js +0 -15
  223. package/src/node/sanitize.js +0 -13
  224. package/src/node/sanitize.spec.js +0 -142
  225. package/src/node/uuid.js +0 -5
  226. package/tsconfig.json +0 -23
  227. package/tsup.config.js +0 -38
  228. 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
- }
@@ -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
- }
@@ -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
- })
@@ -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
- }
@@ -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
- }
@@ -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
- }
@@ -1,4 +0,0 @@
1
- export * from './aria-current.js'
2
- export * from './inconsistent-button-fix.js'
3
- export * from './scroll-container.js'
4
- export * from './traverse-and-scramble.js'
@@ -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
- }
@@ -1,6 +0,0 @@
1
- export * from './item-position.js'
2
- export * from './join.js'
3
- export * from './shuffle.js'
4
- export * from './sort.js'
5
- export * from './split-array.js'
6
- export * from './unique.js'