@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,326 @@
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
+ import type * as bounds from "@/spatial/bounds";
13
+ import type * as dimensions from "@/spatial/dimensions";
14
+ import * as direction from "@/spatial/direction";
15
+ import * as location from "@/spatial/location";
16
+ import * as xy from "@/spatial/xy";
17
+
18
+ const cssPos = z.union([z.number(), z.string()]);
19
+
20
+ const cssBox = z.object({
21
+ top: cssPos,
22
+ left: cssPos,
23
+ width: cssPos,
24
+ height: cssPos,
25
+ });
26
+ const domRect = z.object({
27
+ left: z.number(),
28
+ top: z.number(),
29
+ right: z.number(),
30
+ bottom: z.number(),
31
+ });
32
+ export const box = z.object({
33
+ one: xy.xy,
34
+ two: xy.xy,
35
+ root: location.corner,
36
+ });
37
+
38
+ export type Box = z.infer<typeof box>;
39
+ export type CSS = z.infer<typeof cssBox>;
40
+ export type DOMRect = z.infer<typeof domRect>;
41
+
42
+ type Crude = DOMRect | Box | { getBoundingClientRect: () => DOMRect };
43
+
44
+ /** A box centered at (0,0) with a width and height of 0. */
45
+ export const ZERO = { one: xy.ZERO, two: xy.ZERO, root: location.TOP_LEFT };
46
+
47
+ /**
48
+ * A box centered at (0,0) with a width and height of 1, and rooted in the
49
+ * bottom left. Note that pixel space is typically rooted in the top left.
50
+ */
51
+ export const DECIMAL = { one: xy.ZERO, two: xy.ONE, root: location.BOTTOM_LEFT };
52
+
53
+ export const copy = (b: Box, root?: location.CornerXY): Box => ({
54
+ one: b.one,
55
+ two: b.two,
56
+ root: root ?? b.root,
57
+ });
58
+
59
+ /**
60
+ * Box represents a general box in 2D space. It typically represents a bounding box
61
+ * for a DOM element, but can also represent a box in clip space or decimal space.
62
+ *
63
+ * It'simportant to note that the behavior of a Box varies depending on its coordinate
64
+ * system.Make sure you're aware of which coordinate system you're using.
65
+ *
66
+ * Many of the properties and methods on a Box access the same semantic value. The
67
+ * different accessors are there for ease of use and semantics.
68
+ */
69
+ export const construct = (
70
+ first: number | DOMRect | xy.XY | Box | { getBoundingClientRect: () => DOMRect },
71
+ second?: number | xy.XY | dimensions.Dimensions | dimensions.Signed,
72
+ width: number = 0,
73
+ height: number = 0,
74
+ coordinateRoot?: location.CornerXY,
75
+ ): Box => {
76
+ const b: Box = {
77
+ one: { ...xy.ZERO },
78
+ two: { ...xy.ZERO },
79
+ root: coordinateRoot ?? location.TOP_LEFT,
80
+ };
81
+
82
+ if (typeof first === "number") {
83
+ if (typeof second !== "number")
84
+ throw new Error("Box constructor called with invalid arguments");
85
+ b.one = { x: first, y: second };
86
+ b.two = { x: b.one.x + width, y: b.one.y + height };
87
+ return b;
88
+ }
89
+
90
+ if ("one" in first && "two" in first && "root" in first)
91
+ return { ...first, root: coordinateRoot ?? first.root };
92
+
93
+ if ("getBoundingClientRect" in first) first = first.getBoundingClientRect();
94
+ if ("left" in first) {
95
+ b.one = { x: first.left, y: first.top };
96
+ b.two = { x: first.right, y: first.bottom };
97
+ return b;
98
+ }
99
+
100
+ b.one = first;
101
+ if (second == null) b.two = { x: b.one.x + width, y: b.one.y + height };
102
+ else if (typeof second === "number")
103
+ b.two = { x: b.one.x + second, y: b.one.y + width };
104
+ else if ("width" in second)
105
+ b.two = {
106
+ x: b.one.x + second.width,
107
+ y: b.one.y + second.height,
108
+ };
109
+ else if ("signedWidth" in second)
110
+ b.two = {
111
+ x: b.one.x + second.signedWidth,
112
+ y: b.one.y + second.signedHeight,
113
+ };
114
+ else b.two = second;
115
+ return b;
116
+ };
117
+
118
+ /**
119
+ * Checks if a box contains a point or another box.
120
+ *
121
+ * @param value - The point or box to check.
122
+ * @returns true if the box inclusively contains the point or box and false otherwise.
123
+ */
124
+ export const contains = (b: Crude, value: Box | xy.XY): boolean => {
125
+ const b_ = construct(b);
126
+ if ("one" in value)
127
+ return (
128
+ left(value) >= left(b_) &&
129
+ right(value) <= right(b_) &&
130
+ top(value) >= top(b_) &&
131
+ bottom(value) <= bottom(b_)
132
+ );
133
+ return (
134
+ value.x >= left(b_) &&
135
+ value.x <= right(b_) &&
136
+ value.y >= top(b_) &&
137
+ value.y <= bottom(b_)
138
+ );
139
+ };
140
+
141
+ /**
142
+ * @returns true if the given box is semantically equal to this box and false otherwise.
143
+ */
144
+ export const equals = (a: Box, b: Box): boolean =>
145
+ xy.equals(a.one, b.one) &&
146
+ xy.equals(a.two, b.two) &&
147
+ location.xyEquals(a.root, b.root);
148
+
149
+ /**
150
+ * @returns the dimensions of the box. Note that these dimensions are guaranteed to
151
+ * be positive. To get the signed dimensions, use the `signedDims` property.
152
+ */
153
+ export const dims = (b: Box): dimensions.Dimensions => ({
154
+ width: width(b),
155
+ height: height(b),
156
+ });
157
+
158
+ /**
159
+ * @returns the dimensions of the box. Note that these dimensions may be negative.
160
+ * To get the unsigned dimensions, use the `dims` property.
161
+ */
162
+ export const signedDims = (b: Box): dimensions.Signed => ({
163
+ signedWidth: signedWidth(b),
164
+ signedHeight: signedHeight(b),
165
+ });
166
+
167
+ /**
168
+ * @returns the css representation of the box.
169
+ */
170
+ export const css = (b: Box): CSS => ({
171
+ top: top(b),
172
+ left: left(b),
173
+ width: width(b),
174
+ height: height(b),
175
+ });
176
+
177
+ export const dim = (
178
+ b: Crude,
179
+ dir: direction.Crude,
180
+ signed: boolean = false,
181
+ ): number => {
182
+ const dim: number =
183
+ direction.construct(dir) === "y" ? signedHeight(b) : signedWidth(b);
184
+ return signed ? dim : Math.abs(dim);
185
+ };
186
+
187
+ /** @returns the pont corresponding to the given corner of the box. */
188
+ export const xyLoc = (b: Crude, l: location.XY): xy.XY => {
189
+ const b_ = construct(b);
190
+ return {
191
+ x: l.x === "center" ? center(b_).x : loc(b_, l.x),
192
+ y: l.y === "center" ? center(b_).y : loc(b_, l.y),
193
+ };
194
+ };
195
+
196
+ /**
197
+ * @returns a one dimensional coordinate corresponding to the location of the given
198
+ * side of the box i.e. the x coordinate of the left side, the y coordinate of the
199
+ * top side, etc.
200
+ */
201
+ export const loc = (b: Crude, loc: location.Location): number => {
202
+ const b_ = construct(b);
203
+ const f = location.xyCouple(b_.root).includes(loc) ? Math.min : Math.max;
204
+ return location.X_LOCATIONS.includes(loc as location.X)
205
+ ? f(b_.one.x, b_.two.x)
206
+ : f(b_.one.y, b_.two.y);
207
+ };
208
+
209
+ export const locPoint = (b: Box, loc_: location.Location): xy.XY => {
210
+ const l = loc(b, loc_);
211
+ if (location.X_LOCATIONS.includes(loc_ as location.X))
212
+ return { x: l, y: center(b).y };
213
+ return { x: center(b).x, y: l };
214
+ };
215
+
216
+ export const isZero = (b: Box): boolean => {
217
+ return b.one.x === b.two.x && b.one.y === b.two.y;
218
+ };
219
+
220
+ export const width = (b: Crude): number => dim(b, "x");
221
+
222
+ export const height = (b: Crude): number => dim(b, "y");
223
+
224
+ export const signedWidth = (b: Crude): number => {
225
+ const b_ = construct(b);
226
+ return b_.two.x - b_.one.x;
227
+ };
228
+
229
+ export const signedHeight = (b: Crude): number => {
230
+ const b_ = construct(b);
231
+ return b_.two.y - b_.one.y;
232
+ };
233
+
234
+ export const topLeft = (b: Crude): xy.XY => xyLoc(b, location.TOP_LEFT);
235
+
236
+ export const topRight = (b: Crude): xy.XY => xyLoc(b, location.TOP_RIGHT);
237
+
238
+ export const bottomLeft = (b: Crude): xy.XY => xyLoc(b, location.BOTTOM_LEFT);
239
+
240
+ export const bottomRight = (b: Crude): xy.XY => xyLoc(b, location.BOTTOM_RIGHT);
241
+
242
+ export const right = (b: Crude): number => loc(b, "right");
243
+
244
+ export const bottom = (b: Crude): number => loc(b, "bottom");
245
+
246
+ export const left = (b: Crude): number => loc(b, "left");
247
+
248
+ export const top = (b: Crude): number => loc(b, "top");
249
+
250
+ export const center = (b: Crude): xy.XY =>
251
+ xy.translate(topLeft(b), {
252
+ x: signedWidth(b) / 2,
253
+ y: signedHeight(b) / 2,
254
+ });
255
+
256
+ export const x = (b: Crude): number => {
257
+ const b_ = construct(b);
258
+ return b_.root.x === "left" ? left(b_) : right(b_);
259
+ };
260
+
261
+ export const y = (b: Crude): number => {
262
+ const b_ = construct(b);
263
+ return b_.root.y === "top" ? top(b_) : bottom(b_);
264
+ };
265
+
266
+ export const root = (b: Crude): xy.XY => ({ x: x(b), y: y(b) });
267
+
268
+ export const xBounds = (b: Crude): bounds.Bounds => {
269
+ const b_ = construct(b);
270
+ return { lower: b_.one.x, upper: b_.two.x };
271
+ };
272
+
273
+ export const yBounds = (b: Crude): bounds.Bounds => {
274
+ const b_ = construct(b);
275
+ return { lower: b_.one.y, upper: b_.two.y };
276
+ };
277
+
278
+ export const reRoot = (b: Box, corner: location.CornerXY): Box => copy(b, corner);
279
+
280
+ /**
281
+ * Reposition a box so that it is visible within a given bound.
282
+ *
283
+ * @param target The box to reposition - Only works if the root is topLeft
284
+ * @param bound The box to reposition within - Only works if the root is topLeft
285
+ *
286
+ * @returns the repsoitioned box and a boolean indicating if the box was repositioned
287
+ * or not.
288
+ */
289
+ export const positionSoVisible = (
290
+ target_: Crude,
291
+ bound_: Crude,
292
+ ): [Box, boolean] => {
293
+ const target = construct(target_);
294
+ const bound = construct(bound_);
295
+ if (contains(bound, target)) return [target, false];
296
+ let nextPos: xy.XY;
297
+ if (right(target) > width(target))
298
+ nextPos = xy.construct({ x: x(target) - width(target), y: y(target) });
299
+ else nextPos = xy.construct({ x: x(target), y: y(target) - height(target) });
300
+ return [construct(nextPos, dims(target)), true];
301
+ };
302
+
303
+ /**
304
+ * Reposition a box so that it is centered within a given bound.
305
+ *
306
+ * @param target The box to reposition - Only works if the root is topLeft
307
+ * @param bound The box to reposition within - Only works if the root is topLeft
308
+ * @returns the repsoitioned box
309
+ */
310
+ export const positionInCenter = (
311
+ target_: Crude,
312
+ bound_: Crude,
313
+ ): Box => {
314
+ const target = construct(target_);
315
+ const bound = construct(bound_);
316
+ const x_ = x(bound) + (width(bound) - width(target)) / 2;
317
+ const y_ = y(bound) + (height(bound) - height(target)) / 2;
318
+ return construct({ x: x_, y: y_ }, dims(target));
319
+ };
320
+
321
+ export const isBox = (value: unknown): value is Box => {
322
+ if (typeof value !== "object" || value == null) return false;
323
+ return "one" in value && "two" in value && "root" in value;
324
+ };
325
+
326
+ export const aspect = (b: Box): number => width(b) / height(b);
@@ -0,0 +1,47 @@
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, test } from "vitest";
11
+
12
+ import * as dimensions from "@/spatial/dimensions";
13
+
14
+ describe("Dimensions", () => {
15
+ describe("construction", () => {
16
+ [
17
+ ["from object", { width: 1, height: 2 }],
18
+ ["from couple", [1, 2]],
19
+ ["from dimensions", { width: 1, height: 2 }],
20
+ ["from signed dimensions", { signedWidth: 1, signedHeight: 2 }],
21
+ ["from XY", { x: 1, y: 2 }],
22
+ ].forEach(([name, arg]) => {
23
+ test(name as string, () => {
24
+ const xy = dimensions.construct(arg as dimensions.Crude);
25
+ expect(xy.width).toEqual(1);
26
+ expect(xy.height).toEqual(2);
27
+ });
28
+ });
29
+ });
30
+ test("couple", () => {
31
+ const d = dimensions.construct([1, 2]);
32
+ expect(dimensions.couple(d)).toEqual([1, 2]);
33
+ });
34
+ describe("equals", () => {
35
+ type T = [dimensions.Crude, dimensions.Crude, boolean];
36
+ const TESTS: T[] = [
37
+ [[1, 1], { width: 1, height: 1 }, true],
38
+ [[1, 1], [1, 1], true],
39
+ [{ width: 1, height: 12 }, { width: 1, height: 1 }, false],
40
+ [{ width: 1, height: 12 }, { width: 12, height: 1 }, false],
41
+ [{ width: 1, height: 12 }, { signedWidth: 1, signedHeight: 12 }, true],
42
+ ];
43
+ TESTS.forEach(([one, two, expected], i) =>
44
+ test(`equals ${i}`, () => expect(dimensions.equals(one, two)).toEqual(expected)),
45
+ );
46
+ });
47
+ });
@@ -0,0 +1,64 @@
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
+ import { dimensions, xy, type Dimensions, numberCouple } from "@/spatial/base";
13
+
14
+ export { dimensions, type Dimensions };
15
+
16
+ export const signed = z.object({ signedWidth: z.number(), signedHeight: z.number() });
17
+ export const crude = z.union([dimensions, signed, xy, numberCouple]);
18
+ export type Crude = z.infer<typeof crude>;
19
+
20
+ export const ZERO = { width: 0, height: 0 };
21
+ export const DECIMAL = { width: 1, height: 1 };
22
+
23
+ export const construct = (width: number | Crude, height?: number): Dimensions => {
24
+ if (typeof width === "number") return { width, height: height ?? width };
25
+ if (Array.isArray(width)) return { width: width[0], height: width[1] };
26
+ if ("x" in width) return { width: width.x, height: width.y };
27
+ if ("signedWidth" in width)
28
+ return { width: width.signedWidth, height: width.signedHeight };
29
+ return { ...width };
30
+ };
31
+
32
+ export type Signed = z.infer<typeof signed>;
33
+
34
+ export const equals = (ca: Crude, cb?: Crude | null): boolean => {
35
+ if (cb == null) return false;
36
+ const a = construct(ca);
37
+ const b = construct(cb);
38
+ return a.width === b.width && a.height === b.height;
39
+ };
40
+
41
+ export const swap = (ca: Crude): Dimensions => {
42
+ const a = construct(ca);
43
+ return { width: a.height, height: a.width };
44
+ };
45
+
46
+ export const svgViewBox = (ca: Crude): string => {
47
+ const a = construct(ca);
48
+ return `0 0 ${a.width} ${a.height}`;
49
+ };
50
+
51
+ export const couple = (ca: Crude): [number, number] => {
52
+ const a = construct(ca);
53
+ return [a.width, a.height];
54
+ };
55
+
56
+ export const max = (crude: Crude[]): Dimensions => ({
57
+ width: Math.max(...crude.map((c) => construct(c).width)),
58
+ height: Math.max(...crude.map((c) => construct(c).height)),
59
+ });
60
+
61
+ export const min = (crude: Crude[]): Dimensions => ({
62
+ width: Math.min(...crude.map((c) => construct(c).width)),
63
+ height: Math.min(...crude.map((c) => construct(c).height)),
64
+ });
@@ -0,0 +1,25 @@
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, test } from "vitest";
11
+
12
+ import * as direction from "@/spatial/direction";
13
+
14
+ describe("Direction", () => {
15
+ describe("construction", () => {
16
+ type T = [string, direction.Crude];
17
+ const TESTS: T[] = [
18
+ ["from location", "top"],
19
+ ["from literal", "y"],
20
+ ];
21
+ TESTS.forEach(([name, arg]) =>
22
+ test(name, () => expect(direction.construct(arg)).toEqual("y")),
23
+ );
24
+ });
25
+ });
@@ -0,0 +1,47 @@
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 {
11
+ type Dimension,
12
+ type Direction,
13
+ type Location,
14
+ type direction,
15
+ DIRECTIONS,
16
+ Y_LOCATIONS,
17
+ type YLocation,
18
+ type SignedDimension,
19
+ crudeDirection,
20
+ type CrudeDirection,
21
+ } from "@/spatial/base";
22
+
23
+ export type { Direction, direction };
24
+
25
+ export const crude = crudeDirection;
26
+
27
+ export type Crude = CrudeDirection;
28
+
29
+ export const construct = (c: Crude): Direction => {
30
+ if (DIRECTIONS.includes(c as Direction)) return c as Direction;
31
+ if (Y_LOCATIONS.includes(c as YLocation)) return "y";
32
+ else return "x";
33
+ };
34
+
35
+ export const swap = (direction: Direction): Direction =>
36
+ direction === "x" ? "y" : "x";
37
+
38
+ export const dimension = (direction: Direction): Dimension =>
39
+ direction === "x" ? "width" : "height";
40
+
41
+ export const location = (direction: Direction): Location =>
42
+ direction === "x" ? "left" : "top";
43
+
44
+ export const isDirection = (c: unknown): c is Direction => crude.safeParse(c).success;
45
+
46
+ export const signedDimension = (direction: Direction): SignedDimension =>
47
+ direction === "x" ? "signedWidth" : "signedHeight";
@@ -0,0 +1,17 @@
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 bounds from "@/spatial/bounds";
11
+ export * as box from "@/spatial/box";
12
+ export * as dimensions from "@/spatial/dimensions";
13
+ export * as direction from "@/spatial/direction";
14
+ export * as location from "@/spatial/location";
15
+ export * as xy from "@/spatial/xy";
16
+ export * as scale from "@/spatial/scale";
17
+ export * as spatial from "@/spatial/spatial";
@@ -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 * from "@/spatial/external";
@@ -0,0 +1,24 @@
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, test } from "vitest";
11
+
12
+ import * as location from "@/spatial/location";
13
+
14
+ describe("Location", () => {
15
+ describe("construction", () => {
16
+ [
17
+ ["from valueOf", String("left")],
18
+ ["from string", "left"],
19
+ ["from direction", "x"],
20
+ ].forEach(([name, arg]) =>
21
+ test(name, () => expect(location.construct(arg)).toEqual("left")),
22
+ );
23
+ });
24
+ });
@@ -0,0 +1,124 @@
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
+ import { Case } from "@/case";
13
+ import {
14
+ location,
15
+ type Location,
16
+ xLocation,
17
+ yLocation,
18
+ DIRECTIONS,
19
+ X_LOCATIONS,
20
+ Y_LOCATIONS,
21
+ CENTER_LOCATIONS,
22
+ type XLocation,
23
+ type OuterLocation,
24
+ type YLocation,
25
+ outerLocation,
26
+ type Direction,
27
+ crudeLocation,
28
+ type CrudeLocation,
29
+ } from "@/spatial/base";
30
+
31
+ export {
32
+ location,
33
+ type Location,
34
+ X_LOCATIONS,
35
+ Y_LOCATIONS,
36
+ CENTER_LOCATIONS,
37
+ outerLocation as outer,
38
+ };
39
+
40
+ export const x = xLocation;
41
+ export const y = yLocation;
42
+
43
+ export type X = XLocation;
44
+ export type Y = YLocation;
45
+ export type Outer = OuterLocation;
46
+
47
+ const SWAPPED: Record<Location, Location> = {
48
+ top: "bottom",
49
+ right: "left",
50
+ bottom: "top",
51
+ left: "right",
52
+ center: "center",
53
+ };
54
+
55
+ export const crude = crudeLocation;
56
+
57
+ export type Crude = CrudeLocation;
58
+
59
+ export const construct = (cl: Crude): Location => {
60
+ if (cl instanceof String) return cl as Location;
61
+ if (!DIRECTIONS.includes(cl as Direction)) return cl as Location;
62
+ else if (cl === "x") return "left";
63
+ else return "top";
64
+ };
65
+
66
+ export const swap = (cl: Crude): Location => SWAPPED[construct(cl)];
67
+
68
+ export const direction = (cl: Crude): Direction => {
69
+ const l = construct(cl);
70
+ if (l === "top" || l === "bottom") return "y";
71
+ return "x";
72
+ };
73
+
74
+ export const xy = z.object({ x: location, y: location });
75
+ export const corner = z.object({ x: xLocation, y: yLocation });
76
+
77
+ export type XY = z.infer<typeof xy>;
78
+ export type CornerXY = z.infer<typeof corner>;
79
+
80
+ export const TOP_LEFT: CornerXY = { x: "left", y: "top" };
81
+ export const TOP_RIGHT: CornerXY = { x: "right", y: "top" };
82
+ export const BOTTOM_LEFT: CornerXY = { x: "left", y: "bottom" };
83
+ export const BOTTOM_RIGHT: CornerXY = { x: "right", y: "bottom" };
84
+ export const CENTER: XY = { x: "center", y: "center" };
85
+ export const TOP_CENTER: XY = { x: "center", y: "top" };
86
+ export const BOTTOM_CENTER: XY = { x: "center", y: "bottom" };
87
+ export const RIGHT_CENTER: XY = { x: "right", y: "center" };
88
+ export const LEFT_CENTER: XY = { x: "left", y: "center" };
89
+
90
+ export const xyEquals = (a: XY, b: XY): boolean => a.x === b.x && a.y === b.y;
91
+
92
+ export const xyCouple = (a: XY): [Location, Location] => [a.x, a.y];
93
+
94
+ export const isX = (a: Crude): boolean => direction(construct(a)) === "x";
95
+
96
+ export const isY = (a: Crude): boolean => direction(construct(a)) === "y";
97
+
98
+ export const xyToString = (a: XY): string => `${a.x}${Case.capitalize(a.y)}`;
99
+
100
+ export const constructXY = (x: Crude | XY, y?: Crude): XY => {
101
+ let one: Location;
102
+ let two: Location;
103
+ if (typeof x === "object" && "x" in x) {
104
+ one = x.x;
105
+ two = x.y;
106
+ } else {
107
+ one = construct(x);
108
+ two = construct(y ?? x);
109
+ }
110
+ if (direction(one) === direction(two) && one !== "center" && two !== "center")
111
+ throw new Error(
112
+ `[XYLocation] - encountered two locations with the same direction: ${one.toString()} - ${two.toString()}`,
113
+ );
114
+ const xy = CENTER;
115
+ if (one === "center") {
116
+ if (direction(two) === "x") [xy.x, xy.y] = [two, one];
117
+ else [xy.x, xy.y] = [one, two];
118
+ } else if (two === "center") {
119
+ if (direction(one) === "x") [xy.x, xy.y] = [one, two];
120
+ else [xy.x, xy.y] = [two, one];
121
+ } else if (direction(one) === "x") [xy.x, xy.y] = [one, two];
122
+ else [xy.x, xy.y] = [two, one];
123
+ return xy;
124
+ };