@canvas-commons/2d 0.2.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 (183) hide show
  1. package/LICENSE +22 -0
  2. package/editor/index.css +40 -0
  3. package/editor/index.js +525 -0
  4. package/editor/index.js.map +1 -0
  5. package/lib/index-OvpZ-GsA.d.ts +5673 -0
  6. package/lib/index-OvpZ-GsA.d.ts.map +1 -0
  7. package/lib/index.d.ts +2 -0
  8. package/lib/index.js +10326 -0
  9. package/lib/index.js.map +1 -0
  10. package/lib/jsx-dev-runtime.d.ts +2 -0
  11. package/lib/jsx-dev-runtime.js +2 -0
  12. package/lib/jsx-runtime.d.ts +2 -0
  13. package/lib/jsx-runtime.js +28 -0
  14. package/lib/jsx-runtime.js.map +1 -0
  15. package/package.json +79 -0
  16. package/src/editor/NodeInspectorConfig.tsx +76 -0
  17. package/src/editor/PreviewOverlayConfig.tsx +65 -0
  18. package/src/editor/Provider.tsx +109 -0
  19. package/src/editor/SceneGraphTabConfig.tsx +87 -0
  20. package/src/editor/icons/CircleIcon.tsx +7 -0
  21. package/src/editor/icons/CodeIcon.tsx +8 -0
  22. package/src/editor/icons/CurveIcon.tsx +7 -0
  23. package/src/editor/icons/GridIcon.tsx +7 -0
  24. package/src/editor/icons/IconMap.ts +35 -0
  25. package/src/editor/icons/ImgIcon.tsx +8 -0
  26. package/src/editor/icons/LayoutIcon.tsx +9 -0
  27. package/src/editor/icons/LineIcon.tsx +7 -0
  28. package/src/editor/icons/NodeIcon.tsx +7 -0
  29. package/src/editor/icons/RayIcon.tsx +7 -0
  30. package/src/editor/icons/RectIcon.tsx +7 -0
  31. package/src/editor/icons/ShapeIcon.tsx +7 -0
  32. package/src/editor/icons/TxtIcon.tsx +8 -0
  33. package/src/editor/icons/VideoIcon.tsx +7 -0
  34. package/src/editor/icons/View2DIcon.tsx +10 -0
  35. package/src/editor/index.css +0 -0
  36. package/src/editor/index.ts +19 -0
  37. package/src/editor/shortcuts.ts +27 -0
  38. package/src/editor/tree/DetachedRoot.tsx +27 -0
  39. package/src/editor/tree/NodeElement.tsx +72 -0
  40. package/src/editor/tree/TreeElement.tsx +70 -0
  41. package/src/editor/tree/TreeRoot.tsx +10 -0
  42. package/src/editor/tree/ViewRoot.tsx +20 -0
  43. package/src/editor/tree/index.module.scss +45 -0
  44. package/src/editor/tree/index.ts +4 -0
  45. package/src/editor/tree/navigation.ts +145 -0
  46. package/src/editor/tsconfig.build.json +5 -0
  47. package/src/editor/tsconfig.json +12 -0
  48. package/src/editor/tsdoc.json +4 -0
  49. package/src/editor/utils/SignalSet.ts +37 -0
  50. package/src/editor/utils/index.ts +1 -0
  51. package/src/editor/vite-env.d.ts +1 -0
  52. package/src/lib/code/CodeCursor.ts +468 -0
  53. package/src/lib/code/CodeDiffer.ts +77 -0
  54. package/src/lib/code/CodeFragment.ts +96 -0
  55. package/src/lib/code/CodeHighlighter.ts +73 -0
  56. package/src/lib/code/CodeMetrics.ts +47 -0
  57. package/src/lib/code/CodeRange.test.ts +113 -0
  58. package/src/lib/code/CodeRange.ts +222 -0
  59. package/src/lib/code/CodeScope.ts +100 -0
  60. package/src/lib/code/CodeSelection.ts +28 -0
  61. package/src/lib/code/CodeSignal.ts +348 -0
  62. package/src/lib/code/CodeTokenizer.ts +54 -0
  63. package/src/lib/code/DefaultHighlightStyle.ts +98 -0
  64. package/src/lib/code/LezerHighlighter.ts +113 -0
  65. package/src/lib/code/diff.test.ts +311 -0
  66. package/src/lib/code/diff.ts +319 -0
  67. package/src/lib/code/extractRange.ts +125 -0
  68. package/src/lib/code/index.ts +13 -0
  69. package/src/lib/components/Bezier.ts +103 -0
  70. package/src/lib/components/Camera.ts +401 -0
  71. package/src/lib/components/Circle.ts +310 -0
  72. package/src/lib/components/Code.ts +532 -0
  73. package/src/lib/components/CubicBezier.ts +115 -0
  74. package/src/lib/components/Curve.ts +460 -0
  75. package/src/lib/components/Grid.ts +134 -0
  76. package/src/lib/components/Icon.ts +153 -0
  77. package/src/lib/components/Img.ts +328 -0
  78. package/src/lib/components/Knot.ts +156 -0
  79. package/src/lib/components/Latex.ts +537 -0
  80. package/src/lib/components/Layout.ts +1101 -0
  81. package/src/lib/components/Line.ts +394 -0
  82. package/src/lib/components/Node.ts +1941 -0
  83. package/src/lib/components/Path.ts +132 -0
  84. package/src/lib/components/Polygon.ts +266 -0
  85. package/src/lib/components/QuadBezier.ts +103 -0
  86. package/src/lib/components/Ray.ts +126 -0
  87. package/src/lib/components/Rect.ts +219 -0
  88. package/src/lib/components/SVG.ts +853 -0
  89. package/src/lib/components/Shape.ts +322 -0
  90. package/src/lib/components/Spline.ts +318 -0
  91. package/src/lib/components/Txt.test.tsx +81 -0
  92. package/src/lib/components/Txt.ts +204 -0
  93. package/src/lib/components/TxtLeaf.ts +210 -0
  94. package/src/lib/components/Video.ts +368 -0
  95. package/src/lib/components/View2D.ts +85 -0
  96. package/src/lib/components/__logs__/image-without-source.ts +18 -0
  97. package/src/lib/components/__logs__/line-without-points.ts +31 -0
  98. package/src/lib/components/__logs__/reactive-playback-rate.ts +22 -0
  99. package/src/lib/components/__logs__/spline-with-insufficient-knots.ts +25 -0
  100. package/src/lib/components/__tests__/Camera.test.tsx +73 -0
  101. package/src/lib/components/__tests__/children.test.tsx +142 -0
  102. package/src/lib/components/__tests__/clone.test.tsx +126 -0
  103. package/src/lib/components/__tests__/fontInheritance.test.tsx +102 -0
  104. package/src/lib/components/__tests__/generatorTest.ts +27 -0
  105. package/src/lib/components/__tests__/mockScene2D.ts +50 -0
  106. package/src/lib/components/__tests__/query.test.tsx +122 -0
  107. package/src/lib/components/__tests__/state.test.tsx +60 -0
  108. package/src/lib/components/index.ts +26 -0
  109. package/src/lib/components/types.ts +35 -0
  110. package/src/lib/curves/ArcSegment.ts +169 -0
  111. package/src/lib/curves/CircleSegment.ts +99 -0
  112. package/src/lib/curves/CubicBezierSegment.ts +80 -0
  113. package/src/lib/curves/CurveDrawingInfo.ts +11 -0
  114. package/src/lib/curves/CurvePoint.ts +15 -0
  115. package/src/lib/curves/CurveProfile.ts +28 -0
  116. package/src/lib/curves/KnotInfo.ts +10 -0
  117. package/src/lib/curves/LineSegment.ts +75 -0
  118. package/src/lib/curves/Polynomial.ts +355 -0
  119. package/src/lib/curves/Polynomial2D.ts +62 -0
  120. package/src/lib/curves/PolynomialSegment.ts +151 -0
  121. package/src/lib/curves/QuadBezierSegment.ts +66 -0
  122. package/src/lib/curves/Segment.ts +31 -0
  123. package/src/lib/curves/UniformPolynomialCurveSampler.ts +93 -0
  124. package/src/lib/curves/createCurveProfileLerp.ts +471 -0
  125. package/src/lib/curves/getBezierSplineProfile.ts +227 -0
  126. package/src/lib/curves/getCircleProfile.ts +86 -0
  127. package/src/lib/curves/getPathProfile.ts +177 -0
  128. package/src/lib/curves/getPointAtDistance.ts +21 -0
  129. package/src/lib/curves/getPolylineProfile.test.ts +21 -0
  130. package/src/lib/curves/getPolylineProfile.ts +88 -0
  131. package/src/lib/curves/getRectProfile.ts +138 -0
  132. package/src/lib/curves/index.ts +16 -0
  133. package/src/lib/decorators/canvasStyleSignal.ts +15 -0
  134. package/src/lib/decorators/colorSignal.ts +9 -0
  135. package/src/lib/decorators/compound.ts +85 -0
  136. package/src/lib/decorators/computed.ts +18 -0
  137. package/src/lib/decorators/defaultStyle.ts +15 -0
  138. package/src/lib/decorators/filtersSignal.ts +133 -0
  139. package/src/lib/decorators/index.ts +10 -0
  140. package/src/lib/decorators/initializers.ts +32 -0
  141. package/src/lib/decorators/nodeName.ts +13 -0
  142. package/src/lib/decorators/signal.test.ts +89 -0
  143. package/src/lib/decorators/signal.ts +348 -0
  144. package/src/lib/decorators/spacingSignal.ts +15 -0
  145. package/src/lib/decorators/transformSignals.test.ts +858 -0
  146. package/src/lib/decorators/transformSignals.ts +1633 -0
  147. package/src/lib/decorators/vector2Signal.ts +35 -0
  148. package/src/lib/globals.d.ts +2 -0
  149. package/src/lib/index.ts +9 -0
  150. package/src/lib/jsx-dev-runtime.ts +2 -0
  151. package/src/lib/jsx-runtime.ts +45 -0
  152. package/src/lib/morphers/PathMorpher.ts +8 -0
  153. package/src/lib/morphers/defaultMorpher.ts +43 -0
  154. package/src/lib/morphers/index.ts +4 -0
  155. package/src/lib/morphers/manimMorpher.ts +658 -0
  156. package/src/lib/parse-svg-path.d.ts +14 -0
  157. package/src/lib/partials/Filter.ts +185 -0
  158. package/src/lib/partials/Gradient.ts +103 -0
  159. package/src/lib/partials/Pattern.ts +35 -0
  160. package/src/lib/partials/RoughConfig.ts +180 -0
  161. package/src/lib/partials/ShaderConfig.ts +122 -0
  162. package/src/lib/partials/index.ts +5 -0
  163. package/src/lib/partials/types.ts +58 -0
  164. package/src/lib/scenes/Scene2D.ts +167 -0
  165. package/src/lib/scenes/index.ts +3 -0
  166. package/src/lib/scenes/makeScene2D.ts +19 -0
  167. package/src/lib/scenes/useScene2D.ts +6 -0
  168. package/src/lib/tsconfig.build.json +5 -0
  169. package/src/lib/tsconfig.json +11 -0
  170. package/src/lib/tsdoc.json +4 -0
  171. package/src/lib/utils/CanvasUtils.ts +304 -0
  172. package/src/lib/utils/PathDataBuilder.ts +320 -0
  173. package/src/lib/utils/diff.test.ts +453 -0
  174. package/src/lib/utils/diff.ts +148 -0
  175. package/src/lib/utils/index.ts +5 -0
  176. package/src/lib/utils/is.ts +11 -0
  177. package/src/lib/utils/makeSignalExtensions.ts +29 -0
  178. package/src/lib/utils/rough.ts +513 -0
  179. package/src/lib/utils/withDefaults.tsx +26 -0
  180. package/src/tsconfig.base.json +18 -0
  181. package/src/tsconfig.build.json +8 -0
  182. package/src/tsconfig.json +5 -0
  183. package/tsconfig.project.json +7 -0
