@cntrl-site/sdk-nextjs 1.9.78 → 1.9.80

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 (145) hide show
  1. package/.idea/codeStyles/codeStyleConfig.xml +5 -0
  2. package/.idea/modules.xml +8 -0
  3. package/.idea/sdk-nextjs.iml +12 -0
  4. package/.idea/vcs.xml +6 -0
  5. package/LICENSE +21 -21
  6. package/README.md +4 -4
  7. package/TODO.md +1 -1
  8. package/eslint.config.mjs +35 -35
  9. package/jest.config.js +6 -6
  10. package/lib/common/useItemFXData.js +5 -5
  11. package/lib/components/Article.js +5 -5
  12. package/lib/components/LayoutStyle.js +4 -4
  13. package/lib/components/Section/Section.js +27 -27
  14. package/lib/components/StructuredBlockItem/StructuredBlockItem.js +13 -13
  15. package/lib/components/StructuredBlockItem/StructuredComponent/StructuredComponent.js +12 -12
  16. package/lib/components/StructuredContent/StructuredContent.js +11 -11
  17. package/lib/components/items/CodeEmbedItem/CodeEmbedItem.js +36 -36
  18. package/lib/components/items/ComponentItem/ComponentItem.js +13 -13
  19. package/lib/components/items/CompoundItem/CompoundChild.js +20 -20
  20. package/lib/components/items/CompoundItem/CompoundItem.js +14 -14
  21. package/lib/components/items/CustomItem/CustomItem.js +9 -9
  22. package/lib/components/items/EmbedVideoItem/VimeoEmbed.js +24 -24
  23. package/lib/components/items/EmbedVideoItem/YoutubeEmbed.js +27 -27
  24. package/lib/components/items/FileItem/ImageItem.js +40 -40
  25. package/lib/components/items/FileItem/VideoItem.js +64 -64
  26. package/lib/components/items/GroupItem/GroupItem.js +14 -14
  27. package/lib/components/items/Item.js +30 -30
  28. package/lib/components/items/RectangleItem/RectangleItem.js +27 -27
  29. package/lib/components/items/RichTextItem/RichTextItem.js +22 -22
  30. package/lib/utils/RichTextConverter/RichTextConverter.js +21 -21
  31. package/package.json +62 -62
  32. package/src/ItemGeometry/CSSPropertyNameMap.ts +37 -37
  33. package/src/ItemGeometry/GenericGeometryController.ts +59 -59
  34. package/src/ItemGeometry/ItemGeometryContext.ts +4 -4
  35. package/src/ItemGeometry/ItemGeometryController.ts +22 -22
  36. package/src/ItemGeometry/ItemGeometryService.ts +34 -34
  37. package/src/ItemGeometry/RichTextGeometryController.ts +166 -166
  38. package/src/ItemGeometry/useItemGeometry.ts +25 -25
  39. package/src/common/useCurrentLayout.ts +52 -52
  40. package/src/common/useExemplary.ts +9 -9
  41. package/src/common/useItemFXData.ts +63 -63
  42. package/src/common/useKeyframeValue.ts +85 -85
  43. package/src/common/useRegisterResize.ts +16 -16
  44. package/src/components/Article.tsx +69 -69
  45. package/src/components/ArticleWrapper.tsx +21 -21
  46. package/src/components/Head.tsx +95 -95
  47. package/src/components/LayoutStyle.tsx +21 -21
  48. package/src/components/Page.tsx +47 -47
  49. package/src/components/ScrollPlaybackVideo.tsx +75 -75
  50. package/src/components/Section/Section.tsx +162 -162
  51. package/src/components/Section/SectionImage.tsx +48 -48
  52. package/src/components/Section/SectionVideo.tsx +144 -144
  53. package/src/components/Section/useSectionColor.ts +19 -19
  54. package/src/components/Section/useSectionHeightMap.ts +19 -19
  55. package/src/components/StructuredBlockItem/StructuredBlockItem.tsx +69 -69
  56. package/src/components/StructuredBlockItem/StructuredComponent/StructuredComponent.tsx +66 -66
  57. package/src/components/StructuredContent/StructuredContent.tsx +42 -42
  58. package/src/components/items/CodeEmbedItem/CodeEmbedItem.tsx +148 -148
  59. package/src/components/items/CodeEmbedItem/useCodeEmbedItem.ts +36 -36
  60. package/src/components/items/ComponentItem/ComponentItem.tsx +85 -85
  61. package/src/components/items/ComponentItem/useComponentItem.ts +35 -35
  62. package/src/components/items/CompoundItem/CompoundChild.tsx +151 -151
  63. package/src/components/items/CompoundItem/CompoundItem.tsx +88 -88
  64. package/src/components/items/CompoundItem/useCompoundItem.ts +21 -21
  65. package/src/components/items/CustomItem/CustomItem.tsx +50 -50
  66. package/src/components/items/EmbedVideoItem/VimeoEmbed.tsx +217 -217
  67. package/src/components/items/EmbedVideoItem/YoutubeEmbed.tsx +201 -201
  68. package/src/components/items/EmbedVideoItem/useEmbedVideoItem.ts +46 -46
  69. package/src/components/items/FileItem/ImageItem.tsx +166 -166
  70. package/src/components/items/FileItem/VideoItem.tsx +287 -287
  71. package/src/components/items/FileItem/useFileItem.ts +81 -81
  72. package/src/components/items/GroupItem/GroupItem.tsx +99 -99
  73. package/src/components/items/GroupItem/useGroupItem.ts +34 -34
  74. package/src/components/items/Item.tsx +268 -268
  75. package/src/components/items/LinkWrapper.tsx +39 -39
  76. package/src/components/items/RectangleItem/RectangleItem.tsx +177 -177
  77. package/src/components/items/RectangleItem/useRectangleItem.ts +89 -89
  78. package/src/components/items/RichTextItem/RichTextItem.tsx +113 -113
  79. package/src/components/items/RichTextItem/useRichTextItem.ts +12 -12
  80. package/src/components/items/RichTextWrapper.tsx +15 -15
  81. package/src/components/items/itemsMap.ts +28 -28
  82. package/src/components/items/useDraggable.ts +252 -252
  83. package/src/components/items/useItemAngle.ts +13 -13
  84. package/src/components/items/useItemArea.ts +35 -35
  85. package/src/components/items/useItemPointerEvents.ts +23 -23
  86. package/src/components/items/useItemScale.ts +17 -17
  87. package/src/components/items/useItemTriggers.ts +15 -15
  88. package/src/components/items/useRichTextItemValues.ts +66 -66
  89. package/src/components/items/useSizing.ts +25 -25
  90. package/src/components/items/useStickyItemTop.ts +21 -21
  91. package/src/components/useLayoutContext.ts +7 -7
  92. package/src/index.ts +25 -25
  93. package/src/interactions/InteractionsRegistry.ts +513 -513
  94. package/src/interactions/ItemInteractionCtrl.ts +78 -78
  95. package/src/interactions/getTransition.ts +27 -27
  96. package/src/interactions/types.ts +36 -36
  97. package/src/interactions/useItemInteractionCtrl.ts +18 -18
  98. package/src/provider/ArticleRectContext.ts +4 -4
  99. package/src/provider/CntrlContext.ts +5 -5
  100. package/src/provider/CntrlProvider.tsx +11 -11
  101. package/src/provider/CntrlSdkContext.ts +113 -113
  102. package/src/provider/CustomItemRegistry.ts +14 -14
  103. package/src/provider/CustomItemTypes.ts +3 -3
  104. package/src/provider/CustomSectionRegistry.ts +25 -25
  105. package/src/provider/InteractionsContext.tsx +73 -73
  106. package/src/provider/Keyframes.ts +11 -11
  107. package/src/provider/KeyframesContext.ts +5 -5
  108. package/src/provider/LayoutContext.ts +3 -3
  109. package/src/provider/WebGLContextManagerContext.tsx +4 -4
  110. package/src/provider/defaultContext.ts +8 -8
  111. package/src/provider/useCntrlContext.ts +8 -8
  112. package/src/styled-jsx.d.ts +1 -1
  113. package/src/types/components-utils.d.ts +7 -7
  114. package/src/utils/Animator/Animator.ts +570 -570
  115. package/src/utils/ArticleRectManager/ArticleRectObserver.ts +93 -93
  116. package/src/utils/ArticleRectManager/useArticleRectObserver.ts +17 -17
  117. package/src/utils/ArticleRectManager/useSectionRegistry.ts +11 -11
  118. package/src/utils/CSSNumeric.ts +19 -19
  119. package/src/utils/EventEmitter.ts +41 -41
  120. package/src/utils/RichTextConverter/RichTextConverter.tsx +342 -342
  121. package/src/utils/ScaleAnchorMap.ts +13 -13
  122. package/src/utils/StickyManager/StickyManager.ts +23 -23
  123. package/src/utils/Youtube/YouTubeIframeApiLoader.ts +58 -58
  124. package/src/utils/Youtube/YoutubeIframeApi.ts +101 -101
  125. package/src/utils/Youtube/useYouTubeIframeApi.ts +12 -12
  126. package/src/utils/areFillsVisible/areFillsVisible.test.ts +63 -63
  127. package/src/utils/areFillsVisible/areFillsVisible.ts +37 -37
  128. package/src/utils/binSearchInsertAt.ts +33 -33
  129. package/src/utils/castObject.ts +10 -10
  130. package/src/utils/checkOverflowClipSupport.ts +13 -13
  131. package/src/utils/domDfs.ts +11 -11
  132. package/src/utils/effects/useImageFx.ts +154 -154
  133. package/src/utils/effects/useVideoFx.ts +156 -156
  134. package/src/utils/evaluateComponentBundle.ts +83 -83
  135. package/src/utils/getAnchoredItemTop.ts +12 -12
  136. package/src/utils/getCompoundBoundaryStyles.ts +60 -60
  137. package/src/utils/getFill.ts +46 -46
  138. package/src/utils/getFontFamilyValue.ts +3 -3
  139. package/src/utils/getInvertedRanges.ts +43 -43
  140. package/src/utils/getItemTopStyle.ts +39 -39
  141. package/src/utils/getStyleFromItemStateAndParams.ts +8 -8
  142. package/src/utils/getValidYoutubeUrl.ts +29 -29
  143. package/src/utils/isItemType.ts +5 -5
  144. package/src/utils/rangeMap.ts +13 -13
  145. package/src/utils/useElementRect.ts +23 -23
