@ledgerhq/lumen-utils-shared 0.0.11
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/README.md +32 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +115 -0
- package/dist/lib/cn.d.ts +3 -0
- package/dist/lib/cn.d.ts.map +1 -0
- package/dist/lib/create-safe-context/create-safe-context.d.ts +9 -0
- package/dist/lib/create-safe-context/create-safe-context.d.ts.map +1 -0
- package/dist/lib/create-safe-context/index.d.ts +2 -0
- package/dist/lib/create-safe-context/index.d.ts.map +1 -0
- package/dist/lib/get-object-path/get-object-path.d.ts +18 -0
- package/dist/lib/get-object-path/get-object-path.d.ts.map +1 -0
- package/dist/lib/get-object-path/index.d.ts +2 -0
- package/dist/lib/get-object-path/index.d.ts.map +1 -0
- package/dist/lib/input-formatter/font-size-formatter.d.ts +6 -0
- package/dist/lib/input-formatter/font-size-formatter.d.ts.map +1 -0
- package/dist/lib/input-formatter/format-thousands.d.ts +18 -0
- package/dist/lib/input-formatter/format-thousands.d.ts.map +1 -0
- package/dist/lib/input-formatter/index.d.ts +4 -0
- package/dist/lib/input-formatter/index.d.ts.map +1 -0
- package/dist/lib/input-formatter/text-formatter.d.ts +31 -0
- package/dist/lib/input-formatter/text-formatter.d.ts.map +1 -0
- package/dist/lib/is-text-children/index.d.ts +2 -0
- package/dist/lib/is-text-children/index.d.ts.map +1 -0
- package/dist/lib/is-text-children/is-text-children.d.ts +3 -0
- package/dist/lib/is-text-children/is-text-children.d.ts.map +1 -0
- package/dist/lib/string-utils/index.d.ts +2 -0
- package/dist/lib/string-utils/index.d.ts.map +1 -0
- package/dist/lib/string-utils/string-utils.d.ts +6 -0
- package/dist/lib/string-utils/string-utils.d.ts.map +1 -0
- package/dist/lib/use-merge-ref/index.d.ts +2 -0
- package/dist/lib/use-merge-ref/index.d.ts.map +1 -0
- package/dist/lib/use-merge-ref/use-merge-ref.d.ts +8 -0
- package/dist/lib/use-merge-ref/use-merge-ref.d.ts.map +1 -0
- package/eslint.config.mjs +22 -0
- package/package.json +28 -0
- package/project.json +7 -0
- package/src/index.ts +7 -0
- package/src/lib/cn.ts +6 -0
- package/src/lib/create-safe-context/create-safe-context.test.tsx +69 -0
- package/src/lib/create-safe-context/create-safe-context.tsx +46 -0
- package/src/lib/create-safe-context/index.ts +1 -0
- package/src/lib/get-object-path/get-object-path.test.ts +72 -0
- package/src/lib/get-object-path/get-object-path.ts +31 -0
- package/src/lib/get-object-path/index.ts +1 -0
- package/src/lib/input-formatter/font-size-formatter.ts +16 -0
- package/src/lib/input-formatter/format-thousands.test.ts +36 -0
- package/src/lib/input-formatter/format-thousands.ts +34 -0
- package/src/lib/input-formatter/index.ts +3 -0
- package/src/lib/input-formatter/text-formatter.test.ts +503 -0
- package/src/lib/input-formatter/text-formatter.ts +95 -0
- package/src/lib/is-text-children/index.ts +1 -0
- package/src/lib/is-text-children/is-text-children.test.ts +33 -0
- package/src/lib/is-text-children/is-text-children.ts +9 -0
- package/src/lib/string-utils/index.ts +1 -0
- package/src/lib/string-utils/string-utils.test.ts +85 -0
- package/src/lib/string-utils/string-utils.ts +11 -0
- package/src/lib/use-merge-ref/index.ts +1 -0
- package/src/lib/use-merge-ref/use-merge-ref.ts +41 -0
- package/tsconfig.json +13 -0
- package/tsconfig.lib.json +29 -0
- package/tsconfig.spec.json +37 -0
- package/vite.config.ts +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# utils-shared
|
|
2
|
+
|
|
3
|
+
Shared utility functions for the Lumen design system.
|
|
4
|
+
|
|
5
|
+
## Contributing
|
|
6
|
+
|
|
7
|
+
### Adding New Utilities
|
|
8
|
+
|
|
9
|
+
1. Create your utility function in `src/lib/`
|
|
10
|
+
2. Export it from `src/index.ts`
|
|
11
|
+
3. Add tests in the corresponding `.spec.ts` file
|
|
12
|
+
4. Update this README with usage examples
|
|
13
|
+
|
|
14
|
+
### Development Commands
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# Build the library
|
|
18
|
+
npx nx build @ledgerhq/lumen-utils-shared
|
|
19
|
+
|
|
20
|
+
# Run tests
|
|
21
|
+
npx nx test @ledgerhq/lumen-utils-shared
|
|
22
|
+
|
|
23
|
+
# Lint code
|
|
24
|
+
npx nx lint @ledgerhq/lumen-utils-shared
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Guidelines
|
|
28
|
+
|
|
29
|
+
- Keep utilities pure and side-effect free
|
|
30
|
+
- Add comprehensive tests for all functions
|
|
31
|
+
- Use TypeScript for type safety
|
|
32
|
+
- Follow existing naming conventions
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './lib/string-utils/string-utils';
|
|
2
|
+
export * from './lib/input-formatter';
|
|
3
|
+
export * from './lib/create-safe-context';
|
|
4
|
+
export * from './lib/cn';
|
|
5
|
+
export * from './lib/is-text-children';
|
|
6
|
+
export * from './lib/use-merge-ref';
|
|
7
|
+
export * from './lib/get-object-path';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iCAAiC,CAAC;AAChD,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,UAAU,CAAC;AACzB,cAAc,wBAAwB,CAAC;AACvC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { jsx as h } from "react/jsx-runtime";
|
|
2
|
+
import { createContext as x, useMemo as m, useContext as y } from "react";
|
|
3
|
+
import { clsx as P } from "clsx";
|
|
4
|
+
import { twMerge as S } from "tailwind-merge";
|
|
5
|
+
function w(t) {
|
|
6
|
+
return t ? t.split(/[-_ ]+/).map((n) => n.charAt(0).toUpperCase() + n.slice(1).toLowerCase()).join("") : "";
|
|
7
|
+
}
|
|
8
|
+
const C = 48, A = 17, M = 2;
|
|
9
|
+
function E(t) {
|
|
10
|
+
const n = t.replace(/\D/g, "").length;
|
|
11
|
+
return Math.max(
|
|
12
|
+
A,
|
|
13
|
+
C - n * M
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
function f(t, n = !0) {
|
|
17
|
+
if (!t) return "";
|
|
18
|
+
const i = t.includes("."), [o, r] = t.split("."), c = o.replace(/\B(?=(\d{3})+(?!\d))/g, " ");
|
|
19
|
+
return i && n ? r ? `${c}.${r}` : `${c}.` : c;
|
|
20
|
+
}
|
|
21
|
+
function T(t, n = {}) {
|
|
22
|
+
const {
|
|
23
|
+
allowDecimals: i = !0,
|
|
24
|
+
thousandsSeparator: o = !0,
|
|
25
|
+
maxIntegerLength: r = 9,
|
|
26
|
+
maxDecimalLength: c = 9
|
|
27
|
+
} = n, a = t.replace(",", ".").replace(/[^\d.]/g, "");
|
|
28
|
+
let e = a;
|
|
29
|
+
if (e = e.replace(/^0+(?=\d)/, ""), !i)
|
|
30
|
+
return e = e.replace(/\D/g, ""), r > 0 && e.length > r && (e = e.slice(0, r)), o ? f(e) : e;
|
|
31
|
+
e === "." && (e = "0.");
|
|
32
|
+
const l = e.indexOf(".");
|
|
33
|
+
if (l !== -1) {
|
|
34
|
+
let s = e.slice(0, l), u = e.slice(l + 1).replace(/\./g, "");
|
|
35
|
+
r > 0 && s.length > r && (s = s.slice(0, r)), u = u.slice(0, c), s === "" && (s = "0");
|
|
36
|
+
const d = a.endsWith(".") || e.endsWith(".");
|
|
37
|
+
e = u.length > 0 ? `${s}.${u}` : d ? `${s}.` : s;
|
|
38
|
+
} else
|
|
39
|
+
r > 0 && e.length > r && (e = e.slice(0, r));
|
|
40
|
+
return o ? f(e) : e;
|
|
41
|
+
}
|
|
42
|
+
function _(t, n) {
|
|
43
|
+
const i = x(n), o = ({ children: c, value: a }) => {
|
|
44
|
+
const e = m(
|
|
45
|
+
() => a,
|
|
46
|
+
Object.values(a ?? {})
|
|
47
|
+
);
|
|
48
|
+
return /* @__PURE__ */ h(i.Provider, { value: e, children: c });
|
|
49
|
+
};
|
|
50
|
+
o.displayName = t + "Provider";
|
|
51
|
+
function r({
|
|
52
|
+
consumerName: c,
|
|
53
|
+
contextRequired: a
|
|
54
|
+
}) {
|
|
55
|
+
const e = y(i);
|
|
56
|
+
if (e)
|
|
57
|
+
return e;
|
|
58
|
+
if (a)
|
|
59
|
+
throw new Error(
|
|
60
|
+
`${c} must be used within ${t}`
|
|
61
|
+
);
|
|
62
|
+
return n || {};
|
|
63
|
+
}
|
|
64
|
+
return [o, r];
|
|
65
|
+
}
|
|
66
|
+
function j(...t) {
|
|
67
|
+
return S(P(t));
|
|
68
|
+
}
|
|
69
|
+
const p = (t) => typeof t == "string" || typeof t == "number", F = (t) => Array.isArray(t) ? t.every(p) : p(t);
|
|
70
|
+
function g(t, n) {
|
|
71
|
+
if (typeof t == "function")
|
|
72
|
+
return t(n);
|
|
73
|
+
typeof t == "object" && t !== null && "current" in t && (t.current = n);
|
|
74
|
+
}
|
|
75
|
+
function $(...t) {
|
|
76
|
+
const n = /* @__PURE__ */ new Map();
|
|
77
|
+
return (i) => {
|
|
78
|
+
if (i === null) {
|
|
79
|
+
t.forEach((o) => {
|
|
80
|
+
const r = n.get(o);
|
|
81
|
+
typeof r == "function" && r(), g(o, null);
|
|
82
|
+
}), n.clear();
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
t.forEach((o) => {
|
|
86
|
+
const r = g(o, i);
|
|
87
|
+
typeof r == "function" && n.set(o, r);
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function I(...t) {
|
|
92
|
+
return m(() => $(...t), t);
|
|
93
|
+
}
|
|
94
|
+
const z = (t, n) => {
|
|
95
|
+
let i = t;
|
|
96
|
+
for (const o of n) {
|
|
97
|
+
if (i == null)
|
|
98
|
+
return;
|
|
99
|
+
i = i[o];
|
|
100
|
+
}
|
|
101
|
+
return i;
|
|
102
|
+
};
|
|
103
|
+
export {
|
|
104
|
+
g as assignRef,
|
|
105
|
+
j as cn,
|
|
106
|
+
_ as createSafeContext,
|
|
107
|
+
f as formatThousands,
|
|
108
|
+
E as getFontSize,
|
|
109
|
+
z as getObjectPath,
|
|
110
|
+
F as isTextChildren,
|
|
111
|
+
$ as mergeRefs,
|
|
112
|
+
T as textFormatter,
|
|
113
|
+
w as toPascalCase,
|
|
114
|
+
I as useMergedRef
|
|
115
|
+
};
|
package/dist/lib/cn.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cn.d.ts","sourceRoot":"","sources":["../../src/lib/cn.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAQ,MAAM,MAAM,CAAC;AAGxC,wBAAgB,EAAE,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,UAEzC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { FC, ReactNode } from 'react';
|
|
2
|
+
export declare function createSafeContext<ContextValue extends object>(rootComponentName: string, defaultContext?: ContextValue): readonly [FC<{
|
|
3
|
+
children: ReactNode;
|
|
4
|
+
value: ContextValue;
|
|
5
|
+
}>, <ContextRequired extends boolean = boolean>({ consumerName, contextRequired, }: {
|
|
6
|
+
consumerName: string;
|
|
7
|
+
contextRequired: ContextRequired;
|
|
8
|
+
}) => ContextRequired extends true ? ContextValue : Partial<ContextValue>];
|
|
9
|
+
//# sourceMappingURL=create-safe-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-safe-context.d.ts","sourceRoot":"","sources":["../../../src/lib/create-safe-context/create-safe-context.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAiB,EAAE,EAAE,SAAS,EAAuB,MAAM,OAAO,CAAC;AAE1E,wBAAgB,iBAAiB,CAAC,YAAY,SAAS,MAAM,EAC3D,iBAAiB,EAAE,MAAM,EACzB,cAAc,CAAC,EAAE,YAAY;cAKjB,SAAS;WACZ,YAAY;KAaG,eAAe,SAAS,OAAO,gDAGpD;IACD,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,eAAe,CAAC;CAClC,KAAG,eAAe,SAAS,IAAI,GAAG,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,EAgBxE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/create-safe-context/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Access a nested property in an object using an array path.
|
|
3
|
+
*
|
|
4
|
+
* @param obj - The object to access
|
|
5
|
+
* @param path - Array of keys representing the path to the value
|
|
6
|
+
* @returns The value at the path, or undefined if not found
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* const theme = { colors: { bg: { surface: '#fff' } } };
|
|
11
|
+
*
|
|
12
|
+
* getObjectPath(theme, ['colors', 'bg', 'surface']); // '#fff'
|
|
13
|
+
* getObjectPath(theme, ['colors', 'bg']); // { surface: '#fff' }
|
|
14
|
+
* getObjectPath(theme, ['spacings']); // undefined
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export declare const getObjectPath: <T = unknown>(obj: Record<string, unknown>, path: string[]) => T | undefined;
|
|
18
|
+
//# sourceMappingURL=get-object-path.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-object-path.d.ts","sourceRoot":"","sources":["../../../src/lib/get-object-path/get-object-path.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,aAAa,GAAI,CAAC,GAAG,OAAO,EACvC,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,MAAM,MAAM,EAAE,KACb,CAAC,GAAG,SAWN,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/get-object-path/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"font-size-formatter.d.ts","sourceRoot":"","sources":["../../../src/lib/input-formatter/font-size-formatter.ts"],"names":[],"mappings":"AAIA;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOjD"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formats a number string with space-separated thousands.
|
|
3
|
+
* e.g., '1000000' -> '1 000 000'
|
|
4
|
+
*
|
|
5
|
+
* @returns Formatted string with space-separated thousands
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* formatThousands('1000') // '1 000'
|
|
9
|
+
* formatThousands('1000000') // '1 000 000'
|
|
10
|
+
* formatThousands('1234.5678') // '1 234.5678'
|
|
11
|
+
* formatThousands('1234.5678', false) // '1 235'
|
|
12
|
+
*/
|
|
13
|
+
export declare function formatThousands(
|
|
14
|
+
/** The value to format */
|
|
15
|
+
value: string,
|
|
16
|
+
/** Whether to preserve decimal places */
|
|
17
|
+
preserveDecimals?: boolean): string;
|
|
18
|
+
//# sourceMappingURL=format-thousands.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-thousands.d.ts","sourceRoot":"","sources":["../../../src/lib/input-formatter/format-thousands.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe;AAC7B,0BAA0B;AAC1B,KAAK,EAAE,MAAM;AACb,yCAAyC;AACzC,gBAAgB,UAAO,GACtB,MAAM,CAgBR"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/input-formatter/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type TextFormatterOptions = {
|
|
2
|
+
/** Whether to allow decimal values */
|
|
3
|
+
allowDecimals?: boolean;
|
|
4
|
+
/** Whether to format with space-separated thousands */
|
|
5
|
+
thousandsSeparator?: boolean;
|
|
6
|
+
/** Maximum length for integer part (before decimal) */
|
|
7
|
+
maxIntegerLength?: number;
|
|
8
|
+
/** Maximum length for decimal part (after decimal) */
|
|
9
|
+
maxDecimalLength?: number;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Formats and validates numeric input text for amount inputs.
|
|
13
|
+
* Handles decimal formatting, leading zeros, and prevents multiple decimal points.
|
|
14
|
+
* Can optionally format with space-separated thousands.
|
|
15
|
+
* @returns Formatted and cleaned numeric string
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* textFormatter('01') // '1'
|
|
19
|
+
* textFormatter('.5') // '0.5'
|
|
20
|
+
* textFormatter('1.2.3') // '1.23'
|
|
21
|
+
* textFormatter('1,5') // '1.5'
|
|
22
|
+
* textFormatter('abc123') // '123'
|
|
23
|
+
* textFormatter('1000000', { thousandsSeparator: true }) // '1 000 000'
|
|
24
|
+
* textFormatter('1234.5678', { thousandsSeparator: true }) // '1 234.5678'
|
|
25
|
+
* textFormatter('123456789012', { maxIntegerLength: 9 }) // '123456789'
|
|
26
|
+
* textFormatter('123.123456', { maxDecimalLength: 2 }) // '123.12'
|
|
27
|
+
*/
|
|
28
|
+
export declare function textFormatter(
|
|
29
|
+
/** The input value to format */
|
|
30
|
+
value: string, options?: TextFormatterOptions): string;
|
|
31
|
+
//# sourceMappingURL=text-formatter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text-formatter.d.ts","sourceRoot":"","sources":["../../../src/lib/input-formatter/text-formatter.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,oBAAoB,GAAG;IACjC,sCAAsC;IACtC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,uDAAuD;IACvD,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,uDAAuD;IACvD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,sDAAsD;IACtD,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,aAAa;AAC3B,gCAAgC;AAChC,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,oBAAyB,GACjC,MAAM,CA4DR"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/is-text-children/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"is-text-children.d.ts","sourceRoot":"","sources":["../../../src/lib/is-text-children/is-text-children.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAKlC,eAAO,MAAM,cAAc,GAAI,SAAS,SAAS,YAGhD,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/string-utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"string-utils.d.ts","sourceRoot":"","sources":["../../../src/lib/string-utils/string-utils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAMhD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/use-merge-ref/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Ref, RefCallback } from 'react';
|
|
2
|
+
type PossibleRef<T> = Ref<T> | undefined;
|
|
3
|
+
type RefCleanup = void | (() => void);
|
|
4
|
+
export declare function assignRef<T>(ref: PossibleRef<T>, value: T | null): RefCleanup;
|
|
5
|
+
export declare function mergeRefs<T>(...refs: PossibleRef<T>[]): RefCallback<T>;
|
|
6
|
+
export declare function useMergedRef<T>(...refs: PossibleRef<T>[]): RefCallback<T>;
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=use-merge-ref.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-merge-ref.d.ts","sourceRoot":"","sources":["../../../src/lib/use-merge-ref/use-merge-ref.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAW,KAAK,WAAW,EAAE,MAAM,OAAO,CAAC;AAEvD,KAAK,WAAW,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;AACzC,KAAK,UAAU,GAAG,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC;AAEtC,wBAAgB,SAAS,CAAC,CAAC,EAAE,GAAG,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI,GAAG,UAAU,CAM7E;AAED,wBAAgB,SAAS,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,GAAG,WAAW,CAAC,CAAC,CAAC,CAuBtE;AAED,wBAAgB,YAAY,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,GAAG,WAAW,CAAC,CAAC,CAAC,CAEzE"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import baseConfig from '../../eslint.config.mjs';
|
|
2
|
+
|
|
3
|
+
export default [
|
|
4
|
+
...baseConfig,
|
|
5
|
+
{
|
|
6
|
+
files: ['**/*.json'],
|
|
7
|
+
rules: {
|
|
8
|
+
'@nx/dependency-checks': [
|
|
9
|
+
'error',
|
|
10
|
+
{
|
|
11
|
+
ignoredFiles: [
|
|
12
|
+
'{projectRoot}/eslint.config.{js,cjs,mjs}',
|
|
13
|
+
'{projectRoot}/vite.config.{js,ts,mjs,mts}',
|
|
14
|
+
],
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
},
|
|
18
|
+
languageOptions: {
|
|
19
|
+
parser: await import('jsonc-eslint-parser'),
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
];
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ledgerhq/lumen-utils-shared",
|
|
3
|
+
"version": "0.0.11",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./package.json": "./package.json"
|
|
14
|
+
},
|
|
15
|
+
"nx": {
|
|
16
|
+
"tags": [
|
|
17
|
+
"scope:shared",
|
|
18
|
+
"type:utils"
|
|
19
|
+
]
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"clsx": "^2.1.1",
|
|
23
|
+
"tailwind-merge": "^2.6.0"
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"react": "^18.0.0 || ^19.0.0"
|
|
27
|
+
}
|
|
28
|
+
}
|
package/project.json
ADDED
package/src/index.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from './lib/string-utils/string-utils';
|
|
2
|
+
export * from './lib/input-formatter';
|
|
3
|
+
export * from './lib/create-safe-context';
|
|
4
|
+
export * from './lib/cn';
|
|
5
|
+
export * from './lib/is-text-children';
|
|
6
|
+
export * from './lib/use-merge-ref';
|
|
7
|
+
export * from './lib/get-object-path';
|
package/src/lib/cn.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { cleanup, render } from '@testing-library/react';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { createSafeContext } from './create-safe-context.js';
|
|
4
|
+
|
|
5
|
+
afterEach(() => {
|
|
6
|
+
cleanup();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
describe('createSafeContext', () => {
|
|
10
|
+
it('throws when used without provider and contextRequired = true', () => {
|
|
11
|
+
type Ctx = { value?: number };
|
|
12
|
+
const [, useSafe] = createSafeContext<Ctx>('RootComponent');
|
|
13
|
+
|
|
14
|
+
const Consumer = () => {
|
|
15
|
+
// This should throw because contextRequired is true and there is no Provider
|
|
16
|
+
useSafe({ consumerName: 'Consumer', contextRequired: true });
|
|
17
|
+
return null;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
expect(() => render(React.createElement(Consumer))).toThrow(
|
|
21
|
+
'Consumer must be used within RootComponent',
|
|
22
|
+
);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('returns default context when not wrapped and contextRequired = false', () => {
|
|
26
|
+
type Ctx = { value: number };
|
|
27
|
+
const defaultCtx: Ctx = { value: 42 };
|
|
28
|
+
const [, useSafe] = createSafeContext<Ctx>('RootComponent', defaultCtx);
|
|
29
|
+
|
|
30
|
+
const Consumer = () => {
|
|
31
|
+
const ctx = useSafe({ consumerName: 'Consumer', contextRequired: false });
|
|
32
|
+
return React.createElement('span', { 'data-testid': 'value' }, ctx.value);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const { getByTestId } = render(React.createElement(Consumer));
|
|
36
|
+
expect(getByTestId('value').textContent).toBe('42');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('provides context value via Provider when wrapped', () => {
|
|
40
|
+
type Ctx = { value: number };
|
|
41
|
+
const [Provider, useSafe] = createSafeContext<Ctx>('RootComponent');
|
|
42
|
+
|
|
43
|
+
const Consumer = () => {
|
|
44
|
+
const ctx = useSafe({ consumerName: 'Consumer', contextRequired: false });
|
|
45
|
+
return React.createElement(
|
|
46
|
+
'span',
|
|
47
|
+
{ 'data-testid': 'value' },
|
|
48
|
+
String(ctx.value),
|
|
49
|
+
);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const { getByTestId, rerender } = render(
|
|
53
|
+
React.createElement(Provider, {
|
|
54
|
+
value: { value: 7 },
|
|
55
|
+
children: React.createElement(Consumer),
|
|
56
|
+
}),
|
|
57
|
+
);
|
|
58
|
+
expect(getByTestId('value').textContent).toBe('7');
|
|
59
|
+
|
|
60
|
+
// Verify updates propagate
|
|
61
|
+
rerender(
|
|
62
|
+
React.createElement(Provider, {
|
|
63
|
+
value: { value: 8 },
|
|
64
|
+
children: React.createElement(Consumer),
|
|
65
|
+
}),
|
|
66
|
+
);
|
|
67
|
+
expect(getByTestId('value').textContent).toBe('8');
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { createContext, FC, ReactNode, useContext, useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
export function createSafeContext<ContextValue extends object>(
|
|
4
|
+
rootComponentName: string,
|
|
5
|
+
defaultContext?: ContextValue,
|
|
6
|
+
) {
|
|
7
|
+
const Context = createContext<ContextValue | undefined>(defaultContext);
|
|
8
|
+
|
|
9
|
+
const Provider: FC<{
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
value: ContextValue;
|
|
12
|
+
}> = ({ children, value: context }) => {
|
|
13
|
+
// Only re-memoize when prop values change
|
|
14
|
+
const memoValue = useMemo(
|
|
15
|
+
() => context,
|
|
16
|
+
Object.values(context ?? {}),
|
|
17
|
+
) as ContextValue;
|
|
18
|
+
|
|
19
|
+
return <Context.Provider value={memoValue}>{children}</Context.Provider>;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
Provider.displayName = rootComponentName + 'Provider';
|
|
23
|
+
|
|
24
|
+
function useSafeContext<ContextRequired extends boolean = boolean>({
|
|
25
|
+
consumerName,
|
|
26
|
+
contextRequired,
|
|
27
|
+
}: {
|
|
28
|
+
consumerName: string;
|
|
29
|
+
contextRequired: ContextRequired;
|
|
30
|
+
}): ContextRequired extends true ? ContextValue : Partial<ContextValue> {
|
|
31
|
+
const context = useContext(Context);
|
|
32
|
+
|
|
33
|
+
if (context) {
|
|
34
|
+
return context;
|
|
35
|
+
}
|
|
36
|
+
if (contextRequired) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`${consumerName} must be used within ${rootComponentName}`,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return (defaultContext || {}) as ContextValue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return [Provider, useSafeContext] as const;
|
|
46
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './create-safe-context';
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { getObjectPath } from './get-object-path';
|
|
3
|
+
|
|
4
|
+
describe('getObjectPath', () => {
|
|
5
|
+
const testObj = {
|
|
6
|
+
colors: {
|
|
7
|
+
bg: {
|
|
8
|
+
surface: '#ffffff',
|
|
9
|
+
accent: '#0000ff',
|
|
10
|
+
},
|
|
11
|
+
text: {
|
|
12
|
+
base: '#000000',
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
spacings: {
|
|
16
|
+
s4: 4,
|
|
17
|
+
s8: 8,
|
|
18
|
+
s16: 16,
|
|
19
|
+
},
|
|
20
|
+
nested: {
|
|
21
|
+
deep: {
|
|
22
|
+
value: 'found',
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
it('should access single level path', () => {
|
|
28
|
+
expect(getObjectPath(testObj, ['spacings'])).toEqual({
|
|
29
|
+
s4: 4,
|
|
30
|
+
s8: 8,
|
|
31
|
+
s16: 16,
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should access two level path', () => {
|
|
36
|
+
expect(getObjectPath(testObj, ['colors', 'bg'])).toEqual({
|
|
37
|
+
surface: '#ffffff',
|
|
38
|
+
accent: '#0000ff',
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should access three level path', () => {
|
|
43
|
+
expect(getObjectPath(testObj, ['colors', 'bg', 'surface'])).toBe('#ffffff');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should access deeply nested path', () => {
|
|
47
|
+
expect(getObjectPath(testObj, ['nested', 'deep', 'value'])).toBe('found');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should return undefined for non-existent path', () => {
|
|
51
|
+
expect(getObjectPath(testObj, ['nonexistent'])).toBeUndefined();
|
|
52
|
+
expect(getObjectPath(testObj, ['colors', 'nonexistent'])).toBeUndefined();
|
|
53
|
+
expect(
|
|
54
|
+
getObjectPath(testObj, ['colors', 'bg', 'nonexistent']),
|
|
55
|
+
).toBeUndefined();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should return undefined for path through non-object', () => {
|
|
59
|
+
expect(
|
|
60
|
+
getObjectPath(testObj, ['spacings', 's4', 'invalid']),
|
|
61
|
+
).toBeUndefined();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should handle empty path', () => {
|
|
65
|
+
expect(getObjectPath(testObj, [])).toEqual(testObj);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should work with type parameter', () => {
|
|
69
|
+
const result = getObjectPath<string>(testObj, ['colors', 'bg', 'surface']);
|
|
70
|
+
expect(result).toBe('#ffffff');
|
|
71
|
+
});
|
|
72
|
+
});
|