@@ -0,0 +1,858 @@
1
+ import {Vector2} from '@canvas-commons/core';
2
+ import 'geometry-polyfill';
3
+ import {beforeEach, describe, expect, test} from 'vitest';
4
+ import {Layout} from '../components/Layout';
5
+ import {Rect} from '../components/Rect';
6
+ import {mockScene2D} from '../components/__tests__/mockScene2D';
7
+ import {useScene2D} from '../scenes';
8
+
9
+ describe('Curried Transform Signals', () => {
10
+ mockScene2D();
11
+
12
+ let parent: Rect;
13
+ let child: Rect;
14
+
15
+ beforeEach(() => {
16
+ parent = new Rect({
17
+ position: [100, 100],
18
+ scale: [1, 1],
19
+ rotation: 0,
20
+ size: [200, 200],
21
+ });
22
+
23
+ child = new Rect({
24
+ position: [50, 50],
25
+ scale: [1, 1],
26
+ rotation: 0,
27
+ size: [100, 100],
28
+ });
29
+
30
+ parent.add(child);
31
+
32
+ useScene2D().getView().add(parent);
33
+ });
34
+
35
+ describe('Position relativeTo curried API', () => {
36
+ test('should return curried signal when called with only node', () => {
37
+ const curried = child.position.relativeTo(parent);
38
+
39
+ // Should be callable
40
+ expect(typeof curried).toBe('function');
41
+
42
+ // Should have component accessors
43
+ expect(curried.x).toBeDefined();
44
+ expect(curried.y).toBeDefined();
45
+ expect(typeof curried.x).toBe('function');
46
+ expect(typeof curried.y).toBe('function');
47
+ });
48
+
49
+ test('should get relative position when calling curried signal', () => {
50
+ const curried = child.position.relativeTo(parent);
51
+ const relativePos = curried();
52
+
53
+ expect(relativePos).toBeInstanceOf(Vector2);
54
+ // With simple transforms, this should work
55
+ expect(typeof relativePos.x).toBe('number');
56
+ expect(typeof relativePos.y).toBe('number');
57
+ });
58
+
59
+ test('should set relative position when calling curried signal with value', () => {
60
+ const curried = child.position.relativeTo(parent);
61
+ const originalPos = child.position();
62
+
63
+ // This should work without errors
64
+ curried([100, 200]);
65
+
66
+ // Position should have changed
67
+ expect(child.position()).not.toEqual(originalPos);
68
+ });
69
+
70
+ test('should access x component of relative position', () => {
71
+ const curried = child.position.relativeTo(parent);
72
+
73
+ // Get x component
74
+ const xValue = curried.x();
75
+ expect(typeof xValue).toBe('number');
76
+
77
+ // Set x component
78
+ const originalPos = child.position();
79
+ curried.x(150);
80
+
81
+ // Position should have changed
82
+ expect(child.position()).not.toEqual(originalPos);
83
+ });
84
+
85
+ test('should access y component of relative position', () => {
86
+ const curried = child.position.relativeTo(parent);
87
+
88
+ // Get y component
89
+ const yValue = curried.y();
90
+ expect(typeof yValue).toBe('number');
91
+
92
+ // Set y component
93
+ const originalPos = child.position();
94
+ curried.y(250);
95
+
96
+ // Position should have changed
97
+ expect(child.position()).not.toEqual(originalPos);
98
+
99
+ // Y component should be updated
100
+ expect(curried.y()).toBeCloseTo(250, 0.01);
101
+ });
102
+ });
103
+
104
+ describe('Scale relativeTo curried API', () => {
105
+ test('should return curried signal when called with only node', () => {
106
+ const curried = child.scale.relativeTo(parent);
107
+
108
+ expect(typeof curried).toBe('function');
109
+ expect(curried.x).toBeDefined();
110
+ expect(curried.y).toBeDefined();
111
+ });
112
+ });
113
+
114
+ describe('Rotation relativeTo curried API', () => {
115
+ test('should return curried signal when called with only node', () => {
116
+ const curried = child.rotation.relativeTo(parent);
117
+
118
+ expect(typeof curried).toBe('function');
119
+ });
120
+ });
121
+
122
+ describe('Origin signals with transform methods', () => {
123
+ test('should support transform methods on origin signals', () => {
124
+ const layout = new Layout({size: [200, 100]});
125
+
126
+ // Origin signals should have transform methods
127
+ expect(layout.top.abs).toBeDefined();
128
+ expect(layout.top.view).toBeDefined();
129
+ expect(layout.top.local).toBeDefined();
130
+ expect(layout.top.relativeTo).toBeDefined();
131
+ });
132
+ });
133
+
134
+ describe('Component Tweening', () => {
135
+ test('should support component tweening for enhanced transform methods', () => {
136
+ // Test abs component tweening
137
+ expect(() => {
138
+ child.position.abs.x(100, 1);
139
+ }).not.toThrow();
140
+
141
+ expect(() => {
142
+ child.position.abs.y(200, 1);
143
+ }).not.toThrow();
144
+
145
+ // Test view component tweening
146
+ expect(() => {
147
+ child.position.view.x(300, 1);
148
+ }).not.toThrow();
149
+
150
+ expect(() => {
151
+ child.position.view.y(400, 1);
152
+ }).not.toThrow();
153
+
154
+ // Test local component tweening
155
+ expect(() => {
156
+ child.position.local.x(50, 1);
157
+ }).not.toThrow();
158
+
159
+ expect(() => {
160
+ child.position.local.y(75, 1);
161
+ }).not.toThrow();
162
+ });
163
+
164
+ test('should support component tweening for curried relativeTo', () => {
165
+ const curried = child.position.relativeTo(parent);
166
+
167
+ // Component tweening should now work
168
+ expect(() => {
169
+ curried.x(100, 1); // Duration parameter should work
170
+ }).not.toThrow();
171
+
172
+ expect(() => {
173
+ curried.y(100, 1);
174
+ }).not.toThrow();
175
+ });
176
+
177
+ test('should support component tweening for scale signals', () => {
178
+ const curried = child.scale.relativeTo(parent);
179
+
180
+ // Scale component tweening
181
+ expect(() => {
182
+ curried.x(2, 1);
183
+ }).not.toThrow();
184
+
185
+ expect(() => {
186
+ curried.y(3, 1);
187
+ }).not.toThrow();
188
+
189
+ // Enhanced scale methods
190
+ expect(() => {
191
+ child.scale.abs.x(1.5, 1);
192
+ }).not.toThrow();
193
+
194
+ expect(() => {
195
+ child.scale.view.y(2.5, 1);
196
+ }).not.toThrow();
197
+ });
198
+ });
199
+
200
+ describe('Type safety and error handling', () => {
201
+ test('should maintain type safety with proper return types', () => {
202
+ const curried = child.position.relativeTo(parent);
203
+
204
+ // Getter should return Vector2
205
+ const pos = curried();
206
+ expect(pos).toBeInstanceOf(Vector2);
207
+
208
+ // Component getters should return numbers
209
+ const x = curried.x();
210
+ const y = curried.y();
211
+ expect(typeof x).toBe('number');
212
+ expect(typeof y).toBe('number');
213
+
214
+ // Setters should return the owner (child)
215
+ const result1 = curried([100, 100]);
216
+ expect(result1).toBe(child);
217
+
218
+ const result2 = curried.x(150);
219
+ expect(result2).toBe(child);
220
+ });
221
+ });
222
+
223
+ describe('Coordinate Space Verification', () => {
224
+ test('should correctly handle absolute space transformations', () => {
225
+ // Set up known initial state
226
+ parent.position([100, 100]);
227
+ parent.scale([2, 2]);
228
+ parent.rotation(45);
229
+ child.position([50, 50]);
230
+
231
+ // Test absolute position getter
232
+ const absPos = child.position.abs();
233
+ expect(absPos).toBeInstanceOf(Vector2);
234
+
235
+ // Test absolute position setter
236
+ child.position.abs([200, 300]);
237
+ const newAbsPos = child.position.abs();
238
+ expect(newAbsPos.x).toBeCloseTo(200, 0.01);
239
+ expect(newAbsPos.y).toBeCloseTo(300, 0.01);
240
+
241
+ // Test absolute component setters
242
+ child.position.abs.x(250);
243
+ expect(child.position.abs().x).toBeCloseTo(250, 0.01);
244
+
245
+ child.position.abs.y(350);
246
+ expect(child.position.abs().y).toBeCloseTo(350, 0.01);
247
+ });
248
+
249
+ test('should correctly handle relative space transformations', () => {
250
+ // Set up known state
251
+ parent.position([100, 100]);
252
+ child.position([50, 50]);
253
+
254
+ const curried = child.position.relativeTo(parent);
255
+
256
+ // Test relative position getter
257
+ const relPos = curried();
258
+ expect(relPos).toBeInstanceOf(Vector2);
259
+
260
+ // Test relative position setter
261
+ curried([75, 25]);
262
+ const newRelPos = curried();
263
+ expect(newRelPos.x).toBeCloseTo(75, 0.01);
264
+ expect(newRelPos.y).toBeCloseTo(25, 0.01);
265
+
266
+ // Test relative component setters
267
+ curried.x(100);
268
+ expect(curried.x()).toBeCloseTo(100, 0.01);
269
+
270
+ curried.y(50);
271
+ expect(curried.y()).toBeCloseTo(50, 0.01);
272
+ });
273
+
274
+ test('should correctly handle view space transformations', () => {
275
+ // Test view space getter
276
+ const viewPos = child.position.view();
277
+ expect(viewPos).toBeInstanceOf(Vector2);
278
+
279
+ // Test view space setter
280
+ child.position.view([400, 300]);
281
+ const newViewPos = child.position.view();
282
+ expect(newViewPos.x).toBeCloseTo(400, 0.01);
283
+ expect(newViewPos.y).toBeCloseTo(300, 0.01);
284
+
285
+ // Test view component setters
286
+ child.position.view.x(450);
287
+ expect(child.position.view().x).toBeCloseTo(450, 0.01);
288
+
289
+ child.position.view.y(350);
290
+ expect(child.position.view().y).toBeCloseTo(350, 0.01);
291
+ });
292
+
293
+ test('should correctly handle local space transformations', () => {
294
+ // Local space should be the raw position values
295
+ child.position([60, 80]);
296
+
297
+ // Test local space getter
298
+ const localPos = child.position.local();
299
+ expect(localPos.x).toBeCloseTo(60, 0.01);
300
+ expect(localPos.y).toBeCloseTo(80, 0.01);
301
+
302
+ // Test local space setter
303
+ child.position.local([120, 140]);
304
+ const newLocalPos = child.position.local();
305
+ expect(newLocalPos.x).toBeCloseTo(120, 0.01);
306
+ expect(newLocalPos.y).toBeCloseTo(140, 0.01);
307
+
308
+ // Test local component setters
309
+ child.position.local.x(150);
310
+ expect(child.position.local().x).toBeCloseTo(150, 0.01);
311
+
312
+ child.position.local.y(170);
313
+ expect(child.position.local().y).toBeCloseTo(170, 0.01);
314
+ });
315
+
316
+ test('should maintain consistency between coordinate spaces', () => {
317
+ // Set a known absolute position
318
+ child.position.abs([300, 200]);
319
+ const absPos = child.position.abs();
320
+
321
+ // The absolute position should be consistent
322
+ expect(absPos.x).toBeCloseTo(300, 0.01);
323
+ expect(absPos.y).toBeCloseTo(200, 0.01);
324
+
325
+ // Relative position should reflect the difference from parent
326
+ const relPos = child.position.relativeTo(parent)();
327
+ const parentAbsPos = parent.position.abs();
328
+ expect(relPos.x).toBeCloseTo(absPos.x - parentAbsPos.x, 0.01);
329
+ expect(relPos.y).toBeCloseTo(absPos.y - parentAbsPos.y, 0.01);
330
+ });
331
+ });
332
+
333
+ describe('Consistency across signal types', () => {
334
+ test('should provide consistent API across position, scale, and rotation', () => {
335
+ // All should support curried relativeTo
336
+ const positionCurried = child.position.relativeTo(parent);
337
+ const scaleCurried = child.scale.relativeTo(parent);
338
+ const rotationCurried = child.rotation.relativeTo(parent);
339
+
340
+ // All should be callable functions
341
+ expect(typeof positionCurried).toBe('function');
342
+ expect(typeof scaleCurried).toBe('function');
343
+ expect(typeof rotationCurried).toBe('function');
344
+
345
+ // Vector signals should have component access
346
+ expect(positionCurried.x).toBeDefined();
347
+ expect(positionCurried.y).toBeDefined();
348
+ expect(scaleCurried.x).toBeDefined();
349
+ expect(scaleCurried.y).toBeDefined();
350
+ });
351
+
352
+ test('should work with method chaining', () => {
353
+ // Should be able to chain operations naturally
354
+ const curried = child.position.relativeTo(parent);
355
+
356
+ // Chain component setters
357
+ const result = curried.x(100).position.relativeTo(parent).y(200);
358
+ expect(result).toBe(child);
359
+
360
+ // Verify values were set
361
+ expect(curried.x()).toBeCloseTo(100, 0.01);
362
+ expect(curried.y()).toBeCloseTo(200, 0.01);
363
+ });
364
+ });
365
+
366
+ describe('Layout Origin Signals', () => {
367
+ let layout: Layout;
368
+
369
+ beforeEach(() => {
370
+ layout = new Layout({
371
+ size: [300, 200],
372
+ position: [50, 50],
373
+ });
374
+ useScene2D().getView().add(layout);
375
+ });
376
+
377
+ test('should provide transform methods for layout origin signals', () => {
378
+ // All origin signals should have transform methods
379
+ expect(layout.left.abs).toBeDefined();
380
+ expect(layout.left.view).toBeDefined();
381
+ expect(layout.left.local).toBeDefined();
382
+ expect(layout.left.relativeTo).toBeDefined();
383
+
384
+ expect(layout.top.abs).toBeDefined();
385
+ expect(layout.top.view).toBeDefined();
386
+ expect(layout.top.local).toBeDefined();
387
+ expect(layout.top.relativeTo).toBeDefined();
388
+
389
+ expect(layout.right.abs).toBeDefined();
390
+ expect(layout.right.view).toBeDefined();
391
+ expect(layout.right.local).toBeDefined();
392
+ expect(layout.right.relativeTo).toBeDefined();
393
+
394
+ expect(layout.bottom.abs).toBeDefined();
395
+ expect(layout.bottom.view).toBeDefined();
396
+ expect(layout.bottom.local).toBeDefined();
397
+ expect(layout.bottom.relativeTo).toBeDefined();
398
+ });
399
+
400
+ test('should support component access for layout origin signals', () => {
401
+ // Component access should be available for all transform methods
402
+ expect(layout.left.abs.x).toBeDefined();
403
+ expect(layout.left.abs.y).toBeDefined();
404
+ expect(layout.left.view.x).toBeDefined();
405
+ expect(layout.left.view.y).toBeDefined();
406
+ expect(layout.left.local.x).toBeDefined();
407
+ expect(layout.left.local.y).toBeDefined();
408
+
409
+ expect(layout.top.abs.x).toBeDefined();
410
+ expect(layout.top.abs.y).toBeDefined();
411
+ expect(layout.top.view.x).toBeDefined();
412
+ expect(layout.top.view.y).toBeDefined();
413
+ expect(layout.top.local.x).toBeDefined();
414
+ expect(layout.top.local.y).toBeDefined();
415
+ });
416
+
417
+ test('should allow getting layout origin positions', () => {
418
+ // Test absolute coordinate access
419
+ const leftAbs = layout.left.abs();
420
+ const topAbs = layout.top.abs();
421
+ const rightAbs = layout.right.abs();
422
+ const bottomAbs = layout.bottom.abs();
423
+
424
+ expect(leftAbs).toBeInstanceOf(Vector2);
425
+ expect(topAbs).toBeInstanceOf(Vector2);
426
+ expect(rightAbs).toBeInstanceOf(Vector2);
427
+ expect(bottomAbs).toBeInstanceOf(Vector2);
428
+
429
+ // Test component access
430
+ expect(typeof layout.left.abs.x()).toBe('number');
431
+ expect(typeof layout.left.abs.y()).toBe('number');
432
+ expect(typeof layout.top.view.x()).toBe('number');
433
+ expect(typeof layout.top.view.y()).toBe('number');
434
+ });
435
+
436
+ test('should allow setting layout origin positions', () => {
437
+ // Test setting absolute position
438
+ const originalPosition = layout.position();
439
+ layout.left.abs([100, 150]);
440
+
441
+ // Position should have changed
442
+ expect(layout.position()).not.toEqual(originalPosition);
443
+
444
+ // Test component setters
445
+ layout.top.abs.y(200);
446
+ layout.right.view.x(300);
447
+
448
+ // Should not throw errors
449
+ expect(() => {
450
+ layout.bottom.local([250, 180]);
451
+ }).not.toThrow();
452
+ });
453
+
454
+ test('should support component tweening for layout origin signals', () => {
455
+ // Test component tweening for different coordinate spaces
456
+ expect(() => {
457
+ layout.left.abs.x(100, 1);
458
+ }).not.toThrow();
459
+
460
+ expect(() => {
461
+ layout.top.view.y(150, 1);
462
+ }).not.toThrow();
463
+
464
+ expect(() => {
465
+ layout.right.local.x(200, 1);
466
+ }).not.toThrow();
467
+
468
+ expect(() => {
469
+ layout.bottom.abs.y(250, 1);
470
+ }).not.toThrow();
471
+ });
472
+
473
+ test('should support relativeTo for layout origin signals', () => {
474
+ // Create another layout to use as reference
475
+ const reference = new Layout({
476
+ size: [200, 150],
477
+ position: [0, 0],
478
+ });
479
+ useScene2D().getView().add(reference);
480
+
481
+ // Test getting relative position
482
+ const relativePos = layout.left.relativeTo(reference);
483
+ expect(relativePos).toBeInstanceOf(Vector2);
484
+ });
485
+
486
+ test('should maintain consistency between origin signal coordinate spaces', () => {
487
+ // Set a known position
488
+ layout.position([100, 75]);
489
+
490
+ // Get position through different origin signals
491
+ const leftLocal = layout.left.local();
492
+ const topLocal = layout.top.local();
493
+
494
+ // Local positions should be relative to layout's local coordinate system
495
+ expect(leftLocal).toBeInstanceOf(Vector2);
496
+ expect(topLocal).toBeInstanceOf(Vector2);
497
+
498
+ // Setting position through origin signals should update the layout
499
+ const originalPos = layout.position();
500
+ layout.left.local([150, 100]);
501
+ expect(layout.position()).not.toEqual(originalPos);
502
+ });
503
+
504
+ test('should work with different layout configurations', () => {
505
+ const centerLayout = new Layout({
506
+ size: [200, 100],
507
+ position: [0, 0],
508
+ });
509
+ useScene2D().getView().add(centerLayout);
510
+
511
+ expect(() => {
512
+ centerLayout.left.abs.x(50);
513
+ }).not.toThrow();
514
+
515
+ expect(() => {
516
+ centerLayout.top.view.y(25);
517
+ }).not.toThrow();
518
+
519
+ const topLeftLayout = new Layout({
520
+ size: [200, 100],
521
+ position: [0, 0],
522
+ });
523
+ useScene2D().getView().add(topLeftLayout);
524
+
525
+ expect(() => {
526
+ topLeftLayout.right.abs([100, 50]);
527
+ }).not.toThrow();
528
+
529
+ expect(() => {
530
+ topLeftLayout.bottom.local.y(75);
531
+ }).not.toThrow();
532
+ });
533
+ });
534
+
535
+ describe('Edge Cases and Error Handling', () => {
536
+ test('should handle null or undefined references gracefully', () => {
537
+ // Test relativeTo with null/undefined nodes
538
+ expect(() => {
539
+ child.position.relativeTo(null as any);
540
+ }).not.toThrow();
541
+
542
+ expect(() => {
543
+ child.position.relativeTo(undefined as any);
544
+ }).not.toThrow();
545
+ });
546
+
547
+ test('should handle nodes not in the scene tree', () => {
548
+ const orphanNode = new Rect({
549
+ position: [100, 100],
550
+ size: [50, 50],
551
+ });
552
+
553
+ // Should not throw when working with orphaned nodes
554
+ expect(() => {
555
+ child.position.relativeTo(orphanNode);
556
+ }).not.toThrow();
557
+
558
+ expect(() => {
559
+ orphanNode.position.abs([200, 200]);
560
+ }).not.toThrow();
561
+
562
+ expect(() => {
563
+ orphanNode.position.view.x(150);
564
+ }).not.toThrow();
565
+ });
566
+
567
+ test('should handle invalid vector inputs', () => {
568
+ // Test with various invalid inputs
569
+ expect(() => {
570
+ child.position.abs([NaN, 100]);
571
+ }).not.toThrow();
572
+
573
+ expect(() => {
574
+ child.position.view([100, Infinity]);
575
+ }).not.toThrow();
576
+
577
+ expect(() => {
578
+ child.position.local([-Infinity, -100]);
579
+ }).not.toThrow();
580
+ });
581
+
582
+ test('should handle deeply nested node hierarchies', () => {
583
+ // Create a deep hierarchy
584
+ let currentParent = parent;
585
+ const nodes = [parent];
586
+
587
+ for (let i = 0; i < 10; i++) {
588
+ const newNode = new Rect({
589
+ position: [10, 10],
590
+ size: [50, 50],
591
+ });
592
+ currentParent.add(newNode);
593
+ nodes.push(newNode);
594
+ currentParent = newNode;
595
+ }
596
+
597
+ const deepChild = nodes[nodes.length - 1];
598
+ const rootParent = nodes[0];
599
+
600
+ // Should handle deep transformations
601
+ expect(() => {
602
+ deepChild.position.relativeTo(rootParent);
603
+ }).not.toThrow();
604
+
605
+ expect(() => {
606
+ deepChild.position.abs([500, 500]);
607
+ }).not.toThrow();
608
+
609
+ const relative = deepChild.position.relativeTo(rootParent);
610
+ expect(() => {
611
+ relative.x(250);
612
+ }).not.toThrow();
613
+ });
614
+
615
+ test('should handle circular references in relativeTo', () => {
616
+ // Test circular references
617
+ const nodeA = new Rect({position: [0, 0], size: [100, 100]});
618
+ const nodeB = new Rect({position: [50, 50], size: [100, 100]});
619
+
620
+ nodeA.add(nodeB);
621
+ useScene2D().getView().add(nodeA);
622
+
623
+ // This creates a logical circular reference but should not crash
624
+ expect(() => {
625
+ const relativeA = nodeA.position.relativeTo(nodeB);
626
+ const relativeB = nodeB.position.relativeTo(nodeA);
627
+
628
+ relativeA([100, 100]);
629
+ relativeB([50, 50]);
630
+ }).not.toThrow();
631
+ });
632
+
633
+ test('should handle components with zero or negative size', () => {
634
+ const zeroLayout = new Layout({
635
+ size: [0, 0],
636
+ position: [100, 100],
637
+ });
638
+ useScene2D().getView().add(zeroLayout);
639
+
640
+ expect(() => {
641
+ zeroLayout.left.abs.x(50);
642
+ }).not.toThrow();
643
+
644
+ expect(() => {
645
+ zeroLayout.top.view.y(75);
646
+ }).not.toThrow();
647
+
648
+ const negativeLayout = new Layout({
649
+ size: [-100, -50],
650
+ position: [100, 100],
651
+ });
652
+ useScene2D().getView().add(negativeLayout);
653
+
654
+ expect(() => {
655
+ negativeLayout.right.local([200, 150]);
656
+ }).not.toThrow();
657
+ });
658
+
659
+ test('should handle extreme coordinate values', () => {
660
+ const extremeValues = [
661
+ Number.MAX_SAFE_INTEGER,
662
+ Number.MIN_SAFE_INTEGER,
663
+ 1e10,
664
+ -1e10,
665
+ 0.00001,
666
+ -0.00001,
667
+ ];
668
+
669
+ extremeValues.forEach(value => {
670
+ expect(() => {
671
+ child.position.abs([value, value]);
672
+ }).not.toThrow();
673
+
674
+ expect(() => {
675
+ child.position.view.x(value);
676
+ }).not.toThrow();
677
+
678
+ expect(() => {
679
+ child.position.local.y(value);
680
+ }).not.toThrow();
681
+ });
682
+ });
683
+
684
+ test('should handle transform signals on nodes with complex transformations', () => {
685
+ // Set up complex transformations
686
+ parent.position([200, 150]);
687
+ parent.scale([3, 2]);
688
+ parent.rotation(90);
689
+ parent.skew([30, -15]);
690
+
691
+ child.position([75, 25]);
692
+ child.scale([0.5, 1.5]);
693
+ child.rotation(-45);
694
+
695
+ // Should handle complex transform chains
696
+ expect(() => {
697
+ const relative = child.position.relativeTo(parent);
698
+ relative([100, 200]);
699
+ }).not.toThrow();
700
+
701
+ expect(() => {
702
+ child.position.abs([400, 300]);
703
+ }).not.toThrow();
704
+
705
+ expect(() => {
706
+ child.position.view.x(500);
707
+ }).not.toThrow();
708
+ });
709
+
710
+ test('should handle concurrent modifications', () => {
711
+ // Simulate concurrent modifications
712
+ const curried = child.position.relativeTo(parent);
713
+
714
+ expect(() => {
715
+ // Modify through different interfaces simultaneously
716
+ child.position([100, 100]);
717
+ curried([150, 150]);
718
+ child.position.abs([200, 200]);
719
+ curried.x(250);
720
+ child.position.view.y(300);
721
+ }).not.toThrow();
722
+
723
+ // Values should be consistent after all modifications
724
+ expect(typeof curried.x()).toBe('number');
725
+ expect(typeof curried.y()).toBe('number');
726
+ });
727
+ });
728
+
729
+ describe('Advanced Component Access and Chaining', () => {
730
+ test('should maintain component state across coordinate space changes', () => {
731
+ // Set initial position through one coordinate space
732
+ child.position.abs([100, 150]);
733
+ const absPos = child.position.abs();
734
+
735
+ // Get the same position through different coordinate spaces
736
+ const viewPos = child.position.view();
737
+ const localPos = child.position.local();
738
+
739
+ // All should be valid Vector2 instances
740
+ expect(absPos).toBeInstanceOf(Vector2);
741
+ expect(viewPos).toBeInstanceOf(Vector2);
742
+ expect(localPos).toBeInstanceOf(Vector2);
743
+
744
+ // Modify through one space and verify in another
745
+ child.position.local.x(250);
746
+ const newAbsPos = child.position.abs();
747
+ expect(newAbsPos.x).not.toBe(absPos.x);
748
+ });
749
+
750
+ test('should support layout origin signal component chaining', () => {
751
+ const layout = new Layout({
752
+ size: [200, 100],
753
+ position: [50, 25],
754
+ });
755
+ useScene2D().getView().add(layout);
756
+
757
+ // Test chaining layout origin signals
758
+ const result = layout.left.abs
759
+ .x(100)
760
+ .top.view.y(75)
761
+ .right.local([150, 50]);
762
+
763
+ expect(result).toBe(layout);
764
+
765
+ // Test component access for layout origin signals
766
+ const leftX = layout.left.abs.x;
767
+ const topY = layout.top.view.y;
768
+ const rightLocal = layout.right.local;
769
+
770
+ expect(typeof leftX).toBe('function');
771
+ expect(typeof topY).toBe('function');
772
+ expect(typeof rightLocal).toBe('function');
773
+
774
+ // Should be able to chain layout operations
775
+ expect(() => {
776
+ layout.left.view.x(200).bottom.abs.y(300).top.local([175, 125]);
777
+ }).not.toThrow();
778
+ });
779
+
780
+ test('should support mixed signal type chaining', () => {
781
+ // Test chaining between different signal types (position, scale, rotation)
782
+ const result = child
783
+ .position([100, 100])
784
+ .scale([2, 1.5])
785
+ .rotation(30)
786
+ .position.abs.x(150)
787
+ .scale.view.y(3)
788
+ .rotation(60);
789
+
790
+ expect(result).toBe(child);
791
+
792
+ // Verify final values
793
+ expect(child.position.abs().x).toBeCloseTo(150, 0.01);
794
+ expect(child.rotation()).toBeCloseTo(60, 0.01);
795
+ });
796
+
797
+ test('should support component access with relativeTo chaining', () => {
798
+ // Test component access through relativeTo chains
799
+ const relativePosition = child.position.relativeTo(parent);
800
+ const relativeScale = child.scale.relativeTo(parent);
801
+
802
+ // Should support chaining after relativeTo
803
+ const result = relativePosition
804
+ .x(100)
805
+ .scale.relativeTo(parent)
806
+ .y(2)
807
+ .position.relativeTo(parent)
808
+ .y(150);
809
+
810
+ expect(result).toBe(child);
811
+ expect(relativePosition.x()).toBeCloseTo(100, 0.01);
812
+ expect(relativeScale.y()).toBeCloseTo(2, 0.01);
813
+ });
814
+
815
+ test('should handle rapid successive component modifications', () => {
816
+ // Test rapid modifications through component access
817
+ const iterations = 100;
818
+
819
+ expect(() => {
820
+ for (let i = 0; i < iterations; i++) {
821
+ child.position.abs.x(i);
822
+ child.position.view.y(i * 2);
823
+ child.scale.local.x(1 + i * 0.01);
824
+ }
825
+ }).not.toThrow();
826
+
827
+ // Final values should be from last iteration
828
+ expect(child.position.abs().x).toBeCloseTo(iterations - 1, 0.01);
829
+ expect(child.scale.local().x).toBeCloseTo(
830
+ 1 + (iterations - 1) * 0.01,
831
+ 0.01,
832
+ );
833
+ });
834
+
835
+ test('should support component getter/setter symmetry', () => {
836
+ // Test that getting and setting through components is symmetric
837
+ const testValue = 123.456;
838
+
839
+ // Set through component
840
+ child.position.abs.x(testValue);
841
+ const retrievedValue = child.position.abs.x();
842
+
843
+ expect(retrievedValue).toBeCloseTo(testValue, 0.01);
844
+
845
+ // Test with view coordinates
846
+ child.position.view.y(testValue);
847
+ const retrievedViewValue = child.position.view.y();
848
+
849
+ expect(retrievedViewValue).toBeCloseTo(testValue, 0.01);
850
+
851
+ // Test with local coordinates
852
+ child.position.local.x(testValue);
853
+ const retrievedLocalValue = child.position.local.x();
854
+
855
+ expect(retrievedLocalValue).toBeCloseTo(testValue, 0.01);
856
+ });
857
+ });
858
+ });