@carto/ps-utils 2.0.1 → 2.1.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 (46) hide show
  1. package/dist/index.cjs +1 -1
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.js +436 -74
  4. package/dist/index.js.map +1 -1
  5. package/dist/types/clipboard/copy.d.ts +4 -6
  6. package/dist/types/colors/carto-palettes/carto-palettes.d.ts +47 -0
  7. package/dist/types/colors/hex-to-rgba/hex-to-rgba.d.ts +7 -16
  8. package/dist/types/colors/palette-to-hex/palette-to-hex.d.ts +19 -0
  9. package/dist/types/colors/palette-to-hex/palette-to-hex.test.d.ts +1 -0
  10. package/dist/types/colors/rgba-to-hex/rgba-to-hex.d.ts +7 -19
  11. package/dist/types/debounce/debounce.d.ts +12 -2
  12. package/dist/types/formatters/constants.d.ts +23 -1
  13. package/dist/types/formatters/date/format-date.d.ts +13 -8
  14. package/dist/types/formatters/number/format-number.d.ts +17 -20
  15. package/dist/types/index.d.ts +3 -0
  16. package/dist/types/strings/match-text/match-text.d.ts +10 -6
  17. package/dist/types/strings/normalize/normalize.d.ts +6 -9
  18. package/dist/types/strings/routes/replace-route.d.ts +10 -14
  19. package/package.json +7 -3
  20. package/src/clipboard/copy.test.ts +49 -0
  21. package/src/clipboard/copy.ts +18 -0
  22. package/src/colors/carto-palettes/carto-palettes.ts +355 -0
  23. package/src/colors/hex-to-rgba/hex-to-rgba.test.ts +82 -0
  24. package/src/colors/hex-to-rgba/hex-to-rgba.ts +67 -0
  25. package/src/colors/palette-to-hex/palette-to-hex.test.ts +60 -0
  26. package/src/colors/palette-to-hex/palette-to-hex.ts +45 -0
  27. package/src/colors/rgba-to-hex/rgba-to-hex.test.ts +34 -0
  28. package/src/colors/rgba-to-hex/rgba-to-hex.ts +42 -0
  29. package/src/colors/rgba-to-hex/types.ts +9 -0
  30. package/src/debounce/debounce.test.ts +21 -0
  31. package/src/debounce/debounce.ts +29 -0
  32. package/src/formatters/constants.ts +49 -0
  33. package/src/formatters/date/format-date.test.ts +36 -0
  34. package/src/formatters/date/format-date.ts +34 -0
  35. package/src/formatters/number/format-number.test.ts +49 -0
  36. package/src/formatters/number/format-number.ts +68 -0
  37. package/src/index.ts +18 -0
  38. package/src/strings/match-text/match-text.test.ts +38 -0
  39. package/src/strings/match-text/match-text.ts +36 -0
  40. package/src/strings/match-text/types.ts +15 -0
  41. package/src/strings/normalize/normalize.test.ts +12 -0
  42. package/src/strings/normalize/normalize.ts +22 -0
  43. package/src/strings/normalize/types.ts +15 -0
  44. package/src/strings/routes/replace-route.test.ts +64 -0
  45. package/src/strings/routes/replace-route.ts +39 -0
  46. package/src/strings/routes/types.ts +2 -0
