@shopify/react-native-skia 1.9.0 → 1.10.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 (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>) {