@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,142 @@
1
+ import {createRef, createSignal, range} from '@canvas-commons/core';
2
+ import {describe, expect, it} from 'vitest';
3
+ import {useScene2D} from '../../scenes';
4
+ import {Node} from '../Node';
5
+ import {mockScene2D} from './mockScene2D';
6
+
7
+ describe('children', () => {
8
+ mockScene2D();
9
+
10
+ it('Append children', () => {
11
+ const view = useScene2D().getView();
12
+ const parent = createRef<Node>();
13
+ const childA = createRef<Node>();
14
+ const childB = createRef<Node>();
15
+
16
+ view.add(
17
+ <Node ref={parent}>
18
+ <Node ref={childA} />
19
+ <Node ref={childB} />
20
+ </Node>,
21
+ );
22
+
23
+ expect(childA().parent()).toBe(parent());
24
+ expect(childB().parent()).toBe(parent());
25
+ expect(parent().children()).toEqual([childA(), childB()]);
26
+ });
27
+
28
+ it('Clear children', () => {
29
+ const view = useScene2D().getView();
30
+ const parent = createRef<Node>();
31
+ const childA = createRef<Node>();
32
+ const childB = createRef<Node>();
33
+
34
+ view.add(
35
+ <Node ref={parent}>
36
+ <Node ref={childA} />
37
+ <Node ref={childB} />
38
+ </Node>,
39
+ );
40
+
41
+ parent().removeChildren();
42
+
43
+ expect(childA().parent()).toBe(null);
44
+ expect(childB().parent()).toBe(null);
45
+ expect(parent().children()).toEqual([]);
46
+ });
47
+
48
+ it('Replace children', () => {
49
+ const view = useScene2D().getView();
50
+ const parent = createRef<Node>();
51
+ const childA = <Node />;
52
+ const childB = <Node />;
53
+
54
+ view.add(<Node ref={parent}>{childA}</Node>);
55
+
56
+ parent().children(childB);
57
+
58
+ expect(childA.parent()).toBe(null);
59
+ expect(childB.parent()).toBe(parent());
60
+ expect(parent().children()).toEqual([childB]);
61
+ });
62
+
63
+ it('Take a child from another node', () => {
64
+ const view = useScene2D().getView();
65
+ const parentA = createRef<Node>();
66
+ const parentB = createRef<Node>();
67
+ const child = createRef<Node>();
68
+
69
+ view.add(
70
+ <>
71
+ <Node ref={parentA}>
72
+ <Node ref={child} />
73
+ </Node>
74
+ <Node ref={parentB} />
75
+ </>,
76
+ );
77
+
78
+ expect(parentA().children()).toEqual([child()]);
79
+ expect(parentB().children()).toEqual([]);
80
+ expect(child().parent()).toBe(parentA());
81
+
82
+ parentB().children(child());
83
+
84
+ expect(parentA().children()).toEqual([]);
85
+ expect(parentB().children()).toEqual([child()]);
86
+ expect(child().parent()).toBe(parentB());
87
+ });
88
+
89
+ it('Append children conditionally', () => {
90
+ const view = useScene2D().getView();
91
+ const parent = createRef<Node>();
92
+ const childA = <Node />;
93
+ const childB = <Node />;
94
+ const predicate = createSignal(false);
95
+
96
+ view.add(<Node ref={parent}>{() => (predicate() ? childA : childB)}</Node>);
97
+
98
+ expect(parent().children()).toEqual([childB]);
99
+ expect(childA.parent()).toBe(null);
100
+ expect(childB.parent()).toBe(parent());
101
+
102
+ predicate(true);
103
+
104
+ expect(parent().children()).toEqual([childA]);
105
+ expect(childA.parent()).toBe(parent());
106
+ expect(childB.parent()).toBe(null);
107
+ });
108
+
109
+ it('Spawn reactive children', () => {
110
+ const view = useScene2D().getView();
111
+ const parent = createRef<Node>();
112
+ const count = createSignal(3);
113
+
114
+ view.add(
115
+ <Node ref={parent}>{() => range(count()).map(() => <Node />)}</Node>,
116
+ );
117
+
118
+ expect(parent().children().length).toBe(3);
119
+
120
+ count(5);
121
+
122
+ expect(parent().children().length).toBe(5);
123
+ });
124
+
125
+ it('Replace a spawner', () => {
126
+ const view = useScene2D().getView();
127
+ const parent = createRef<Node>();
128
+ const childA = <Node />;
129
+ const childB = <Node />;
130
+
131
+ view.add(<Node ref={parent}>{() => childA}</Node>);
132
+
133
+ expect(parent().children()).toEqual([childA]);
134
+ expect(childA.parent()).toBe(parent());
135
+
136
+ parent().children(childB);
137
+
138
+ expect(childA.parent()).toBe(null);
139
+ expect(childB.parent()).toBe(parent());
140
+ expect(parent().children()).toEqual([childB]);
141
+ });
142
+ });
@@ -0,0 +1,126 @@
1
+ import {Vector2, createSignal, range} from '@canvas-commons/core';
2
+ import {describe, expect, it} from 'vitest';
3
+ import {Circle} from '../Circle';
4
+ import {Node} from '../Node';
5
+ import {mockScene2D} from './mockScene2D';
6
+
7
+ describe('clone', () => {
8
+ mockScene2D();
9
+
10
+ it('Normal clone', () => {
11
+ const signal = createSignal(45);
12
+ const template = (
13
+ <Circle lineWidth={8} startAngle={signal} end={0.5} />
14
+ ) as Circle;
15
+ const clone = template.clone({end: 0});
16
+
17
+ expect(clone.lineWidth()).toBe(8);
18
+ expect(clone.startAngle()).toBe(45);
19
+ expect(clone.end()).toBe(0);
20
+ expect(clone.startArrow.context.isInitial()).toBe(true);
21
+
22
+ signal(90);
23
+
24
+ expect(clone.startAngle()).toBe(90);
25
+ });
26
+
27
+ it('Reactive clone', () => {
28
+ const signal = createSignal(45);
29
+ const template = (
30
+ <Circle lineWidth={8} startAngle={signal} end={0.5} />
31
+ ) as Circle;
32
+ const clone = template.reactiveClone({end: 0});
33
+
34
+ expect(clone.lineWidth()).toBe(8);
35
+ expect(clone.startAngle()).toBe(45);
36
+ expect(clone.end()).toBe(0);
37
+ expect(clone.startArrow.context.isInitial()).toBe(false);
38
+
39
+ template.lineWidth(16);
40
+ template.startArrow(true);
41
+ template.end(0.25);
42
+ signal(90);
43
+
44
+ expect(clone.lineWidth()).toBe(16);
45
+ expect(clone.startAngle()).toBe(90);
46
+ expect(clone.startArrow()).toBe(true);
47
+ expect(clone.end()).toBe(0);
48
+ });
49
+
50
+ it('Snapshot clone', () => {
51
+ const signal = createSignal(45);
52
+ const template = (
53
+ <Circle lineWidth={8} startAngle={signal} end={0.5} />
54
+ ) as Circle;
55
+ const clone = template.snapshotClone({end: 0});
56
+
57
+ expect(clone.lineWidth()).toBe(8);
58
+ expect(clone.startAngle()).toBe(45);
59
+ expect(clone.end()).toBe(0);
60
+ expect(clone.startArrow.context.isInitial()).toBe(true);
61
+
62
+ template.lineWidth(16);
63
+ template.startArrow(true);
64
+ signal(90);
65
+
66
+ expect(clone.lineWidth()).toBe(8);
67
+ expect(clone.startAngle()).toBe(45);
68
+ expect(clone.startArrow()).toBe(false);
69
+ });
70
+
71
+ it('Clone compound signal', () => {
72
+ const signal = Vector2.createSignal(200);
73
+ const template = (<Circle anchor={1} position={signal} />) as Circle;
74
+ const clone = template.clone({x: 100, anchorY: -1});
75
+
76
+ expect(clone.x()).toBe(100);
77
+ expect(clone.y()).toBe(200);
78
+ expect(clone.anchor.x()).toBe(1);
79
+ expect(clone.anchor.y()).toBe(-1);
80
+
81
+ signal([300, 400]);
82
+
83
+ expect(clone.x()).toBe(100);
84
+ expect(clone.y()).toBe(400);
85
+ });
86
+
87
+ it('Clone children', () => {
88
+ const signal = createSignal(45);
89
+ const template = (
90
+ <Node>
91
+ <Circle lineWidth={8} startAngle={signal} />
92
+ </Node>
93
+ );
94
+ const clone = template.clone();
95
+
96
+ expect(clone.children().length).toBe(1);
97
+ expect(clone.childAs(0)).not.toBe(template.childAs(0));
98
+ expect(clone.childAs<Circle>(0)!.lineWidth()).toBe(8);
99
+ expect(clone.childAs<Circle>(0)!.startAngle()).toBe(45);
100
+
101
+ signal(90);
102
+
103
+ expect(clone.childAs<Circle>(0)!.startAngle()).toBe(90);
104
+ });
105
+
106
+ it('Clone spawner', () => {
107
+ const count = createSignal(3);
108
+ const template = (
109
+ <Node>
110
+ {() =>
111
+ range(count()).map(() => (
112
+ <Circle lineWidth={8} startAngle={count} end={0.5} />
113
+ ))
114
+ }
115
+ </Node>
116
+ );
117
+ const clone = template.clone();
118
+
119
+ expect(clone.children().length).toBe(3);
120
+ expect(clone.childAs(0)).not.toBe(template.childAs(0));
121
+
122
+ count(5);
123
+
124
+ expect(clone.children().length).toBe(5);
125
+ });
126
+ });
@@ -0,0 +1,102 @@
1
+ import {createRef} from '@canvas-commons/core';
2
+ import {describe, expect, it} from 'vitest';
3
+ import {useScene2D} from '../../scenes';
4
+ import {Layout} from '../Layout';
5
+ import {Txt} from '../Txt';
6
+ import {mockScene2D} from './mockScene2D';
7
+
8
+ describe('font inheritance', () => {
9
+ mockScene2D();
10
+
11
+ it('inherits fontSize from the view to a direct Txt child', () => {
12
+ const view = useScene2D().getView();
13
+ view.fontSize(100);
14
+ const txt = createRef<Txt>();
15
+
16
+ view.add(<Txt ref={txt}>hello</Txt>);
17
+
18
+ expect(txt().fontSize()).toBe(100);
19
+ });
20
+
21
+ it('inherits fontFamily from the view to a direct Txt child', () => {
22
+ const view = useScene2D().getView();
23
+ view.fontFamily('Comic Sans');
24
+ const txt = createRef<Txt>();
25
+
26
+ view.add(<Txt ref={txt}>hello</Txt>);
27
+
28
+ expect(txt().fontFamily()).toBe('Comic Sans');
29
+ });
30
+
31
+ it('inherits fontSize through a non-layout Layout wrapper', () => {
32
+ const view = useScene2D().getView();
33
+ view.fontSize(72);
34
+ const txt = createRef<Txt>();
35
+
36
+ view.add(
37
+ <Layout>
38
+ <Txt ref={txt}>hello</Txt>
39
+ </Layout>,
40
+ );
41
+
42
+ expect(txt().fontSize()).toBe(72);
43
+ });
44
+
45
+ it('inherits fontSize through a layout-enabled Layout wrapper', () => {
46
+ const view = useScene2D().getView();
47
+ view.fontSize(64);
48
+ const txt = createRef<Txt>();
49
+
50
+ view.add(
51
+ <Layout layout>
52
+ <Txt ref={txt}>hello</Txt>
53
+ </Layout>,
54
+ );
55
+
56
+ expect(txt().fontSize()).toBe(64);
57
+ });
58
+
59
+ it('explicit fontSize on child overrides inherited value', () => {
60
+ const view = useScene2D().getView();
61
+ view.fontSize(100);
62
+ const txt = createRef<Txt>();
63
+
64
+ view.add(
65
+ <Txt ref={txt} fontSize={25}>
66
+ hello
67
+ </Txt>,
68
+ );
69
+
70
+ expect(txt().fontSize()).toBe(25);
71
+ });
72
+
73
+ it('layout={false} on a Layout disables inheritance and falls back to initial', () => {
74
+ const view = useScene2D().getView();
75
+ view.fontSize(100);
76
+ const layout = createRef<Layout>();
77
+
78
+ view.add(<Layout ref={layout} layout={false} />);
79
+
80
+ expect(layout().fontSize()).toBe(48);
81
+ });
82
+
83
+ it('inherits the full set of text properties from the view', () => {
84
+ const view = useScene2D().getView();
85
+ view.fontWeight(800);
86
+ view.fontStyle('italic');
87
+ view.lineHeight(42);
88
+ view.letterSpacing(3);
89
+ view.textAlign('center');
90
+ view.textWrap(true);
91
+ const txt = createRef<Txt>();
92
+
93
+ view.add(<Txt ref={txt}>hello</Txt>);
94
+
95
+ expect(txt().fontWeight()).toBe(800);
96
+ expect(txt().fontStyle()).toBe('italic');
97
+ expect(txt().lineHeight()).toBe(42);
98
+ expect(txt().letterSpacing()).toBe(3);
99
+ expect(txt().textAlign()).toBe('center');
100
+ expect(txt().textWrap()).toBe(true);
101
+ });
102
+ });
@@ -0,0 +1,27 @@
1
+ import {ThreadGeneratorFactory, threads} from '@canvas-commons/core';
2
+ import {useScene2D} from '../../scenes';
3
+ import {View2D} from '../View2D';
4
+
5
+ /**
6
+ * Turn a generator factory into a test function.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * it(
11
+ * 'Example test',
12
+ * generatorTest(function* () {
13
+ * yield* waitFor(1);
14
+ * expect(useTime()).toBe(1);
15
+ * }),
16
+ * );
17
+ * ```
18
+ *
19
+ * @param factory - The generator factory to test.
20
+ */
21
+ export function generatorTest(factory: ThreadGeneratorFactory<View2D>) {
22
+ return () => {
23
+ const view = useScene2D().getView();
24
+ const tasks = threads(() => factory(view));
25
+ [...tasks];
26
+ };
27
+ }
@@ -0,0 +1,50 @@
1
+ import {
2
+ FullSceneDescription,
3
+ PlaybackManager,
4
+ PlaybackStatus,
5
+ ReadOnlyTimeEvents,
6
+ ThreadGeneratorFactory,
7
+ ValueDispatcher,
8
+ Vector2,
9
+ endPlayback,
10
+ endScene,
11
+ startPlayback,
12
+ startScene,
13
+ } from '@canvas-commons/core';
14
+ import {afterAll, beforeAll, beforeEach} from 'vitest';
15
+ import {Scene2D, makeScene2D} from '../../scenes';
16
+ import {View2D} from '../View2D';
17
+
18
+ /**
19
+ * Set up the test environment to support creating nodes.
20
+ *
21
+ * @remarks
22
+ * Should be called inside a `describe()` block.
23
+ * Due to js-dom limitations, layouts are not correctly computed.
24
+ */
25
+ export function mockScene2D() {
26
+ const playback = new PlaybackManager();
27
+ const status = new PlaybackStatus(playback);
28
+ const description = {
29
+ ...makeScene2D(function* () {
30
+ // do nothing
31
+ }),
32
+ name: 'test',
33
+ size: new Vector2(1920, 1080),
34
+ resolutionScale: 1,
35
+ timeEventsClass: ReadOnlyTimeEvents,
36
+ playback: status,
37
+ } as unknown as FullSceneDescription<ThreadGeneratorFactory<View2D>>;
38
+ description.onReplaced = new ValueDispatcher(description);
39
+ const scene = new Scene2D(description);
40
+
41
+ beforeAll(() => {
42
+ startScene(scene);
43
+ startPlayback(status);
44
+ });
45
+ afterAll(() => {
46
+ endPlayback(status);
47
+ endScene(scene);
48
+ });
49
+ beforeEach(() => scene.reset());
50
+ }
@@ -0,0 +1,122 @@
1
+ import {createRef, createRefArray} from '@canvas-commons/core';
2
+ import {describe, expect, it} from 'vitest';
3
+ import {useScene2D} from '../../scenes';
4
+ import {is} from '../../utils';
5
+ import {Circle} from '../Circle';
6
+ import {Rect} from '../Rect';
7
+ import {mockScene2D} from './mockScene2D';
8
+
9
+ describe('query', () => {
10
+ mockScene2D();
11
+
12
+ it('Query items based on their type', () => {
13
+ const view = useScene2D().getView();
14
+ const circles = createRefArray<Circle>();
15
+ view.add(
16
+ <>
17
+ <Rect />
18
+ <Circle ref={circles} />
19
+ <Rect />
20
+ <Circle ref={circles} />
21
+ <Rect />
22
+ </>,
23
+ );
24
+
25
+ const query = view.findAll(is(Circle));
26
+ expect(query.length).toBe(2);
27
+ expect(query).toEqual([...circles]);
28
+ });
29
+
30
+ it('Query items based on a custom predicate', () => {
31
+ const view = useScene2D().getView();
32
+ const nodes = createRefArray<Node>();
33
+ view.add(
34
+ <>
35
+ <Rect ref={nodes} scale={2} />
36
+ <Rect />
37
+ <Circle />
38
+ <Circle ref={nodes} scale={2} />
39
+ </>,
40
+ );
41
+
42
+ const query = view.findAll(node => node.scale.x() === 2);
43
+ expect(query.length).toBe(2);
44
+ expect(query).toEqual([...nodes]);
45
+ });
46
+
47
+ it('Query the first matching child', () => {
48
+ const view = useScene2D().getView();
49
+ const match = createRef<Circle>();
50
+ view.add(
51
+ <>
52
+ <Rect />
53
+ <Rect>
54
+ <Rect />
55
+ <Circle ref={match} />
56
+ </Rect>
57
+ <Circle />
58
+ <Rect />
59
+ </>,
60
+ );
61
+
62
+ const query = view.findFirst(is(Circle));
63
+ expect(query).toBe(match());
64
+ });
65
+
66
+ it('Query the last matching child', () => {
67
+ const view = useScene2D().getView();
68
+ const match = createRef<Circle>();
69
+ view.add(
70
+ <>
71
+ <Rect />
72
+ <Circle />
73
+ <Circle>
74
+ <Circle />
75
+ <Rect />
76
+ <Circle ref={match} />
77
+ </Circle>
78
+ <Rect />
79
+ </>,
80
+ );
81
+
82
+ const query = view.findLast(is(Circle));
83
+ expect(query?.key).toBe(match().key);
84
+ });
85
+
86
+ it('Query the first matching ancestor', () => {
87
+ const view = useScene2D().getView();
88
+ const child = createRef<Circle>();
89
+ const match = createRef<Circle>();
90
+ view.add(
91
+ <Circle>
92
+ <Circle ref={match}>
93
+ <Rect>
94
+ <Circle />
95
+ <Circle ref={child} />
96
+ <Circle />
97
+ </Rect>
98
+ </Circle>
99
+ </Circle>,
100
+ );
101
+
102
+ const query = child().findAncestor(is(Circle));
103
+ expect(query).toBe(match());
104
+ });
105
+
106
+ it('Retrieve a node by its key', () => {
107
+ const view = useScene2D().getView();
108
+ const match = createRef<Circle>();
109
+ view.add(
110
+ <>
111
+ <Circle>
112
+ <Rect />
113
+ <Circle ref={match} key="test-key" />
114
+ </Circle>
115
+ <Rect />
116
+ </>,
117
+ );
118
+
119
+ const query = view.findKey('test-key');
120
+ expect(query).toBe(match());
121
+ });
122
+ });
@@ -0,0 +1,60 @@
1
+ import {createSignal, join, waitFor} from '@canvas-commons/core';
2
+ import {describe, expect, it} from 'vitest';
3
+ import {Circle} from '../Circle';
4
+ import {generatorTest} from './generatorTest';
5
+ import {mockScene2D} from './mockScene2D';
6
+
7
+ describe('state', () => {
8
+ mockScene2D();
9
+
10
+ it('Restoring state', () => {
11
+ const signal = createSignal(45);
12
+ const circle = (
13
+ <Circle lineWidth={8} startAngle={signal} end={0.5} />
14
+ ) as Circle;
15
+ circle.save();
16
+
17
+ circle.lineWidth(16);
18
+ circle.startAngle(90);
19
+ circle.end(0.25);
20
+
21
+ circle.restore();
22
+
23
+ expect(circle.lineWidth()).toBe(8);
24
+ expect(circle.startAngle()).toBe(45);
25
+ expect(circle.end()).toBe(0.5);
26
+
27
+ signal(180);
28
+
29
+ expect(circle.startAngle()).toBe(45);
30
+ expect(circle.startArrow.context.isInitial()).toBe(true);
31
+ });
32
+
33
+ it(
34
+ 'Tweening state',
35
+ generatorTest(function* () {
36
+ const signal = createSignal(20);
37
+ const circle = (
38
+ <Circle lineWidth={8} startAngle={signal} end={0.5} />
39
+ ) as Circle;
40
+ circle.save();
41
+
42
+ circle.lineWidth(16);
43
+ circle.startAngle(40);
44
+ circle.end(0.3);
45
+
46
+ const task = yield circle.restore(2);
47
+ yield* waitFor(1);
48
+
49
+ expect(circle.lineWidth()).closeTo(12, 0.0001);
50
+ expect(circle.startAngle()).closeTo(30, 0.0001);
51
+ expect(circle.end()).closeTo(0.4, 0.0001);
52
+
53
+ yield* join(task);
54
+
55
+ expect(circle.lineWidth()).toBe(8);
56
+ expect(circle.startAngle()).toBe(20);
57
+ expect(circle.end()).toBe(0.5);
58
+ }),
59
+ );
60
+ });
@@ -0,0 +1,26 @@
1
+ export * from './Bezier';
2
+ export * from './Camera';
3
+ export * from './Circle';
4
+ export * from './Code';
5
+ export * from './CubicBezier';
6
+ export * from './Curve';
7
+ export * from './Grid';
8
+ export * from './Icon';
9
+ export * from './Img';
10
+ export * from './Knot';
11
+ export * from './Latex';
12
+ export * from './Layout';
13
+ export * from './Line';
14
+ export * from './Node';
15
+ export * from './Path';
16
+ export * from './Polygon';
17
+ export * from './QuadBezier';
18
+ export * from './Ray';
19
+ export * from './Rect';
20
+ export * from './Shape';
21
+ export * from './Spline';
22
+ export * from './SVG';
23
+ export * from './Txt';
24
+ export * from './types';
25
+ export * from './Video';
26
+ export * from './View2D';
@@ -0,0 +1,35 @@
1
+ import type {ReferenceReceiver} from '@canvas-commons/core';
2
+ import type {Node} from './Node';
3
+
4
+ export type ComponentChild =
5
+ | Node
6
+ | object
7
+ | string
8
+ | number
9
+ | bigint
10
+ | boolean
11
+ | null
12
+ | undefined;
13
+
14
+ export type ComponentChildren = ComponentChild | ComponentChild[];
15
+ export type NodeChildren = Node | Node[];
16
+
17
+ export type PropsOf<T> =
18
+ T extends NodeConstructor<infer P>
19
+ ? P
20
+ : T extends FunctionComponent<infer P>
21
+ ? P
22
+ : never;
23
+
24
+ export interface JSXProps {
25
+ children?: ComponentChildren;
26
+ ref?: ReferenceReceiver<Node>;
27
+ }
28
+
29
+ export interface FunctionComponent<T = any> {
30
+ (props: T): Node | null;
31
+ }
32
+
33
+ export interface NodeConstructor<TProps = any, TNode = Node> {
34
+ new (props: TProps): TNode;
35
+ }