@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,138 @@
1
+ import {BBox, Spacing, Vector2} from '@canvas-commons/core';
2
+ import {adjustRectRadius} from '../utils';
3
+ import {CircleSegment} from './CircleSegment';
4
+ import {CubicBezierSegment} from './CubicBezierSegment';
5
+ import {CurveProfile} from './CurveProfile';
6
+ import {LineSegment} from './LineSegment';
7
+ import {Segment} from './Segment';
8
+
9
+ export function getRectProfile(
10
+ rect: BBox,
11
+ radius: Spacing,
12
+ smoothCorners: boolean,
13
+ cornerSharpness: number,
14
+ ): CurveProfile {
15
+ const profile: CurveProfile = {
16
+ arcLength: 0,
17
+ segments: [],
18
+ minSin: 1,
19
+ };
20
+
21
+ const topLeft = adjustRectRadius(radius.top, radius.right, radius.left, rect);
22
+ const topRight = adjustRectRadius(
23
+ radius.right,
24
+ radius.top,
25
+ radius.bottom,
26
+ rect,
27
+ );
28
+ const bottomRight = adjustRectRadius(
29
+ radius.bottom,
30
+ radius.left,
31
+ radius.right,
32
+ rect,
33
+ );
34
+ const bottomLeft = adjustRectRadius(
35
+ radius.left,
36
+ radius.bottom,
37
+ radius.top,
38
+ rect,
39
+ );
40
+
41
+ let from = new Vector2(rect.left + topLeft, rect.top);
42
+ let to = new Vector2(rect.right - topRight, rect.top);
43
+ addSegment(profile, new LineSegment(from, to));
44
+
45
+ from = new Vector2(rect.right, rect.top + topRight);
46
+ to = new Vector2(rect.right, rect.bottom - bottomRight);
47
+ if (topRight > 0) {
48
+ addCornerSegment(
49
+ profile,
50
+ from.addX(-topRight),
51
+ topRight,
52
+ Vector2.down,
53
+ Vector2.right,
54
+ smoothCorners,
55
+ cornerSharpness,
56
+ );
57
+ }
58
+ addSegment(profile, new LineSegment(from, to));
59
+
60
+ from = new Vector2(rect.right - bottomRight, rect.bottom);
61
+ to = new Vector2(rect.left + bottomLeft, rect.bottom);
62
+ if (bottomRight > 0) {
63
+ addCornerSegment(
64
+ profile,
65
+ from.addY(-bottomRight),
66
+ bottomRight,
67
+ Vector2.right,
68
+ Vector2.up,
69
+ smoothCorners,
70
+ cornerSharpness,
71
+ );
72
+ }
73
+ addSegment(profile, new LineSegment(from, to));
74
+
75
+ from = new Vector2(rect.left, rect.bottom - bottomLeft);
76
+ to = new Vector2(rect.left, rect.top + topLeft);
77
+ if (bottomLeft > 0) {
78
+ addCornerSegment(
79
+ profile,
80
+ from.addX(bottomLeft),
81
+ bottomLeft,
82
+ Vector2.up,
83
+ Vector2.left,
84
+ smoothCorners,
85
+ cornerSharpness,
86
+ );
87
+ }
88
+ addSegment(profile, new LineSegment(from, to));
89
+
90
+ from = new Vector2(rect.left + topLeft, rect.top);
91
+ if (topLeft > 0) {
92
+ addCornerSegment(
93
+ profile,
94
+ from.addY(topLeft),
95
+ topLeft,
96
+ Vector2.left,
97
+ Vector2.down,
98
+ smoothCorners,
99
+ cornerSharpness,
100
+ );
101
+ }
102
+
103
+ return profile;
104
+ }
105
+
106
+ function addSegment(profile: CurveProfile, segment: Segment) {
107
+ profile.segments.push(segment);
108
+ profile.arcLength += segment.arcLength;
109
+ }
110
+
111
+ function addCornerSegment(
112
+ profile: CurveProfile,
113
+ center: Vector2,
114
+ radius: number,
115
+ fromNormal: Vector2,
116
+ toNormal: Vector2,
117
+ smooth: boolean,
118
+ sharpness: number,
119
+ ) {
120
+ const from = center.add(fromNormal.scale(radius));
121
+ const to = center.add(toNormal.scale(radius));
122
+ if (smooth) {
123
+ addSegment(
124
+ profile,
125
+ new CubicBezierSegment(
126
+ from,
127
+ from.add(toNormal.scale(sharpness * radius)),
128
+ to.add(fromNormal.scale(sharpness * radius)),
129
+ to,
130
+ ),
131
+ );
132
+ } else {
133
+ addSegment(
134
+ profile,
135
+ new CircleSegment(center, radius, fromNormal, toNormal, false),
136
+ );
137
+ }
138
+ }
@@ -0,0 +1,16 @@
1
+ export * from './CircleSegment';
2
+ export * from './CubicBezierSegment';
3
+ export * from './CurveDrawingInfo';
4
+ export * from './CurvePoint';
5
+ export * from './CurveProfile';
6
+ export * from './getBezierSplineProfile';
7
+ export * from './getCircleProfile';
8
+ export * from './getPointAtDistance';
9
+ export * from './getPolylineProfile';
10
+ export * from './getRectProfile';
11
+ export * from './KnotInfo';
12
+ export * from './LineSegment';
13
+ export * from './Polynomial';
14
+ export * from './Polynomial2D';
15
+ export * from './QuadBezierSegment';
16
+ export * from './Segment';
@@ -0,0 +1,15 @@
1
+ import {Color, Signal} from '@canvas-commons/core';
2
+ import type {CanvasStyle, PossibleCanvasStyle} from '../partials';
3
+ import {canvasStyleParser} from '../utils';
4
+ import {initial, interpolation, parser, signal} from './signal';
5
+
6
+ export type CanvasStyleSignal<T> = Signal<PossibleCanvasStyle, CanvasStyle, T>;
7
+
8
+ export function canvasStyleSignal(): PropertyDecorator {
9
+ return (target, key) => {
10
+ signal()(target, key);
11
+ parser(canvasStyleParser)(target, key);
12
+ interpolation(Color.lerp)(target, key);
13
+ initial(null)(target, key);
14
+ };
15
+ }
@@ -0,0 +1,9 @@
1
+ import {Color} from '@canvas-commons/core';
2
+ import {signal, wrapper} from './signal';
3
+
4
+ export function colorSignal(): PropertyDecorator {
5
+ return (target, key) => {
6
+ signal()(target, key);
7
+ wrapper(Color)(target, key);
8
+ };
9
+ }
@@ -0,0 +1,85 @@
1
+ import {
2
+ CompoundSignalContext,
3
+ SignalContext,
4
+ deepLerp,
5
+ map,
6
+ modify,
7
+ useLogger,
8
+ } from '@canvas-commons/core';
9
+ import {makeSignalExtensions} from '../utils/makeSignalExtensions';
10
+ import {addInitializer} from './initializers';
11
+ import {getPropertyMetaOrCreate} from './signal';
12
+
13
+ /**
14
+ * Create a compound property decorator.
15
+ *
16
+ * @remarks
17
+ * This decorator turns a given property into a signal consisting of one or more
18
+ * nested signals.
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * class Example {
23
+ * \@compound({x: 'scaleX', y: 'scaleY'})
24
+ * public declare readonly scale: Signal<Vector2, this>;
25
+ *
26
+ * public setScale() {
27
+ * this.scale({x: 7, y: 3});
28
+ * // same as:
29
+ * this.scale.x(7).scale.y(3);
30
+ * }
31
+ * }
32
+ * ```
33
+ *
34
+ * @param entries - A record mapping the property in the compound object to the
35
+ * corresponding property on the owner node.
36
+ */
37
+ export function compound<
38
+ TSetterValue,
39
+ TValue extends TSetterValue,
40
+ TKeys extends keyof TValue = keyof TValue,
41
+ TOwner = void,
42
+ >(
43
+ entries: Record<string, string>,
44
+ klass: typeof CompoundSignalContext<
45
+ TSetterValue,
46
+ TValue,
47
+ TKeys,
48
+ TOwner
49
+ > = CompoundSignalContext,
50
+ ): PropertyDecorator {
51
+ return (target, key) => {
52
+ const meta = getPropertyMetaOrCreate<any>(target, key);
53
+ meta.compound = true;
54
+ meta.compoundEntries = Object.entries(entries);
55
+
56
+ addInitializer(target, (instance: any) => {
57
+ if (!meta.parser) {
58
+ useLogger().error(`Missing parser decorator for "${key.toString()}"`);
59
+ return;
60
+ }
61
+
62
+ const initial = meta.default;
63
+ const parser = meta.parser.bind(instance);
64
+ const signalContext = new klass(
65
+ meta.compoundEntries.map(([key, property]) => {
66
+ const signal = new SignalContext(
67
+ modify(initial, value => parser(value)[key]),
68
+ <any>map,
69
+ instance,
70
+ undefined,
71
+ makeSignalExtensions(undefined, instance, property),
72
+ ).toSignal();
73
+ return [key as TKeys, signal];
74
+ }),
75
+ parser,
76
+ initial,
77
+ meta.interpolationFunction ?? deepLerp,
78
+ instance,
79
+ makeSignalExtensions(meta, instance, <string>key),
80
+ );
81
+
82
+ instance[key] = signalContext.toSignal();
83
+ });
84
+ };
85
+ }
@@ -0,0 +1,18 @@
1
+ import {createComputed} from '@canvas-commons/core';
2
+ import {addInitializer} from './initializers';
3
+
4
+ /**
5
+ * Create a computed method decorator.
6
+ *
7
+ * @remarks
8
+ * This decorator turns the given method into a computed value.
9
+ * See {@link createComputed} for more information.
10
+ */
11
+ export function computed(): MethodDecorator {
12
+ return (target: any, key) => {
13
+ addInitializer(target, (instance: any) => {
14
+ const method = Object.getPrototypeOf(instance)[key];
15
+ instance[key] = createComputed(method.bind(instance), instance);
16
+ });
17
+ };
18
+ }
@@ -0,0 +1,15 @@
1
+ import {capitalize} from '@canvas-commons/core';
2
+ import {Layout} from '../components';
3
+
4
+ export function defaultStyle<T>(initial?: T): PropertyDecorator {
5
+ return (target: any, key) => {
6
+ target[`getDefault${capitalize(<string>key)}`] = function (this: Layout) {
7
+ const parent = this.parentTransform();
8
+ if (parent && this.layout() !== false) {
9
+ return (parent as any)[key]();
10
+ }
11
+
12
+ return initial;
13
+ };
14
+ };
15
+ }
@@ -0,0 +1,133 @@
1
+ import {
2
+ Signal,
3
+ SignalContext,
4
+ SignalValue,
5
+ SimpleSignal,
6
+ ThreadGenerator,
7
+ TimingFunction,
8
+ all,
9
+ deepLerp,
10
+ easeInOutCubic,
11
+ unwrap,
12
+ } from '@canvas-commons/core';
13
+ import {FILTERS, Filter, FilterName} from '../partials';
14
+ import {addInitializer} from './initializers';
15
+ import {getPropertyMetaOrCreate} from './signal';
16
+
17
+ export type FiltersSignal<TOwner> = Signal<
18
+ Filter[],
19
+ Filter[],
20
+ TOwner,
21
+ FiltersSignalContext<TOwner>
22
+ > & {
23
+ [K in FilterName]: SimpleSignal<number, TOwner>;
24
+ };
25
+
26
+ export class FiltersSignalContext<TOwner> extends SignalContext<
27
+ Filter[],
28
+ Filter[],
29
+ TOwner
30
+ > {
31
+ public constructor(initial: Filter[], owner: TOwner) {
32
+ super(initial, deepLerp, owner);
33
+
34
+ for (const filter in FILTERS) {
35
+ const props = FILTERS[filter];
36
+ Object.defineProperty(this.invokable, filter, {
37
+ value: (
38
+ newValue?: SignalValue<number>,
39
+ duration?: number,
40
+ timingFunction: TimingFunction = easeInOutCubic,
41
+ ) => {
42
+ if (newValue === undefined) {
43
+ return (
44
+ this.get()
45
+ ?.find(filter => filter.name === props.name)
46
+ ?.value() ??
47
+ props.default ??
48
+ 0
49
+ );
50
+ }
51
+
52
+ let instance = this.get()?.find(filter => filter.name === props.name);
53
+ if (!instance) {
54
+ instance = new Filter(props);
55
+ this.set([...this.get(), instance]);
56
+ }
57
+
58
+ if (duration === undefined) {
59
+ instance.value(newValue);
60
+ return this.owner;
61
+ }
62
+
63
+ return instance.value(newValue, duration, timingFunction);
64
+ },
65
+ });
66
+ }
67
+ }
68
+
69
+ public override *tweener(
70
+ value: SignalValue<Filter[]>,
71
+ duration: number,
72
+ timingFunction: TimingFunction,
73
+ ): ThreadGenerator {
74
+ const from = this.get();
75
+ const to = unwrap(value);
76
+
77
+ if (areFiltersCompatible(from, to)) {
78
+ yield* all(
79
+ ...from.map((filter, i) =>
80
+ filter.value(to[i].value(), duration, timingFunction),
81
+ ),
82
+ );
83
+ this.set(to);
84
+ return;
85
+ }
86
+
87
+ for (const filter of to) {
88
+ filter.value(filter.default);
89
+ }
90
+
91
+ const toValues = to.map(filter => filter.value.context.raw());
92
+ const partialDuration =
93
+ from.length > 0 && to.length > 0 ? duration / 2 : duration;
94
+ if (from.length > 0) {
95
+ yield* all(
96
+ ...from.map(filter =>
97
+ filter.value(filter.default, partialDuration, timingFunction),
98
+ ),
99
+ );
100
+ }
101
+ this.set(to);
102
+ if (to.length > 0) {
103
+ yield* all(
104
+ ...to.map((filter, index) =>
105
+ filter.value(toValues[index]!, partialDuration, timingFunction),
106
+ ),
107
+ );
108
+ }
109
+ }
110
+ }
111
+
112
+ export function filtersSignal(): PropertyDecorator {
113
+ return (target: any, key) => {
114
+ const meta = getPropertyMetaOrCreate<Filter[]>(target, key);
115
+ addInitializer(target, (instance: any) => {
116
+ instance[key] = new FiltersSignalContext(
117
+ meta.default ?? [],
118
+ instance,
119
+ ).toSignal();
120
+ });
121
+ };
122
+ }
123
+
124
+ function areFiltersCompatible(a: Filter[], b: Filter[]) {
125
+ if (a.length !== b.length) return false;
126
+ for (let i = 0; i < a.length; i++) {
127
+ if (a[i].name !== b[i].name) {
128
+ return false;
129
+ }
130
+ }
131
+
132
+ return true;
133
+ }
@@ -0,0 +1,10 @@
1
+ export * from './canvasStyleSignal';
2
+ export * from './colorSignal';
3
+ export * from './compound';
4
+ export * from './computed';
5
+ export * from './defaultStyle';
6
+ export * from './filtersSignal';
7
+ export * from './initializers';
8
+ export * from './nodeName';
9
+ export * from './signal';
10
+ export * from './vector2Signal';
@@ -0,0 +1,32 @@
1
+ const INITIALIZERS = Symbol.for('@canvas-commons/2d/decorators/initializers');
2
+
3
+ export type Initializer<T> = (instance: T, context?: any) => void;
4
+
5
+ export function addInitializer<T>(target: any, initializer: Initializer<T>) {
6
+ if (!target[INITIALIZERS]) {
7
+ target[INITIALIZERS] = [];
8
+ } else if (
9
+ // if one of the prototypes has initializers
10
+ target[INITIALIZERS] &&
11
+ // and it's not the target object itself
12
+ !Object.prototype.hasOwnProperty.call(target, INITIALIZERS)
13
+ ) {
14
+ const base = Object.getPrototypeOf(target);
15
+ target[INITIALIZERS] = [...base[INITIALIZERS]];
16
+ }
17
+
18
+ target[INITIALIZERS].push(initializer);
19
+ }
20
+
21
+ export function initialize(target: any, context?: any) {
22
+ if (target[INITIALIZERS]) {
23
+ try {
24
+ target[INITIALIZERS].forEach((initializer: Initializer<any>) =>
25
+ initializer(target, context),
26
+ );
27
+ } catch (e: any) {
28
+ e.inspect ??= target.key;
29
+ throw e;
30
+ }
31
+ }
32
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @internal
3
+ */
4
+ export const NODE_NAME = Symbol.for('@canvas-commons/2d/nodeName');
5
+
6
+ /**
7
+ * @internal
8
+ */
9
+ export function nodeName(name: string) {
10
+ return function (target: any) {
11
+ target.prototype[NODE_NAME] = name;
12
+ };
13
+ }
@@ -0,0 +1,89 @@
1
+ import {DEFAULT, SimpleSignal} from '@canvas-commons/core';
2
+ import {beforeEach, describe, expect, test, vi} from 'vitest';
3
+ import {initial, initializeSignals, parser, signal} from './signal';
4
+
5
+ interface OwnerProps {
6
+ integer?: number;
7
+ custom?: number;
8
+ }
9
+
10
+ class Owner {
11
+ @initial(2.2)
12
+ @parser((value: number) => Math.round(value))
13
+ @signal()
14
+ declare public readonly integer: SimpleSignal<number>;
15
+
16
+ @initial(0)
17
+ @signal()
18
+ declare public readonly custom: SimpleSignal<number>;
19
+ public getCustom() {
20
+ return 4;
21
+ }
22
+ public setCustom() {
23
+ // do nothing
24
+ }
25
+
26
+ public constructor(props: OwnerProps = {}) {
27
+ initializeSignals(this, props);
28
+ }
29
+ }
30
+
31
+ const GetterMock = vi.spyOn(Owner.prototype, 'getCustom');
32
+ const SetterMock = vi.spyOn(Owner.prototype, 'setCustom');
33
+
34
+ describe('signal', () => {
35
+ beforeEach(() => {
36
+ GetterMock.mockClear();
37
+ SetterMock.mockClear();
38
+ });
39
+
40
+ test('Has initial value', () => {
41
+ const instance = new Owner();
42
+
43
+ expect(instance.integer()).toBe(2);
44
+ expect(instance.integer.isInitial()).toBe(true);
45
+ });
46
+
47
+ test('Overrides the value', () => {
48
+ const instance = new Owner({integer: 4});
49
+
50
+ expect(instance.integer()).toBe(4);
51
+ expect(instance.integer.isInitial()).toBe(false);
52
+ });
53
+
54
+ test('Resets the value to default', () => {
55
+ const instance = new Owner({integer: 4});
56
+ instance.integer(DEFAULT);
57
+
58
+ expect(instance.integer()).toBe(2);
59
+ expect(instance.integer.isInitial()).toBe(true);
60
+ });
61
+
62
+ test('Parses the value', () => {
63
+ const instance = new Owner();
64
+ instance.integer(2.7);
65
+
66
+ expect(instance.integer()).toBe(3);
67
+ });
68
+
69
+ test('Uses the custom getter', () => {
70
+ const instance = new Owner();
71
+ const value = instance.custom();
72
+
73
+ expect(value).toBe(4);
74
+ expect(GetterMock).toBeCalledTimes(1);
75
+ });
76
+
77
+ test('Calls the setter with the initial value', () => {
78
+ new Owner();
79
+
80
+ expect(SetterMock).toBeCalledWith(0);
81
+ });
82
+
83
+ test('Uses the setter', () => {
84
+ const instance = new Owner({custom: 1});
85
+ instance.custom(2);
86
+
87
+ expect(SetterMock).toBeCalledTimes(3);
88
+ });
89
+ });