@radio-garden/ditojs-utils 2.85.0-0.5067ad799

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 (104) hide show
  1. package/README.md +6 -0
  2. package/package.json +43 -0
  3. package/src/__snapshots__/index.test.js.snap +88 -0
  4. package/src/array/flatten.js +14 -0
  5. package/src/array/flattten.test.js +29 -0
  6. package/src/array/index.js +2 -0
  7. package/src/array/shuffle.js +11 -0
  8. package/src/array/shuffle.test.js +25 -0
  9. package/src/base/base.js +118 -0
  10. package/src/base/base.test.js +590 -0
  11. package/src/base/index.js +1 -0
  12. package/src/class/index.js +1 -0
  13. package/src/class/mixin.js +29 -0
  14. package/src/class/mixin.test.js +70 -0
  15. package/src/dataPath/getEntriesAtDataPath.js +67 -0
  16. package/src/dataPath/getEntriesAtDataPath.test.js +204 -0
  17. package/src/dataPath/getValueAtDataPath.js +45 -0
  18. package/src/dataPath/getValueAtDataPath.test.js +140 -0
  19. package/src/dataPath/index.js +6 -0
  20. package/src/dataPath/normalizeDataPath.js +27 -0
  21. package/src/dataPath/normalizeDataPath.test.js +36 -0
  22. package/src/dataPath/parseDataPath.js +16 -0
  23. package/src/dataPath/parseDataPath.test.js +67 -0
  24. package/src/dataPath/setDataPathEntries.js +8 -0
  25. package/src/dataPath/setDataPathEntries.test.js +36 -0
  26. package/src/dataPath/setValueAtDataPath.js +13 -0
  27. package/src/dataPath/setValueAtDataPath.test.js +34 -0
  28. package/src/function/deprecate.js +9 -0
  29. package/src/function/index.js +4 -0
  30. package/src/function/toAsync.js +9 -0
  31. package/src/function/toAsync.test.js +31 -0
  32. package/src/function/toCallback.js +11 -0
  33. package/src/function/toCallback.test.js +29 -0
  34. package/src/function/toPromiseCallback.js +9 -0
  35. package/src/function/toPromiseCallback.test.js +24 -0
  36. package/src/html/escapeHtml.js +11 -0
  37. package/src/html/escapeHtml.test.js +19 -0
  38. package/src/html/index.js +2 -0
  39. package/src/html/stripHtml.js +23 -0
  40. package/src/html/stripHtml.test.js +37 -0
  41. package/src/index.js +10 -0
  42. package/src/index.test.js +7 -0
  43. package/src/object/asCallback.js +9 -0
  44. package/src/object/clone.js +75 -0
  45. package/src/object/clone.test.js +131 -0
  46. package/src/object/equals.js +36 -0
  47. package/src/object/equals.test.js +269 -0
  48. package/src/object/groupBy.js +15 -0
  49. package/src/object/groupBy.test.js +70 -0
  50. package/src/object/index.js +8 -0
  51. package/src/object/mapKeys.js +7 -0
  52. package/src/object/mapKeys.test.js +16 -0
  53. package/src/object/mapValues.js +9 -0
  54. package/src/object/mapValues.test.js +38 -0
  55. package/src/object/mergeDeeply.js +47 -0
  56. package/src/object/mergeDeeply.test.js +152 -0
  57. package/src/object/pick.js +11 -0
  58. package/src/object/pick.test.js +23 -0
  59. package/src/object/pickBy.js +11 -0
  60. package/src/object/pickBy.test.js +48 -0
  61. package/src/promise/index.js +2 -0
  62. package/src/promise/mapConcurrently.js +33 -0
  63. package/src/promise/mapSequentially.js +9 -0
  64. package/src/string/camelize.js +14 -0
  65. package/src/string/camelize.test.js +37 -0
  66. package/src/string/capitalize.js +7 -0
  67. package/src/string/capitalize.test.js +33 -0
  68. package/src/string/decamelize.js +27 -0
  69. package/src/string/decamelize.test.js +83 -0
  70. package/src/string/deindent.js +69 -0
  71. package/src/string/deindent.test.js +181 -0
  72. package/src/string/escapeRegexp.js +3 -0
  73. package/src/string/format.js +109 -0
  74. package/src/string/format.test.js +196 -0
  75. package/src/string/formatDate.js +13 -0
  76. package/src/string/formatDate.test.js +28 -0
  77. package/src/string/getCommonPrefix.js +35 -0
  78. package/src/string/getCommonPrefix.test.js +23 -0
  79. package/src/string/index.js +15 -0
  80. package/src/string/isAbsoluteUrl.js +7 -0
  81. package/src/string/isAbsoluteUrl.test.js +15 -0
  82. package/src/string/isCreditCard.js +21 -0
  83. package/src/string/isCreditCard.test.js +50 -0
  84. package/src/string/isDomain.js +9 -0
  85. package/src/string/isDomain.test.js +15 -0
  86. package/src/string/isEmail.js +6 -0
  87. package/src/string/isEmail.test.js +37 -0
  88. package/src/string/isHostname.js +8 -0
  89. package/src/string/isHostname.test.js +12 -0
  90. package/src/string/isUrl.js +23 -0
  91. package/src/string/isUrl.test.js +1595 -0
  92. package/src/string/labelize.js +17 -0
  93. package/src/string/labelize.test.js +39 -0
  94. package/src/timer/debounce.js +34 -0
  95. package/src/timer/debounce.test.js +101 -0
  96. package/src/timer/debounceAsync.js +60 -0
  97. package/src/timer/debounceAsync.test.js +143 -0
  98. package/src/timer/index.js +2 -0
  99. package/types/index.d.ts +939 -0
  100. package/types/tests/base.test-d.ts +172 -0
  101. package/types/tests/datapath.test-d.ts +75 -0
  102. package/types/tests/function.test-d.ts +137 -0
  103. package/types/tests/object.test-d.ts +190 -0
  104. package/types/tests/promise.test-d.ts +66 -0
