@codeleap/utils 6.2.3 → 6.8.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.
package/src/react.tsx CHANGED
@@ -2,6 +2,7 @@ import equals from 'deep-equal'
2
2
  import React from 'react'
3
3
  import { TypeGuards } from '@codeleap/types'
4
4
 
5
+ /** Deep structural equality backed by the `deep-equal` package. */
5
6
  export const deepEqual = equals
6
7
 
7
8
  type ArePropsEqualOptions<MergedObject> = {
@@ -9,6 +10,13 @@ type ArePropsEqualOptions<MergedObject> = {
9
10
  excludeKeys?: string[]
10
11
  }
11
12
 
13
+ /**
14
+ * Checks a specific subset of props for deep equality between two render cycles.
15
+ *
16
+ * `excludeKeys` are deleted from each compared value **in place** before
17
+ * comparison — this mutates the items inside `previous` and `next`.
18
+ * Pass only the keys you want to ignore, and be aware of the side-effect.
19
+ */
12
20
  export function arePropsEqual<A, B>(
13
21
  previous: A,
14
22
  next: B,
@@ -17,8 +25,8 @@ export function arePropsEqual<A, B>(
17
25
  const { check, excludeKeys = [] } = options
18
26
 
19
27
  for (const c of check) {
20
- const nextItem = next[c as string]
21
- const prevItem = previous[c as string]
28
+ const nextItem = (next as any)[c as string]
29
+ const prevItem = (previous as any)[c as string]
22
30
 
23
31
  for (const key of excludeKeys) {
24
32
  if (nextItem?.[key]) delete nextItem[key]
@@ -35,20 +43,35 @@ export function arePropsEqual<A, B>(
35
43
  return true
36
44
  }
37
45
 
38
- export const flattenChildren = (children, flat = []) => {
46
+ /**
47
+ * Recursively collects a React children tree into a single flat array.
48
+ *
49
+ * Uses `React.Children.toArray` at each level (which assigns stable keys), then
50
+ * follows any `children` prop on the top-level element to continue flattening.
51
+ * Only one level of `props.children` nesting is followed per call — deeply
52
+ * nested component trees (not just wrapper nodes) are not fully unwound.
53
+ */
54
+ export const flattenChildren = (children: any, flat: React.ReactNode[] = []): React.ReactNode[] => {
39
55
  flat = [...flat, ...React.Children.toArray(children)]
40
56
 
41
- if (children.props && children.props.children) {
57
+ if (children?.props && children.props.children) {
42
58
  return flattenChildren(children.props.children, flat)
43
59
  }
44
60
 
45
61
  return flat
46
62
  }
47
63
 
48
- export const simplifyChildren = children => {
64
+ /**
65
+ * Flattens a children tree and strips the `children` prop from each element's
66
+ * props, returning a serialisable summary of the tree.
67
+ *
68
+ * Intended for introspection and testing — not for re-rendering, since `ref`
69
+ * and event-handler functions are preserved as-is.
70
+ */
71
+ export const simplifyChildren = (children: any) => {
49
72
  const flat = flattenChildren(children)
50
73
 
51
- return flat.map(
74
+ return (flat as any[]).map(
52
75
  ({
53
76
  key,
54
77
  ref,
@@ -63,6 +86,18 @@ export const simplifyChildren = children => {
63
86
  )
64
87
  }
65
88
 
89
+ /**
90
+ * Renders a slot that accepts a component, a props object, or a pre-rendered node.
91
+ *
92
+ * Resolution order:
93
+ * 1. If `ComponentOrProps` is a function → render it as `<ComponentOrProps {...props} />`.
94
+ * 2. If it is `null`, `undefined`, or an empty object → return `null`.
95
+ * 3. If it is already a valid React element → return it as-is.
96
+ * 4. Otherwise treat it as a props object and render `<DefaultComponent {...props} {...ComponentOrProps} />`.
97
+ *
98
+ * This lets call sites pass either a component override, extra props, or nothing,
99
+ * without needing separate conditional rendering logic.
100
+ */
66
101
  export function getRenderedComponent<P = any>(
67
102
  ComponentOrProps: React.ComponentType<P> | P | React.ReactNode | null | undefined,
68
103
  DefaultComponent: React.ComponentType<P>,
@@ -71,14 +106,14 @@ export function getRenderedComponent<P = any>(
71
106
  const _ComponentOrProps = ComponentOrProps as any
72
107
  const _DefaultComponent = DefaultComponent as any
73
108
 
74
- if (TypeGuards.isNil(ComponentOrProps) || Object.keys(ComponentOrProps).length === 0) {
75
- return null
76
- }
77
-
78
109
  if (TypeGuards.isFunction(ComponentOrProps)) {
79
110
  return <_ComponentOrProps {...props as unknown as P} />
80
111
  }
81
112
 
113
+ if (TypeGuards.isNil(ComponentOrProps) || Object.keys(ComponentOrProps as object).length === 0) {
114
+ return null
115
+ }
116
+
82
117
  if (React.isValidElement(ComponentOrProps)) {
83
118
  return ComponentOrProps
84
119
  }
@@ -91,10 +126,10 @@ export function getRenderedComponent<P = any>(
91
126
  /**
92
127
  * Memoizes a React functional component to prevent unnecessary re-renders.
93
128
  *
94
- * This function wraps a React functional component using `React.memo`, which provides
95
- * a mechanism for memoizing the result of rendering the component. By default, it
129
+ * This function wraps a React functional component using `React.memo`, which provides
130
+ * a mechanism for memoizing the result of rendering the component. By default, it
96
131
  * uses a comparison function that always returns `true`, indicating that the component
97
- * should not re-render, regardless of prop changes. This behavior essentially freezes
132
+ * should not re-render, regardless of prop changes. This behavior essentially freezes
98
133
  * the component's rendering until explicitly updated.
99
134
  *
100
135
  * @template P - The type of the component's props.
@@ -108,7 +143,7 @@ export function memoize<P extends object>(ComponentToMemoize: React.FunctionComp
108
143
  /**
109
144
  * Checks whether a specific property in two sets of props is equal.
110
145
  *
111
- * This function compares the value of a given property (`prop`) between two objects
146
+ * This function compares the value of a given property (`prop`) between two objects
112
147
  * (`prevProps` and `nextProps`) using a deep equality check (`equals`).
113
148
  *
114
149
  * @template P - The type of the props object.
@@ -128,7 +163,7 @@ export function memoChecker<P>(prop: keyof P, prevProps: P, nextProps: P): boole
128
163
  * Memoizes a React functional component based on specific props.
129
164
  *
130
165
  * This function wraps a React functional component using `React.memo` with a custom comparison
131
- * function. The comparison function checks if the specified properties (passed as `check`)
166
+ * function. The comparison function checks if the specified properties (passed as `check`)
132
167
  * remain equal between renders. If all specified properties are equal, the component will not re-render.
133
168
  *
134
169
  * @template P - The type of the component's props.
@@ -163,7 +198,7 @@ export function memoChecker<P>(prop: keyof P, prevProps: P, nextProps: P): boole
163
198
  */
164
199
  export function memoBy<P extends object>(
165
200
  ComponentToMemoize: React.FunctionComponent<P>,
166
- check: keyof P | Array<keyof P>
201
+ check: keyof P | Array<keyof P>,
167
202
  ): React.NamedExoticComponent<P> {
168
203
  return React.memo(ComponentToMemoize, (prevProps, nextProps) => {
169
204
  const checks = Array.isArray(check) ? check : [check]
package/src/string.ts CHANGED
@@ -1,27 +1,55 @@
1
+ /** Collapses all newline characters in `text` to single spaces. */
1
2
  export function singleLine(text: string) {
2
3
  return text?.replace(/\n/g, ' ')
3
4
  }
4
5
 
5
- export function stringiparse(string) {
6
+ /**
7
+ * Round-trips a value through `JSON.stringify` → `JSON.parse`.
8
+ *
9
+ * Strips non-serialisable properties (functions, `undefined`, class instances)
10
+ * and produces a plain-object clone. Throws if the value contains circular
11
+ * references.
12
+ */
13
+ export function stringiparse(string: string) {
6
14
  return JSON.parse(JSON.stringify(string))
7
15
  }
8
16
 
17
+ /**
18
+ * Changes only the first character of `str`.
19
+ *
20
+ * When `reverse` is `true` the first character is lowercased instead of
21
+ * uppercased — useful for converting PascalCase to camelCase.
22
+ */
9
23
  export function capitalize(str: string, reverse = false) {
10
24
  if (!str.length) return str
11
25
  const firstChar = reverse ? str[0].toLowerCase() : str[0].toUpperCase()
12
26
  return firstChar + str.substring(1)
13
27
  }
14
28
 
29
+ /**
30
+ * Returns `true` if `char` is an uppercase Latin or extended Latin character.
31
+ *
32
+ * Covers the Unicode range `U+0080`–`U+024F` in addition to `A–Z`, so
33
+ * accented capitals (e.g. `É`, `Ñ`) are recognised correctly.
34
+ */
15
35
  export function isUppercase(char: string) {
16
36
  return /[A-Z]|[\u0080-\u024F]/.test(char) && char.toUpperCase() === char
17
37
  }
18
38
 
39
+ /** Returns `true` when `char` is **not** considered uppercase by {@link isUppercase}. */
19
40
  export function isLowercase(char: string) {
20
41
  return !isUppercase(char)
21
42
  }
22
43
 
44
+ /**
45
+ * Converts a camelCase or PascalCase identifier to a space-separated, title-cased string.
46
+ *
47
+ * A space is inserted before each uppercase letter that is immediately preceded
48
+ * by a lowercase letter. The first character is always uppercased.
49
+ * Consecutive uppercase runs (e.g. acronyms like "URL") are not split.
50
+ */
23
51
  export function humanizeCamelCase(str: string) {
24
- const characters = []
52
+ const characters: string[] = []
25
53
  let previousCharacter = ''
26
54
  str.split('').forEach((char, idx) => {
27
55
  if (idx === 0) {
@@ -40,6 +68,13 @@ export function humanizeCamelCase(str: string) {
40
68
  return characters.join('')
41
69
  }
42
70
 
71
+ /**
72
+ * Truncates `str` to `maxLen` characters, appending `'...'` when truncation occurs.
73
+ *
74
+ * The three dots count toward `maxLen`, so the returned string never exceeds
75
+ * `maxLen` characters when truncated. Strings already at or below `maxLen` are
76
+ * returned unchanged.
77
+ */
43
78
  export function ellipsis(str: string, maxLen: number) {
44
79
  if (str.length - 3 > maxLen) {
45
80
  return str.slice(0, maxLen - 3) + '...'
package/package.json.bak DELETED
@@ -1,31 +0,0 @@
1
- {
2
- "name": "@codeleap/utils",
3
- "version": "6.2.3",
4
- "main": "src/index.ts",
5
- "license": "UNLICENSED",
6
- "repository": {
7
- "url": "https://github.com/codeleap-uk/internal-libs-monorepo.git",
8
- "type": "git",
9
- "directory": "packages/utils"
10
- },
11
- "devDependencies": {
12
- "@codeleap/config": "workspace:*",
13
- "@codeleap/types": "workspace:*",
14
- "ts-node-dev": "1.1.8"
15
- },
16
- "scripts": {
17
- "build": "echo 'No build needed'"
18
- },
19
- "peerDependencies": {
20
- "@codeleap/types": "workspace:*",
21
- "axios": "^1.7.9",
22
- "dayjs": "1.11.18",
23
- "typescript": "5.5.2",
24
- "react": "19.1.0",
25
- "@tanstack/react-query": "5.89.0"
26
- },
27
- "dependencies": {
28
- "tinycolor2": "^1.4.2",
29
- "deep-equal": "^2.0.5"
30
- }
31
- }