@synnaxlabs/x 0.44.3 → 0.45.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 (134) hide show
  1. package/.turbo/turbo-build.log +37 -41
  2. package/dist/{base-DFq0vvGn.js → base-DRybODwJ.js} +16 -12
  3. package/dist/base-KIBsp6TI.cjs +1 -0
  4. package/dist/binary.cjs +1 -1
  5. package/dist/binary.js +1 -1
  6. package/dist/{bounds-CRK04jp7.cjs → bounds-C2TKFgVk.cjs} +1 -1
  7. package/dist/{bounds-8OC_obRs.js → bounds-DeUXrllt.js} +11 -11
  8. package/dist/bounds.cjs +1 -1
  9. package/dist/bounds.js +1 -1
  10. package/dist/{box-CO_2_DGG.js → box-BYuq-Gjx.js} +67 -73
  11. package/dist/box-Blu-4d1n.cjs +1 -0
  12. package/dist/box.cjs +1 -1
  13. package/dist/box.js +1 -1
  14. package/dist/caseconv.cjs +1 -1
  15. package/dist/caseconv.js +1 -1
  16. package/dist/compare.cjs +1 -1
  17. package/dist/compare.js +1 -1
  18. package/dist/{dimensions-D2QGoNXO.cjs → dimensions-Cg5Owbwn.cjs} +1 -1
  19. package/dist/{dimensions-CRgergMS.js → dimensions-DC0uLPwn.js} +1 -1
  20. package/dist/dimensions.cjs +1 -1
  21. package/dist/dimensions.js +1 -1
  22. package/dist/direction-C_b4tfRN.js +19 -0
  23. package/dist/direction-DqQB9M37.cjs +1 -0
  24. package/dist/direction.cjs +1 -1
  25. package/dist/direction.js +1 -1
  26. package/dist/{index-B5l_quQn.js → index-BHXRDFYj.js} +1 -1
  27. package/dist/index-Bxlv0uf_.js +57 -0
  28. package/dist/{index-CBMHFqs4.js → index-C452Pas0.js} +25 -22
  29. package/dist/{index-BQe8OIgm.cjs → index-CwGPVvbl.cjs} +1 -1
  30. package/dist/index-uDxeM-cl.cjs +1 -0
  31. package/dist/index-xaxa1hoa.cjs +1 -0
  32. package/dist/index.cjs +3 -3
  33. package/dist/index.js +452 -392
  34. package/dist/location-BPoXwOni.cjs +1 -0
  35. package/dist/{location-Ar5y2DX2.js → location-CVxysrHI.js} +40 -44
  36. package/dist/location.cjs +1 -1
  37. package/dist/location.js +1 -1
  38. package/dist/record-BwjIgrpU.cjs +1 -0
  39. package/dist/record-tSFQKmdG.js +19 -0
  40. package/dist/record.cjs +1 -1
  41. package/dist/record.js +1 -1
  42. package/dist/{scale-C3fEtXxW.cjs → scale-BHs716im.cjs} +1 -1
  43. package/dist/{scale-Db1Gunj0.js → scale-DjxC6ep2.js} +4 -4
  44. package/dist/scale.cjs +1 -1
  45. package/dist/scale.js +1 -1
  46. package/dist/series-C6ZwNf8i.cjs +6 -0
  47. package/dist/{series-Cl3Vh_u-.js → series-W5Aafjeu.js} +303 -270
  48. package/dist/{spatial-BSWPzMkK.js → spatial-DnsaOypA.js} +1 -1
  49. package/dist/{spatial-DGpZ2sO3.cjs → spatial-DrxzaD5U.cjs} +1 -1
  50. package/dist/spatial.cjs +1 -1
  51. package/dist/spatial.js +14 -16
  52. package/dist/src/caseconv/caseconv.d.ts +9 -0
  53. package/dist/src/caseconv/caseconv.d.ts.map +1 -1
  54. package/dist/src/color/color.d.ts +7 -0
  55. package/dist/src/color/color.d.ts.map +1 -1
  56. package/dist/src/index.d.ts +1 -0
  57. package/dist/src/index.d.ts.map +1 -1
  58. package/dist/src/primitive/primitive.d.ts +4 -0
  59. package/dist/src/primitive/primitive.d.ts.map +1 -1
  60. package/dist/src/record/record.d.ts +18 -0
  61. package/dist/src/record/record.d.ts.map +1 -1
  62. package/dist/src/spatial/base.d.ts +12 -0
  63. package/dist/src/spatial/base.d.ts.map +1 -1
  64. package/dist/src/spatial/direction/direction.d.ts +5 -1
  65. package/dist/src/spatial/direction/direction.d.ts.map +1 -1
  66. package/dist/src/spatial/external.d.ts +0 -1
  67. package/dist/src/spatial/external.d.ts.map +1 -1
  68. package/dist/src/spatial/location/location.d.ts +2 -2
  69. package/dist/src/spatial/location/location.d.ts.map +1 -1
  70. package/dist/src/spatial/xy/xy.d.ts +3 -0
  71. package/dist/src/spatial/xy/xy.d.ts.map +1 -1
  72. package/dist/src/status/status.d.ts +2 -1
  73. package/dist/src/status/status.d.ts.map +1 -1
  74. package/dist/src/telem/series.d.ts +1 -0
  75. package/dist/src/telem/series.d.ts.map +1 -1
  76. package/dist/src/telem/telem.d.ts +23 -6
  77. package/dist/src/telem/telem.d.ts.map +1 -1
  78. package/dist/src/undefined.d.ts +2 -0
  79. package/dist/src/undefined.d.ts.map +1 -0
  80. package/dist/telem.cjs +1 -1
  81. package/dist/telem.js +1 -1
  82. package/dist/xy-DWwtHmgn.cjs +1 -0
  83. package/dist/{xy-C_-hb3Q2.js → xy-DYPw8-8C.js} +41 -31
  84. package/dist/xy.cjs +1 -1
  85. package/dist/xy.js +1 -1
  86. package/package.json +2 -7
  87. package/src/caseconv/caseconv.spec.ts +30 -0
  88. package/src/caseconv/caseconv.ts +46 -0
  89. package/src/color/color.spec.ts +40 -0
  90. package/src/color/color.ts +72 -0
  91. package/src/index.ts +1 -0
  92. package/src/primitive/primitive.spec.ts +9 -0
  93. package/src/primitive/primitive.ts +9 -0
  94. package/src/record/record.spec.ts +31 -0
  95. package/src/record/record.ts +23 -0
  96. package/src/spatial/base.ts +4 -0
  97. package/src/spatial/direction/direction.spec.ts +35 -1
  98. package/src/spatial/direction/direction.ts +12 -0
  99. package/src/spatial/external.ts +0 -1
  100. package/src/spatial/location/location.ts +2 -0
  101. package/src/spatial/xy/xy.spec.ts +124 -7
  102. package/src/spatial/xy/xy.ts +15 -2
  103. package/src/status/status.spec.ts +52 -0
  104. package/src/status/status.ts +19 -6
  105. package/src/telem/series.ts +2 -0
  106. package/src/telem/telem.spec.ts +352 -0
  107. package/src/telem/telem.ts +85 -18
  108. package/src/{spatial/position/index.ts → undefined.ts} +5 -1
  109. package/tsconfig.tsbuildinfo +1 -1
  110. package/vite.config.ts +0 -1
  111. package/dist/base-BAM2mqCy.cjs +0 -1
  112. package/dist/box-Cxki783Y.cjs +0 -1
  113. package/dist/direction-386XDm2w.cjs +0 -1
  114. package/dist/direction-8etxfKaR.js +0 -17
  115. package/dist/index-D2xcvEO5.js +0 -46
  116. package/dist/index-DOJlZHqJ.cjs +0 -1
  117. package/dist/index-DdhM_E4k.cjs +0 -1
  118. package/dist/location-DZi8ftXp.cjs +0 -1
  119. package/dist/position-DSy2hONH.cjs +0 -1
  120. package/dist/position-PQ6op54I.js +0 -85
  121. package/dist/position.cjs +0 -1
  122. package/dist/position.js +0 -4
  123. package/dist/record-CAcQ5PNX.js +0 -14
  124. package/dist/record-YvCh7bzB.cjs +0 -1
  125. package/dist/series-BcF7A8Je.cjs +0 -6
  126. package/dist/src/spatial/position/index.d.ts +0 -2
  127. package/dist/src/spatial/position/index.d.ts.map +0 -1
  128. package/dist/src/spatial/position/position.d.ts +0 -20
  129. package/dist/src/spatial/position/position.d.ts.map +0 -1
  130. package/dist/src/spatial/position/position.spec.d.ts +0 -2
  131. package/dist/src/spatial/position/position.spec.d.ts.map +0 -1
  132. package/dist/xy-CUE3QDNn.cjs +0 -1
  133. package/src/spatial/position/position.spec.ts +0 -211
  134. package/src/spatial/position/position.ts +0 -157
