@shopify/react-native-skia 1.9.0 → 1.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (96) hide show
  1. package/cpp/api/JsiSkCanvas.h +1 -1
  2. package/lib/commonjs/index.d.ts +1 -1
  3. package/lib/commonjs/index.js +8 -8
  4. package/lib/commonjs/index.js.map +1 -1
  5. package/lib/commonjs/renderer/Canvas.d.ts +9 -10
  6. package/lib/commonjs/renderer/Canvas.js +67 -59
  7. package/lib/commonjs/renderer/Canvas.js.map +1 -1
  8. package/lib/commonjs/renderer/CanvasOld.d.ts +11 -0
  9. package/lib/commonjs/renderer/CanvasOld.js +96 -0
  10. package/lib/commonjs/renderer/CanvasOld.js.map +1 -0
  11. package/lib/commonjs/sksg/Container.js +12 -8
  12. package/lib/commonjs/sksg/Container.js.map +1 -1
  13. package/lib/commonjs/sksg/HostConfig2.d.ts +19 -0
  14. package/lib/commonjs/sksg/HostConfig2.js +159 -0
  15. package/lib/commonjs/sksg/HostConfig2.js.map +1 -0
  16. package/lib/commonjs/sksg/Recorder/Core.d.ts +42 -37
  17. package/lib/commonjs/sksg/Recorder/Core.js +46 -38
  18. package/lib/commonjs/sksg/Recorder/Core.js.map +1 -1
  19. package/lib/commonjs/sksg/Recorder/Player.js +6 -2
  20. package/lib/commonjs/sksg/Recorder/Player.js.map +1 -1
  21. package/lib/commonjs/sksg/Recorder/Recorder.d.ts +4 -0
  22. package/lib/commonjs/sksg/Recorder/Recorder.js +14 -2
  23. package/lib/commonjs/sksg/Recorder/Recorder.js.map +1 -1
  24. package/lib/commonjs/sksg/Recorder/Visitor.js +6 -0
  25. package/lib/commonjs/sksg/Recorder/Visitor.js.map +1 -1
  26. package/lib/module/index.d.ts +1 -1
  27. package/lib/module/index.js +1 -1
  28. package/lib/module/index.js.map +1 -1
  29. package/lib/module/renderer/Canvas.d.ts +9 -10
  30. package/lib/module/renderer/Canvas.js +65 -55
  31. package/lib/module/renderer/Canvas.js.map +1 -1
  32. package/lib/module/renderer/CanvasOld.d.ts +11 -0
  33. package/lib/module/renderer/CanvasOld.js +87 -0
  34. package/lib/module/renderer/CanvasOld.js.map +1 -0
  35. package/lib/module/sksg/Container.js +12 -8
  36. package/lib/module/sksg/Container.js.map +1 -1
  37. package/lib/module/sksg/HostConfig2.d.ts +19 -0
  38. package/lib/module/sksg/HostConfig2.js +152 -0
  39. package/lib/module/sksg/HostConfig2.js.map +1 -0
  40. package/lib/module/sksg/Recorder/Core.d.ts +42 -37
  41. package/lib/module/sksg/Recorder/Core.js +44 -37
  42. package/lib/module/sksg/Recorder/Core.js.map +1 -1
  43. package/lib/module/sksg/Recorder/Player.js +7 -3
  44. package/lib/module/sksg/Recorder/Player.js.map +1 -1
  45. package/lib/module/sksg/Recorder/Recorder.d.ts +4 -0
  46. package/lib/module/sksg/Recorder/Recorder.js +14 -2
  47. package/lib/module/sksg/Recorder/Recorder.js.map +1 -1
  48. package/lib/module/sksg/Recorder/Visitor.js +6 -0
  49. package/lib/module/sksg/Recorder/Visitor.js.map +1 -1
  50. package/lib/typescript/lib/commonjs/renderer/Canvas.d.ts +2 -2
  51. package/lib/typescript/lib/commonjs/renderer/CanvasOld.d.ts +3 -0
  52. package/lib/typescript/lib/commonjs/sksg/HostConfig2.d.ts +44 -0
  53. package/lib/typescript/lib/commonjs/sksg/Recorder/Core.d.ts +1 -0
  54. package/lib/typescript/lib/commonjs/sksg/Recorder/Recorder.d.ts +2 -0
  55. package/lib/typescript/lib/module/index.d.ts +1 -1
  56. package/lib/typescript/lib/module/renderer/Canvas.d.ts +1 -3
  57. package/lib/typescript/lib/module/renderer/CanvasOld.d.ts +3 -0
  58. package/lib/typescript/lib/module/sksg/HostConfig2.d.ts +43 -0
  59. package/lib/typescript/lib/module/sksg/Recorder/Core.d.ts +1 -0
  60. package/lib/typescript/lib/module/sksg/Recorder/Recorder.d.ts +2 -0
  61. package/lib/typescript/src/index.d.ts +1 -1
  62. package/lib/typescript/src/renderer/Canvas.d.ts +9 -10
  63. package/lib/typescript/src/renderer/CanvasOld.d.ts +11 -0
  64. package/lib/typescript/src/sksg/HostConfig2.d.ts +19 -0
  65. package/lib/typescript/src/sksg/Recorder/Core.d.ts +42 -37
  66. package/lib/typescript/src/sksg/Recorder/Recorder.d.ts +4 -0
  67. package/package.json +3 -2
  68. package/src/index.ts +1 -1
  69. package/src/renderer/Canvas.tsx +80 -78
  70. package/src/renderer/CanvasOld.tsx +126 -0
  71. package/src/sksg/Container.ts +7 -4
  72. package/src/sksg/HostConfig2.ts +247 -0
  73. package/src/sksg/Recorder/Core.ts +11 -0
  74. package/src/sksg/Recorder/Player.ts +7 -3
  75. package/src/sksg/Recorder/Recorder.ts +16 -2
  76. package/src/sksg/Recorder/Visitor.ts +6 -0
  77. package/lib/commonjs/renderer/Canvas2.d.ts +0 -10
  78. package/lib/commonjs/renderer/Canvas2.js +0 -104
  79. package/lib/commonjs/renderer/Canvas2.js.map +0 -1
  80. package/lib/commonjs/renderer/Canvas2.web.d.ts +0 -3
  81. package/lib/commonjs/renderer/Canvas2.web.js +0 -9
  82. package/lib/commonjs/renderer/Canvas2.web.js.map +0 -1
  83. package/lib/module/renderer/Canvas2.d.ts +0 -10
  84. package/lib/module/renderer/Canvas2.js +0 -97
  85. package/lib/module/renderer/Canvas2.js.map +0 -1
  86. package/lib/module/renderer/Canvas2.web.d.ts +0 -3
  87. package/lib/module/renderer/Canvas2.web.js +0 -3
  88. package/lib/module/renderer/Canvas2.web.js.map +0 -1
  89. package/lib/typescript/lib/commonjs/renderer/Canvas2.d.ts +0 -3
  90. package/lib/typescript/lib/commonjs/renderer/Canvas2.web.d.ts +0 -2
  91. package/lib/typescript/lib/module/renderer/Canvas2.d.ts +0 -1
  92. package/lib/typescript/lib/module/renderer/Canvas2.web.d.ts +0 -1
  93. package/lib/typescript/src/renderer/Canvas2.d.ts +0 -10
  94. package/lib/typescript/src/renderer/Canvas2.web.d.ts +0 -3
  95. package/src/renderer/Canvas2.tsx +0 -128
  96. package/src/renderer/Canvas2.web.tsx +0 -6