@@ -1,33 +1,33 @@
1
- type CompareFn<L, R> = (lhs: L, rhs: R) => number;
2
- type InsertStrategy<T> = (list: T[], item: T, compare: CompareFn<T, T>) => number;
3
-
4
- export function binSearchInsertAt<T, K>(list: T[], item: K, compare: CompareFn<K, T>): number {
5
- switch (list.length) {
6
- case 0: return 0;
7
- case 1: return compare(item, list[0]) < 0 ? 0 : 1;
8
- }
9
- let start = 0;
10
- let end = list.length;
11
- let pivot: number;
12
- let cmpResult: number;
13
- while (start < end) {
14
- pivot = start + Math.floor((end - start) / 2);
15
- cmpResult = compare(item, list[pivot]);
16
- if (cmpResult === 0) return pivot + 1;
17
- if (cmpResult < 0) {
18
- end = pivot;
19
- }
20
- if (cmpResult > 0) {
21
- start = pivot + 1;
22
- }
23
- }
24
- if (start >= list.length) return list.length;
25
- return compare(item, list[start]) < 0 ? start : start + 1;
26
- }
27
-
28
- export function createInsert<T>(start: InsertStrategy<T>, compare: CompareFn<T, T>): (list: T[], item: T) => void {
29
- return (list: T[], item: T): void => {
30
- const pos = start(list, item, compare);
31
- list.splice(pos, 0, item);
32
- };
33
- }
1
+ type CompareFn<L, R> = (lhs: L, rhs: R) => number;
2
+ type InsertStrategy<T> = (list: T[], item: T, compare: CompareFn<T, T>) => number;
3
+
4
+ export function binSearchInsertAt<T, K>(list: T[], item: K, compare: CompareFn<K, T>): number {
5
+ switch (list.length) {
6
+ case 0: return 0;
7
+ case 1: return compare(item, list[0]) < 0 ? 0 : 1;
8
+ }
9
+ let start = 0;
10
+ let end = list.length;
11
+ let pivot: number;
12
+ let cmpResult: number;
13
+ while (start < end) {
14
+ pivot = start + Math.floor((end - start) / 2);
15
+ cmpResult = compare(item, list[pivot]);
16
+ if (cmpResult === 0) return pivot + 1;
17
+ if (cmpResult < 0) {
18
+ end = pivot;
19
+ }
20
+ if (cmpResult > 0) {
21
+ start = pivot + 1;
22
+ }
23
+ }
24
+ if (start >= list.length) return list.length;
25
+ return compare(item, list[start]) < 0 ? start : start + 1;
26
+ }
27
+
28
+ export function createInsert<T>(start: InsertStrategy<T>, compare: CompareFn<T, T>): (list: T[], item: T) => void {
29
+ return (list: T[], item: T): void => {
30
+ const pos = start(list, item, compare);
31
+ list.splice(pos, 0, item);
32
+ };
33
+ }
@@ -1,10 +1,10 @@
1
- type Ctor = {
2
- new(...args: any): any;
3
- };
4
-
5
- export function castObject<O extends Ctor>(value: unknown, ctor: O): InstanceType<O> {
6
- if (value instanceof ctor) {
7
- return value;
8
- }
9
- throw new TypeError(`passed value "${String(value)}" is not instance of ${ctor.name}`);
10
- }
1
+ type Ctor = {
2
+ new(...args: any): any;
3
+ };
4
+
5
+ export function castObject<O extends Ctor>(value: unknown, ctor: O): InstanceType<O> {
6
+ if (value instanceof ctor) {
7
+ return value;
8
+ }
9
+ throw new TypeError(`passed value "${String(value)}" is not instance of ${ctor.name}`);
10
+ }
@@ -1,13 +1,13 @@
1
- export function isOverflowClipSupported(): boolean {
2
- if (typeof window === 'undefined') return false;
3
- if (typeof CSS !== 'undefined' && CSS.supports) {
4
- return CSS.supports('overflow', 'clip');
5
- }
6
- try {
7
- const testElement = document.createElement('div');
8
- testElement.style.overflow = 'clip';
9
- return testElement.style.overflow === 'clip';
10
- } catch {
11
- return false;
12
- }
13
- }
1
+ export function isOverflowClipSupported(): boolean {
2
+ if (typeof window === 'undefined') return false;
3
+ if (typeof CSS !== 'undefined' && CSS.supports) {
4
+ return CSS.supports('overflow', 'clip');
5
+ }
6
+ try {
7
+ const testElement = document.createElement('div');
8
+ testElement.style.overflow = 'clip';
9
+ return testElement.style.overflow === 'clip';
10
+ } catch {
11
+ return false;
12
+ }
13
+ }
@@ -1,11 +1,11 @@
1
- export function domDfs(root: Element[], visitor: (element: Element) => void) {
2
- const q: Element[] = root;
3
- for (let i = 0; i < q.length; i += 1) {
4
- if (q[i].children.length > 0) {
5
- q.push(...q[i].children);
6
- }
7
- }
8
- for (let i = q.length - 1; i >= 0; i -= 1) {
9
- visitor(q[i]);
10
- }
11
- }
1
+ export function domDfs(root: Element[], visitor: (element: Element) => void) {
2
+ const q: Element[] = root;
3
+ for (let i = 0; i < q.length; i += 1) {
4
+ if (q[i].children.length > 0) {
5
+ q.push(...q[i].children);
6
+ }
7
+ }
8
+ for (let i = q.length - 1; i >= 0; i -= 1) {
9
+ visitor(q[i]);
10
+ }
11
+ }
@@ -1,154 +1,154 @@
1
- import { useContext, useEffect, useMemo, useRef, useState } from 'react';
2
- import { MediaEffect, ImageTextureManager } from '@cntrl-site/effects';
3
- import { rangeMap } from '../rangeMap';
4
- import { WebglContextManagerContext } from '../../provider/WebGLContextManagerContext';
5
-
6
- interface FxParams {
7
- imageUrl?: string;
8
- fragmentShader: string | null;
9
- controlsValues?: Record<string, number | [number, number]>;
10
- texturesUrls: string[];
11
- }
12
-
13
- export function useImageFx(
14
- canvas: HTMLCanvasElement | null | undefined,
15
- enabled: boolean,
16
- {
17
- imageUrl,
18
- fragmentShader,
19
- controlsValues,
20
- texturesUrls
21
- }: FxParams,
22
- width: number,
23
- height: number
24
- ): boolean {
25
- const webGLContextManager = useContext(WebglContextManagerContext);
26
- const [isFXAllowed, setIsFXAllowed] = useState(true);
27
- const glRef = useRef<WebGL2RenderingContext | null>(null);
28
- const isRenderingRef = useRef(false);
29
- const frameRef = useRef<number>();
30
- const imageTextureManager = useMemo(() => {
31
- if (!imageUrl || !enabled) return;
32
- return new ImageTextureManager(imageUrl);
33
- }, [imageUrl, enabled]);
34
- const imageFx = useMemo<MediaEffect | undefined>(() => {
35
- if (!imageTextureManager) return;
36
- return new MediaEffect(
37
- imageTextureManager,
38
- texturesUrls,
39
- fragmentShader!,
40
- {
41
- time: 0,
42
- cursor: [0, 0],
43
- ...controlsValues
44
- },
45
- width,
46
- height
47
- );
48
- }, [imageTextureManager, fragmentShader, width, height, texturesUrls]);
49
-
50
- useEffect(() => {
51
- if (!imageFx || !controlsValues) return;
52
- for (const [key, value] of Object.entries(controlsValues)) {
53
- imageFx.setParam(key, value);
54
- }
55
- }, [imageFx, controlsValues]);
56
-
57
- useEffect(() => {
58
- if (!canvas || !imageFx) return;
59
- const handleMouseMove = (evt: MouseEvent) => {
60
- const rect = canvas.getBoundingClientRect();
61
- const x = rangeMap(evt.clientX, rect.left, rect.left + rect.width, 0, 1, true);
62
- const y = rangeMap(evt.clientY, rect.top, rect.top + rect.height, 0, 1, true);
63
- imageFx.setParam('cursor', [x, y]);
64
- };
65
- window.addEventListener('mousemove', handleMouseMove, { capture: true, passive: true });
66
- return () => {
67
- window.removeEventListener('mousemove', handleMouseMove);
68
- };
69
- }, [canvas, imageFx]);
70
-
71
- useEffect(() => {
72
- if (!enabled || !canvas || !imageFx) return;
73
-
74
- let time = 0;
75
-
76
- const startRendering = () => {
77
- if (isRenderingRef.current) return;
78
-
79
- const gl = webGLContextManager.getContext(canvas);
80
- if (!gl) {
81
- return;
82
- }
83
-
84
- glRef.current = gl;
85
- isRenderingRef.current = true;
86
-
87
- imageFx.prepare(gl);
88
-
89
- const renderFrame = () => {
90
- if (!isRenderingRef.current || !glRef.current) return;
91
-
92
- time += 0.1;
93
-
94
- webGLContextManager.updateContextSize(glRef.current, canvas.width, canvas.height);
95
-
96
- imageFx.setViewport(Math.floor(canvas.width), Math.floor(canvas.height));
97
- imageFx.setParam('time', time);
98
- try {
99
- imageFx.render(glRef.current);
100
- } catch {
101
- setIsFXAllowed(false);
102
- }
103
- webGLContextManager.renderToCanvas(glRef.current, canvas);
104
-
105
- frameRef.current = requestAnimationFrame(renderFrame);
106
- };
107
-
108
- frameRef.current = requestAnimationFrame(renderFrame);
109
- };
110
-
111
- const stopRendering = () => {
112
- if (!isRenderingRef.current) return;
113
-
114
- if (frameRef.current) {
115
- cancelAnimationFrame(frameRef.current);
116
- frameRef.current = undefined;
117
- }
118
-
119
- if (glRef.current) {
120
- webGLContextManager.releaseContext(canvas);
121
- glRef.current = null;
122
- }
123
-
124
- const ctx = canvas.getContext('2d');
125
- if (ctx) {
126
- ctx.clearRect(0, 0, canvas.width, canvas.height);
127
- }
128
-
129
- isRenderingRef.current = false;
130
- };
131
-
132
- const observer = new IntersectionObserver(
133
- ([entry]) => {
134
- if (entry.isIntersecting) {
135
- startRendering();
136
- } else {
137
- stopRendering();
138
- }
139
- },
140
- {
141
- threshold: 0,
142
- rootMargin: '75px'
143
- }
144
- );
145
-
146
- observer.observe(canvas);
147
-
148
- return () => {
149
- stopRendering();
150
- observer.disconnect();
151
- };
152
- }, [canvas, imageFx, enabled]);
153
- return isFXAllowed;
154
- }
1
+ import { useContext, useEffect, useMemo, useRef, useState } from 'react';
2
+ import { MediaEffect, ImageTextureManager } from '@cntrl-site/effects';
3
+ import { rangeMap } from '../rangeMap';
4
+ import { WebglContextManagerContext } from '../../provider/WebGLContextManagerContext';
5
+
6
+ interface FxParams {
7
+ imageUrl?: string;
8
+ fragmentShader: string | null;
9
+ controlsValues?: Record<string, number | [number, number]>;
10
+ texturesUrls: string[];
11
+ }
12
+
13
+ export function useImageFx(
14
+ canvas: HTMLCanvasElement | null | undefined,
15
+ enabled: boolean,
16
+ {
17
+ imageUrl,
18
+ fragmentShader,
19
+ controlsValues,
20
+ texturesUrls
21
+ }: FxParams,
22
+ width: number,
23
+ height: number
24
+ ): boolean {
25
+ const webGLContextManager = useContext(WebglContextManagerContext);
26
+ const [isFXAllowed, setIsFXAllowed] = useState(true);
27
+ const glRef = useRef<WebGL2RenderingContext | null>(null);
28
+ const isRenderingRef = useRef(false);
29
+ const frameRef = useRef<number>();
30
+ const imageTextureManager = useMemo(() => {
31
+ if (!imageUrl || !enabled) return;
32
+ return new ImageTextureManager(imageUrl);
33
+ }, [imageUrl, enabled]);
34
+ const imageFx = useMemo<MediaEffect | undefined>(() => {
35
+ if (!imageTextureManager) return;
36
+ return new MediaEffect(
37
+ imageTextureManager,
38
+ texturesUrls,
39
+ fragmentShader!,
40
+ {
41
+ time: 0,
42
+ cursor: [0, 0],
43
+ ...controlsValues
44
+ },
45
+ width,
46
+ height
47
+ );
48
+ }, [imageTextureManager, fragmentShader, width, height, texturesUrls]);
49
+
50
+ useEffect(() => {
51
+ if (!imageFx || !controlsValues) return;
52
+ for (const [key, value] of Object.entries(controlsValues)) {
53
+ imageFx.setParam(key, value);
54
+ }
55
+ }, [imageFx, controlsValues]);
56
+
57
+ useEffect(() => {
58
+ if (!canvas || !imageFx) return;
59
+ const handleMouseMove = (evt: MouseEvent) => {
60
+ const rect = canvas.getBoundingClientRect();
61
+ const x = rangeMap(evt.clientX, rect.left, rect.left + rect.width, 0, 1, true);
62
+ const y = rangeMap(evt.clientY, rect.top, rect.top + rect.height, 0, 1, true);
63
+ imageFx.setParam('cursor', [x, y]);
64
+ };
65
+ window.addEventListener('mousemove', handleMouseMove, { capture: true, passive: true });
66
+ return () => {
67
+ window.removeEventListener('mousemove', handleMouseMove);
68
+ };
69
+ }, [canvas, imageFx]);
70
+
71
+ useEffect(() => {
72
+ if (!enabled || !canvas || !imageFx) return;
73
+
74
+ let time = 0;
75
+
76
+ const startRendering = () => {
77
+ if (isRenderingRef.current) return;
78
+
79
+ const gl = webGLContextManager.getContext(canvas);
80
+ if (!gl) {
81
+ return;
82
+ }
83
+
84
+ glRef.current = gl;
85
+ isRenderingRef.current = true;
86
+
87
+ imageFx.prepare(gl);
88
+
89
+ const renderFrame = () => {
90
+ if (!isRenderingRef.current || !glRef.current) return;
91
+
92
+ time += 0.1;
93
+
94
+ webGLContextManager.updateContextSize(glRef.current, canvas.width, canvas.height);
95
+
96
+ imageFx.setViewport(Math.floor(canvas.width), Math.floor(canvas.height));
97
+ imageFx.setParam('time', time);
98
+ try {
99
+ imageFx.render(glRef.current);
100
+ } catch {
101
+ setIsFXAllowed(false);
102
+ }
103
+ webGLContextManager.renderToCanvas(glRef.current, canvas);
104
+
105
+ frameRef.current = requestAnimationFrame(renderFrame);
106
+ };
107
+
108
+ frameRef.current = requestAnimationFrame(renderFrame);
109
+ };
110
+
111
+ const stopRendering = () => {
112
+ if (!isRenderingRef.current) return;
113
+
114
+ if (frameRef.current) {
115
+ cancelAnimationFrame(frameRef.current);
116
+ frameRef.current = undefined;
117
+ }
118
+
119
+ if (glRef.current) {
120
+ webGLContextManager.releaseContext(canvas);
121
+ glRef.current = null;
122
+ }
123
+
124
+ const ctx = canvas.getContext('2d');
125
+ if (ctx) {
126
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
127
+ }
128
+
129
+ isRenderingRef.current = false;
130
+ };
131
+
132
+ const observer = new IntersectionObserver(
133
+ ([entry]) => {
134
+ if (entry.isIntersecting) {
135
+ startRendering();
136
+ } else {
137
+ stopRendering();
138
+ }
139
+ },
140
+ {
141
+ threshold: 0,
142
+ rootMargin: '75px'
143
+ }
144
+ );
145
+
146
+ observer.observe(canvas);
147
+
148
+ return () => {
149
+ stopRendering();
150
+ observer.disconnect();
151
+ };
152
+ }, [canvas, imageFx, enabled]);
153
+ return isFXAllowed;
154
+ }