@@ -1,9 +1,9 @@
1
- import { z as g } from "zod";
2
- import { x as p, n as w, d as I, s as A, i as M } from "./base-DFq0vvGn.js";
3
- const X = g.union([
4
- g.number(),
1
+ import { z as b } from "zod";
2
+ import { x as p, n as v, d as I, s as A, j as M } from "./base-DRybODwJ.js";
3
+ const E = b.union([
4
+ b.number(),
5
5
  p,
6
- w,
6
+ v,
7
7
  I,
8
8
  A,
9
9
  M
@@ -13,10 +13,10 @@ const X = g.union([
13
13
  return t === "x" ? { x: n, y: 0 } : { x: 0, y: n };
14
14
  }
15
15
  return typeof t == "number" ? { x: t, y: n ?? t } : Array.isArray(t) ? { x: t[0], y: t[1] } : "signedWidth" in t ? { x: t.signedWidth, y: t.signedHeight } : "clientX" in t ? { x: t.clientX, y: t.clientY } : "width" in t ? { x: t.width, y: t.height } : { x: t.x, y: t.y };
16
- }, d = Object.freeze({ x: 0, y: 0 }), Y = Object.freeze({ x: 1, y: 1 }), Z = Object.freeze({ x: 1 / 0, y: 1 / 0 }), E = Object.freeze({ x: NaN, y: NaN }), O = (t, n, e = 0) => {
16
+ }, d = Object.freeze({ x: 0, y: 0 }), T = Object.freeze({ x: 1, y: 1 }), X = Object.freeze({ x: 1 / 0, y: 1 / 0 }), Y = Object.freeze({ x: NaN, y: NaN }), O = (t, n, e = 0) => {
17
17
  const r = s(t), o = s(n);
18
18
  return e === 0 ? r.x === o.x && r.y === o.y : Math.abs(r.x - o.x) <= e && Math.abs(r.y - o.y) <= e;
19
- }, T = (t) => O(t, d), b = (t, n, e) => {
19
+ }, Z = (t) => O(t, d), h = (t, n, e) => {
20
20
  const r = s(t), o = s(n, e);
21
21
  return { x: r.x * o.x, y: r.y * o.y };
22
22
  }, P = (t, n) => {
@@ -25,10 +25,20 @@ const X = g.union([
25
25
  }, _ = (t, n) => {
26
26
  const e = s(t);
27
27
  return { x: e.x, y: e.y + n };
28
- }, z = (t, n, e, ...r) => typeof n == "string" && typeof e == "number" ? n === "x" ? P(t, e) : _(t, e) : [t, n, e ?? d, ...r].reduce((o, y) => {
29
- const c = s(y);
30
- return { x: o.x + c.x, y: o.y + c.y };
31
- }, d), q = (t, n, e) => {
28
+ }, j = (t, n, e, ...r) => {
29
+ if (typeof n == "string") {
30
+ if (typeof e != "number") throw new Error("The value must be a number.");
31
+ return n === "x" ? P(t, e) : _(t, e);
32
+ }
33
+ if (typeof n == "object" && "x" in n && typeof n.x == "string") {
34
+ const o = s(e), y = s(t);
35
+ return n.x === "left" ? o.x = -o.x : n.x === "center" && (o.x = 0), n.y === "top" ? o.y = -o.y : n.y === "center" && (o.y = 0), { x: y.x + o.x, y: y.y + o.y };
36
+ }
37
+ return [t, n, e ?? d, ...r].reduce((o, y) => {
38
+ const c = s(y);
39
+ return { x: o.x + c.x, y: o.y + c.y };
40
+ }, d);
41
+ }, q = (t, n, e) => {
32
42
  const r = s(t);
33
43
  return n === "x" ? { x: e, y: r.y } : { x: r.x, y: e };
34
44
  }, H = (t, n) => {
@@ -58,12 +68,12 @@ const X = g.union([
58
68
  }, m = (t) => {
59
69
  const n = s(t), e = Math.hypot(n.x, n.y);
60
70
  return e === 0 ? { x: 0, y: 0 } : { x: -n.y / e, y: n.x / e };
61
- }, j = (t) => {
71
+ }, w = (t) => {
62
72
  const n = s(t), e = Math.hypot(n.x, n.y);
63
73
  return e === 0 ? { x: 0, y: 0 } : { x: n.x / e, y: n.y / e };
64
- }, F = (...t) => {
65
- const n = t.reduce((e, r) => z(e, r), d);
66
- return b(n, 1 / t.length);
74
+ }, z = (...t) => {
75
+ const n = t.reduce((e, r) => j(e, r), d);
76
+ return h(n, 1 / t.length);
67
77
  }, $ = (t, n) => {
68
78
  const e = [];
69
79
  for (let r = 0; r < t.length; r++) {
@@ -78,12 +88,12 @@ const X = g.union([
78
88
  } else {
79
89
  const x = t[r - 1], u = t[r + 1], a = f(o, x), l = f(u, o);
80
90
  y = m(a), c = m(l);
81
- const v = Math.acos(
91
+ const F = Math.acos(
82
92
  (a.x * l.x + a.y * l.y) / (Math.hypot(a.x, a.y) * Math.hypot(l.x, l.y))
83
- ), h = Math.sin(v / 2);
84
- h === 0 ? i = n : i = n / h, N = j(F(y, c));
93
+ ), g = Math.sin(F / 2);
94
+ g === 0 ? i = n : i = n / g, N = w(z(y, c));
85
95
  }
86
- e.push(b(N, i));
96
+ e.push(h(N, i));
87
97
  }
88
98
  return e;
89
99
  }, k = (t) => {
@@ -94,30 +104,30 @@ const X = g.union([
94
104
  return { x: Math.round(n.x), y: Math.round(n.y) };
95
105
  }, K = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
96
106
  __proto__: null,
97
- INFINITY: Z,
98
- NAN: E,
99
- ONE: Y,
107
+ INFINITY: X,
108
+ NAN: Y,
109
+ ONE: T,
100
110
  ZERO: d,
101
- average: F,
111
+ average: z,
102
112
  calculateMiters: $,
103
113
  clientXY: M,
104
114
  construct: s,
105
115
  couple: D,
106
- crudeZ: X,
116
+ crudeZ: E,
107
117
  css: L,
108
118
  distance: H,
109
119
  equals: O,
110
120
  isFinite: C,
111
121
  isNan: W,
112
- isZero: T,
122
+ isZero: Z,
113
123
  normal: m,
114
- normalize: j,
124
+ normalize: w,
115
125
  round: B,
116
- scale: b,
126
+ scale: h,
117
127
  set: q,
118
128
  sub: f,
119
129
  swap: k,
120
- translate: z,
130
+ translate: j,
121
131
  translateX: P,
122
132
  translateY: _,
123
133
  translation: S,
@@ -125,13 +135,13 @@ const X = g.union([
125
135
  xy: p
126
136
  }, Symbol.toStringTag, { value: "Module" }));
127
137
  export {
128
- Y as O,
138
+ T as O,
129
139
  d as Z,
130
140
  R as a,
131
- X as b,
141
+ E as b,
132
142
  s as c,
133
143
  O as e,
134
144
  B as r,
135
- z as t,
145
+ j as t,
136
146
  K as x
137
147
  };
package/dist/xy.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./xy-CUE3QDNn.cjs");exports.xy=e.xy;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./xy-DWwtHmgn.cjs");exports.xy=e.xy;
package/dist/xy.js CHANGED
@@ -1,4 +1,4 @@
1
- import { x as r } from "./xy-C_-hb3Q2.js";
1
+ import { x as r } from "./xy-DYPw8-8C.js";
2
2
  export {
3
3
  r as xy
4
4
  };
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@synnaxlabs/x",
3
- "version": "0.44.3",
3
+ "version": "0.45.0",
4
4
  "type": "module",
5
5
  "description": "Common Utilities for Synnax Labs",
6
- "repository": "https://github.com/synnaxlabs/synnax/tree/main/x/go",
6
+ "repository": "https://github.com/synnaxlabs/synnax/tree/main/x/ts",
7
7
  "keywords": [
8
8
  "synnax",
9
9
  "utilities",
@@ -95,11 +95,6 @@
95
95
  "import": "./dist/location.js",
96
96
  "require": "./dist/location.cjs"
97
97
  },
98
- "./position": {
99
- "types": "./dist/src/spatial/position/index.d.ts",
100
- "import": "./dist/position.js",
101
- "require": "./dist/position.cjs"
102
- },
103
98
  "./scale": {
104
99
  "types": "./dist/src/spatial/scale/index.d.ts",
105
100
  "import": "./dist/scale.js",
@@ -127,4 +127,34 @@ describe("caseconv", () => {
127
127
  });
128
128
  });
129
129
  });
130
+ describe("toProperNoun", () => {
131
+ const SPECS: [string, string][] = [
132
+ ["fooBar", "Foo Bar"],
133
+ ["foo_bar", "Foo Bar"],
134
+ ["foo-bar", "Foo Bar"],
135
+ ["FooBar", "Foo Bar"],
136
+ ["foo_bar_baz", "Foo Bar Baz"],
137
+ ["fooBarBaz", "Foo Bar Baz"],
138
+ ["foo-bar-baz", "Foo Bar Baz"],
139
+ ["XMLParser", "XML Parser"],
140
+ ["parseXMLDocument", "Parse XML Document"],
141
+ ["IODevice", "IO Device"],
142
+ ["temperature_sensor", "Temperature Sensor"],
143
+ ["pressure-gauge", "Pressure Gauge"],
144
+ ["flowMeter", "Flow Meter"],
145
+ ["my_custom_symbol", "My Custom Symbol"],
146
+ ["valve-actuator-v2", "Valve Actuator V2"],
147
+ ["PIDController", "PID Controller"],
148
+ ["", ""],
149
+ ["a", "A"],
150
+ ["ABC", "ABC"],
151
+ ["test123value", "Test123value"],
152
+ ["test_123_value", "Test 123 Value"],
153
+ ];
154
+ SPECS.forEach(([input, expected]) => {
155
+ it(`should convert ${input} to ${expected}`, () => {
156
+ expect(caseconv.toProperNoun(input)).toBe(expected);
157
+ });
158
+ });
159
+ });
130
160
  });
@@ -165,3 +165,49 @@ const toKebabStr = (str: string): string =>
165
165
  * @returns The converted string in kebab-case
166
166
  */
167
167
  export const toKebab = createConverter(toKebabStr);
168
+
169
+ /**
170
+ * Converts a string to proper noun format.
171
+ * Handles snake_case, kebab-case, camelCase, and PascalCase.
172
+ * Capitalizes the first letter of each word.
173
+ *
174
+ * @param str - The string to convert
175
+ * @returns The converted string in proper noun format
176
+ */
177
+ const toProperNounStr = (str: string): string => {
178
+ if (str.length === 0) return str;
179
+
180
+ // Replace underscores and hyphens with spaces
181
+ let result = str.replace(/[_-]/g, " ");
182
+
183
+ // Insert spaces before capital letters (for camelCase/PascalCase)
184
+ // but not at the start or when there are consecutive capitals
185
+ result = result.replace(
186
+ /([a-z0-9])([A-Z])/g,
187
+ (_, p1: string, p2: string) => `${p1} ${p2}`
188
+ );
189
+
190
+ // Handle consecutive capitals (e.g., "XMLParser" -> "XML Parser")
191
+ result = result.replace(
192
+ /([A-Z]+)([A-Z][a-z])/g,
193
+ (_, p1: string, p2: string) => `${p1} ${p2}`
194
+ );
195
+
196
+ // Clean up multiple spaces
197
+ result = result.replace(/\s+/g, " ").trim();
198
+
199
+ // Capitalize first letter of each word (proper noun format)
200
+ result = result.replace(/\b\w/g, (char) => char.toUpperCase());
201
+
202
+ return result;
203
+ };
204
+
205
+ /**
206
+ * Converts a string to proper noun format.
207
+ * Handles snake_case, kebab-case, camelCase, and PascalCase.
208
+ * Each word is capitalized.
209
+ *
210
+ * @param str - The string to convert
211
+ * @returns The converted string in proper noun format
212
+ */
213
+ export const toProperNoun = createConverter(toProperNounStr);
@@ -757,6 +757,46 @@ describe("color.Color", () => {
757
757
  });
758
758
  });
759
759
 
760
+ describe("fromCSS", () => {
761
+ test("parses hex colors", () => {
762
+ expect(color.fromCSS("#ff0000")).toEqual([255, 0, 0, 1]);
763
+ expect(color.fromCSS("#00ff00")).toEqual([0, 255, 0, 1]);
764
+ expect(color.fromCSS("#0000ff")).toEqual([0, 0, 255, 1]);
765
+ expect(color.fromCSS("#f00")).toEqual([255, 0, 0, 1]);
766
+ expect(color.fromCSS("#0f0")).toEqual([0, 255, 0, 1]);
767
+ expect(color.fromCSS("#00f")).toEqual([0, 0, 255, 1]);
768
+ });
769
+
770
+ test("parses rgb/rgba colors", () => {
771
+ expect(color.fromCSS("rgb(255, 0, 0)")).toEqual([255, 0, 0, 1]);
772
+ expect(color.fromCSS("rgb(0, 255, 0)")).toEqual([0, 255, 0, 1]);
773
+ expect(color.fromCSS("rgba(0, 0, 255, 0.5)")).toEqual([0, 0, 255, 0.5]);
774
+ expect(color.fromCSS("rgba(128, 128, 128, 1)")).toEqual([128, 128, 128, 1]);
775
+ });
776
+
777
+ test("parses named colors", () => {
778
+ expect(color.fromCSS("red")).toEqual([255, 0, 0, 1]);
779
+ expect(color.fromCSS("green")).toEqual([0, 128, 0, 1]);
780
+ expect(color.fromCSS("blue")).toEqual([0, 0, 255, 1]);
781
+ expect(color.fromCSS("black")).toEqual([0, 0, 0, 1]);
782
+ expect(color.fromCSS("white")).toEqual([255, 255, 255, 1]);
783
+ });
784
+
785
+ test("handles case insensitive input", () => {
786
+ expect(color.fromCSS("RED")).toEqual([255, 0, 0, 1]);
787
+ expect(color.fromCSS("Green")).toEqual([0, 128, 0, 1]);
788
+ expect(color.fromCSS("BLUE")).toEqual([0, 0, 255, 1]);
789
+ });
790
+
791
+ test("returns undefined for invalid input", () => {
792
+ expect(color.fromCSS("")).toBeUndefined();
793
+ expect(color.fromCSS("none")).toBeUndefined();
794
+ expect(color.fromCSS("transparent")).toBeUndefined();
795
+ expect(color.fromCSS("invalid")).toBeUndefined();
796
+ expect(color.fromCSS("#gggggg")).toBeUndefined();
797
+ });
798
+ });
799
+
760
800
  describe("isZero", () => {
761
801
  test("returns true for zero color", () => {
762
802
  expect(color.isZero([0, 0, 0, 0])).toBe(true);
@@ -282,6 +282,78 @@ const rgbaToHex = (n: number): string => Math.floor(n).toString(16).padStart(2,
282
282
  const hexToRgba = (s: string, n: number): number => parseInt(s.slice(n, n + 2), 16);
283
283
  const stripHash = (hex: string): string => (hex.startsWith("#") ? hex.slice(1) : hex);
284
284
 
285
+ const NAMED: Record<string, string> = {
286
+ black: "#000000",
287
+ white: "#ffffff",
288
+ red: "#ff0000",
289
+ green: "#008000",
290
+ blue: "#0000ff",
291
+ yellow: "#ffff00",
292
+ cyan: "#00ffff",
293
+ magenta: "#ff00ff",
294
+ silver: "#c0c0c0",
295
+ gray: "#808080",
296
+ grey: "#808080",
297
+ maroon: "#800000",
298
+ olive: "#808000",
299
+ lime: "#00ff00",
300
+ aqua: "#00ffff",
301
+ teal: "#008080",
302
+ navy: "#000080",
303
+ fuchsia: "#ff00ff",
304
+ purple: "#800080",
305
+ orange: "#ffa500",
306
+ brown: "#a52a2a",
307
+ tan: "#d2b48c",
308
+ gold: "#ffd700",
309
+ indigo: "#4b0082",
310
+ violet: "#ee82ee",
311
+ pink: "#ffc0cb",
312
+ coral: "#ff7f50",
313
+ salmon: "#fa8072",
314
+ khaki: "#f0e68c",
315
+ crimson: "#dc143c",
316
+ transparent: "transparent",
317
+ };
318
+
319
+ /**
320
+ * Parses a CSS color string into a Color.
321
+ * Supports hex colors, rgb/rgba functions, and named colors.
322
+ * @param cssColor - The CSS color string to parse
323
+ * @returns The parsed color or undefined if invalid
324
+ */
325
+ export const fromCSS = (cssColor: string): Color | undefined => {
326
+ if (!cssColor) return undefined;
327
+ const trimmed = cssColor.trim().toLowerCase();
328
+ if (trimmed === "transparent" || trimmed === "none") return undefined;
329
+ if (trimmed.startsWith("#")) {
330
+ if (trimmed.length === 4) {
331
+ const r = trimmed[1];
332
+ const g = trimmed[2];
333
+ const b = trimmed[3];
334
+ const expanded = `#${r}${r}${g}${g}${b}${b}`;
335
+ if (hexZ.safeParse(expanded).success) return fromHex(expanded);
336
+ }
337
+ if (
338
+ (trimmed.length === 7 || trimmed.length === 9) &&
339
+ hexZ.safeParse(trimmed).success
340
+ )
341
+ return fromHex(trimmed);
342
+ return undefined;
343
+ }
344
+ if (trimmed.startsWith("rgb")) {
345
+ const match = trimmed.match(
346
+ /rgba?\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*([\d.]+))?\s*\)/,
347
+ );
348
+ if (match) {
349
+ const [, r, g, b, a] = match;
350
+ return [parseInt(r), parseInt(g), parseInt(b), a ? parseFloat(a) : 1];
351
+ }
352
+ }
353
+ if (NAMED[trimmed]) return fromHex(NAMED[trimmed]);
354
+ return undefined;
355
+ };
356
+
285
357
  /** @returns parse a color from an HSLA tuple. */
286
358
  export const fromHSLA = (hsla: HSLA): RGBA => {
287
359
  hsla = hslaZ.parse(hsla);
package/src/index.ts CHANGED
@@ -49,6 +49,7 @@ export * from "@/sync";
49
49
  export * from "@/telem";
50
50
  export * from "@/testutil";
51
51
  export * from "@/transform";
52
+ export * from "@/undefined";
52
53
  export * from "@/unique";
53
54
  export * from "@/url";
54
55
  export * from "@/uuid";
@@ -124,6 +124,15 @@ describe("primitive", () => {
124
124
  });
125
125
  expect(decoded.myDog).toBeInstanceOf(MyValueExtension);
126
126
  });
127
+
128
+ describe("isCrudeValueExtension", () => {
129
+ it("should return true for a CrudeValueExtension", () => {
130
+ expect(primitive.isCrudeValueExtension({ value: 12n })).toEqual(true);
131
+ });
132
+ it("should return false for a non-CrudeValueExtension", () => {
133
+ expect(primitive.isCrudeValueExtension(12n)).toEqual(false);
134
+ });
135
+ });
127
136
  });
128
137
  });
