@synnaxlabs/x 0.7.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 (164) hide show
  1. package/.eslintrc.cjs +18 -0
  2. package/.turbo/turbo-build.log +16 -0
  3. package/.vscode/settings.json +3 -0
  4. package/LICENSE +4 -0
  5. package/README.md +44 -0
  6. package/dist/binary/encoder.d.ts +59 -0
  7. package/dist/binary/encoder.spec.d.ts +1 -0
  8. package/dist/binary/index.d.ts +1 -0
  9. package/dist/case.d.ts +5 -0
  10. package/dist/change/change.d.ts +32 -0
  11. package/dist/change/index.d.ts +1 -0
  12. package/dist/clamp.d.ts +1 -0
  13. package/dist/compare/compare.d.ts +39 -0
  14. package/dist/compare/index.d.ts +1 -0
  15. package/dist/debounce.d.ts +2 -0
  16. package/dist/deep/copy.d.ts +1 -0
  17. package/dist/deep/delete.d.ts +2 -0
  18. package/dist/deep/delete.spec.d.ts +1 -0
  19. package/dist/deep/equal.d.ts +8 -0
  20. package/dist/deep/equal.spec.d.ts +1 -0
  21. package/dist/deep/external.d.ts +7 -0
  22. package/dist/deep/index.d.ts +1 -0
  23. package/dist/deep/key.d.ts +30 -0
  24. package/dist/deep/memo.d.ts +1 -0
  25. package/dist/deep/merge.d.ts +2 -0
  26. package/dist/deep/merge.spec.d.ts +1 -0
  27. package/dist/deep/partial.d.ts +3 -0
  28. package/dist/destructor.d.ts +1 -0
  29. package/dist/identity.d.ts +1 -0
  30. package/dist/index.d.ts +25 -0
  31. package/dist/join.d.ts +1 -0
  32. package/dist/kv/index.d.ts +1 -0
  33. package/dist/kv/types.d.ts +32 -0
  34. package/dist/mock/MockGLBufferController.d.ts +20 -0
  35. package/dist/mock/index.d.ts +1 -0
  36. package/dist/observe/index.d.ts +1 -0
  37. package/dist/observe/observe.d.ts +11 -0
  38. package/dist/optional.d.ts +1 -0
  39. package/dist/primitive.d.ts +8 -0
  40. package/dist/record.d.ts +14 -0
  41. package/dist/renderable.d.ts +4 -0
  42. package/dist/runtime/detect.d.ts +9 -0
  43. package/dist/runtime/external.d.ts +2 -0
  44. package/dist/runtime/index.d.ts +1 -0
  45. package/dist/runtime/os.d.ts +9 -0
  46. package/dist/search.d.ts +15 -0
  47. package/dist/spatial/base.d.ts +102 -0
  48. package/dist/spatial/bounds.d.ts +31 -0
  49. package/dist/spatial/bounds.spec.d.ts +1 -0
  50. package/dist/spatial/box.d.ts +265 -0
  51. package/dist/spatial/box.spec.d.ts +1 -0
  52. package/dist/spatial/dimensions.d.ts +59 -0
  53. package/dist/spatial/dimensions.spec.d.ts +1 -0
  54. package/dist/spatial/direction.d.ts +10 -0
  55. package/dist/spatial/direction.spec.d.ts +1 -0
  56. package/dist/spatial/external.d.ts +8 -0
  57. package/dist/spatial/index.d.ts +1 -0
  58. package/dist/spatial/location.d.ts +52 -0
  59. package/dist/spatial/location.spec.d.ts +1 -0
  60. package/dist/spatial/position.d.ts +2 -0
  61. package/dist/spatial/scale.d.ts +241 -0
  62. package/dist/spatial/scale.spec.d.ts +1 -0
  63. package/dist/spatial/spatial.d.ts +1 -0
  64. package/dist/spatial/xy.d.ts +116 -0
  65. package/dist/spatial/xy.spec.d.ts +1 -0
  66. package/dist/telem/encode.d.ts +1 -0
  67. package/dist/telem/generate.d.ts +2 -0
  68. package/dist/telem/gl.d.ts +11 -0
  69. package/dist/telem/index.d.ts +3 -0
  70. package/dist/telem/series.d.ts +104 -0
  71. package/dist/telem/series.spec.d.ts +1 -0
  72. package/dist/telem/telem.d.ts +633 -0
  73. package/dist/telem/telem.spec.d.ts +1 -0
  74. package/dist/toArray.d.ts +1 -0
  75. package/dist/transform.d.ts +1 -0
  76. package/dist/unique.d.ts +1 -0
  77. package/dist/url/index.d.ts +1 -0
  78. package/dist/url/url.d.ts +46 -0
  79. package/dist/url/url.spec.d.ts +1 -0
  80. package/dist/worker/worker.d.ts +32 -0
  81. package/dist/worker/worker.spec.d.ts +1 -0
  82. package/dist/x.cjs.js +9046 -0
  83. package/dist/x.cjs.js.map +1 -0
  84. package/dist/x.es.js +9047 -0
  85. package/dist/x.es.js.map +1 -0
  86. package/package.json +42 -0
  87. package/src/binary/encoder.spec.ts +31 -0
  88. package/src/binary/encoder.ts +118 -0
  89. package/src/binary/index.ts +10 -0
  90. package/src/case.ts +31 -0
  91. package/src/change/change.ts +31 -0
  92. package/src/change/index.ts +10 -0
  93. package/src/clamp.ts +14 -0
  94. package/src/compare/compare.ts +116 -0
  95. package/src/compare/index.ts +10 -0
  96. package/src/debounce.ts +45 -0
  97. package/src/deep/copy.ts +13 -0
  98. package/src/deep/delete.spec.ts +36 -0
  99. package/src/deep/delete.ts +27 -0
  100. package/src/deep/equal.spec.ts +82 -0
  101. package/src/deep/equal.ts +65 -0
  102. package/src/deep/external.ts +15 -0
  103. package/src/deep/index.ts +10 -0
  104. package/src/deep/key.ts +46 -0
  105. package/src/deep/merge.spec.ts +63 -0
  106. package/src/deep/merge.ts +41 -0
  107. package/src/deep/partial.ts +14 -0
  108. package/src/destructor.ts +10 -0
  109. package/src/identity.ts +14 -0
  110. package/src/index.ts +34 -0
  111. package/src/join.ts +14 -0
  112. package/src/kv/index.ts +10 -0
  113. package/src/kv/types.ts +52 -0
  114. package/src/mock/MockGLBufferController.ts +70 -0
  115. package/src/mock/index.ts +10 -0
  116. package/src/observe/index.ts +10 -0
  117. package/src/observe/observe.ts +33 -0
  118. package/src/optional.ts +10 -0
  119. package/src/primitive.ts +46 -0
  120. package/src/record.ts +45 -0
  121. package/src/renderable.ts +20 -0
  122. package/src/runtime/detect.ts +34 -0
  123. package/src/runtime/external.ts +11 -0
  124. package/src/runtime/index.ts +10 -0
  125. package/src/runtime/os.ts +38 -0
  126. package/src/search.ts +40 -0
  127. package/src/spatial/base.ts +80 -0
  128. package/src/spatial/bounds.spec.ts +99 -0
  129. package/src/spatial/bounds.ts +80 -0
  130. package/src/spatial/box.spec.ts +137 -0
  131. package/src/spatial/box.ts +326 -0
  132. package/src/spatial/dimensions.spec.ts +47 -0
  133. package/src/spatial/dimensions.ts +64 -0
  134. package/src/spatial/direction.spec.ts +25 -0
  135. package/src/spatial/direction.ts +47 -0
  136. package/src/spatial/external.ts +17 -0
  137. package/src/spatial/index.ts +10 -0
  138. package/src/spatial/location.spec.ts +24 -0
  139. package/src/spatial/location.ts +124 -0
  140. package/src/spatial/position.ts +26 -0
  141. package/src/spatial/scale.spec.ts +74 -0
  142. package/src/spatial/scale.ts +351 -0
  143. package/src/spatial/spatial.ts +17 -0
  144. package/src/spatial/xy.spec.ts +68 -0
  145. package/src/spatial/xy.ts +164 -0
  146. package/src/telem/encode.ts +22 -0
  147. package/src/telem/generate.ts +19 -0
  148. package/src/telem/gl.ts +22 -0
  149. package/src/telem/index.ts +12 -0
  150. package/src/telem/series.spec.ts +289 -0
  151. package/src/telem/series.ts +449 -0
  152. package/src/telem/telem.spec.ts +302 -0
  153. package/src/telem/telem.ts +1237 -0
  154. package/src/toArray.ts +11 -0
  155. package/src/transform.ts +10 -0
  156. package/src/unique.ts +10 -0
  157. package/src/url/index.ts +10 -0
  158. package/src/url/url.spec.ts +47 -0
  159. package/src/url/url.ts +113 -0
  160. package/src/worker/worker.spec.ts +41 -0
  161. package/src/worker/worker.ts +86 -0
  162. package/tsconfig.json +7 -0
  163. package/tsconfig.vite.json +4 -0
  164. package/vite.config.ts +23 -0
