@splendidlabz/utils 1.3.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.
Files changed (93) hide show
  1. package/.eslintrc.cjs +3 -0
  2. package/.turbo/turbo-lint.log +10 -0
  3. package/.turbo/turbo-test.log +10 -0
  4. package/CHANGELOG.md +37 -0
  5. package/actions/index.js +5 -0
  6. package/actions/masonry.js +38 -0
  7. package/actions/mutation-observer.js +43 -0
  8. package/actions/prefer-horizontal-scroll.js +125 -0
  9. package/actions/resize-observer.js +38 -0
  10. package/actions/sticky.js +88 -0
  11. package/dom/bounding-box.js +54 -0
  12. package/dom/clipboard.js +14 -0
  13. package/dom/cookie.js +31 -0
  14. package/dom/css-vars.js +11 -0
  15. package/dom/events.js +76 -0
  16. package/dom/events.test.js +99 -0
  17. package/dom/focusable.js +56 -0
  18. package/dom/font-size.js +24 -0
  19. package/dom/get-element.js +69 -0
  20. package/dom/hash.js +9 -0
  21. package/dom/index.js +18 -0
  22. package/dom/keyboard.js +39 -0
  23. package/dom/local-store.js +29 -0
  24. package/dom/media.js +27 -0
  25. package/dom/pkce.js +34 -0
  26. package/dom/query-params.js +14 -0
  27. package/dom/random-string.js +18 -0
  28. package/dom/session-store.js +29 -0
  29. package/dom/trap-focus.js +55 -0
  30. package/dom/ui/aria-current.js +20 -0
  31. package/dom/ui/inconsistent-button-fix.js +8 -0
  32. package/dom/ui/index.js +4 -0
  33. package/dom/ui/scroll-container.js +25 -0
  34. package/dom/ui/traverse-and-scramble.js +40 -0
  35. package/lib/arrays/index.js +62 -0
  36. package/lib/auth/index.js +1 -0
  37. package/lib/auth/route-manager.js +44 -0
  38. package/lib/checks.js +11 -0
  39. package/lib/date/days.js +9 -0
  40. package/lib/date/index.js +2 -0
  41. package/lib/date/months.js +74 -0
  42. package/lib/form/form-data.js +73 -0
  43. package/lib/form/index.js +2 -0
  44. package/lib/form/sanitize.js +47 -0
  45. package/lib/functions/debounce.js +18 -0
  46. package/lib/functions/env.js +5 -0
  47. package/lib/functions/functional.js +31 -0
  48. package/lib/functions/index.js +5 -0
  49. package/lib/functions/throttle.js +11 -0
  50. package/lib/functions/timeout.js +15 -0
  51. package/lib/index.js +12 -0
  52. package/lib/index.test.js +9 -0
  53. package/lib/numbers/index.js +10 -0
  54. package/lib/objects/camelcase-keys.js +17 -0
  55. package/lib/objects/camelcase-keys.test.js +72 -0
  56. package/lib/objects/empty.js +10 -0
  57. package/lib/objects/extend.js +16 -0
  58. package/lib/objects/extend.test.js +35 -0
  59. package/lib/objects/flatten.js +19 -0
  60. package/lib/objects/index.js +12 -0
  61. package/lib/objects/json.js +7 -0
  62. package/lib/objects/loop.js +11 -0
  63. package/lib/objects/loop.test.js +31 -0
  64. package/lib/objects/mix/mix.js +100 -0
  65. package/lib/objects/mix/mix.md +78 -0
  66. package/lib/objects/mix/mix.test.js +324 -0
  67. package/lib/objects/nested-property.js +36 -0
  68. package/lib/objects/normalize-object.js +10 -0
  69. package/lib/objects/omit-empty.js +23 -0
  70. package/lib/objects/omit-empty.test.js +85 -0
  71. package/lib/objects/size.js +41 -0
  72. package/lib/objects/split.js +19 -0
  73. package/lib/promises/index.js +1 -0
  74. package/lib/promises/reject.js +17 -0
  75. package/lib/strings/convert-case/convert-case.js +86 -0
  76. package/lib/strings/convert-case/convert-case.md +44 -0
  77. package/lib/strings/convert-case/convert-case.test.js +50 -0
  78. package/lib/strings/index.js +4 -0
  79. package/lib/strings/markdown.js +39 -0
  80. package/lib/strings/pluralize.js +2 -0
  81. package/lib/strings/query-string.js +18 -0
  82. package/lib/style/index.js +11 -0
  83. package/lib/symbols/index.js +1 -0
  84. package/lib/symbols/symbols.js +9 -0
  85. package/node/common.js +5 -0
  86. package/node/dirname.js +17 -0
  87. package/node/file-cache.js +135 -0
  88. package/node/file.js +13 -0
  89. package/node/hash.js +5 -0
  90. package/node/index.js +6 -0
  91. package/node/pkce.js +33 -0
  92. package/node/random-string.js +10 -0
  93. package/package.json +35 -0
