@uxf/core 11.80.0 → 11.85.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/README.md CHANGED
@@ -125,6 +125,63 @@ useIsomorphicLayoutEffect(() => {
125
125
 
126
126
  > **Note**: Requires valid `line-height` and `font-size` styles for accurate sizing.
127
127
 
128
+ ### assertNever
129
+
130
+ Checks that value is always type "never".
131
+ ```ts
132
+ switch(value) {
133
+ case "a":
134
+ return "A";
135
+ case "b":
136
+ return "B";
137
+ default:
138
+ return assertNever(value);
139
+ }
140
+ ```
141
+
142
+ ## assertNotNil
143
+
144
+ ```tsx
145
+ import { assertNotNil } from "@uxf/core/utils/assert-not-nil";
146
+
147
+ const testObject: { value: number | null } = { value: 10 };
148
+
149
+ assertNotNil(testObject.value);
150
+
151
+ // is the same as
152
+
153
+ if (isNil(testObject.value)) {
154
+ throw new Error("Value is null");
155
+ }
156
+ ```
157
+
158
+ ## camelCaseToDash
159
+
160
+ ```tsx
161
+ import { camelCaseToDash } from "@uxf/core/utils/camelCaseToDash";
162
+
163
+ const example = camelCaseToDash("fooBar"); /* returns "foo-bar" */
164
+ ```
165
+
166
+ ## capitalize
167
+
168
+ ```tsx
169
+ import { capitalize } from "@uxf/core/utils/capitalize";
170
+
171
+ const example = capitalize("hello world"); /* returns "Hello world" */
172
+ ```
173
+
174
+ ## composeRefs
175
+
176
+ ```tsx
177
+ import { composeRefs } from "@uxf/core/utils/composeRefs";
178
+
179
+ const firstRef = useRef<HTMLDivElement>(null);
180
+ const secondRef = useRef<HTMLDivElement>(null);
181
+
182
+ const example = <div ref={composeRefs(firstRef, secondRef)} />;
183
+ ```
184
+
128
185
  ### cx, cxa
129
186
  It is our fork of `clsx` library https://github.com/lukeed/clsx
130
187
 
@@ -175,9 +232,30 @@ cxa("foo", [1 && "bar", { baz:false, bat:null }, ["hello", ["world"]]], "cya");
175
232
  //=> "foo bar hello world cya"
176
233
  ```
177
234
 
235
+ ### deepEqualIgnoringKeyOrder
236
+
237
+ deepEqualIgnoringKeyOrder compares two values for deep equality while ignoring the order of object keys. It serializes values in a stable way, ensuring that objects with the same data but different key orders are treated as equal. Arrays remain order-sensitive, circular references are handled safely, and primitives are compared by value.
238
+
239
+ Use this helper for equality checks in tests, memoization, caching, or change detection scenarios where object key order shouldn’t matter. It’s especially useful when comparing payloads, configs, or API responses that may have non-deterministic key ordering.
240
+
241
+ ```typescript
242
+ import { deepEqualIgnoringKeyOrder } from "./deep-equal-ingoring-key-order";
243
+
244
+ const a = { b: 2, a: 1, nested: { y: 2, x: 1 } };
245
+ const b = { nested: { x: 1, y: 2 }, a: 1, b: 2 };
246
+ console.log(deepEqualIgnoringKeyOrder(a, b)); // true
247
+
248
+ const arr1 = [{ a: 1, b: 2 }, { c: 3, d: 4 }];
249
+ const arr2 = [{ b: 2, a: 1 }, { d: 4, c: 3 }];
250
+ console.log(deepEqualIgnoringKeyOrder(arr1, arr2)); // true (objects equal, same array order)
251
+
252
+ const arr3 = [{ d: 4, c: 3 }, { b: 2, a: 1 }];
253
+ console.log(deepEqualIgnoringKeyOrder(arr1, arr3)); // false (array order differs)
254
+ ```
255
+
178
256
  ### downloadFile
179
257
 
180
- Intended as only way to programmatically download file if there is no option to use native anchor with `download` html attribute (eg. in form submit events).
258
+ Intended as only way to programmatically download file if there is no option to use native anchor with `download` html attribute (eg. in form submit events).
181
259
 
182
260
  ```ts
183
261
  import { downloadFile } from "@uxf/core/utils/download-file";
@@ -199,6 +277,14 @@ escapeQuotes('The "quick" fox');
199
277
  // Output: The \"quick\" fox
200
278
  ```
201
279
 
280
+ ## filterNullish
281
+
282
+ ```tsx
283
+ import { filterNullish } from "@uxf/core/utils/filter-nullish";
284
+
285
+ filterNullish([0, "text", null, undefined, [], {}]); /* returns [0, "text", [], {}] */
286
+ ```
287
+
202
288
  ### formatBytes
203
289
 
204
290
  Appends suitable unit to the byte value of data size.
@@ -207,18 +293,102 @@ formatBytes(17.5 * 1024);
207
293
  //=> "17.5 kB"
208
294
  ```
209
295
 
210
- ### assertNever
296
+ ## isEmpty
211
297
 
212
- Checks that value is always type "never".
213
- ```ts
214
- switch(value) {
215
- case "a":
216
- return "A";
217
- case "b":
218
- return "B";
219
- default:
220
- return assertNever(value);
221
- }
298
+ ```tsx
299
+ import { isEmpty } from "@uxf/core/utils/is-empty";
300
+
301
+ isEmpty("not-empty"); /* returns false */
302
+ isEmpty(""); /* returns true */
303
+ isEmpty(["1"]); /* returns false */
304
+ isEmpty([]); /* returns true */
305
+ ```
306
+
307
+ ## isBrowser / isServer
308
+
309
+ ```tsx
310
+ import { isBrowser } from "@uxf/core/utils/isBrowser";
311
+ import { isServer } from "@uxf/core/utils/isServer";
312
+
313
+ const browserExample = isBrowser; /* returns true if DOM is available */
314
+ const serverExample = isServer; /* returns true if DOM is NOT available */
315
+ ```
316
+
317
+ ## isNil
318
+
319
+ ```tsx
320
+ import { isNil } from "@uxf/core/utils/is-nil";
321
+
322
+ isNil(null); /* returns true */
323
+ isNil(undefined); /* returns true */
324
+ isNil(true); /* returns false */
325
+ isNil(1); /* returns false */
326
+ isNil(0); /* returns false */
327
+ isNil([]); /* returns false */
328
+ isNil("string"); /* returns false */
329
+ ```
330
+
331
+ ## isNotNil
332
+
333
+ ```tsx
334
+ import { isNotNil } from "@uxf/core/utils/is-not-nil";
335
+
336
+ isNotNil(null); /* returns false */
337
+ isNotNil(undefined); /* returns false */
338
+ isNotNil(true); /* returns true */
339
+ isNotNil(1); /* returns true */
340
+ isNotNil(0); /* returns true */
341
+ isNotNil([]); /* returns true */
342
+ isNotNil("string"); /* returns true */
343
+ ```
344
+
345
+ ```tsx
346
+ import { last } from "@uxf/core/utils/last";
347
+
348
+ last([1, 2]); /* returns 2 */
349
+ last([]); /* returns undefined */
350
+ ```
351
+
352
+ ## slugify
353
+
354
+ ```tsx
355
+ import { slugify } from "@uxf/core/utils/slugify";
356
+
357
+ const example = slugify("Jak se dnes máte?"); /* returns "jak-se-dnes-mate" */
358
+ ```
359
+
360
+ ### stableStringify
361
+
362
+ Deterministically converts any JavaScript value into a JSON string by recursively sorting object keys while preserving array order. It safely handles circular references by replacing them with the string "[Circular]" and never mutates the input. This makes it ideal for creating stable cache keys, hashing inputs, logging, or equality checks that should ignore object key order.
363
+
364
+ ```typescript
365
+ // Example usage
366
+ import { stableStringify } from "./stable-stringify";
367
+
368
+ // Key order does not affect the output
369
+ const a = { b: 2, a: 1, nested: { y: 2, x: 1 } };
370
+ const b = { nested: { x: 1, y: 2 }, a: 1, b: 2 };
371
+
372
+ console.log(stableStringify(a));
373
+ // -> {"a":1,"b":2,"nested":{"x":1,"y":2}}
374
+
375
+ console.log(stableStringify(b));
376
+ // -> {"a":1,"b":2,"nested":{"x":1,"y":2}} // same as above
377
+
378
+ // Circular references are handled
379
+ const obj: any = { name: "root" };
380
+ obj.self = obj;
381
+
382
+ console.log(stableStringify(obj));
383
+ // -> {"name":"root","self":"[Circular]"}
384
+ ```
385
+
386
+ ## trimTrailingZeros
387
+
388
+ ```tsx
389
+ import { trimTrailingZeros } from "@uxf/core/utils/trimTrailingZeros";
390
+
391
+ const example = trimTrailingZeros("120,450"); /* returns "120,45" */
222
392
  ```
223
393
 
224
394
  ## Validators
@@ -228,3 +398,4 @@ import { Validator } from "@uxf/core";
228
398
  Validator.isEmail("...");
229
399
  Validator.isPhone("...");
230
400
  ```
401
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uxf/core",
3
- "version": "11.80.0",
3
+ "version": "11.85.0",
4
4
  "description": "UXF Core",
5
5
  "author": "Petr Vejvoda <vejvoda@uxf.cz>",
6
6
  "homepage": "https://gitlab.com/uxf-npm/core#readme",
@@ -0,0 +1 @@
1
+ export declare function deepEqualIgnoringKeyOrder(a: unknown, b: unknown): boolean;
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.deepEqualIgnoringKeyOrder = deepEqualIgnoringKeyOrder;
4
+ const stable_stringify_1 = require("./stable-stringify");
5
+ function deepEqualIgnoringKeyOrder(a, b) {
6
+ return (0, stable_stringify_1.stableStringify)(a) === (0, stable_stringify_1.stableStringify)(b);
7
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ // packages/core/utils/deep-equal-ingoring-key-order.test.ts
4
+ const deep_equal_ingoring_key_order_1 = require("./deep-equal-ingoring-key-order");
5
+ describe("deepEqualIgnoringKeyOrder", () => {
6
+ it("returns true for objects with same keys in different orders", () => {
7
+ const a = { b: 2, a: 1, c: 3 };
8
+ const b = { c: 3, b: 2, a: 1 };
9
+ expect((0, deep_equal_ingoring_key_order_1.deepEqualIgnoringKeyOrder)(a, b)).toBe(true);
10
+ });
11
+ it("returns true for nested objects with different key orders", () => {
12
+ const a = { z: 0, nested: { b: 2, a: 1 }, another: { y: 2, x: 1 } };
13
+ const b = { another: { x: 1, y: 2 }, nested: { a: 1, b: 2 }, z: 0 };
14
+ expect((0, deep_equal_ingoring_key_order_1.deepEqualIgnoringKeyOrder)(a, b)).toBe(true);
15
+ });
16
+ it("returns true for arrays where items are objects with different key orders", () => {
17
+ const a = [
18
+ { b: 2, a: 1 },
19
+ { d: 4, c: 3 },
20
+ ];
21
+ const b = [
22
+ { a: 1, b: 2 },
23
+ { c: 3, d: 4 },
24
+ ];
25
+ expect((0, deep_equal_ingoring_key_order_1.deepEqualIgnoringKeyOrder)(a, b)).toBe(true);
26
+ });
27
+ it("returns false when array order differs", () => {
28
+ const a = [
29
+ { a: 1, b: 2 },
30
+ { c: 3, d: 4 },
31
+ ];
32
+ const b = [
33
+ { c: 3, d: 4 },
34
+ { a: 1, b: 2 },
35
+ ];
36
+ expect((0, deep_equal_ingoring_key_order_1.deepEqualIgnoringKeyOrder)(a, b)).toBe(false);
37
+ });
38
+ it("returns false when values differ", () => {
39
+ const a = { a: 1, b: 2 };
40
+ const b = { a: 1, b: 3 };
41
+ expect((0, deep_equal_ingoring_key_order_1.deepEqualIgnoringKeyOrder)(a, b)).toBe(false);
42
+ });
43
+ it("returns false for different types", () => {
44
+ expect((0, deep_equal_ingoring_key_order_1.deepEqualIgnoringKeyOrder)(1, "1")).toBe(false);
45
+ expect((0, deep_equal_ingoring_key_order_1.deepEqualIgnoringKeyOrder)(true, "true")).toBe(false);
46
+ expect((0, deep_equal_ingoring_key_order_1.deepEqualIgnoringKeyOrder)(null, {})).toBe(false);
47
+ });
48
+ it("returns true for identical primitives", () => {
49
+ expect((0, deep_equal_ingoring_key_order_1.deepEqualIgnoringKeyOrder)(null, null)).toBe(true);
50
+ expect((0, deep_equal_ingoring_key_order_1.deepEqualIgnoringKeyOrder)(42, 42)).toBe(true);
51
+ expect((0, deep_equal_ingoring_key_order_1.deepEqualIgnoringKeyOrder)(true, true)).toBe(true);
52
+ expect((0, deep_equal_ingoring_key_order_1.deepEqualIgnoringKeyOrder)("hello", "hello")).toBe(true);
53
+ });
54
+ it("handles equal circular object references as equal", () => {
55
+ const a = { name: "root" };
56
+ a.self = a;
57
+ const b = { name: "root" };
58
+ b.self = b;
59
+ expect((0, deep_equal_ingoring_key_order_1.deepEqualIgnoringKeyOrder)(a, b)).toBe(true);
60
+ });
61
+ it("distinguishes different circular shapes", () => {
62
+ const x = { a: 1 };
63
+ x.self = x; // self points to parent
64
+ const y = { a: 1, self: {} };
65
+ y.self.self = y.self; // self points to itself (nested circular)
66
+ expect((0, deep_equal_ingoring_key_order_1.deepEqualIgnoringKeyOrder)(x, y)).toBe(false);
67
+ });
68
+ it("works with nested circular structures", () => {
69
+ const a = { id: 1, child: { id: 2 } };
70
+ a.child.parent = a;
71
+ const b = { child: { id: 2 }, id: 1 };
72
+ b.child.parent = b;
73
+ expect((0, deep_equal_ingoring_key_order_1.deepEqualIgnoringKeyOrder)(a, b)).toBe(true);
74
+ });
75
+ });
@@ -0,0 +1 @@
1
+ export declare function stableStringify(value: unknown): string;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.stableStringify = stableStringify;
4
+ function stableStringify(value) {
5
+ const seen = new WeakSet();
6
+ function normalize(val) {
7
+ if (val && typeof val === "object") {
8
+ if (seen.has(val)) {
9
+ return "[Circular]";
10
+ }
11
+ seen.add(val);
12
+ if (Array.isArray(val)) {
13
+ return val.map(normalize);
14
+ }
15
+ // plain object: sort keys
16
+ const out = {};
17
+ for (const key of Object.keys(val).sort()) {
18
+ out[key] = normalize(val[key]);
19
+ }
20
+ return out;
21
+ }
22
+ return val;
23
+ }
24
+ return JSON.stringify(normalize(value));
25
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const stable_stringify_1 = require("./stable-stringify");
4
+ describe("stableStringify", () => {
5
+ it("produces identical strings for objects with the same keys in different orders", () => {
6
+ const a = { b: 2, a: 1, c: 3 };
7
+ const b = { c: 3, b: 2, a: 1 };
8
+ expect((0, stable_stringify_1.stableStringify)(a)).toBe((0, stable_stringify_1.stableStringify)(b));
9
+ expect((0, stable_stringify_1.stableStringify)(a)).toBe('{"a":1,"b":2,"c":3}');
10
+ });
11
+ it("sorts keys recursively in nested objects", () => {
12
+ const a = { z: 0, nested: { b: 2, a: 1 }, another: { y: 2, x: 1 } };
13
+ const b = { another: { x: 1, y: 2 }, nested: { a: 1, b: 2 }, z: 0 };
14
+ const sa = (0, stable_stringify_1.stableStringify)(a);
15
+ const sb = (0, stable_stringify_1.stableStringify)(b);
16
+ expect(sa).toBe(sb);
17
+ expect(sa).toBe('{"another":{"x":1,"y":2},"nested":{"a":1,"b":2},"z":0}');
18
+ });
19
+ it("normalizes arrays while preserving array order", () => {
20
+ const a = [
21
+ { b: 2, a: 1 },
22
+ { d: 4, c: 3 },
23
+ ];
24
+ const b = [
25
+ { a: 1, b: 2 },
26
+ { c: 3, d: 4 },
27
+ ];
28
+ expect((0, stable_stringify_1.stableStringify)(a)).toBe((0, stable_stringify_1.stableStringify)(b));
29
+ expect((0, stable_stringify_1.stableStringify)(a)).toBe('[{"a":1,"b":2},{"c":3,"d":4}]');
30
+ });
31
+ it("handles circular references in objects", () => {
32
+ const obj = { name: "root" };
33
+ obj.self = obj;
34
+ expect((0, stable_stringify_1.stableStringify)(obj)).toBe('{"name":"root","self":"[Circular]"}');
35
+ });
36
+ it("handles circular references in arrays", () => {
37
+ const arr = [];
38
+ arr.push(arr);
39
+ expect((0, stable_stringify_1.stableStringify)(arr)).toBe('["[Circular]"]');
40
+ });
41
+ it("handles mixed nested circular structures", () => {
42
+ const a = { id: 1, child: { id: 2 } };
43
+ a.child.parent = a;
44
+ const str = (0, stable_stringify_1.stableStringify)(a);
45
+ expect(str).toBe('{"child":{"id":2,"parent":"[Circular]"},"id":1}');
46
+ });
47
+ it("handles primitives", () => {
48
+ expect((0, stable_stringify_1.stableStringify)(null)).toBe("null");
49
+ expect((0, stable_stringify_1.stableStringify)(42)).toBe("42");
50
+ expect((0, stable_stringify_1.stableStringify)(true)).toBe("true");
51
+ expect((0, stable_stringify_1.stableStringify)("hello")).toBe('"hello"');
52
+ });
53
+ it("does not mutate the input object", () => {
54
+ const input = { b: 2, a: 1, nested: { y: 2, x: 1 } };
55
+ const snapshot = JSON.parse(JSON.stringify(input));
56
+ void (0, stable_stringify_1.stableStringify)(input);
57
+ expect(input).toEqual(snapshot);
58
+ });
59
+ });