@@ -1,32 +1,24 @@
1
- import React, {
2
- useEffect,
1
+ import {
2
+ forwardRef,
3
3
  useCallback,
4
+ useEffect,
5
+ useImperativeHandle,
4
6
  useMemo,
5
- forwardRef,
6
7
  useRef,
7
8
  } from "react";
8
- import type {
9
- RefObject,
10
- ReactNode,
11
- MutableRefObject,
12
- ForwardedRef,
13
- FunctionComponent,
14
- } from "react";
15
- import type { LayoutChangeEvent } from "react-native";
9
+ import type { LayoutChangeEvent, ViewProps } from "react-native";
10
+ import type { SharedValue } from "react-native-reanimated";
16
11
 
17
- import { SkiaDomView } from "../views";
12
+ import { SkiaViewNativeId } from "../views/SkiaViewNativeId";
13
+ import SkiaPictureViewNativeComponent from "../specs/SkiaPictureViewNativeComponent";
14
+ import type { SkRect, SkSize } from "../skia/types";
15
+ import { SkiaSGRoot } from "../sksg/Reconciler";
16
+ import { Skia } from "../skia";
18
17
  import type { SkiaBaseViewProps } from "../views";
19
18
 
20
- import { SkiaRoot } from "./Reconciler";
21
-
22
- export const useCanvasRef = () => useRef<SkiaDomView>(null);
23
-
24
- export interface CanvasProps extends SkiaBaseViewProps {
25
- ref?: RefObject<SkiaDomView>;
26
- children: ReactNode;
27
- mode?: "default" | "continuous";
28
- }
19
+ const NativeSkiaPictureView = SkiaPictureViewNativeComponent;
29
20
 
21
+ // TODO: no need to go through the JS thread for this
30
22
  const useOnSizeEvent = (
31
23
  resultValue: SkiaBaseViewProps["onSize"],
32
24
  onLayout?: (event: LayoutChangeEvent) => void
@@ -46,39 +38,40 @@ const useOnSizeEvent = (
46
38
  );
47
39
  };
48
40
 
49
- export const Canvas = forwardRef<SkiaDomView, CanvasProps>(
41
+ export interface CanvasProps extends ViewProps {
42
+ debug?: boolean;
43
+ opaque?: boolean;
44
+ onSize?: SharedValue<SkSize>;
45
+ mode?: "continuous" | "default";
46
+ }
47
+
48
+ export const Canvas = forwardRef(
50
49
  (
51
50
  {
52
- children,
53
- style,
51
+ mode,
54
52
  debug,
55
- mode = "default",
56
- onSize: _onSize,
53
+ opaque,
54
+ children,
55
+ onSize,
57
56
  onLayout: _onLayout,
58
- ...props
59
- },
60
- forwardedRef
57
+ ...viewProps
58
+ }: CanvasProps,
59
+ ref
61
60
  ) => {
62
- const onLayout = useOnSizeEvent(_onSize, _onLayout);
63
- const innerRef = useCanvasRef();
64
- const ref = useCombinedRefs(forwardedRef, innerRef);
65
- const redraw = useCallback(() => {
66
- innerRef.current?.redraw();
67
- }, [innerRef]);
68
- const getNativeId = useCallback(() => {
69
- const id = innerRef.current?.nativeId ?? -1;
70
- return id;
71
- }, [innerRef]);
61
+ const rafId = useRef<number | null>(null);
62
+ const onLayout = useOnSizeEvent(onSize, _onLayout);
63
+ // Native ID
64
+ const nativeId = useMemo(() => {
65
+ return SkiaViewNativeId.current++;
66
+ }, []);
72
67
 
73
- const root = useMemo(
74
- () => new SkiaRoot(redraw, getNativeId),
75
- [redraw, getNativeId]
76
- );
68
+ // Root
69
+ const root = useMemo(() => new SkiaSGRoot(Skia, nativeId), [nativeId]);
77
70
 
78
- // Render effect
71
+ // Render effects
79
72
  useEffect(() => {
80
73
  root.render(children);
81
- }, [children, root, redraw]);
74
+ }, [children, root]);
82
75
 
83
76
  useEffect(() => {
84
77
  return () => {
@@ -86,41 +79,50 @@ export const Canvas = forwardRef<SkiaDomView, CanvasProps>(
86
79
  };
87
80
  }, [root]);
88
81
 
82
+ const requestRedraw = useCallback(() => {
83
+ rafId.current = requestAnimationFrame(() => {
84
+ root.render(children);
85
+ if (mode === "continuous") {
86
+ requestRedraw();
87
+ }
88
+ });
89
+ }, [children, mode, root]);
90
+
91
+ useEffect(() => {
92
+ if (mode === "continuous") {
93
+ console.warn("The `mode` property in `Canvas` is deprecated.");
94
+ requestRedraw();
95
+ }
96
+ return () => {
97
+ if (rafId.current !== null) {
98
+ cancelAnimationFrame(rafId.current);
99
+ }
100
+ };
101
+ }, [mode, requestRedraw]);
102
+ // Component methods
103
+ useImperativeHandle(ref, () => ({
104
+ makeImageSnapshot: (rect?: SkRect) => {
105
+ return SkiaViewApi.makeImageSnapshot(nativeId, rect);
106
+ },
107
+ makeImageSnapshotAsync: (rect?: SkRect) => {
108
+ return SkiaViewApi.makeImageSnapshotAsync(nativeId, rect);
109
+ },
110
+ redraw: () => {
111
+ SkiaViewApi.requestRedraw(nativeId);
112
+ },
113
+ getNativeId: () => {
114
+ return nativeId;
115
+ },
116
+ }));
89
117
  return (
90
- <SkiaDomView
91
- ref={ref}
92
- style={style}
93
- root={root.dom}
94
- onLayout={onLayout}
118
+ <NativeSkiaPictureView
119
+ collapsable={false}
120
+ nativeID={`${nativeId}`}
95
121
  debug={debug}
96
- mode={mode}
97
- {...props}
122
+ opaque={opaque}
123
+ onLayout={onLayout}
124
+ {...viewProps}
98
125
  />
99
126
  );
100
127
  }
101
- ) as FunctionComponent<CanvasProps & React.RefAttributes<SkiaDomView>>;
102
-
103
- /**
104
- * Combines a list of refs into a single ref. This can be used to provide
105
- * both a forwarded ref and an internal ref keeping the same functionality
106
- * on both of the refs.
107
- * @param refs Array of refs to combine
108
- * @returns A single ref that can be used in a ref prop.
109
- */
110
- const useCombinedRefs = <T,>(
111
- ...refs: Array<MutableRefObject<T> | ForwardedRef<T>>
112
- ) => {
113
- const targetRef = React.useRef<T>(null);
114
- React.useEffect(() => {
115
- refs.forEach((ref) => {
116
- if (ref) {
117
- if (typeof ref === "function") {
118
- ref(targetRef.current);
119
- } else {
120
- ref.current = targetRef.current;
121
- }
122
- }
123
- });
124
- }, [refs]);
125
- return targetRef;
126
- };
128
+ );
@@ -0,0 +1,126 @@
1
+ import React, {
2
+ useEffect,
3
+ useCallback,
4
+ useMemo,
5
+ forwardRef,
6
+ useRef,
7
+ } from "react";
8
+ import type {
9
+ RefObject,
10
+ ReactNode,
11
+ MutableRefObject,
12
+ ForwardedRef,
13
+ FunctionComponent,
14
+ } from "react";
15
+ import type { LayoutChangeEvent } from "react-native";
16
+
17
+ import { SkiaDomView } from "../views";
18
+ import type { SkiaBaseViewProps } from "../views";
19
+
20
+ import { SkiaRoot } from "./Reconciler";
21
+
22
+ export const useCanvasRef = () => useRef<SkiaDomView>(null);
23
+
24
+ export interface CanvasOldProps extends SkiaBaseViewProps {
25
+ ref?: RefObject<SkiaDomView>;
26
+ children: ReactNode;
27
+ mode?: "default" | "continuous";
28
+ }
29
+
30
+ const useOnSizeEvent = (
31
+ resultValue: SkiaBaseViewProps["onSize"],
32
+ onLayout?: (event: LayoutChangeEvent) => void
33
+ ) => {
34
+ return useCallback(
35
+ (event: LayoutChangeEvent) => {
36
+ if (onLayout) {
37
+ onLayout(event);
38
+ }
39
+ const { width, height } = event.nativeEvent.layout;
40
+
41
+ if (resultValue) {
42
+ resultValue.value = { width, height };
43
+ }
44
+ },
45
+ [onLayout, resultValue]
46
+ );
47
+ };
48
+
49
+ export const CanvasOld = forwardRef<SkiaDomView, CanvasOldProps>(
50
+ (
51
+ {
52
+ children,
53
+ style,
54
+ debug,
55
+ mode = "default",
56
+ onSize: _onSize,
57
+ onLayout: _onLayout,
58
+ ...props
59
+ },
60
+ forwardedRef
61
+ ) => {
62
+ const onLayout = useOnSizeEvent(_onSize, _onLayout);
63
+ const innerRef = useCanvasRef();
64
+ const ref = useCombinedRefs(forwardedRef, innerRef);
65
+ const redraw = useCallback(() => {
66
+ innerRef.current?.redraw();
67
+ }, [innerRef]);
68
+ const getNativeId = useCallback(() => {
69
+ const id = innerRef.current?.nativeId ?? -1;
70
+ return id;
71
+ }, [innerRef]);
72
+
73
+ const root = useMemo(
74
+ () => new SkiaRoot(redraw, getNativeId),
75
+ [redraw, getNativeId]
76
+ );
77
+
78
+ // Render effect
79
+ useEffect(() => {
80
+ root.render(children);
81
+ }, [children, root, redraw]);
82
+
83
+ useEffect(() => {
84
+ return () => {
85
+ root.unmount();
86
+ };
87
+ }, [root]);
88
+
89
+ return (
90
+ <SkiaDomView
91
+ ref={ref}
92
+ style={style}
93
+ root={root.dom}
94
+ onLayout={onLayout}
95
+ debug={debug}
96
+ mode={mode}
97
+ {...props}
98
+ />
99
+ );
100
+ }
101
+ ) as FunctionComponent<CanvasOldProps & React.RefAttributes<SkiaDomView>>;
102
+
103
+ /**
104
+ * Combines a list of refs into a single ref. This can be used to provide
105
+ * both a forwarded ref and an internal ref keeping the same functionality
106
+ * on both of the refs.
107
+ * @param refs Array of refs to combine
108
+ * @returns A single ref that can be used in a ref prop.
109
+ */
110
+ const useCombinedRefs = <T,>(
111
+ ...refs: Array<MutableRefObject<T> | ForwardedRef<T>>
112
+ ) => {
113
+ const targetRef = React.useRef<T>(null);
114
+ React.useEffect(() => {
115
+ refs.forEach((ref) => {
116
+ if (ref) {
117
+ if (typeof ref === "function") {
118
+ ref(targetRef.current);
119
+ } else {
120
+ ref.current = targetRef.current;
121
+ }
122
+ }
123
+ });
124
+ }, [refs]);
125
+ return targetRef;
126
+ };
@@ -14,10 +14,9 @@ const drawOnscreen = (Skia: Skia, nativeId: number, recording: Recording) => {
14
14
 
15
15
  const rec = Skia.PictureRecorder();
16
16
  const canvas = rec.beginRecording();
17
- // const start = performance.now();
17
+ //const start = performance.now();
18
18
 
19
19
  const ctx = createDrawingContext(Skia, recording.paintPool, canvas);
20
- //console.log(recording.commands);
21
20
  replay(ctx, recording.commands);
22
21
  const picture = rec.finishRecordingAsPicture();
23
22
  //const end = performance.now();
@@ -42,7 +41,7 @@ export abstract class Container {
42
41
  this.recording.paintPool,
43
42
  canvas
44
43
  );
45
- //console.log(this._recording);
44
+ //console.log(this.recording.commands);
46
45
  replay(ctx, this.recording.commands);
47
46
  }