129
138
  });
@@ -10,6 +10,15 @@
10
10
  /** Union of types that are primitive values or can be converted to primitive values */
11
11
  export type Value = string | number | bigint | boolean | Stringer | null | undefined;
12
12
 
13
+ export type CrudeValueExtension<V extends NonNullable<Value>> = {
14
+ value: V;
15
+ };
16
+
17
+ export const isCrudeValueExtension = <V extends NonNullable<Value>>(
18
+ value: unknown,
19
+ ): value is CrudeValueExtension<V> =>
20
+ value != null && typeof value === "object" && "value" in value;
21
+
13
22
  /**
14
23
  * ValueExtension is a utility class that can be extended in order to implement objects
15
24
  * that pseudo-extend a primitive value with additional functionality.
@@ -316,4 +316,35 @@ describe("record", () => {
316
316
  });
317
317
  });
318
318
  });
319
+
320
+ describe("omit", () => {
321
+ type Object = { [key: string]: number };
322
+ it("should return the object if no keys are provided", () =>
323
+ expect(record.omit({ a: 1, b: 2, c: 3 })).toEqual({ a: 1, b: 2, c: 3 }));
324
+
325
+ it("should return the object for a single key", () =>
326
+ expect(record.omit({ a: 1, b: 2, c: 3 }, "a")).toEqual({ b: 2, c: 3 }));
327
+
328
+ it("should return the object for multiple keys", () =>
329
+ expect(record.omit({ a: 1, b: 2, c: 3 }, "a", "b")).toEqual({ c: 3 }));
330
+
331
+ it("should not mutate the original object", () => {
332
+ const obj = { a: 1, b: 2, c: 3 };
333
+ const result = record.omit(obj, "a", "b");
334
+ expect(result).toEqual({ c: 3 });
335
+ expect(obj).toEqual({ a: 1, b: 2, c: 3 });
336
+ });
337
+
338
+ it("should be a no-op if the keys are not present", () => {
339
+ const obj: Object = { a: 1, b: 2, c: 3 };
340
+ const result = record.omit(obj, "d", "e");
341
+ expect(result).toEqual({ a: 1, b: 2, c: 3 });
342
+ });
343
+
344
+ it("should handle empty objects", () => {
345
+ const obj: Object = {};
346
+ const result = record.omit(obj, "a", "b", "c");
347
+ expect(result).toEqual({});
348
+ });
349
+ });
319
350
  });
@@ -116,3 +116,26 @@ export const map = <T extends Record<Key, unknown>, U>(
116
116
  */