@@ -0,0 +1,21 @@
1
+ import { describe, expect, it, vi } from 'vitest'
2
+ import { debounce } from './debounce'
3
+
4
+ describe('debounce', () => {
5
+ it('should debounce a function', () => {
6
+ vi.useFakeTimers()
7
+
8
+ const mockFn = vi.fn()
9
+ const debouncedFn = debounce(mockFn, 1000)
10
+
11
+ debouncedFn()
12
+ expect(mockFn).not.toBeCalled()
13
+
14
+ vi.advanceTimersByTime(500)
15
+ debouncedFn()
16
+ expect(mockFn).not.toBeCalled()
17
+
18
+ vi.advanceTimersByTime(1000)
19
+ expect(mockFn).toHaveBeenCalled()
20
+ })
21
+ })
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Creates a debounced version of a function that delays execution until after the specified wait time has elapsed since the last invocation.
3
+ *
4
+ * @param func - The function to debounce.
5
+ * @param waitFor - The delay in milliseconds to wait before executing.
6
+ * @returns A debounced version of the provided function.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * const debouncedSearch = debounce((query: string) => {
11
+ * fetchResults(query)
12
+ * }, 300)
13
+ *
14
+ * debouncedSearch('hello') // Only executes after 300ms of inactivity
15
+ * ```
16
+ */
17
+ export function debounce<F extends (...args: unknown[]) => unknown>(
18
+ func: F,
19
+ waitFor: number,
20
+ ): (...args: Parameters<F>) => ReturnType<F> {
21
+ let timeout: ReturnType<typeof setTimeout>
22
+
23
+ const debounced = (...args: Parameters<F>) => {
24
+ clearTimeout(timeout)
25
+ timeout = setTimeout(() => func(...args), waitFor)
26
+ }
27
+
28
+ return debounced as (...args: Parameters<F>) => ReturnType<F>
29
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Default configuration options for the formatter utilities (`formatNumber`, `formatCurrency`, `formatDate`).
3
+ *
4
+ * @remarks
5
+ * Contains preset `Intl.NumberFormatOptions` and `Intl.DateTimeFormatOptions` for three keys:
6
+ * - `NUMBER` - Compact decimal notation with 1 fraction digit.
7
+ * - `CURRENCY` - Compact USD currency with 2 fraction digits.
8
+ * - `DATE` - Numeric year, 2-digit month and day.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * import { DEFAULT_FORMATTERS_CONFIG } from '@carto/ps-utils'
13
+ *
14
+ * // Override defaults for a custom number format
15
+ * const options = { ...DEFAULT_FORMATTERS_CONFIG.NUMBER, maximumFractionDigits: 3 }
16
+ * ```
17
+ */
18
+ export type DefaultFormatterKey = 'NUMBER' | 'CURRENCY' | 'DATE'
19
+
20
+ export const DEFAULT_FORMATTERS_CONFIG: Record<
21
+ string,
22
+ Intl.NumberFormatOptions | Intl.DateTimeFormatOptions
23
+ > & {
24
+ NUMBER: Intl.NumberFormatOptions
25
+ CURRENCY: Intl.NumberFormatOptions
26
+ DATE: Intl.DateTimeFormatOptions
27
+ } = {
28
+ NUMBER: {
29
+ style: 'decimal',
30
+ maximumFractionDigits: 1,
31
+ minimumFractionDigits: 0,
32
+ notation: 'compact',
33
+ compactDisplay: 'short',
34
+ },
35
+
36
+ CURRENCY: {
37
+ style: 'currency',
38
+ currency: 'USD',
39
+ maximumFractionDigits: 2,
40
+ minimumFractionDigits: 2,
41
+ notation: 'compact',
42
+ compactDisplay: 'short',
43
+ },
44
+ DATE: {
45
+ year: 'numeric',
46
+ month: '2-digit',
47
+ day: '2-digit',
48
+ },
49
+ }
@@ -0,0 +1,36 @@
1
+ import { describe, expect, test } from 'vitest'
2
+ import { formatDate } from './format-date'
3
+
4
+ describe('formatDate', () => {
5
+ const date = new Date(1668771515109)
6
+
7
+ test('should format in the specified locale', () => {
8
+ const result = formatDate(date, 'es-ES')
9
+ expect(result).toBe('18/11/2022')
10
+ })
11
+
12
+ test('should format with the specified style', () => {
13
+ const result = formatDate(date, 'es-ES', {
14
+ month: 'long',
15
+ })
16
+ expect(result).toBe('18 de noviembre de 2022')
17
+ })
18
+
19
+ test('should format using specific options', () => {
20
+ const result = formatDate(date, 'en-GB', {
21
+ hour: 'numeric',
22
+ hourCycle: 'h12',
23
+ dayPeriod: 'short',
24
+ timeZone: 'UTC',
25
+ })
26
+ expect(result).toBe('18/11/2022, 11 in the morning')
27
+ })
28
+ test('should format using explicit Y/M/D options', () => {
29
+ const result = formatDate(date, 'es-ES', {
30
+ year: 'numeric',
31
+ month: '2-digit',
32
+ day: '2-digit',
33
+ })
34
+ expect(result).toBe('18/11/2022')
35
+ })
36
+ })
@@ -0,0 +1,34 @@
1
+ import { DEFAULT_FORMATTERS_CONFIG } from '../constants'
2
+
3
+ /**
4
+ * Formats a date or timestamp according to locale-specific conventions using the `Intl.DateTimeFormat` API.
5
+ *
6
+ * @param value - The date to format (Date object or timestamp in milliseconds).
7
+ * @param locales - Locale identifier(s) for formatting (e.g., `'en-US'`, `'fr-FR'`).
8
+ * @param options - Date/time formatting options to override the defaults (numeric year, 2-digit month and day).
9
+ * When `dateStyle` or `timeStyle` is provided the defaults are not applied, as they are incompatible with
10
+ * individual date/time component options.
11
+ * @returns The formatted date string.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * formatDate(new Date('2024-03-15'), 'en-US') // "03/15/2024"
16
+ * formatDate(new Date('2024-03-15'), 'en-US', { month: 'long' }) // "March 15, 2024"
17
+ * ```
18
+ */
19
+ export function formatDate(
20
+ value: number | Date,
21
+ locales: string | string[],
22
+ options: Intl.DateTimeFormatOptions = {},
23
+ ): string {
24
+ const hasStyle = options.dateStyle !== undefined || options.timeStyle !== undefined
25
+
26
+ const formatterOptions: Intl.DateTimeFormatOptions = hasStyle
27
+ ? options
28
+ : {
29
+ ...DEFAULT_FORMATTERS_CONFIG.DATE,
30
+ ...options,
31
+ }
32
+
33
+ return Intl.DateTimeFormat(locales, formatterOptions).format(value)
34
+ }
@@ -0,0 +1,49 @@
1
+ import { describe, expect, test } from 'vitest'
2
+ import { formatCurrency, formatNumber } from './format-number'
3
+
4
+ describe('utils :: formatters :: number', () => {
5
+ describe('formatNumber', () => {
6
+ test('should use specified locale', () => {
7
+ const result = formatNumber(3001, 'es-ES')
8
+ expect(result).toBe('3 mil')
9
+ })
10
+
11
+ test('should use specified style', () => {
12
+ const result = formatNumber(45, 'es-ES', {
13
+ style: 'unit',
14
+ unit: 'liter',
15
+ unitDisplay: 'long',
16
+ })
17
+ expect(result).toBe('45 litros')
18
+ })
19
+
20
+ test('should use specified style', () => {
21
+ const result = formatNumber(45123, 'en-US')
22
+ expect(result).toBe('45.1K')
23
+ })
24
+ })
25
+ describe('formatCurrency', () => {
26
+ test('should return a currency formatted string', () => {
27
+ const result = formatCurrency(3001, 'en-US')
28
+ expect(result).toBe('$3.00K')
29
+ })
30
+ test('should return a currency formatted string with overrided options', () => {
31
+ const result = formatCurrency(3001, 'en-US', {
32
+ style: 'currency',
33
+ currency: 'USD',
34
+ minimumFractionDigits: 0,
35
+ maximumFractionDigits: 0,
36
+ })
37
+ expect(result).toBe('$3K')
38
+ })
39
+ test('should return a currency formatted string with negative number', () => {
40
+ const result = formatCurrency(-1, 'en-US', {
41
+ style: 'currency',
42
+ currency: 'USD',
43
+ minimumFractionDigits: 0,
44
+ maximumFractionDigits: 0,
45
+ })
46
+ expect(result).toBe('-$1')
47
+ })
48
+ })
49
+ })
@@ -0,0 +1,68 @@
1
+ import { DEFAULT_FORMATTERS_CONFIG } from '../constants'
2
+
3
+ /**
4
+ * Formats a number using `Intl.NumberFormat` with the given locale and options.
5
+ *
6
+ * @param value - The number to format.
7
+ * @param locale - The locale identifier (e.g., `'en-US'`, `'es-ES'`).
8
+ * @param options - `Intl.NumberFormatOptions` for customization (style, unit, etc.).
9
+ * @returns The formatted number string.
10
+ */
11
+ function format(
12
+ value: number,
13
+ locale: string,
14
+ options?: Intl.NumberFormatOptions,
15
+ ): string {
16
+ return Intl.NumberFormat(locale, options).format(value)
17
+ }
18
+
19
+ /**
20
+ * Formats a number with locale-aware separators, compact notation, and customizable precision.
21
+ *
22
+ * @param value - The number to format.
23
+ * @param locale - The locale identifier (e.g., `'en-US'`, `'es-ES'`).
24
+ * @param options - `Intl.NumberFormatOptions` to override the defaults (compact decimal with 1 fraction digit).
25
+ * @returns The formatted number string.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * formatNumber(123456, 'en-US') // "123.5K"
30
+ * formatNumber(1234567, 'en-US') // "1.2M"
31
+ * formatNumber(123456, 'es-ES') // "123 mil"
32
+ * ```
33
+ */
34
+ export function formatNumber(
35
+ value: number,
36
+ locale: string,
37
+ options: Intl.NumberFormatOptions = {},
38
+ ): string {
39
+ return format(value, locale, {
40
+ ...DEFAULT_FORMATTERS_CONFIG.NUMBER,
41
+ ...options,
42
+ })
43
+ }
44
+
45
+ /**
46
+ * Formats a number as a currency value with proper symbols and regional conventions.
47
+ *
48
+ * @param value - The monetary value to format.
49
+ * @param locale - The locale identifier (e.g., `'en-US'`, `'fr-FR'`).
50
+ * @param options - `Intl.NumberFormatOptions` to override the defaults (compact USD with 2 fraction digits).
51
+ * @returns The formatted currency string.
52
+ *
53
+ * @example
54
+ * ```ts
55
+ * formatCurrency(123456, 'en-US') // "$123.46K"
56
+ * formatCurrency(123456, 'fr-FR', { currency: 'EUR' }) // "123,46 k €"
57
+ * ```
58
+ */
59
+ export function formatCurrency(
60
+ value: number,
61
+ locale: string,
62
+ options: Intl.NumberFormatOptions = {},
63
+ ): string {
64
+ return format(value, locale, {
65
+ ...DEFAULT_FORMATTERS_CONFIG.CURRENCY,
66
+ ...options,
67
+ })
68
+ }
package/src/index.ts ADDED
@@ -0,0 +1,18 @@
1
+ export { copy } from './clipboard/copy'
2
+
3
+ export { CARTO_PALETTES } from './colors/carto-palettes/carto-palettes'
4
+ export type { PaletteName } from './colors/carto-palettes/carto-palettes'
5
+ export { hexToRgba } from './colors/hex-to-rgba/hex-to-rgba'
6
+ export { paletteToHex } from './colors/palette-to-hex/palette-to-hex'
7
+ export { rgbaToHex } from './colors/rgba-to-hex/rgba-to-hex'
8
+
9
+ export { debounce } from './debounce/debounce'
10
+
11
+ export { DEFAULT_FORMATTERS_CONFIG } from './formatters/constants'
12
+ export { formatDate } from './formatters/date/format-date'
13
+ export { formatCurrency, formatNumber } from './formatters/number/format-number'
14
+
15
+ export { matchText } from './strings/match-text/match-text'
16
+ export { normalize } from './strings/normalize/normalize'
17
+ export type { Route, Values } from './strings/routes/types'
18
+ export { replaceRoute } from './strings/routes/replace-route'
@@ -0,0 +1,38 @@
1
+ import { describe, expect, test } from 'vitest'
2
+ import { matchText } from './match-text'
3
+
4
+ const name = 'John'
5
+ const data = [{ name: name }, { name: 'Jane' }]
6
+
7
+ describe.each([
8
+ { text: 'joh', expected: [data[0]] },
9
+ { text: 'jane', expected: [data[1]] },
10
+ { text: 'jone', expected: [] },
11
+ ])('matchText: with data as array and matchFn', ({ text, expected }) => {
12
+ test(`returns ${JSON.stringify(expected)}`, () => {
13
+ expect(
14
+ matchText(text, data, {
15
+ matchFn: (data) => data.name,
16
+ }),
17
+ ).toEqual(expected)
18
+ })
19
+ })
20
+
21
+ describe.each([
22
+ { text: 'joh', expected: data[0]!.name },
23
+ { text: 'joh', expected: name },
24
+ { text: 'jah', expected: null },
25
+ ])('matchText: with simple data', ({ text, expected }) => {
26
+ test(`returns ${expected}`, () => {
27
+ expect(matchText(text, name)).toEqual(expected)
28
+ })
29
+ })
30
+
31
+ describe.each([{ text: 'joh', expected: [data[0]!.name] }])(
32
+ 'matchText with data as array and without matchFn',
33
+ ({ text, expected }) => {
34
+ test(`returns ${JSON.stringify(expected)}`, () => {
35
+ expect(matchText(text, [data[0]!.name])).toEqual(expected)
36
+ })
37
+ },
38
+ )
@@ -0,0 +1,36 @@
1
+ import type { MatchTextOptions } from './types'
2
+ import { normalize } from '../normalize/normalize'
3
+
4
+ /**
5
+ * Performs intelligent text matching with optional Unicode normalization and custom matching functions.
6
+ *
7
+ * @param value - The text or RegExp pattern to match against.
8
+ * @param data - The data to search (array of items or a single string).
9
+ * @param options - Optional configuration for custom match function and normalization.
10
+ * @returns Matched items from an array, the original string if it matches, or `null`.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * matchText('york', ['New York', 'Los Angeles']) // ['New York']
15
+ * matchText('barce', locations, { matchFn: (item) => item.name })
16
+ * ```
17
+ */
18
+ export function matchText<T>(
19
+ value: string | RegExp,
20
+ data: T[] | string,
21
+ { matchFn, normalizeOptions }: MatchTextOptions<T> = {},
22
+ ): T[] | string | null {
23
+ let regex = value
24
+ if (typeof value === 'string') {
25
+ regex = new RegExp(normalize(value.toLowerCase(), normalizeOptions), 'gi')
26
+ }
27
+
28
+ if (!Array.isArray(data)) {
29
+ return normalize(data).match(regex) ? data : null
30
+ }
31
+
32
+ return data.filter((match) => {
33
+ const newData = (matchFn ? matchFn(match) : match) as string
34
+ return normalize(newData).match(regex)
35
+ })
36
+ }
@@ -0,0 +1,15 @@
1
+ import type { NormalizeOptions } from '../normalize/types'
2
+
3
+ /**
4
+ * `MatchTextOptions` is an object with an optional `matchFn` property that is a function
5
+ * that takes a generic type `T` and returns a string, and an optional `normalizeOptions`
6
+ * property that is an object with the normalize options.
7
+ * @property matchFn - A function that takes in the data and returns a string to match
8
+ * against.
9
+ * @property {NormalizeOptions} normalizeOptions - This is an object that contains the
10
+ * options for the normalize function.
11
+ */
12
+ export interface MatchTextOptions<T> {
13
+ matchFn?: (data: T) => string
14
+ normalizeOptions?: NormalizeOptions
15
+ }
@@ -0,0 +1,12 @@
1
+ import { describe, expect, test } from 'vitest'
2
+ import { normalize } from './normalize'
3
+
4
+ describe.each([
5
+ { text: 'test', expected: 'test' },
6
+ { text: 'accént', expected: 'accent' },
7
+ { text: 'áccént', expected: 'accent' },
8
+ ])('normalize(%s, %i)', ({ text, expected }) => {
9
+ test(`returns ${expected}`, () => {
10
+ expect(normalize(text)).toEqual(expected)
11
+ })
12
+ })
@@ -0,0 +1,22 @@
1
+ import type { NormalizeOptions } from './types'
2
+
3
+ /**
4
+ * Normalizes a Unicode string by removing diacritics and special characters for consistent text comparison.
5
+ *
6
+ * @param data - The string to normalize.
7
+ * @param options - Normalization options including Unicode form and replacement pattern.
8
+ * @returns The normalized string with diacritics removed.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * normalize('café') // 'cafe'
13
+ * normalize('São Paulo') // 'Sao Paulo'
14
+ * ```
15
+ */
16
+ export function normalize(
17
+ data: string,
18
+ { format = 'NFD', replaceUnicode = '[\u0300-\u036f]' }: NormalizeOptions = {},
19
+ ): string {
20
+ const unicode = new RegExp(replaceUnicode, 'g')
21
+ return data.normalize(format).replace(unicode, '')
22
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * `NormalizeOptions` is an object with optional properties `format` and `replaceUnicode`.
3
+ *
4
+ * The `format` property is a string that can be one of four values: `'NFD'`, `'NFKD'`,
5
+ * `'NFC'`, or `'NFKC'`.
6
+ *
7
+ * The `replaceUnicode` property is a string.
8
+ * @property {'NFD' | 'NFKD' | 'NFC' | 'NFKC'} format - The normalization form to use.
9
+ * @property {string} replaceUnicode - The unicode character to replace the unicode
10
+ * characters with. By default is `[\u0300-\u036f]`.
11
+ */
12
+ export interface NormalizeOptions {
13
+ format?: 'NFD' | 'NFKD' | 'NFC' | 'NFKC'
14
+ replaceUnicode?: string
15
+ }
@@ -0,0 +1,64 @@
1
+ import { describe, expect, test } from 'vitest'
2
+ import { replaceRoute } from './replace-route'
3
+
4
+ const route = '/detail/:id/:year'
5
+
6
+ describe('replaceRoute: should replace route', () => {
7
+ test('should replace route with array', () => {
8
+ const result = replaceRoute(route, ['1', '2021'])
9
+ expect(result).toBe('/detail/1/2021')
10
+ })
11
+
12
+ test('should replace route with object', () => {
13
+ const result = replaceRoute(route, { id: '1', year: '2021' })
14
+ expect(result).toBe('/detail/1/2021')
15
+ })
16
+
17
+ test('should handle missing values in array', () => {
18
+ const result = replaceRoute(route, ['1'])
19
+ expect(result).toBe('/detail/1/')
20
+ })
21
+
22
+ test('should handle missing values in object', () => {
23
+ const result = replaceRoute(route, { id: '1' })
24
+ expect(result).toBe('/detail/1/')
25
+ })
26
+
27
+ test('should handle route with no templates', () => {
28
+ const result = replaceRoute('/static/path', ['1', '2'])
29
+ expect(result).toBe('/static/path')
30
+ })
31
+
32
+ test('should handle empty values', () => {
33
+ const result = replaceRoute(route, [])
34
+ expect(result).toBe('/detail//')
35
+ })
36
+
37
+ test('should handle undefined values', () => {
38
+ const result = replaceRoute(route)
39
+ expect(result).toBe('/detail//')
40
+ })
41
+
42
+ test('should handle numeric values with array', () => {
43
+ const result = replaceRoute(route, [123, 2024])
44
+ expect(result).toBe('/detail/123/2024')
45
+ })
46
+
47
+ test('should handle numeric values with object', () => {
48
+ const result = replaceRoute(route, { id: 456, year: 2025 })
49
+ expect(result).toBe('/detail/456/2025')
50
+ })
51
+
52
+ test('should handle numeric values converting to string', () => {
53
+ const result = replaceRoute('/user/:id', [999])
54
+ expect(result).toBe('/user/999')
55
+ })
56
+
57
+ test('should handle missing object properties', () => {
58
+ const result = replaceRoute('/user/:id/:name', { id: '1' } as Record<
59
+ string,
60
+ string | number
61
+ >)
62
+ expect(result).toBe('/user/1/')
63
+ })
64
+ })
@@ -0,0 +1,39 @@
1
+ import type { Route, Values } from './types'
2
+
3
+ const regex = /:+[a-z]+/gi
4
+
5
+ /**
6
+ * Dynamically replaces route parameters (`:paramName`) with values from an object or array.
7
+ *
8
+ * @param data - The route template string containing `:paramName` placeholders.
9
+ * @param values - An object or array of values to substitute into the route.
10
+ * @returns The route string with parameters replaced.
11
+ *
12
+ * @remarks
13
+ * Missing values are replaced with an empty string.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * replaceRoute('/user/:id', { id: '123' }) // '/user/123'
18
+ * replaceRoute('/tiles/:z/:x/:y', { z: 10, x: 512, y: 256 }) // '/tiles/10/512/256'
19
+ * replaceRoute('/user/:id/:name', ['123', 'john']) // '/user/123/john'
20
+ * ```
21
+ */
22
+ export function replaceRoute(data: Route, values: Values = []): Route {
23
+ let _data = data
24
+ const templates = _data.match(regex) ?? []
25
+ const isArray = Array.isArray(values)
26
+
27
+ templates.forEach((template, index) => {
28
+ let value
29
+ if (!isArray) {
30
+ const key = template.substring(1)
31
+ value = values[key]
32
+ } else {
33
+ value = values[index]
34
+ }
35
+ _data = _data.replace(template, value?.toString() ?? '')
36
+ })
37
+
38
+ return _data
39
+ }
@@ -0,0 +1,2 @@
1
+ export type Route = string
2
+ export type Values = Record<string, string | number> | (string | number)[]