48
47
 
@@ -88,13 +87,17 @@ class ReanimatedContainer extends Container {
88
87
  commands: record.commands,
89
88
  paintPool: record.paintPool,
90
89
  };
90
+ const { nativeId, Skia, recording } = this;
91
91
  if (animationValues.size > 0) {
92
- const { nativeId, Skia, recording } = this;
93
92
  this.mapperId = Rea.startMapper(() => {
94
93
  "worklet";
95
94
  drawOnscreen(Skia, nativeId, recording!);
96
95
  }, Array.from(animationValues));
97
96
  }
97
+ Rea.runOnUI(() => {
98
+ "worklet";
99
+ drawOnscreen(Skia, nativeId, recording!);
100
+ })();
98
101
  }
99
102
  }
100
103
 
@@ -0,0 +1,247 @@
1
+ /*global NodeJS*/
2
+ import type { Fiber, HostConfig } from "react-reconciler";
3
+ import { DefaultEventPriority } from "react-reconciler/constants";
4
+
5
+ import type { NodeType } from "../dom/types";
6
+ import { shallowEq } from "../renderer/typeddash";
7
+
8
+ import type { Container } from "./Container";
9
+ import type { Node } from "./Node";
10
+
11
+ const DEBUG = false;
12
+ export const debug = (...args: Parameters<typeof console.log>) => {
13
+ if (DEBUG) {
14
+ console.log(...args);
15
+ }
16
+ };
17
+
18
+ type Instance = Node<unknown>;
19
+
20
+ type Props = object;
21
+ type TextInstance = Node<unknown>;
22
+ type SuspenseInstance = Instance;
23
+ type HydratableInstance = Instance;
24
+ type PublicInstance = Instance;
25
+ type HostContext = null;
26
+ type UpdatePayload = Container;
27
+ type ChildSet = unknown;
28
+ type TimeoutHandle = NodeJS.Timeout;
29
+ type NoTimeout = -1;
30
+
31
+ type SkiaHostConfig = HostConfig<
32
+ NodeType,
33
+ Props,
34
+ Container,
35
+ Instance,
36
+ TextInstance,
37
+ SuspenseInstance,
38
+ HydratableInstance,
39
+ PublicInstance,
40
+ HostContext,
41
+ UpdatePayload,
42
+ ChildSet,
43
+ TimeoutHandle,
44
+ NoTimeout
45
+ >;
46
+
47
+ const appendNode = (parent: Node<unknown>, child: Node<unknown>) => {
48
+ parent.children.push(child);
49
+ };
50
+
51
+ const removeNode = (parent: Node<unknown>, child: Node<unknown>) => {
52
+ parent.children.splice(parent.children.indexOf(child), 1);
53
+ };
54
+
55
+ const insertBefore = (
56
+ parent: Node<unknown>,
57
+ child: Node<unknown>,
58
+ before: Node<unknown>
59
+ ) => {
60
+ parent.children.splice(parent.children.indexOf(before), 0, child);
61
+ };
62
+
63
+ export const sksgHostConfig: SkiaHostConfig = {
64
+ /**
65
+ * This function is used by the reconciler in order to calculate current time for prioritising work.
66
+ */
67
+ supportsMutation: true,
68
+ isPrimaryRenderer: false,
69
+ supportsPersistence: false,
70
+ supportsHydration: false,
71
+ //supportsMicrotask: true,
72
+
73
+ scheduleTimeout: setTimeout,
74
+ cancelTimeout: clearTimeout,
75
+ noTimeout: -1,
76
+
77
+ appendChildToContainer(container, child) {
78
+ debug("appendChildToContainer");
79
+ container.root.push(child);
80
+ },
81
+
82
+ appendChild(parent, child) {
83
+ debug("appendChild", parent, child);
84
+ appendNode(parent, child);
85
+ },
86
+
87
+ getRootHostContext: (_rootContainerInstance: Container) => {
88
+ debug("getRootHostContext");
89
+ return null;
90
+ },
91
+
92
+ getChildHostContext(_parentHostContext, _type, _rootContainerInstance) {
93
+ debug("getChildHostContext");
94
+ return null;
95
+ },
96
+
97
+ shouldSetTextContent(_type, _props) {
98
+ return false;
99
+ },
100
+
101
+ createTextInstance(
102
+ _text,
103
+ _rootContainerInstance,
104
+ _hostContext,
105
+ _internalInstanceHandle
106
+ ) {
107
+ debug("createTextInstance");
108
+ // return SpanNode({}, text) as SkNode;
109
+ throw new Error("Text nodes are not supported yet");
110
+ },
111
+
112
+ createInstance(
113
+ type,
114
+ propsWithChildren,
115
+ _container,
116
+ _hostContext,
117
+ _internalInstanceHandle
118
+ ) {
119
+ debug("createInstance", type);
120
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
121
+ const { children, ...props } = propsWithChildren as any;
122
+ debug("createInstance", type);
123
+ const instance = {
124
+ type,
125
+ props,
126
+ children: [],
127
+ };
128
+ return instance;
129
+ },
130
+
131
+ appendInitialChild(parentInstance, child) {
132
+ debug("appendInitialChild");
133
+ appendNode(parentInstance, child);
134
+ },
135
+
136
+ finalizeInitialChildren(
137
+ parentInstance,
138
+ _type,
139
+ _props,
140
+ _rootContainerInstance,
141
+ _hostContext
142
+ ) {
143
+ debug("finalizeInitialChildren", parentInstance);
144
+ return false;
145
+ },
146
+
147
+ commitMount() {
148
+ // if finalizeInitialChildren = true
149
+ debug("commitMount");
150
+ },
151
+
152
+ prepareForCommit(_containerInfo) {
153
+ debug("prepareForCommit");
154
+ return null;
155
+ },
156
+
157
+ resetAfterCommit(container) {
158
+ debug("resetAfterCommit");
159
+ container.redraw();
160
+ },
161
+
162
+ getPublicInstance(node: Instance) {
163
+ debug("getPublicInstance");
164
+ return node;
165
+ },
166
+
167
+ prepareUpdate: (
168
+ _instance,
169
+ type,
170
+ oldProps,
171
+ newProps,
172
+ rootContainerInstance,
173
+ _hostContext
174
+ ) => {
175
+ debug("prepareUpdate");
176
+ const propsAreEqual = shallowEq(oldProps, newProps);
177
+ if (propsAreEqual) {
178
+ return null;
179
+ }
180
+ debug("update ", type);
181
+ return rootContainerInstance;
182
+ },
183
+
184
+ commitUpdate(
185
+ instance,
186
+ _updatePayload,
187
+ type,
188
+ prevProps,
189
+ nextProps,
190
+ _internalHandle
191
+ ) {
192
+ debug("commitUpdate: ", type);
193
+ if (shallowEq(prevProps, nextProps)) {
194
+ return;
195
+ }
196
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
197
+ const { children, ...props } = nextProps as any;
198
+ instance.props = props;
199
+ },
200
+
201
+ commitTextUpdate: (
202
+ _textInstance: TextInstance,
203
+ _oldText: string,
204
+ _newText: string
205
+ ) => {
206
+ // textInstance.instance = newText;
207
+ },
208
+
209
+ clearContainer: (container) => {
210
+ debug("clearContainer");
211
+ container.root = [];
212
+ },
213
+
214
+ preparePortalMount: () => {
215
+ debug("preparePortalMount");
216
+ },
217
+
218
+ removeChild: (parent, child) => {
219
+ removeNode(parent, child);
220
+ },
221
+
222
+ removeChildFromContainer: (container, child) => {
223
+ container.root.splice(container.root.indexOf(child), 1);
224
+ },
225
+
226
+ insertInContainerBefore: (container, child, before) => {
227
+ container.root.splice(container.root.indexOf(before), 0, child);
228
+ },
229
+
230
+ insertBefore: (parent, child, before) => {
231
+ insertBefore(parent, child, before);
232
+ },
233
+
234
+ // see https://github.com/pmndrs/react-three-fiber/pull/2360#discussion_r916356874
235
+ getCurrentEventPriority: () => DefaultEventPriority,
236
+ beforeActiveInstanceBlur: () => {},
237
+ afterActiveInstanceBlur: () => {},
238
+ detachDeletedInstance: () => {},
239
+
240
+ getInstanceFromNode: function (_node): Fiber | null | undefined {
241
+ return null;
242
+ },
243
+ prepareScopeUpdate: function (_scopeInstance, _instance): void {},
244
+ getInstanceFromScope: function (_scopeInstance): Instance | null {
245
+ return null;
246
+ },
247
+ };
@@ -27,6 +27,7 @@ import type {
27
27
 
28
28
  // export enum CommandType {
29
29
  // // Context
30
+ // Group = "Group",
30
31
  // SavePaint = "SavePaint",
31
32
  // RestorePaint = "RestorePaint",
32
33
  // SaveCTM = "SaveCTM",
@@ -68,6 +69,7 @@ import type {
68
69
  // }
69
70
  export enum CommandType {
70
71
  // Context
72
+ Group,
71
73
  SavePaint,
72
74
  RestorePaint,
73
75
  SaveCTM,
@@ -133,6 +135,15 @@ export const isCommand = <T extends CommandType>(
133
135
  return command.type === type;
134
136
  };
135
137
 
138
+ interface GroupCommand extends Command<CommandType.Group> {
139
+ children: Command[];
140
+ }
141
+
142
+ export const isGroup = (command: Command): command is GroupCommand => {
143
+ "worklet";
144
+ return command.type === CommandType.Group;
145
+ };
146
+
136
147
  interface Props {
137
148
  [CommandType.DrawImage]: ImageProps;
138
149
  [CommandType.DrawCircle]: CircleProps;
@@ -43,14 +43,18 @@ import {
43
43
  CommandType,
44
44
  isCommand,
45
45
  isDrawCommand,
46
+ isGroup,
46
47
  materializeProps,
47
48
  type Command,
48
49
  } from "./Core";
49
50
  import type { DrawingContext } from "./DrawingContext";
50
51
 
51
- const play = (ctx: DrawingContext, command: Command) => {
52
+ function play(ctx: DrawingContext, command: Command) {
52
53
  "worklet";
53
-
54
+ if (isGroup(command)) {
55
+ command.children.forEach((child) => play(ctx, child));
56
+ return;
57
+ }
54
58
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
55
59
  materializeProps(command as any);
56
60
  if (isCommand(command, CommandType.SaveBackdropFilter)) {
@@ -150,7 +154,7 @@ const play = (ctx: DrawingContext, command: Command) => {
150
154
  ctx.paints.pop();
151
155
  });
152
156
  }
153
- };
157
+ }
154
158
 
155
159
  export const replay = (ctx: DrawingContext, commands: Command[]) => {
156
160
  "worklet";
@@ -46,8 +46,13 @@ interface AnimationValues {
46
46
 
47
47
  export class Recorder {
48
48
  commands: Command[] = [];
49
+ cursors: Command[][] = [];
49
50
  animationValues: Set<SharedValue<unknown>> = new Set();
50
51
 
52
+ constructor() {
53
+ this.cursors.push(this.commands);
54
+ }
55
+
51
56
  getRecording(): Recording & AnimationValues {
52
57
  return {
53
58
  commands: this.commands,
@@ -64,7 +69,6 @@ export class Recorder {
64
69
  const prop = props[key];
65
70
  if (isSharedValue(prop)) {
66
71
  this.animationValues.add(prop);
67
- props[key] = prop.value;
68
72
  animatedProps[key] = prop;
69
73
  hasAnimatedProps = true;
70
74
  }
@@ -85,7 +89,17 @@ export class Recorder {
85
89
  command.animatedProps = animatedProps;
86
90
  }
87
91
  }
88
- this.commands.push(command);
92
+ this.cursors[this.cursors.length - 1].push(command);
93
+ }
94
+
95
+ saveGroup() {
96
+ const children: Command[] = [];
97
+ this.add({ type: CommandType.Group, children });
98
+ this.cursors.push(children);
99
+ }
100
+
101
+ restoreGroup() {
102
+ this.cursors.pop();
89
103
  }
90
104
 
91
105
  savePaint(props: AnimatedProps<PaintProps>) {