117
117
  export const purgeUndefined = <T extends Record<Key, unknown>>(obj: T): T =>
118
118
  Object.fromEntries(entries(obj).filter(([_, value]) => value !== undefined)) as T;
119
+
120
+ /**
121
+ * Removes specified keys from an object. This creates a shallow copy of the object and
122
+ * removes the keys instead of mutating the original object.
123
+ *
124
+ * @template T - The type of the input object
125
+ * @template K - The type of the keys to remove
126
+ * @param obj - The object to remove keys from
127
+ * @param keys - The keys to remove from the object
128
+ * @returns A new object with the specified keys removed
129
+ *
130
+ * @example
131
+ * ```typescript
132
+ * const obj = { a: 1, b: 2, c: 3 };
133
+ * const omitted = record.omit(obj, "b", "c");
134
+ * // Result: { a: 1 }
135
+ * ```
136
+ */
137
+ export const omit = <T, K extends keyof T>(obj: T, ...keys: K[]): Omit<T, K> => {
138
+ const result = { ...obj };
139
+ for (const key of keys) delete result[key];
140
+ return result;
141
+ };
@@ -83,6 +83,10 @@ export type CrudeBounds<T extends number | bigint = number> =
83
83
  | Bounds<T>
84
84
  | NumberCouple<T>;
