@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,74 @@
1
+ export const MONTHS = [
2
+ {
3
+ long: 'January',
4
+ short: 'jan',
5
+ numeric: 1,
6
+ lastDay: { default: 31, leapYear: 31 },
7
+ },
8
+ {
9
+ long: 'February',
10
+ short: 'feb',
11
+ numeric: 2,
12
+ lastDay: { default: 28, leapYear: 29 },
13
+ },
14
+ {
15
+ long: 'March',
16
+ short: 'mar',
17
+ numeric: 3,
18
+ lastDay: { default: 31, leapYear: 31 },
19
+ },
20
+ {
21
+ long: 'April',
22
+ short: 'apr',
23
+ numeric: 4,
24
+ lastDay: { default: 30, leapYear: 30 },
25
+ },
26
+ {
27
+ long: 'May',
28
+ short: 'may',
29
+ numeric: 5,
30
+ lastDay: { default: 31, leapYear: 31 },
31
+ },
32
+ {
33
+ long: 'June',
34
+ short: 'jun',
35
+ numeric: 6,
36
+ lastDay: { default: 30, leapYear: 30 },
37
+ },
38
+ {
39
+ long: 'July',
40
+ short: 'jul',
41
+ numeric: 7,
42
+ lastDay: { default: 31, leapYear: 31 },
43
+ },
44
+ {
45
+ long: 'August',
46
+ short: 'aug',
47
+ numeric: 8,
48
+ lastDay: { default: 31, leapYear: 31 },
49
+ },
50
+ {
51
+ long: 'September',
52
+ short: 'sep',
53
+ numeric: 9,
54
+ lastDay: { default: 30, leapYear: 30 },
55
+ },
56
+ {
57
+ long: 'October',
58
+ short: 'oct',
59
+ numeric: 10,
60
+ lastDay: { default: 31, leapYear: 31 },
61
+ },
62
+ {
63
+ long: 'November',
64
+ short: 'nov',
65
+ numeric: 11,
66
+ lastDay: { default: 30, leapYear: 30 },
67
+ },
68
+ {
69
+ long: 'December',
70
+ short: 'dec',
71
+ numeric: 12,
72
+ lastDay: { default: 31, leapYear: 31 },
73
+ },
74
+ ]
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Converts Form Data into an object
3
+ * @param {FormData} formData
4
+ * @returns Object
5
+ */
6
+ export function formDataToObject(formData) {
7
+ const obj = {}
8
+ for (let [key, value] of formData) {
9
+ if (typeof value === 'string') value = value.trim()
10
+
11
+ if (key.includes('[]')) {
12
+ // Handle array fields
13
+ const arrayKey = key.replace('[]', '')
14
+ if (!obj[arrayKey]) obj[arrayKey] = []
15
+ obj[arrayKey].push(value)
16
+ } else {
17
+ // Handle normal string fields
18
+ obj[key] = value
19
+ }
20
+ }
21
+ return obj
22
+ }
23
+
24
+ export function flattenArrayFields(inputObject) {
25
+ const result = {}
26
+
27
+ for (const [key, value] of Object.entries(inputObject)) {
28
+ // Regex to match keys that follow pattern: arrayName[index][property]
29
+ const match = key.match(/^(\w+)(?:\[(\d+)\])?(?:\[(\w+)\])?$/)
30
+ if (match) {
31
+ const [, baseKey, index, property] = match
32
+
33
+ if (index !== undefined) {
34
+ // Handle array of objects
35
+ if (!result[baseKey]) result[baseKey] = []
36
+ const arrayIndex = parseInt(index)
37
+ if (!result[baseKey][arrayIndex]) result[baseKey][arrayIndex] = {}
38
+ if (property) {
39
+ result[baseKey][arrayIndex][property] = value
40
+ } else {
41
+ result[baseKey][arrayIndex] = value
42
+ }
43
+ } else if (property) {
44
+ // Handle single object
45
+ if (!result[baseKey]) result[baseKey] = {}
46
+ result[baseKey][property] = value
47
+ } else {
48
+ // Handle simple key-value
49
+ result[key] = value
50
+ }
51
+ } else {
52
+ // Copy key-value pair for unmatched keys
53
+ result[key] = value
54
+ }
55
+ }
56
+
57
+ // Remove empty objects from arrays and convert array-like objects to actual arrays
58
+ for (const key in result) {
59
+ if (typeof result[key] === 'object' && result[key] !== null) {
60
+ if (Object.keys(result[key]).every(k => !isNaN(parseInt(k)))) {
61
+ // Convert to array and filter out empty items
62
+ result[key] = Object.values(result[key]).filter(item => {
63
+ if (typeof item === 'object') {
64
+ return Object.values(item).some(v => v !== '')
65
+ }
66
+ return item !== ''
67
+ })
68
+ }
69
+ }
70
+ }
71
+
72
+ return result
73
+ }
@@ -0,0 +1,2 @@
1
+ export * from './form-data.js'
2
+ export * from './sanitize.js'
@@ -0,0 +1,47 @@
1
+ import DOMPurify from 'dompurify'
2
+
3
+ /**
4
+ * Sanitizes HTML string using DOMPurify.
5
+ * @param {string} html - The HTML string to sanitize.
6
+ * @param {Object} [options={}] - DOMPurify configuration options.
7
+ * @returns {string} The sanitized HTML string.
8
+ */
9
+ export function sanitize(html, options = {}) {
10
+ return DOMPurify.sanitize(html, options)
11
+ }
12
+
13
+ /**
14
+ * Recursively sanitizes entries in an object or array.
15
+ * @param {Array} entries - The entries to sanitize.
16
+ * @param {Object} [options={}] - DOMPurify configuration options.
17
+ * @returns {Array} The sanitized entries.
18
+ */
19
+ export function sanitizeEntries(entries, options = {}) {
20
+ return entries.map(([key, value]) => {
21
+ if (Array.isArray(value)) {
22
+ return [
23
+ key,
24
+ value.map(item => sanitizeEntries(Object.entries(item), options)),
25
+ ]
26
+ }
27
+
28
+ if (value && typeof value === 'object') {
29
+ return [
30
+ key,
31
+ Object.fromEntries(sanitizeEntries(Object.entries(value), options)),
32
+ ]
33
+ }
34
+
35
+ return [key, sanitize(value, options)]
36
+ })
37
+ }
38
+
39
+ /**
40
+ * Sanitizes all string values in an object.
41
+ * @param {Object} obj - The object to sanitize.
42
+ * @param {Object} [options={}] - DOMPurify configuration options.
43
+ * @returns {Object} A new object with all string values sanitized.
44
+ */
45
+ export function sanitizeObject(obj, options = {}) {
46
+ return Object.fromEntries(sanitizeEntries(Object.entries(obj), options))
47
+ }
@@ -0,0 +1,18 @@
1
+ export function debounce(callback, wait, immediate = false) {
2
+ let timeout
3
+
4
+ return function () {
5
+ const context = this
6
+ const args = arguments
7
+
8
+ const callNow = immediate && !timeout
9
+ clearTimeout(timeout)
10
+ timeout = setTimeout(later, wait)
11
+ if (callNow) callback.apply(context, args)
12
+
13
+ function later() {
14
+ timeout = null
15
+ if (!immediate) callback.apply(context, args)
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,5 @@
1
+ // Checks whether current environment is browser or node
2
+ export function getEnv() {
3
+ if (typeof window === 'object') return 'browser'
4
+ if (typeof global === 'object') return 'node'
5
+ }
@@ -0,0 +1,31 @@
1
+ export function curry(fn) {
2
+ return function curried(...args) {
3
+ if (args.length >= fn.length) {
4
+ return fn.apply(this, args)
5
+ } else {
6
+ return function (...moreArgs) {
7
+ return curried.apply(this, args.concat(moreArgs))
8
+ }
9
+ }
10
+ }
11
+ }
12
+
13
+ export function compose(...fns) {
14
+ return function (value) {
15
+ return fns.reduceRight((acc, fn) => fn(acc), value)
16
+ }
17
+ }
18
+
19
+ export function pipe(...fns) {
20
+ return function (value) {
21
+ return fns.reduce((acc, fn) => fn(acc), value)
22
+ }
23
+ }
24
+
25
+ export function times(fn, n) {
26
+ const result = []
27
+ for (let i = 0; i < n; i++) {
28
+ result.push(fn(i))
29
+ }
30
+ return result
31
+ }
@@ -0,0 +1,5 @@
1
+ export * from './debounce.js'
2
+ export * from './env.js'
3
+ export * from './functional.js'
4
+ export * from './throttle.js'
5
+ export * from './timeout.js'
@@ -0,0 +1,11 @@
1
+ export function throttle(callback, wait) {
2
+ let lastCall = 0
3
+
4
+ return function (...args) {
5
+ const now = new Date().getTime()
6
+ if (now - lastCall >= wait) {
7
+ callback(...args)
8
+ lastCall = now
9
+ }
10
+ }
11
+ }
@@ -0,0 +1,15 @@
1
+ // setTimeout for task ticking
2
+ // reverses wait and callback functions of timeouts because it makes more sense when reading the code
3
+ export function timeout(wait, callback) {
4
+ setTimeout(callback, wait)
5
+ }
6
+
7
+ // Promise based timeouts for microtask ticking
8
+ export function delay(ms) {
9
+ return new Promise(resolve => setTimeout(resolve, ms))
10
+ }
11
+
12
+ // Alias of delay
13
+ export function wait(ms) {
14
+ return delay(ms)
15
+ }
package/lib/index.js ADDED
@@ -0,0 +1,12 @@
1
+ export * from './arrays/index.js'
2
+ export * from './auth/index.js'
3
+ export * from './checks.js'
4
+ export * from './date/index.js'
5
+ export * from './form/index.js'
6
+ export * from './functions/index.js'
7
+ export * from './numbers/index.js'
8
+ export * from './objects/index.js'
9
+ export * from './promises/index.js'
10
+ export * from './strings/index.js'
11
+ export * from './style/index.js'
12
+ export * from './symbols/index.js'
@@ -0,0 +1,9 @@
1
+ // Possible to test???
2
+ import * as test from './index.js'
3
+
4
+ import { expect, it } from 'vitest'
5
+
6
+ it('Import Test', () => {
7
+ console.log(test)
8
+ console.log('hello')
9
+ })
@@ -0,0 +1,10 @@
1
+ export function splitUnit(string) {
2
+ // If there are no units, return the number straight.
3
+ if (typeof string === 'number') return [string, null]
4
+
5
+ const regex = /(-?\d*\.?\d+)([a-z]+)/i
6
+ const match = string.match(regex)
7
+
8
+ if (match) return [parseFloat(match[1]), match[2]]
9
+ return [parseFloat(string), null] // sometimes unitless numbers make it here if was typed into a string. This catches and returns it.
10
+ }
@@ -0,0 +1,17 @@
1
+ import { objectForEach } from './loop.js'
2
+ import { toCamel } from '../strings/convert-case/convert-case.js'
3
+
4
+ export function camelCaseKeys(obj) {
5
+ if (typeof obj !== 'object' || obj === null) {
6
+ return obj
7
+ }
8
+
9
+ const result = {}
10
+
11
+ objectForEach(obj, ({ key, value }) => {
12
+ const camelCaseKey = toCamel(key)
13
+ result[camelCaseKey] = camelCaseKeys(value)
14
+ })
15
+
16
+ return result
17
+ }
@@ -0,0 +1,72 @@
1
+ import { expect, it } from 'vitest'
2
+
3
+ import { camelCaseKeys } from './camelcase-keys.js'
4
+
5
+ it('Camelcase Basic', ctx => {
6
+ const object = {
7
+ lowercase: 1,
8
+ 'Title Case': 2,
9
+ 'kebab-case': 3,
10
+ PascalCase: 4,
11
+ }
12
+
13
+ const expected = {
14
+ lowercase: 1,
15
+ titleCase: 2,
16
+ kebabCase: 3,
17
+ pascalCase: 4,
18
+ }
19
+
20
+ const result = camelCaseKeys(object)
21
+ expect(result).toEqual(expected)
22
+ })
23
+
24
+ it('Camelcase Nested', ctx => {
25
+ const object = {
26
+ one: {
27
+ lowercase: 1,
28
+ 'Title Case': 2,
29
+ 'kebab-case': 3,
30
+ PascalCase: 4,
31
+ },
32
+ }
33
+
34
+ const expected = {
35
+ one: {
36
+ lowercase: 1,
37
+ titleCase: 2,
38
+ kebabCase: 3,
39
+ pascalCase: 4,
40
+ },
41
+ }
42
+
43
+ const result = camelCaseKeys(object)
44
+ expect(result).toEqual(expected)
45
+ })
46
+
47
+ it('Camelcase Deeply Nested', ctx => {
48
+ const object = {
49
+ one: {
50
+ two: {
51
+ lowercase: 1,
52
+ 'Title Case': 2,
53
+ 'kebab-case': 3,
54
+ PascalCase: 4,
55
+ },
56
+ },
57
+ }
58
+
59
+ const expected = {
60
+ one: {
61
+ two: {
62
+ lowercase: 1,
63
+ titleCase: 2,
64
+ kebabCase: 3,
65
+ pascalCase: 4,
66
+ },
67
+ },
68
+ }
69
+
70
+ const result = camelCaseKeys(object)
71
+ expect(result).toEqual(expected)
72
+ })
@@ -0,0 +1,10 @@
1
+ export function isEmpty(obj) {
2
+ if (typeof obj === 'undefined') return true
3
+ if (obj === null) return true
4
+ if (Array.isArray(obj)) return obj.length === 0
5
+ return Object.keys(obj).length === 0
6
+ }
7
+
8
+ export function notEmpty(obj) {
9
+ return !isEmpty(obj)
10
+ }
@@ -0,0 +1,16 @@
1
+ import { objectForEach, objectMap } from './loop.js'
2
+
3
+ import { curry } from '../functions/functional.js'
4
+ import { mix } from './mix/mix.js'
5
+
6
+ // Need to manually curry these functions.
7
+
8
+ export function extendObject(object) {
9
+ const orig = object
10
+ const extended = mix({}, object)
11
+
12
+ extended.forEach = curry(objectForEach)(orig)
13
+ extended.map = curry(objectMap)(orig)
14
+
15
+ return { orig, extended }
16
+ }
@@ -0,0 +1,35 @@
1
+ import { beforeEach, expect, it } from 'vitest'
2
+
3
+ import { extendObject } from '.'
4
+
5
+ beforeEach(ctx => {
6
+ ctx.object = {
7
+ one: 1,
8
+ two: 2,
9
+ }
10
+ })
11
+
12
+ it('Object Extension', ctx => {
13
+ const { object } = ctx
14
+ const { extended, orig } = extendObject(object)
15
+
16
+ // Orig should be untouched
17
+ // Object should also be untouched
18
+ expect(object).toEqual(orig)
19
+ expect(object.forEach).toBeUndefined()
20
+ expect(object.map).toBeUndefined()
21
+
22
+ // Extended should be extended
23
+ expect(extended.one).toBe(1)
24
+ expect(extended.two).toBe(2)
25
+ expect(extended.forEach).toBeDefined()
26
+ expect(extended.map).toBeDefined()
27
+ })
28
+
29
+ it('Curried methods', ctx => {
30
+ const { extended } = extendObject(ctx.object)
31
+
32
+ // Extended methods should not be part of the object when looping
33
+ const test = extended.map(({ key, value }) => value)
34
+ expect(test).toEqual([1, 2])
35
+ })
@@ -0,0 +1,19 @@
1
+ import { isObject } from '../checks.js'
2
+
3
+ export function flattenObject(obj, { separator, prefix = '' }) {
4
+ return Object.entries(obj).reduce((acc, [key, value]) => {
5
+ const newKey = prefix ? `${prefix}${separator}${key}` : key
6
+
7
+ const isAnObject = isObject(value)
8
+ const hasNestedObject = isAnObject && Object.values(value).some(isObject)
9
+
10
+ if (hasNestedObject) {
11
+ // If the value is an object, recursively flatten it.
12
+ Object.assign(acc, flattenObject(value, { separator, prefix: newKey }))
13
+ } else {
14
+ // Directly assign terminal values to the new key in the accumulator.
15
+ acc[newKey] = value
16
+ }
17
+ return acc
18
+ }, {})
19
+ }
@@ -0,0 +1,12 @@
1
+ export * from './camelcase-keys.js'
2
+ export * from './empty.js'
3
+ export * from './extend.js'
4
+ export * from './flatten.js'
5
+ export * from './json.js'
6
+ export * from './loop.js'
7
+ export * from './mix/mix.js'
8
+ export * from './nested-property.js'
9
+ export * from './normalize-object.js'
10
+ export * from './omit-empty.js'
11
+ export * from './size.js'
12
+ export * from './split.js'
@@ -0,0 +1,7 @@
1
+ export function parseJSON(value, defaultValue = {}) {
2
+ try {
3
+ return JSON.parse(value)
4
+ } catch (e) {
5
+ return defaultValue
6
+ }
7
+ }
@@ -0,0 +1,11 @@
1
+ export function objectForEach(object, callback) {
2
+ Object.entries(object).forEach(([key, value]) => {
3
+ callback({ key, value })
4
+ })
5
+ }
6
+
7
+ export function objectMap(object, callback) {
8
+ return Object.entries(object).map(([key, value]) => {
9
+ return callback({ key, value })
10
+ })
11
+ }
@@ -0,0 +1,31 @@
1
+ import { beforeEach, expect, it } from 'vitest'
2
+ import { objectForEach, objectMap } from './loop.js'
3
+
4
+ beforeEach(ctx => {
5
+ ctx.obj = {
6
+ one: 1,
7
+ two: 2,
8
+ }
9
+ })
10
+
11
+ it('Object For Each', ctx => {
12
+ const { obj } = ctx
13
+ const newObject = {}
14
+ function callback({ key, value }) {
15
+ newObject[key] = value
16
+ }
17
+
18
+ objectForEach(obj, callback)
19
+ expect(newObject).toEqual(obj)
20
+ })
21
+
22
+ it('Object Map', ctx => {
23
+ const { obj } = ctx
24
+
25
+ function callback({ key, value }) {
26
+ return value
27
+ }
28
+
29
+ const res = objectMap(obj, callback)
30
+ expect(res).toEqual([1, 2])
31
+ })
@@ -0,0 +1,100 @@
1
+ export function mix(...sources) {
2
+ const result = {}
3
+ for (const source of sources) {
4
+ merge(result, source)
5
+ }
6
+ return result
7
+ }
8
+
9
+ function merge(output, input) {
10
+ const props = Object.keys(input)
11
+
12
+ for (const prop of props) {
13
+ // Prevents Prototype Pollution
14
+ if (prop === '__proto__') continue
15
+
16
+ const descriptor = Object.getOwnPropertyDescriptor(input, prop)
17
+ const value = descriptor.value
18
+ if (value) descriptor.value = cloneDescriptorValue(value)
19
+
20
+ // If don't have prop => Define property
21
+ if (!output[prop]) {
22
+ Object.defineProperty(output, prop, descriptor)
23
+ continue
24
+ }
25
+
26
+ // If have prop, but type is not object => Overwrite by redefining property
27
+ if (typeof output[prop] !== 'object') {
28
+ Object.defineProperty(output, prop, descriptor)
29
+ continue
30
+ }
31
+
32
+ // If have prop, but type is Object => Concat the arrays together.
33
+ if (objectType(descriptor.value) === '[object Array]') {
34
+ output[prop] = output[prop].concat(descriptor.value)
35
+ continue
36
+ }
37
+
38
+ // If have prop, but type is Object => Merge.
39
+ merge(output[prop], descriptor.value)
40
+
41
+ // Not sure if I have to deal with overwriting maps / dates. When it comes, I guess!
42
+ }
43
+ }
44
+
45
+ // Creates a deep clone for each value
46
+ function cloneDescriptorValue(value) {
47
+ // Arrays
48
+ if (objectType(value) === '[object Array]') {
49
+ const array = []
50
+ for (let v of value) {
51
+ v = cloneDescriptorValue(v)
52
+ array.push(v)
53
+ }
54
+ return array
55
+ }
56
+
57
+ // Objects
58
+ if (objectType(value) === '[object Object]') {
59
+ const obj = {}
60
+ const props = Object.keys(value)
61
+ for (const prop of props) {
62
+ const descriptor = Object.getOwnPropertyDescriptor(value, prop)
63
+ if (descriptor.value)
64
+ descriptor.value = cloneDescriptorValue(descriptor.value)
65
+ Object.defineProperty(obj, prop, descriptor)
66
+ }
67
+ return obj
68
+ }
69
+
70
+ // Other Types of Objects
71
+ if (objectType(value) === '[object Date]') {
72
+ return new Date(value.getTime())
73
+ }
74
+
75
+ if (objectType(value) === '[object Map]') {
76
+ const map = new Map()
77
+ for (const entry of value) {
78
+ map.set(entry[0], cloneDescriptorValue(entry[1]))
79
+ }
80
+ return map
81
+ }
82
+
83
+ if (objectType(value) === '[object Set]') {
84
+ const set = new Set()
85
+ for (const entry of value.entries()) {
86
+ set.add(cloneDescriptorValue(entry[0]))
87
+ }
88
+ return set
89
+ }
90
+
91
+ // Types we don't need to clone or cannot clone.
92
+ // Examples:
93
+ // - Primitives don't need to clone
94
+ // - Functions cannot clone
95
+ return value
96
+ }
97
+
98
+ function objectType(value) {
99
+ return Object.prototype.toString.call(value)
100
+ }