@@ -0,0 +1,34 @@
1
+ // Copyright 2023 Synnax Labs, Inc.
2
+ //
3
+ // Use of this software is governed by the Business Source License included in the file
4
+ // licenses/BSL.txt.
5
+ //
6
+ // As of the Change Date specified in that file, in accordance with the Business Source
7
+ // License, use of this software will be governed by the Apache License, Version 2.0,
8
+ // included in the file licenses/APL.txt.
9
+
10
+ /** JavaScript runtime environments. */
11
+ export type Runtime = "browser" | "node" | "webworker";
12
+
13
+ /**
14
+ * Does best effort detection of the runtime environment.
15
+ *
16
+ * @returns The runtime environment.
17
+ */
18
+ export const detect = (): Runtime => {
19
+ if (
20
+ // eslint-disable-next-line @typescript-eslint/prefer-optional-chain
21
+ typeof process !== "undefined" &&
22
+ process.versions != null &&
23
+ process.versions.node != null
24
+ )
25
+ return "node";
26
+
27
+ // eslint-disable-next-line @typescript-eslint/prefer-optional-chain
28
+ if (typeof window === "undefined" || window.document === undefined)
29
+ return "webworker";
30
+
31
+ return "browser";
32
+ };
33
+
34
+ export const RUNTIME = detect();
@@ -0,0 +1,11 @@
1
+ // Copyright 2023 Synnax Labs, Inc.
2
+ //
3
+ // Use of this software is governed by the Business Source License included in the file
4
+ // licenses/BSL.txt.
5
+ //
6
+ // As of the Change Date specified in that file, in accordance with the Business Source
7
+ // License, use of this software will be governed by the Apache License, Version 2.0,
8
+ // included in the file licenses/APL.txt.
9
+
10
+ export * from "@/runtime/detect";
11
+ export * from "@/runtime/os";
@@ -0,0 +1,10 @@
1
+ // Copyright 2023 Synnax Labs, Inc.
2
+ //
3
+ // Use of this software is governed by the Business Source License included in the file
4
+ // licenses/BSL.txt.
5
+ //
6
+ // As of the Change Date specified in that file, in accordance with the Business Source
7
+ // License, use of this software will be governed by the Apache License, Version 2.0,
8
+ // included in the file licenses/APL.txt.
9
+
10
+ export * as runtime from "@/runtime/external";
@@ -0,0 +1,38 @@
1
+ // Copyright 2023 Synnax Labs, Inc.
2
+ //
3
+ // Use of this software is governed by the Business Source License included in the file
4
+ // licenses/BSL.txt.
5
+ //
6
+ // As of the Change Date specified in that file, in accordance with the Business Source
7
+ // License, use of this software will be governed by the Apache License, Version 2.0,
8
+ // included in the file licenses/APL.txt.
9
+
10
+ import { z } from "zod";
11
+
12
+ export const OPERATING_SYSTEMS = ["MacOS", "Windows", "Linux", "Docker"] as const;
13
+ export const osZ = z.enum(OPERATING_SYSTEMS);
14
+ export type OS = (typeof OPERATING_SYSTEMS)[number];
15
+
16
+ export interface GetOSProps {
17
+ force?: OS;
18
+ default?: OS;
19
+ }
20
+
21
+ let os: OS | undefined;
22
+
23
+ const evalOS = (): OS | undefined => {
24
+ if (typeof window === "undefined") return undefined;
25
+ const userAgent = window.navigator.userAgent.toLowerCase();
26
+ if (userAgent.includes("mac")) return "MacOS";
27
+ else if (userAgent.includes("win")) return "Windows";
28
+ else if (userAgent.includes("linux")) return "Linux";
29
+ return undefined;
30
+ };
31
+
32
+ export const getOS = (props: GetOSProps = {}): OS | undefined => {
33
+ const { force, default: default_ } = props;
34
+ if (force != null) return force;
35
+ if (os != null) return os;
36
+ os = evalOS();
37
+ return os ?? default_;
38
+ };
package/src/search.ts ADDED
@@ -0,0 +1,40 @@
1
+ // Copyright 2023 Synnax Labs, Inc.
2
+ //
3
+ // Use of this software is governed by the Business Source License included in the file
4
+ // licenses/BSL.txt.
5
+ //
6
+ // As of the Change Date specified in that file, in accordance with the Business Source
7
+ // License, use of this software will be governed by the Apache License, Version 2.0,
8
+ // included in the file licenses/APL.txt.
9
+
10
+ import { type CompareF } from "@/compare/compare";
11
+ import { type Key, type KeyedRecord } from "@/record";
12
+
13
+ const binary = <T>(arr: T[], target: T, compare: CompareF<T>): number => {
14
+ let left = 0;
15
+ let right = arr.length - 1;
16
+ while (left <= right) {
17
+ const mid = Math.floor((left + right) / 2);
18
+ const cmp = compare(arr[mid], target);
19
+ if (cmp === 0) return mid;
20
+ if (cmp < 0) left = mid + 1;
21
+ else right = mid - 1;
22
+ }
23
+ return -1;
24
+ };
25
+
26
+ export const Search = {
27
+ binary,
28
+ };
29
+
30
+ export interface TermSearcher<T, K extends Key, E extends KeyedRecord<K, E>> {
31
+ search: (term: T) => E[];
32
+ retrieve: (keys: K[]) => E[];
33
+ page: (offset: number, limit: number) => E[];
34
+ }
35
+
36
+ export interface AsyncTermSearcher<T, K extends Key, E extends KeyedRecord<K, E>> {
37
+ search: (term: T) => Promise<E[]>;
38
+ retrieve: (keys: K[]) => Promise<E[]>;
39
+ page: (offset: number, limit: number) => Promise<E[]>;
40
+ }
@@ -0,0 +1,80 @@
1
+ // Copyright 2023 Synnax Labs, Inc.
2
+ //
3
+ // Use of this software is governed by the Business Source License included in the file
4
+ // licenses/BSL.txt.
5
+ //
6
+ // As of the Change Date specified in that file, in accordance with the Business Source
7
+ // License, use of this software will be governed by the Apache License, Version 2.0,
8
+ // included in the file licenses/APL.txt.
9
+
10
+ import { z } from "zod";
11
+
12
+ export const numberCouple = z.tuple([z.number(), z.number()]);
13
+ export type NumberCouple = z.infer<typeof numberCouple>;
14
+
15
+ // Dimensions
16
+
17
+ export const dimensions = z.object({ width: z.number(), height: z.number() });
18
+ export type Dimensions = z.infer<typeof dimensions>;
19
+ export const signedDimensions = z.object({
20
+ signedWidth: z.number(),
21
+ signedHeight: z.number(),
22
+ });
23
+ export const DIMENSIONS = ["width", "height"] as const;
24
+ export const dimension = z.enum(DIMENSIONS);
25
+ export type Dimension = (typeof DIMENSIONS)[number];
26
+ export const ALIGNMENTS = ["start", "center", "end"] as const;
27
+ export const SIGNED_DIMENSIONS = ["signedWidth", "signedHeight"] as const;
28
+ export const signedDimension = z.enum(SIGNED_DIMENSIONS);
29
+ export type SignedDimension = (typeof SIGNED_DIMENSIONS)[number];
30
+
31
+ // XY
32
+
33
+ export const xy = z.object({ x: z.number(), y: z.number() });
34
+ export type XY = z.infer<typeof xy>;
35
+ export const clientXY = z.object({ clientX: z.number(), clientY: z.number() });
36
+ export type ClientXY = z.infer<typeof clientXY>;
37
+
38
+ // Direction
39
+
40
+ export const DIRECTIONS = ["x", "y"] as const;
41
+ export const direction = z.enum(DIRECTIONS);
42
+ export type Direction = z.infer<typeof direction>;
43
+
44
+ // Location
45
+
46
+ export const OUTER_LOCATIONS = ["top", "right", "bottom", "left"] as const;
47
+ export const outerLocation = z.enum(OUTER_LOCATIONS);
48
+ export type OuterLocation = (typeof OUTER_LOCATIONS)[number];
49
+ export const X_LOCATIONS = ["left", "right"] as const;
50
+ export const xLocation = z.enum(X_LOCATIONS);
51
+ export type XLocation = (typeof X_LOCATIONS)[number];
52
+ export const Y_LOCATIONS = ["top", "bottom"] as const;
53
+ export const yLocation = z.enum(Y_LOCATIONS);
54
+ export type YLocation = (typeof Y_LOCATIONS)[number];
55
+ export const CENTER_LOCATIONS = ["center"] as const;
56
+ export const centerlocation = z.enum(CENTER_LOCATIONS);
57
+ export type CenterLocation = (typeof CENTER_LOCATIONS)[number];
58
+ export const LOCATIONS = [...OUTER_LOCATIONS, ...CENTER_LOCATIONS] as const;
59
+ export const location = z.enum(LOCATIONS);
60
+ export type Location = z.infer<typeof location>;
61
+
62
+ // Alignment
63
+
64
+ export const alignment = z.enum(ALIGNMENTS);
65
+ export type Alignment = (typeof ALIGNMENTS)[number];
66
+ export const ORDERS = ["first", "last"] as const;
67
+ export const order = z.enum(ORDERS);
68
+ export type Order = (typeof ORDERS)[number];
69
+
70
+ // Bounds
71
+
72
+ export const bounds = z.object({ lower: z.number(), upper: z.number() });
73
+ export type Bounds = z.infer<typeof bounds>;
74
+
75
+ export const crudeBounds = z.union([bounds, numberCouple]);
76
+ export type CrudeBounds = z.infer<typeof crudeBounds>;
77
+ export const crudeDirection = z.union([direction, location]);
78
+ export type CrudeDirection = z.infer<typeof crudeDirection>;
79
+ export const crudeLocation = z.union([direction, location, z.instanceof(String)]);
80
+ export type CrudeLocation = z.infer<typeof crudeLocation>;
@@ -0,0 +1,99 @@
1
+ // Copyright 2023 Synnax Labs, Inc.
2
+ //
3
+ // Use of this software is governed by the Business Source License included in the file
4
+ // licenses/BSL.txt.
5
+ //
6
+ // As of the Change Date specified in that file, in accordance with the Business Source
7
+ // License, use of this software will be governed by the Apache License, Version 2.0,
8
+ // included in the file licenses/APL.txt.
9
+
10
+ import { describe, expect, it, test } from "vitest";
11
+
12
+ import * as bounds from "@/spatial/bounds";
13
+
14
+ describe("Bounds", () => {
15
+ describe("construction", () => {
16
+ type T = [string, bounds.Crude];
17
+ const TESTS: T[] = [
18
+ ["from couple", [1, 2]],
19
+ ["from bounds", { lower: 1, upper: 2 }],
20
+ ["from invalid bounds", { upper: 1, lower: 2 }],
21
+ ];
22
+ TESTS.forEach(([name, arg]) => {
23
+ test(name, () => {
24
+ const bound = bounds.construct(arg);
25
+ expect(bound.lower).toEqual(1);
26
+ expect(bound.upper).toEqual(2);
27
+ });
28
+ });
29
+ it("should consider a single argument as the upper bound", () => {
30
+ const bound = bounds.construct(1);
31
+ expect(bound.lower).toEqual(0);
32
+ expect(bound.upper).toEqual(1);
33
+ });
34
+ });
35
+ describe("contains", () => {
36
+ it("should return true if the value is within the bounds", () => {
37
+ const b = bounds.construct([1, 2]);
38
+ expect(bounds.contains(b, 1.5)).toEqual(true);
39
+ });
40
+ it("should return false if the value is outside the bounds", () => {
41
+ const b = bounds.construct([1, 2]);
42
+ expect(bounds.contains(b, 2.5)).toEqual(false);
43
+ });
44
+ it("should return true if the value is equal to the lower bound", () => {
45
+ const b = bounds.construct([1, 2]);
46
+ expect(bounds.contains(b, 1)).toEqual(true);
47
+ });
48
+ it("should return false if the value is equal to the upper bound", () => {
49
+ const b = bounds.construct([1, 2]);
50
+ expect(bounds.contains(b, 2)).toEqual(false);
51
+ });
52
+ });
53
+ describe("span", () => {
54
+ it("should return the span of the bound", () => {
55
+ const b = bounds.construct([1, 2]);
56
+ expect(bounds.span(b)).toEqual(1);
57
+ });
58
+ });
59
+ test("isZero", () => {
60
+ const b = bounds.construct([0, 0]);
61
+ expect(bounds.isZero(b)).toEqual(true);
62
+ });
63
+ test("spanIsZero", () => {
64
+ const b = bounds.construct([1, 1]);
65
+ expect(bounds.spanIsZero(b)).toEqual(true);
66
+ });
67
+ describe("max", () => {
68
+ it("should return the bound with the maximum possible span", () => {
69
+ const args: bounds.Crude[] = [
70
+ [1, 2],
71
+ [-1, 1],
72
+ ];
73
+ const bound = bounds.max(args);
74
+ expect(bound.lower).toEqual(-1);
75
+ expect(bound.upper).toEqual(2);
76
+ });
77
+ });
78
+ describe("min", () => {
79
+ it("should return the bound with the minimum possible span", () => {
80
+ const args: bounds.Crude[] = [
81
+ [1, 2],
82
+ [-1, 1],
83
+ ];
84
+ const bound = bounds.min(args);
85
+ expect(bound.lower).toEqual(1);
86
+ expect(bound.upper).toEqual(1);
87
+ });
88
+ });
89
+ describe("isFinite", () => {
90
+ it("should return false if either bound is infinite", () => {
91
+ const b = bounds.construct([1, Infinity]);
92
+ expect(bounds.isFinite(b)).toEqual(false);
93
+ });
94
+ it("should return true if both bounds are finite", () => {
95
+ const b = bounds.construct([1, 2]);
96
+ expect(bounds.isFinite(b)).toEqual(true);
97
+ });
98
+ });
99
+ });
@@ -0,0 +1,80 @@
1
+ // Copyright 2023 Synnax Labs, Inc.
2
+ //
3
+ // Use of this software is governed by the Business Source License included in the file
4
+ // licenses/BSL.txt.
5
+ //
6
+ // As of the Change Date specified in that file, in accordance with the Business Source
7
+ // License, use of this software will be governed by the Apache License, Version 2.0,
8
+ // included in the file licenses/APL.txt.
9
+
10
+ import { type Bounds, bounds, type CrudeBounds } from "@/spatial/base";
11
+
12
+ export { type Bounds, bounds };
13
+
14
+ export type Crude = CrudeBounds;
15
+
16
+ export const construct = (lower: number | Crude, upper?: number): Bounds => {
17
+ const b = { lower: 0, upper: 0 };
18
+ if (typeof lower === "number") {
19
+ if (upper != null) {
20
+ b.lower = lower;
21
+ b.upper = upper;
22
+ } else {
23
+ b.lower = 0;
24
+ b.upper = lower;
25
+ }
26
+ } else if (Array.isArray(lower)) {
27
+ [b.lower, b.upper] = lower;
28
+ } else {
29
+ b.lower = lower.lower;
30
+ b.upper = lower.upper;
31
+ }
32
+ return makeValid(b);
33
+ };
34
+
35
+ export const ZERO = { lower: 0, upper: 0 };
36
+
37
+ export const INFINITE = { lower: -Infinity, upper: Infinity };
38
+
39
+ export const DECIMAL = { lower: 0, upper: 1 };
40
+
41
+ export const CLIP = { lower: -1, upper: 1 };
42
+
43
+ export const equals = (a?: Bounds, b?: Bounds): boolean =>
44
+ a?.lower === b?.lower && a?.upper === b?.upper;
45
+
46
+ export const makeValid = (a: Bounds): Bounds => {
47
+ if (a.lower > a.upper) return { lower: a.upper, upper: a.lower };
48
+ return a;
49
+ };
50
+
51
+ export const clamp = (bounds: Bounds, target: number): number => {
52
+ if (target < bounds.lower) return bounds.lower;
53
+ if (target >= bounds.upper) return bounds.upper - 1;
54
+ return target;
55
+ };
56
+
57
+ export const contains = (bounds: Bounds, target: number): boolean =>
58
+ target >= bounds.lower && target < bounds.upper;
59
+
60
+ export const overlapsWith = (a: Bounds, b: Bounds): boolean =>
61
+ contains(a, a.lower) || contains(b, b.upper - 1);
62
+
63
+ export const span = (a: Bounds): number => a.upper - a.lower;
64
+
65
+ export const isZero = (a: Bounds): boolean => a.lower === 0 && a.upper === 0;
66
+
67
+ export const spanIsZero = (a: Bounds): boolean => span(a) === 0;
68
+
69
+ export const isFinite = (a: Bounds): boolean =>
70
+ Number.isFinite(a.lower) && Number.isFinite(a.upper);
71
+
72
+ export const max = (bounds: Crude[]): Bounds => ({
73
+ lower: Math.min(...bounds.map((b) => construct(b).lower)),
74
+ upper: Math.max(...bounds.map((b) => construct(b).upper)),
75
+ });
76
+
77
+ export const min = (bounds: Crude[]): Bounds => ({
78
+ lower: Math.max(...bounds.map((b) => construct(b).lower)),
79
+ upper: Math.min(...bounds.map((b) => construct(b).upper)),
80
+ });
@@ -0,0 +1,137 @@
1
+ // Copyright 2023 Synnax Labs, Inc.
2
+ //
3
+ // Use of this software is governed by the Business Source License included in the file
4
+ // licenses/BSL.txt.
5
+ //
6
+ // As of the Change Date specified in that file, in accordance with the Business Source
7
+ // License, use of this software will be governed by the Apache License, Version 2.0,
8
+ // included in the file licenses/APL.txt.
9
+
10
+ import { describe, test, expect, it } from "vitest";
11
+
12
+ import * as box from "@/spatial/box";
13
+ import * as location from "@/spatial/location";
14
+ import type * as xy from "@/spatial/xy";
15
+
16
+ describe("Box", () => {
17
+ describe("construction", () => {
18
+ test("from dom rect", () => {
19
+ const b = box.construct({ left: 0, top: 0, right: 10, bottom: 10 });
20
+ expect(box.topLeft(b)).toEqual({ x: 0, y: 0 });
21
+ expect(box.topRight(b)).toEqual({ x: 10, y: 0 });
22
+ expect(box.bottomLeft(b)).toEqual({ x: 0, y: 10 });
23
+ expect(box.bottomRight(b)).toEqual({ x: 10, y: 10 });
24
+ });
25
+ test("from two points", () => {
26
+ const b = box.construct({ x: 0, y: 0 }, { x: 10, y: 10 });
27
+ expect(box.topLeft(b)).toEqual({ x: 0, y: 0 });
28
+ expect(box.topRight(b)).toEqual({ x: 10, y: 0 });
29
+ expect(box.bottomLeft(b)).toEqual({ x: 0, y: 10 });
30
+ expect(box.bottomRight(b)).toEqual({ x: 10, y: 10 });
31
+ });
32
+ test("from point and dimensions", () => {
33
+ const b = box.construct({ x: 0, y: 0 }, { width: 10, height: 10 });
34
+ expect(box.topLeft(b)).toEqual({ x: 0, y: 0 });
35
+ expect(box.topRight(b)).toEqual({ x: 10, y: 0 });
36
+ expect(box.bottomLeft(b)).toEqual({ x: 0, y: 10 });
37
+ expect(box.bottomRight(b)).toEqual({ x: 10, y: 10 });
38
+ });
39
+ test("from point and width and height", () => {
40
+ const b = box.construct({ x: 0, y: 0 }, 10, 10);
41
+ expect(box.topLeft(b)).toEqual({ x: 0, y: 0 });
42
+ expect(box.topRight(b)).toEqual({ x: 10, y: 0 });
43
+ expect(box.bottomLeft(b)).toEqual({ x: 0, y: 10 });
44
+ expect(box.bottomRight(b)).toEqual({ x: 10, y: 10 });
45
+ });
46
+ test("from raw params", () => {
47
+ const b = box.construct(0, 0, 10, 10);
48
+ expect(box.topLeft(b)).toEqual({ x: 0, y: 0 });
49
+ expect(box.topRight(b)).toEqual({ x: 10, y: 0 });
50
+ expect(box.bottomLeft(b)).toEqual({ x: 0, y: 10 });
51
+ expect(box.bottomRight(b)).toEqual({ x: 10, y: 10 });
52
+ });
53
+ });
54
+ describe("zod schema", () => {
55
+ const CASES: Array<[string, unknown]> = [
56
+ [
57
+ "raw string root",
58
+ {
59
+ root: { x: "left", y: "top" },
60
+ one: { x: 0, y: 0 },
61
+ two: { x: 10, y: 10 },
62
+ },
63
+ ],
64
+ [
65
+ "string instance root",
66
+ {
67
+ root: { x: String("left"), y: String("top") },
68
+ one: { x: 0, y: 0 },
69
+ two: { x: 10, y: 10 },
70
+ },
71
+ ],
72
+ ];
73
+ CASES.forEach(([title, value]) => {
74
+ it(`should parse ${title}`, () => {
75
+ expect(() => box.box.parse(value)).not.toThrow();
76
+ });
77
+ });
78
+ });
79
+ describe("properties", () => {
80
+ const b = box.construct(20, 30, 40, 50);
81
+ describe("loc", () => {
82
+ const v: location.Location[] = ["left", "right", "top", "bottom"];
83
+ const expected: number[] = [20, 60, 30, 80];
84
+ v.forEach((v, i) => {
85
+ test(`loc-${v}`, () => expect(box.loc(b, v)).toEqual(expected[i]));
86
+ });
87
+ });
88
+ describe("xyLoc", () => {
89
+ const v: location.XY[] = [
90
+ location.BOTTOM_CENTER,
91
+ location.LEFT_CENTER,
92
+ location.RIGHT_CENTER,
93
+ location.TOP_CENTER,
94
+ location.BOTTOM_LEFT,
95
+ location.BOTTOM_RIGHT,
96
+ location.TOP_LEFT,
97
+ location.TOP_RIGHT,
98
+ ];
99
+ const expected: xy.Crude[] = [
100
+ { x: 40, y: 80 },
101
+ { x: 20, y: 55 },
102
+ { x: 60, y: 55 },
103
+ { x: 40, y: 30 },
104
+ { x: 20, y: 80 },
105
+ { x: 60, y: 80 },
106
+ { x: 20, y: 30 },
107
+ { x: 60, y: 30 },
108
+ ];
109
+ v.forEach((v, i) => {
110
+ test(`xyLoc-${location.xyToString(v)}`, () =>
111
+ expect(box.xyLoc(b, v)).toEqual(expected[i]));
112
+ });
113
+ });
114
+ });
115
+ describe("equality", () => {
116
+ it("should be equal to itself", () => {
117
+ const b = box.construct(0, 0, 10, 10);
118
+ expect(box.equals(b, b)).toBe(true);
119
+ });
120
+ it("should be equal to a box with the same values", () => {
121
+ const b = box.construct(0, 0, 10, 10);
122
+ const b2 = box.construct(0, 0, 10, 10);
123
+ expect(box.equals(b, b2)).toBe(true);
124
+ });
125
+ });
126
+ describe("positionInCenterOf", () => {
127
+ it("should position the box in the center of the other box", () => {
128
+ let b = box.construct(0, 0, 10, 10);
129
+ const b2 = box.construct(0, 0, 20, 20);
130
+ b = box.positionInCenter(b, b2);
131
+ expect(box.topLeft(b)).toEqual({ x: 5, y: 5 });
132
+ expect(box.topRight(b)).toEqual({ x: 15, y: 5 });
133
+ expect(box.bottomLeft(b)).toEqual({ x: 5, y: 15 });
134
+ expect(box.bottomRight(b)).toEqual({ x: 15, y: 15 });
135
+ });
136
+ });
137
+ });