85
85
  export const crudeDirection = z.enum([...direction.options, ...location.options]);
86
+ export const crudeXDirection = z.enum(["x", "left", "right"]);
87
+ export const crudeYDirection = z.enum(["y", "top", "bottom"]);
86
88
  export type CrudeDirection = z.infer<typeof crudeDirection>;
89
+ export type CrudeXDirection = z.infer<typeof crudeXDirection>;
90
+ export type CrudeYDirection = z.infer<typeof crudeYDirection>;
87
91
  export const crudeLocation = z.union([direction, location, z.instanceof(String)]);
88
92
  export type CrudeLocation = z.infer<typeof crudeLocation>;
@@ -7,7 +7,7 @@
7
7
  // License, use of this software will be governed by the Apache License, Version 2.0,
8
8
  // included in the file licenses/APL.txt.
9
9
 
10
- import { describe, expect, test } from "vitest";
10
+ import { describe, expect, it, test } from "vitest";
11
11
 
12
12
  import * as direction from "@/spatial/direction/direction";
13
13
 
@@ -22,4 +22,38 @@ describe("Direction", () => {
22
22
  test(name, () => expect(direction.construct(arg)).toEqual("y")),
23
23
  );
24
24
  });
25
+
26
+ describe("isX", () => {
27
+ const TESTS: [direction.Crude, boolean][] = [
28
+ ["x", true],
29
+ ["y", false],
30
+ ["left", true],
31
+ ["right", true],
32
+ ["top", false],
33
+ ["bottom", false],
34
+ ["center", false],
35
+ ];
36
+ TESTS.forEach(([arg, expected]) => {
37
+ it(`should return ${expected} for ${arg}`, () => {
38
+ expect(direction.isX(arg)).toBe(expected);
39
+ });
40
+ });
41
+ });
42
+
43
+ describe("isY", () => {
44
+ const TESTS: [direction.Crude, boolean][] = [
45
+ ["x", false],
46
+ ["y", true],
47
+ ["left", false],
48
+ ["right", false],
49
+ ["top", true],
50
+ ["bottom", true],
51
+ ["center", false],
52
+ ];
53
+ TESTS.forEach(([arg, expected]) => {
54
+ it(`should return ${expected} for ${arg}`, () => {
55
+ expect(direction.isY(arg)).toBe(expected);
56
+ });
57
+ });
58
+ });
25
59
  });