@@ -0,0 +1,56 @@
1
+ export function Focusable(element = document) {
2
+ return {
3
+ get length() {
4
+ return this.keyboard.length
5
+ },
6
+
7
+ get all() {
8
+ return [
9
+ ...element.querySelectorAll(
10
+ `a,
11
+ button,
12
+ input,
13
+ textarea,
14
+ select,
15
+ details,
16
+ iframe,
17
+ embed,
18
+ object,
19
+ summary,
20
+ dialog,
21
+ audio[controls],
22
+ video[controls],
23
+ [contenteditable],
24
+ [tabindex]
25
+ `
26
+ ),
27
+ ].filter(el => {
28
+ if (el.hasAttribute('disabled')) return false
29
+ if (el.hasAttribute('hidden')) return false
30
+ if (window.getComputedStyle(el).display === 'none') return false
31
+
32
+ return true
33
+ })
34
+ },
35
+
36
+ get keyboard() {
37
+ return this.all.filter(el => el.tabIndex > -1)
38
+ },
39
+
40
+ get first() {
41
+ return this.keyboard[0]
42
+ },
43
+
44
+ get last() {
45
+ return this.keyboard[this.length - 1]
46
+ },
47
+
48
+ next(index) {
49
+ return this.keyboard[index + 1] || null
50
+ },
51
+
52
+ prev(index) {
53
+ return this.keyboard[index + 1] || null
54
+ },
55
+ }
56
+ }
@@ -0,0 +1,24 @@
1
+ import { splitUnit } from '../lib/index.js'
2
+
3
+ /* globals getComputedStyle */
4
+ export function em(element = document.body, multiple = 1) {
5
+ const unit = parseFloat(getComputedStyle(element)['font-size'])
6
+ return Math.round(unit * multiple)
7
+ }
8
+
9
+ export function rem(multiple = 1) {
10
+ const unit = parseFloat(getComputedStyle(document.body)['font-size'])
11
+ return Math.round(unit * multiple)
12
+ }
13
+
14
+ export function toPx(value, element = null) {
15
+ const [amt, unit] = splitUnit(value)
16
+ let finalValue = 0
17
+
18
+ if (unit === 'rem') finalValue = rem(amt)
19
+ if (unit === 'em') finalValue = em(element, amt)
20
+ if (unit === 'px') finalValue = amt
21
+ if (!unit) finalValue = amt // Default to px offsets
22
+
23
+ return finalValue
24
+ }
@@ -0,0 +1,69 @@
1
+ // Returns the target HTMLElement.
2
+ // If the selector is already an HTMLElement, it will return the selector itself.
3
+ // Otherwise, it will return the first element that matches the selector.
4
+
5
+ export function getElement(selector) {
6
+ if (!selector) return
7
+ if (selector instanceof HTMLElement) return selector
8
+ return document.querySelector(selector)
9
+ }
10
+
11
+ export function getChildrenElements(node) {
12
+ let children = node.children
13
+
14
+ // Compensate for Astro Slots
15
+ if (children[0]?.nodeName === 'ASTRO-SLOT') children = children[0].children
16
+ return Array.from(children)
17
+ }
18
+
19
+ export function getSiblingElements(element) {
20
+ return Array.from(element.parentElement.children).filter(
21
+ child => child !== element,
22
+ )
23
+ }
24
+
25
+ export function getSelfIndex(element) {
26
+ return Array.from(element.parentElement.children).indexOf(element)
27
+ }
28
+
29
+ export function isAncestor(ancestor, element, { searchLevels = 0 } = {}) {
30
+ if (ancestor === element) return true
31
+
32
+ let parent = element.parentElement
33
+ // Infinite Search
34
+ if (searchLevels === 0) {
35
+ while (parent) {
36
+ if (ancestor === parent) return true
37
+ parent = parent.parentElement
38
+ }
39
+ } else {
40
+ // Search X levels upwards
41
+ for (let i = 0; i < searchLevels; i++) {
42
+ if (ancestor === parent) return true
43
+ parent = parent.parentElement
44
+ }
45
+ }
46
+
47
+ return false
48
+ }
49
+
50
+ export function getAncestorWithSiblings(element, { limit = false } = {}) {
51
+ const ancestor = element.parentElement
52
+ const siblings = getSiblingElements(ancestor)
53
+
54
+ // Found
55
+ if (siblings.length) return siblings
56
+
57
+ // No more ancestors
58
+ if (!ancestor) return null
59
+
60
+ // Unlimited upwards search
61
+ if (limit === false) return getAncestorWithSiblings(ancestor)
62
+
63
+ // Limited search
64
+ if (limit > 0)
65
+ return getAncestorWithSiblings(ancestor, { searchLevels: limit - 1 })
66
+
67
+ // Limit reached, end search.
68
+ if (limit === 0) return null
69
+ }
package/dom/hash.js ADDED
@@ -0,0 +1,9 @@
1
+ export async function sha256Hash(string) {
2
+ const utf8 = new TextEncoder().encode(string)
3
+ const hashBuffer = await crypto.subtle.digest('SHA-256', utf8)
4
+ const hashArray = Array.from(new Uint8Array(hashBuffer))
5
+ const hashHex = hashArray
6
+ .map(bytes => bytes.toString(16).padStart(2, '0'))
7
+ .join('')
8
+ return hashHex
9
+ }
package/dom/index.js ADDED
@@ -0,0 +1,18 @@
1
+ export * from './bounding-box.js'
2
+ export * from './clipboard.js'
3
+ export * from './cookie.js'
4
+ export * from './css-vars.js'
5
+ export * from './events.js'
6
+ export * from './focusable.js'
7
+ export * from './font-size.js'
8
+ export * from './get-element.js'
9
+ export * from './hash.js'
10
+ export * from './keyboard.js'
11
+ export * from './local-store.js'
12
+ export * from './media.js'
13
+ export * from './pkce.js'
14
+ export * from './query-params.js'
15
+ export * from './random-string.js'
16
+ export * from './session-store.js'
17
+ export * from './trap-focus.js'
18
+ export * from './ui/index.js'
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Event is Tab (without shift)
3
+ * @param {Object} event - The event object
4
+ * @returns {Boolean}
5
+ */
6
+ export function isTab(event) {
7
+ return !event.shiftKey && event.key === 'Tab'
8
+ }
9
+
10
+ /**
11
+ * Event is shift + Tab
12
+ * @param {Object} event - The event object
13
+ * @returns {Boolean}
14
+ */
15
+ export function isShiftTab(event) {
16
+ return event.shiftKey && event.key === 'Tab'
17
+ }
18
+
19
+ /**
20
+ * Matches both Tab and Shift+Tab
21
+ * @param {Object} event - The event object
22
+ * @returns
23
+ */
24
+ export function isTabKey(key) {
25
+ return key === 'Tab'
26
+ }
27
+
28
+ /**
29
+ * Event is an arrow key
30
+ * @param {Object} event - The event object
31
+ * @returns {Boolean}
32
+ */
33
+ export function isArrowKey(key) {
34
+ return ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(key)
35
+ }
36
+
37
+ export function isEscapeKey(key) {
38
+ return key === 'Escape'
39
+ }
@@ -0,0 +1,29 @@
1
+ // ========================
2
+ // Local Storage
3
+ // ========================
4
+ export const localStore = {
5
+ get(key) {
6
+ const value = localStorage.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
+ localStorage.setItem(key, value)
21
+ } else {
22
+ localStorage.setItem(key, JSON.stringify(value))
23
+ }
24
+ },
25
+
26
+ remove(key) {
27
+ localStorage.removeItem(key)
28
+ },
29
+ }
package/dom/media.js ADDED
@@ -0,0 +1,27 @@
1
+ export async function imagesLoaded(node) {
2
+ const images = Array.from(node.querySelectorAll('img'))
3
+ const promises = images.map(img => {
4
+ return new Promise((resolve, reject) => {
5
+ if (img.complete) return resolve()
6
+ img.onload = resolve
7
+ img.onerror = reject
8
+ })
9
+ })
10
+ return Promise.all(promises)
11
+ }
12
+
13
+ export async function videosLoaded(node) {
14
+ const videos = Array.from(node.querySelectorAll('video'))
15
+ const promises = videos.map(video => {
16
+ return new Promise((resolve, reject) => {
17
+ if (video.readyState === 4) return resolve() // HAVE_ENOUGH_DATA
18
+ video.onloadedmetadata = resolve
19
+ video.onerror = reject
20
+ })
21
+ })
22
+ return Promise.all(promises)
23
+ }
24
+
25
+ export async function mediaLoaded(node) {
26
+ return Promise.all([imagesLoaded(node), videosLoaded(node)])
27
+ }
package/dom/pkce.js ADDED
@@ -0,0 +1,34 @@
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
+ }
@@ -0,0 +1,14 @@
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
+ }
@@ -0,0 +1,18 @@
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) {
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
+ }
@@ -0,0 +1,29 @@
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
+ }
@@ -0,0 +1,55 @@
1
+ import { Focusable } from './focusable.js'
2
+ import { isShiftTab, isTab } from './keyboard.js'
3
+
4
+ export function trapFocus({ event, focusables }) {
5
+ if (event.key !== 'Tab') return
6
+ if (isTab(event) && document.activeElement === focusables.last) {
7
+ event.preventDefault()
8
+ focusables.first.focus()
9
+ }
10
+
11
+ // User presses shift + tab from the first focusable element.
12
+ // Redirects to the last focusable element.
13
+ if (isShiftTab(event) && document.activeElement === focusables.first) {
14
+ event.preventDefault()
15
+ focusables.last.focus()
16
+ }
17
+ }
18
+
19
+ /* Returns focus back to trigger node when user is done tabbing through the content */
20
+ export function manageFocus({
21
+ event,
22
+ triggerNode,
23
+ contentNode,
24
+ focusables,
25
+ tabOutFocus = 'next', // 'trigger'
26
+ onLeave = function () {},
27
+ }) {
28
+ const activeEl = document.activeElement
29
+
30
+ if (activeEl === contentNode && isShiftTab(event)) return backFocus()
31
+ if (activeEl === contentNode && focusables.length === 0 && isTab(event))
32
+ return nextFocus()
33
+
34
+ if (activeEl === focusables.last && isTab(event)) return nextFocus()
35
+ if (activeEl === focusables.first && isShiftTab(event)) return backFocus()
36
+
37
+ function backFocus() {
38
+ event.preventDefault()
39
+ triggerNode.focus()
40
+ onLeave('back')
41
+ }
42
+
43
+ function nextFocus() {
44
+ if (tabOutFocus === 'trigger') triggerNode.focus()
45
+ if (tabOutFocus === 'next') {
46
+ event.preventDefault()
47
+ const DOMFocusables = Focusable().keyboard
48
+ const index = DOMFocusables.findIndex(e => e === triggerNode)
49
+ const next = DOMFocusables[index + 1]
50
+ if (next) next.focus()
51
+ else triggerNode.focus()
52
+ }
53
+ onLeave('forward')
54
+ }
55
+ }
@@ -0,0 +1,20 @@
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 goToElement(
14
+ element,
15
+ { scrollBehaviour = 'instant', scrollMargin = '3rem' } = {},
16
+ ) {
17
+ if (!element) return
18
+ element.style.scrollMarginTop = scrollMargin
19
+ element.scrollIntoView({ behavior: scrollBehaviour })
20
+ }
@@ -0,0 +1,8 @@
1
+ export function inconsistentButtonFix() {
2
+ // https://zellwk.com/blog/inconsistent-button-behavior/
3
+ document.addEventListener('click', function (event) {
4
+ if (event.target.matches('button')) {
5
+ event.target.focus()
6
+ }
7
+ })
8
+ }
@@ -0,0 +1,4 @@
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'
@@ -0,0 +1,25 @@
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
+ }
@@ -0,0 +1,40 @@
1
+ // Adjusted function to take excludes as an argument
2
+ export function traverseAndScramble(node, { excludes } = {}) {
3
+ // 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
+ ) {
8
+ return // Skip this node and its children
9
+ }
10
+
11
+ node.childNodes.forEach(child => {
12
+ if (child.nodeType === 3) {
13
+ // Node.TEXT_NODE
14
+ child.nodeValue = scrambleText(child.nodeValue)
15
+ } else if (child.nodeType === 1) {
16
+ // Node.ELEMENT_NODE
17
+ traverseAndScramble(child, { excludes }) // Recurse into child elements with excludes
18
+ }
19
+ })
20
+ }
21
+
22
+ // Function to scramble text remains unchanged
23
+ function scrambleText(text) {
24
+ return text
25
+ .split('')
26
+ .map(char => {
27
+ if (/^[A-Za-z0-9]$/.test(char)) {
28
+ const randomCharCode =
29
+ Math.random() < 26 / 36
30
+ ? Math.floor(Math.random() * 26) +
31
+ (char === char.toUpperCase() ? 65 : 97) // A-Z
32
+ : Math.floor(Math.random() * 10) + 48 // 0-9
33
+ return String.fromCharCode(randomCharCode)
34
+ } else {
35
+ // Keep punctuations and other symbols intact
36
+ return char
37
+ }
38
+ })
39
+ .join('')
40
+ }
@@ -0,0 +1,62 @@
1
+ export function last(array, index) {
2
+ return index === array.length - 1
3
+ }
4
+
5
+ // Fisher Yates Shuffle without mutating the original array
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
+ }
@@ -0,0 +1 @@
1
+ export * from './route-manager.js'
@@ -0,0 +1,44 @@
1
+ /**
2
+ // Usage ... Probably should use either protected or bypass, not both.
3
+ @example
4
+ const config = {
5
+ routeConfig: {
6
+ startsWith: ['/dashboard']
7
+ }
8
+ bypassRoutes: {
9
+ is: ['/'],
10
+ startsWith: ['/fonts', '/auth', '/favicon', '/images'],
11
+ includes: ['/meh'],
12
+ }
13
+ }
14
+ const {isProtectedRoute, isBypassRoute} = AuthManager(config)
15
+ if (!isProtectedRoute(context.url.pathname)) return next() // Handle only protected routes
16
+ if (isBypassRoute(context.url.pathname)) return next() // Bypass all non protected routes
17
+ */
18
+
19
+ export function RouteManager(config) {
20
+ const { protectedRoutes, bypassRoutes } = config
21
+ return {
22
+ isProtectedRoute(pathname) {
23
+ return isMatch(protectedRoutes, pathname)
24
+ },
25
+ isBypassRoute(pathname) {
26
+ return isMatch(bypassRoutes, pathname)
27
+ },
28
+ }
29
+ }
30
+
31
+ // Alias for RouteManager
32
+ export function AuthManager(config) {
33
+ return RouteManager(config)
34
+ }
35
+
36
+ function isMatch(routeConfig, pathname) {
37
+ const { is, startsWith, includes } = routeConfig
38
+ const isExactMatch = is && is.some(u => pathname === u)
39
+ const startsWithMatch =
40
+ startsWith && startsWith.some(u => pathname.startsWith(u))
41
+ const includesMatch = includes && includes.some(u => pathname.includes(u))
42
+
43
+ return isExactMatch || startsWithMatch || includesMatch
44
+ }
package/lib/checks.js ADDED
@@ -0,0 +1,11 @@
1
+ export function isObject(x) {
2
+ return typeof x === 'object' && !Array.isArray(x) && x !== null
3
+ }
4
+
5
+ export function isArray(x) {
6
+ return Array.isArray(x)
7
+ }
8
+
9
+ export function notObject(x) {
10
+ return !isObject(x)
11
+ }
@@ -0,0 +1,9 @@
1
+ export const DAYS = [
2
+ { short: 'Sun', long: 'Sunday', numeric: 1 },
3
+ { short: 'Mon', long: 'Monday', numeric: 2 },
4
+ { short: 'Tue', long: 'Tuesday', numeric: 3 },
5
+ { short: 'Wed', long: 'Wednesday', numeric: 4 },
6
+ { short: 'Thu', long: 'Thursday', numeric: 5 },
7
+ { short: 'Fri', long: 'Friday', numeric: 6 },
8
+ { short: 'Sat', long: 'Saturday', numeric: 7 },
9
+ ]
@@ -0,0 +1,2 @@
1
+ export * from './months.js'
2
+ export * from './days.js'