@@ -0,0 +1,48 @@
1
+ import { pickBy } from './pickBy.js'
2
+
3
+ describe('pickBy()', () => {
4
+ it('should work with a callback argument', () => {
5
+ const object = { a: 1, b: 2, c: 3, d: 4 }
6
+ const actual = pickBy(object, n => n === 1 || n === 3)
7
+ expect(actual).toStrictEqual({ a: 1, c: 3 })
8
+ })
9
+
10
+ it('should support string property accessor for truthy values', () => {
11
+ const users = {
12
+ user1: { name: 'Alice', active: true },
13
+ user2: { name: 'Bob', active: false },
14
+ user3: { name: 'Charlie', active: true }
15
+ }
16
+ const actual = pickBy(users, 'active')
17
+ expect(actual).toStrictEqual({
18
+ user1: { name: 'Alice', active: true },
19
+ user3: { name: 'Charlie', active: true }
20
+ })
21
+ })
22
+
23
+ it('should filter by numeric property with string accessor', () => {
24
+ const items = {
25
+ a: { score: 0 },
26
+ b: { score: 5 },
27
+ c: { score: 10 }
28
+ }
29
+ const actual = pickBy(items, 'score')
30
+ expect(actual).toStrictEqual({
31
+ b: { score: 5 },
32
+ c: { score: 10 }
33
+ })
34
+ })
35
+
36
+ it('should filter by string property with string accessor', () => {
37
+ const data = {
38
+ a: { label: '' },
39
+ b: { label: 'test' },
40
+ c: { label: 'value' }
41
+ }
42
+ const actual = pickBy(data, 'label')
43
+ expect(actual).toStrictEqual({
44
+ b: { label: 'test' },
45
+ c: { label: 'value' }
46
+ })
47
+ })
48
+ })
@@ -0,0 +1,2 @@
1
+ export * from './mapConcurrently.js'
2
+ export * from './mapSequentially.js'
@@ -0,0 +1,33 @@
1
+ export async function mapConcurrently(
2
+ input,
3
+ callback,
4
+ { concurrency = 0 } = {}
5
+ ) {
6
+ const array = await input
7
+ if (!concurrency) {
8
+ return Promise.all(array.map(callback))
9
+ }
10
+
11
+ const promises = []
12
+ const results = []
13
+ let index = 0
14
+
15
+ function next() {
16
+ if (index < array.length) {
17
+ const i = index++
18
+ return Promise.resolve(callback(array[i], i, array)).then(result => {
19
+ results[i] = result
20
+ return next()
21
+ })
22
+ }
23
+ return null
24
+ }
25
+
26
+ while (concurrency-- > 0) {
27
+ const promise = next()
28
+ if (!promise) break
29
+ promises.push(promise)
30
+ }
31
+
32
+ return Promise.all(promises).then(() => results)
33
+ }
@@ -0,0 +1,9 @@
1
+ export async function mapSequentially(input, callback) {
2
+ const array = await input
3
+ const results = []
4
+ let index = 0
5
+ for (const item of array) {
6
+ results.push(await callback(item, index++))
7
+ }
8
+ return results
9
+ }
@@ -0,0 +1,14 @@
1
+ export function camelize(str, pascalCase = false) {
2
+ return str
3
+ ? str
4
+ // Trim beginnings and ends
5
+ .replace(/^[-_\s]+|[-_\s]+$/g, '')
6
+ .replace(
7
+ /(^|[-_\s]+)(\w)([A-Z]*)([a-z]*)/g,
8
+ (all, sep, first, upperRest, lowerRest) =>
9
+ `${
10
+ pascalCase || sep ? first.toUpperCase() : first.toLowerCase()
11
+ }${upperRest.toLowerCase()}${lowerRest}`
12
+ )
13
+ : ''
14
+ }
@@ -0,0 +1,37 @@
1
+ import { camelize } from './camelize.js'
2
+
3
+ const strings = [
4
+ 'foo bar', 'Foo bar', 'foo Bar', 'Foo Bar', 'FOO BAR', 'FooBar', 'fooBar'
5
+ ]
6
+
7
+ describe('camelize()', () => {
8
+ describe.each([
9
+ [undefined, 'fooBar'],
10
+ [false, 'fooBar'],
11
+ [true, 'FooBar']
12
+ ])(
13
+ 'camelize(value, %o)',
14
+ (pascalCase, expected) => {
15
+ describe.each([
16
+ ...strings,
17
+ 'foo-bar',
18
+ 'foo_bar',
19
+ '--foo-bar--',
20
+ '__foo_bar__'
21
+ ])(
22
+ `camelize(%o, ${pascalCase})`,
23
+ value => {
24
+ it(`returns ${expected}`, () => {
25
+ expect(camelize(value, pascalCase)).toBe(expected)
26
+ })
27
+ }
28
+ )
29
+ }
30
+ )
31
+
32
+ it('should return an empty string if nothing can be processed', () => {
33
+ expect(camelize()).toBe('')
34
+ expect(camelize(null)).toBe('')
35
+ expect(camelize('')).toBe('')
36
+ })
37
+ })
@@ -0,0 +1,7 @@
1
+ export function capitalize(str) {
2
+ return str
3
+ ? str.replace(/(^|[^a-zA-Z\u00C0-\u017F'])([a-zA-Z\u00C0-\u017F])/g, chr =>
4
+ chr.toUpperCase()
5
+ )
6
+ : ''
7
+ }
@@ -0,0 +1,33 @@
1
+ import { capitalize } from './capitalize.js'
2
+
3
+ describe('capitalize()', () => {
4
+ it('should capitalize hyphenated text', () => {
5
+ expect(capitalize('some-hyphenated-text')).toBe('Some-Hyphenated-Text')
6
+ })
7
+
8
+ it('should capitalize underscored text', () => {
9
+ expect(capitalize('some_underscored_text')).toBe('Some_Underscored_Text')
10
+ })
11
+
12
+ it('should capitalize a camel-cased text', () => {
13
+ expect(capitalize('someCamelCasedText')).toBe('SomeCamelCasedText')
14
+ })
15
+
16
+ it('should capitalize normal text', () => {
17
+ expect(capitalize('some normal text')).toBe('Some Normal Text')
18
+ })
19
+
20
+ it('should consider numbers as word-breaks', () => {
21
+ expect(capitalize('one234five')).toBe('One234Five')
22
+ })
23
+
24
+ it('should support umlauts', () => {
25
+ expect(capitalize('mäuse häuschen')).toBe('Mäuse Häuschen')
26
+ })
27
+
28
+ it('should return an empty string if nothing can be processed', () => {
29
+ expect(capitalize()).toBe('')
30
+ expect(capitalize(null)).toBe('')
31
+ expect(capitalize('')).toBe('')
32
+ })
33
+ })
@@ -0,0 +1,27 @@
1
+ export function decamelize(str, sep = ' ') {
2
+ return str
3
+ ? str
4
+ .replace(
5
+ // TODO: Once JavaScript supports Unicode property escapes in regexps,
6
+ // switch to better parsing that matches non-ASCII uppercase letters:
7
+ // /([\p{Ll}\d])(\p{Lu})/gu
8
+ // See: https://caniuse.com/?search=Unicode%20property%20escape
9
+ /(^\s+)|(\s+$)|([a-z\d])([A-Z])|(?<=\S)\s+(?=\S)/g,
10
+ (all, leading, trailing, lower, upper) =>
11
+ lower
12
+ ? `${lower}${sep}${upper}`
13
+ : leading || trailing
14
+ ? ''
15
+ : sep
16
+ )
17
+ .toLowerCase()
18
+ : ''
19
+ }
20
+
21
+ export function hyphenate(str) {
22
+ return decamelize(str, '-')
23
+ }
24
+
25
+ export function underscore(str) {
26
+ return decamelize(str, '_')
27
+ }
@@ -0,0 +1,83 @@
1
+ import { decamelize, hyphenate, underscore } from './decamelize.js'
2
+
3
+ const strings = [
4
+ 'foo bar', 'Foo bar', 'foo Bar', 'Foo Bar', 'FOO BAR', 'FooBar', 'fooBar'
5
+ ]
6
+
7
+ describe('decamelize()', () => {
8
+ const expected = 'foo bar'
9
+ describe.each(strings)(
10
+ 'decamelize(%o)',
11
+ value => {
12
+ it(`returns ${expected}`, () => {
13
+ expect(decamelize(value)).toBe(expected)
14
+ })
15
+ }
16
+ )
17
+
18
+ it('should return an empty string if nothing can be processed', () => {
19
+ expect(decamelize()).toBe('')
20
+ expect(decamelize(null)).toBe('')
21
+ expect(decamelize('')).toBe('')
22
+ })
23
+ })
24
+
25
+ describe('hyphenate()', () => {
26
+ const expected = 'foo-bar'
27
+ describe.each(strings)(
28
+ 'hyphenate(%o)',
29
+ value => {
30
+ it(`returns ${expected}`, () => {
31
+ expect(hyphenate(value)).toBe(expected)
32
+ })
33
+ }
34
+ )
35
+
36
+ it('should hyphenate single space between words', () => {
37
+ expect(hyphenate('Foo Bar')).toBe('foo-bar')
38
+ })
39
+
40
+ it('should hyphenate multiple spaces', () => {
41
+ expect(hyphenate('Foo Bar')).toBe('foo-bar')
42
+ })
43
+
44
+ it('should trim leading whitespace', () => {
45
+ expect(hyphenate(' Foo Bar')).toBe('foo-bar')
46
+ })
47
+
48
+ it('should trim trailing whitespace', () => {
49
+ expect(hyphenate('Foo Bar ')).toBe('foo-bar')
50
+ })
51
+
52
+ it('should hyphenate special characters correctly', () => {
53
+ expect(hyphenate('Foo & Bar')).toBe('foo-&-bar')
54
+ })
55
+ })
56
+
57
+ describe('underscore()', () => {
58
+ const expected = 'foo_bar'
59
+ describe.each(strings)(
60
+ 'underscore(%o)',
61
+ value => {
62
+ it(`returns ${expected}`, () => {
63
+ expect(underscore(value)).toBe(expected)
64
+ })
65
+ }
66
+ )
67
+
68
+ it('should underscore multiple spaces', () => {
69
+ expect(underscore('Foo Bar')).toBe('foo_bar')
70
+ })
71
+
72
+ it('should trim leading whitespace', () => {
73
+ expect(underscore(' Foo Bar')).toBe('foo_bar')
74
+ })
75
+
76
+ it('should trim trailing whitespace', () => {
77
+ expect(underscore('Foo Bar ')).toBe('foo_bar')
78
+ })
79
+
80
+ it('should underscore special characters correctly', () => {
81
+ expect(underscore('Foo & Bar')).toBe('foo_&_bar')
82
+ })
83
+ })
@@ -0,0 +1,69 @@
1
+ import { asArray } from '../base/index.js'
2
+
3
+ // ES6 string tag that strips indentation from multi-line strings
4
+ // Based on, and further improved from https://github.com/dmnd/dedent
5
+ export function deindent(strings, ...values) {
6
+ // Small helper to determine the actual line-break used in the strings, based
7
+ // on the line-breaks encountered.
8
+ const getLineBreak = line => line.match(/^(\n|\r\n|\r)/)?.[1] || '\n'
9
+
10
+ strings = asArray(strings)
11
+ // First, perform interpolation
12
+ let parts = []
13
+ for (let i = 0; i < strings.length; i++) {
14
+ parts.push(
15
+ strings[i]
16
+ // Join lines when there is a line-break suppressed by a preceding '\\'
17
+ .replace(/\\\n\s*/g, '')
18
+ // Handle escaped back-ticks
19
+ .replace(/\\`/g, '`')
20
+ )
21
+ if (i < values.length) {
22
+ // See if the value itself contains multiple lines, and if so, indent
23
+ // each of them by the whitespace that precedes it except the first.
24
+ const value = values[i].toString()
25
+ const lines = value.split(/\n|\r\n|\r/)
26
+ if (lines.length > 1) {
27
+ // Determine the indent by finding the immediately preceding white-space
28
+ // up to the previous line-break or the beginning of the string.
29
+ // (?:^[\n\r]*|[\n\r]) # Start at either the beginning or the previous
30
+ // # line break.
31
+ // (?:[\n\r]*) # The line break
32
+ // (\s+) # Collect the indenting white-space...
33
+ // (?:[^\n\r]*)$ # ...up to the end or the next word, but in last
34
+ // # line, by making sure no line breaks follow
35
+ // # until the end.
36
+ const str = parts.join('')
37
+ const match = str.match(
38
+ /((?:^[\n\r]*|[\n\r])(?:[\n\r]*))(\s+)(?:[^\n\r]*)$/
39
+ )
40
+ const [, lineBreaks = '', indent = ''] = match || []
41
+ parts = [str]
42
+ parts.push(lines.join(`${getLineBreak(lineBreaks)}${indent}`))
43
+ } else {
44
+ parts.push(value)
45
+ }
46
+ }
47
+ }
48
+ const str = parts.join('')
49
+ const lines = str.split(/\n|\r\n|\r/)
50
+ // Remove the first line-break at the beginning of the result, to allow this:
51
+ // const value = `
52
+ // content...
53
+ // ` // line break at the end remains
54
+ let offset = 0
55
+ if (!lines[0]) {
56
+ lines.shift()
57
+ offset++
58
+ }
59
+ let indent = Infinity
60
+ for (const line of lines) {
61
+ const match = line.match(/^(\s*)\S+/)
62
+ if (match) {
63
+ indent = Math.min(indent, match[1].length)
64
+ }
65
+ }
66
+ const index = lines[0].length + offset
67
+ const lineBreak = getLineBreak(str.slice(index, index + 2))
68
+ return lines.map(line => line.slice(indent)).join(lineBreak)
69
+ }
@@ -0,0 +1,181 @@
1
+ import { deindent } from './deindent.js'
2
+
3
+ describe('deindent()', () => {
4
+ it('should deindent indented multi-line strings', () => {
5
+ expect(
6
+ deindent`
7
+ some
8
+ indented
9
+ text`
10
+ ).toBe(
11
+ `some
12
+ indented
13
+ text`
14
+ )
15
+ })
16
+
17
+ it('should preserve the last line-break', () => {
18
+ expect(
19
+ deindent`
20
+ some
21
+ indented
22
+ text
23
+ `
24
+ ).toBe(
25
+ `some
26
+ indented
27
+ text
28
+ `
29
+ )
30
+ })
31
+
32
+ it('should only swallow the first line-break, not those following', () => {
33
+ expect(
34
+ deindent`
35
+
36
+
37
+ some
38
+ indented
39
+ text
40
+ `
41
+ ).toBe(
42
+ `
43
+
44
+ some
45
+ indented
46
+ text
47
+ `
48
+ )
49
+ })
50
+
51
+ it('should not swallow the empty line-breaks at the end', () => {
52
+ expect(
53
+ deindent`
54
+ some
55
+ indented
56
+ text
57
+
58
+
59
+ `
60
+ ).toBe(
61
+ `some
62
+ indented
63
+ text
64
+
65
+
66
+ `
67
+ )
68
+ })
69
+
70
+ it('should handle multi-line strings without first or last line', () => {
71
+ expect(
72
+ deindent`some
73
+ indented
74
+ text
75
+ some
76
+ indented
77
+ text`
78
+ ).toBe(
79
+ `some
80
+ indented
81
+ text
82
+ some
83
+ indented
84
+ text`
85
+ )
86
+ })
87
+
88
+ it('should handle multi-line strings without first or last line', () => {
89
+ expect(
90
+ deindent` some
91
+ indented
92
+ text`
93
+ ).toBe(
94
+ `some
95
+ indented
96
+ text`
97
+ )
98
+ })
99
+
100
+ it('should correctly indent nested single-line string values', () => {
101
+ const singleLineText = 'single-line text'
102
+ expect(
103
+ deindent`
104
+ some
105
+ indented
106
+ ${singleLineText}
107
+ `
108
+ ).toBe(
109
+ `some
110
+ indented
111
+ single-line text
112
+ `
113
+ )
114
+ })
115
+
116
+ it('should correctly indent unnested multi-line string values', () => {
117
+ const multiLineText = 'multi-\nline\ntext'
118
+ expect(
119
+ deindent`
120
+ some
121
+ indented
122
+ ${multiLineText}
123
+ `
124
+ ).toBe(
125
+ `some
126
+ indented
127
+ multi-
128
+ line
129
+ text
130
+ `
131
+ )
132
+ })
133
+
134
+ it('should correctly indent nested multi-line string values', () => {
135
+ const multiLineText = 'multi-\nline\ntext'
136
+ expect(
137
+ deindent`
138
+ some
139
+ indented
140
+ ${multiLineText}
141
+ `
142
+ ).toBe(
143
+ `some
144
+ indented
145
+ multi-
146
+ line
147
+ text
148
+ `
149
+ )
150
+ })
151
+
152
+ it('should maintain the indent of prefixed multi-line string values', () => {
153
+ const multiLineText = 'multi-\nline\ntext'
154
+ expect(
155
+ deindent`
156
+ some
157
+ indented
158
+ content: ${multiLineText}
159
+ `
160
+ ).toBe(
161
+ `some
162
+ indented
163
+ content: multi-
164
+ line
165
+ text
166
+ `
167
+ )
168
+ })
169
+
170
+ it('should not deindent if the first line starts with text', () => {
171
+ expect(
172
+ deindent`some
173
+ indented
174
+ text`
175
+ ).toBe(
176
+ `some
177
+ indented
178
+ text`
179
+ )
180
+ })
181
+ })
@@ -0,0 +1,3 @@
1
+ export function escapeRegexp(string) {
2
+ return string.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')
3
+ }
@@ -0,0 +1,109 @@
1
+ import { isNumber, isDate, isObject } from '../base/index.js'
2
+
3
+ export const defaultFormats = {
4
+ number: {
5
+ style: 'decimal'
6
+ },
7
+ date: {
8
+ day: 'numeric',
9
+ month: 'long',
10
+ year: 'numeric'
11
+ },
12
+ time: {
13
+ hour: '2-digit',
14
+ minute: '2-digit',
15
+ second: '2-digit'
16
+ }
17
+ }
18
+
19
+ export function format(value, {
20
+ locale = 'en-US',
21
+ number,
22
+ date,
23
+ time,
24
+ defaults = defaultFormats
25
+ } = {}) {
26
+ const formats = { number, date, time }
27
+ const defaultValue = number || date || time ? undefined : true
28
+ const getOptions = name => {
29
+ const defaultOption = defaults[name]
30
+ const option = formats[name] ?? defaultValue
31
+ return option === true
32
+ ? defaultOption
33
+ : isObject(option)
34
+ ? Object.entries({
35
+ ...defaultOption,
36
+ ...option
37
+ }).reduce((opt, [key, value]) => {
38
+ if (value !== false) {
39
+ opt[key] = value
40
+ }
41
+ return opt
42
+ }, {})
43
+ : {}
44
+ }
45
+
46
+ if (value != null) {
47
+ if (number) {
48
+ value = isNumber(value) ? value : parseFloat(value)
49
+ } else if (date || time) {
50
+ value = isDate(value) ? value : new Date(value)
51
+ }
52
+ }
53
+
54
+ let options
55
+ if (isNumber(value)) {
56
+ if (number === false) {
57
+ return ''
58
+ } else {
59
+ options = getOptions('number')
60
+ if (options.format) {
61
+ // Support custom post-formatting of number formats,
62
+ // e.g. to replace decimals and such:
63
+ const parts = new Intl.NumberFormat(locale, options)
64
+ .formatToParts(value)
65
+ return parts
66
+ .map(
67
+ ({ type, value }) => options.format(value, type, options) ?? value
68
+ )
69
+ .join('')
70
+ }
71
+ }
72
+ } else if (isDate(value)) {
73
+ if (date === false && time === false) {
74
+ return ''
75
+ } else {
76
+ // Flatten the separate date / time options down to one
77
+ const opts = {
78
+ date: getOptions('date'),
79
+ time: getOptions('time')
80
+ }
81
+ options = {
82
+ ...opts.date,
83
+ ...opts.time
84
+ }
85
+ if (options.format) {
86
+ // Support custom post-formatting of both time and date formats,
87
+ // e.g. to replace separators and such:
88
+ const parts = new Intl.DateTimeFormat(locale, options)
89
+ .formatToParts(value)
90
+ let modeOpts = null
91
+ return parts
92
+ .map(({ type, value }) => {
93
+ if (type !== 'literal') {
94
+ // Switch mode between date/time to pick the right format method:
95
+ const mode = ['weekday', 'day', 'month', 'year'].includes(type)
96
+ ? 'date'
97
+ : 'time'
98
+ modeOpts = opts[mode]
99
+ }
100
+ return modeOpts?.format?.(value, type, modeOpts) ?? value
101
+ })
102
+ .join('')
103
+ }
104
+ }
105
+ }
106
+ return value != null
107
+ ? value.toLocaleString(locale, options)
108
+ : value
109
+ }