@@ -10,6 +10,8 @@
10
10
  import {
11
11
  type CrudeDirection,
12
12
  crudeDirection,
13
+ type CrudeXDirection,
14
+ type CrudeYDirection,
13
15
  type Dimension,
14
16
  type Direction,
15
17
  direction,
@@ -25,6 +27,8 @@ export { Direction, direction, DIRECTIONS };
25
27
  export const crude = crudeDirection;
26
28
 
27
29
  export type Crude = CrudeDirection;
30
+ export type CrudeX = CrudeXDirection;
31
+ export type CrudeY = CrudeYDirection;
28
32
 
29
33
  export const construct = (c: Crude): Direction => {
30
34
  if (DIRECTIONS.includes(c as Direction)) return c as Direction;
@@ -45,3 +49,11 @@ export const isDirection = (c: unknown): c is Direction => crude.safeParse(c).su
45
49
 
46
50
  export const signedDimension = (direction: CrudeDirection): SignedDimension =>
47
51
  construct(direction) === "x" ? "signedWidth" : "signedHeight";
52
+
53
+ export const isX = (direction: CrudeDirection): direction is CrudeXDirection => {
54
+ if (direction === "center") return false;
55
+ return construct(direction) === "x";
56
+ };
57
+
58
+ export const isY = (direction: CrudeDirection): direction is CrudeYDirection =>
59
+ construct(direction) === "y";
@@ -12,7 +12,6 @@ export { box } from "@/spatial/box";
12
12
  export * from "@/spatial/dimensions";
13
13
  export * from "@/spatial/direction";
14
14
  export * from "@/spatial/location";
15
- export * from "@/spatial/position";
16
15
  export * from "@/spatial/scale";
17
16
  export * as spatial from "@/spatial/spatial";
18
17
  export * from "@/spatial/xy";
@@ -20,6 +20,7 @@ import {
20
20
  DIRECTIONS,
21
21
  type Location,
22
22
  location,
23
+ OUTER_LOCATIONS,
23
24
  type OuterLocation,
24
25
  outerLocation,
25
26
  X_LOCATIONS,
@@ -35,6 +36,7 @@ export {
35
36
  type Location,
36
37
  location,
37
38
  outerLocation as outer,
39
+ OUTER_LOCATIONS,
38
40
  X_LOCATIONS,
39
41
  Y_LOCATIONS,
40
42
  };