@cntrl-site/sdk-nextjs 1.9.43 → 1.9.44

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 (137) 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 +7 -7
  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/items/CodeEmbedItem/CodeEmbedItem.js +36 -36
  15. package/lib/components/items/ComponentItem/ComponentItem.js +13 -13
  16. package/lib/components/items/CompoundItem/CompoundChild.js +20 -20
  17. package/lib/components/items/CompoundItem/CompoundItem.js +14 -14
  18. package/lib/components/items/CustomItem/CustomItem.js +9 -9
  19. package/lib/components/items/EmbedVideoItem/VimeoEmbed.js +24 -24
  20. package/lib/components/items/EmbedVideoItem/YoutubeEmbed.js +27 -27
  21. package/lib/components/items/FileItem/ImageItem.js +40 -40
  22. package/lib/components/items/FileItem/VideoItem.js +64 -64
  23. package/lib/components/items/GroupItem/GroupItem.js +14 -14
  24. package/lib/components/items/Item.js +30 -30
  25. package/lib/components/items/RectangleItem/RectangleItem.js +29 -29
  26. package/lib/components/items/RichTextItem/RichTextItem.js +22 -22
  27. package/lib/utils/RichTextConverter/RichTextConverter.js +21 -21
  28. package/package.json +60 -60
  29. package/src/ItemGeometry/CSSPropertyNameMap.ts +37 -37
  30. package/src/ItemGeometry/GenericGeometryController.ts +59 -59
  31. package/src/ItemGeometry/ItemGeometryContext.ts +4 -4
  32. package/src/ItemGeometry/ItemGeometryController.ts +22 -22
  33. package/src/ItemGeometry/ItemGeometryService.ts +34 -34
  34. package/src/ItemGeometry/RichTextGeometryController.ts +166 -166
  35. package/src/ItemGeometry/useItemGeometry.ts +25 -25
  36. package/src/common/useCurrentLayout.ts +52 -52
  37. package/src/common/useExemplary.ts +9 -9
  38. package/src/common/useItemFXData.ts +47 -47
  39. package/src/common/useKeyframeValue.ts +85 -85
  40. package/src/common/useRegisterResize.ts +16 -16
  41. package/src/components/Article.tsx +75 -75
  42. package/src/components/ArticleWrapper.tsx +21 -21
  43. package/src/components/Head.tsx +56 -56
  44. package/src/components/LayoutStyle.tsx +21 -21
  45. package/src/components/Page.tsx +38 -38
  46. package/src/components/ScrollPlaybackVideo.tsx +56 -56
  47. package/src/components/Section/Section.tsx +129 -129
  48. package/src/components/Section/SectionImage.tsx +48 -48
  49. package/src/components/Section/SectionVideo.tsx +144 -144
  50. package/src/components/Section/useSectionColor.ts +19 -19
  51. package/src/components/Section/useSectionHeightMap.ts +19 -19
  52. package/src/components/items/CodeEmbedItem/CodeEmbedItem.tsx +147 -147
  53. package/src/components/items/CodeEmbedItem/useCodeEmbedItem.ts +36 -36
  54. package/src/components/items/ComponentItem/ComponentItem.tsx +72 -72
  55. package/src/components/items/ComponentItem/useComponentItem.ts +35 -35
  56. package/src/components/items/CompoundItem/CompoundChild.tsx +150 -150
  57. package/src/components/items/CompoundItem/CompoundItem.tsx +87 -87
  58. package/src/components/items/CompoundItem/useCompoundItem.ts +21 -21
  59. package/src/components/items/CustomItem/CustomItem.tsx +50 -50
  60. package/src/components/items/EmbedVideoItem/VimeoEmbed.tsx +216 -216
  61. package/src/components/items/EmbedVideoItem/YoutubeEmbed.tsx +200 -200
  62. package/src/components/items/EmbedVideoItem/useEmbedVideoItem.ts +46 -46
  63. package/src/components/items/FileItem/ImageItem.tsx +165 -165
  64. package/src/components/items/FileItem/VideoItem.tsx +282 -282
  65. package/src/components/items/FileItem/useFileItem.ts +81 -81
  66. package/src/components/items/GroupItem/GroupItem.tsx +98 -98
  67. package/src/components/items/GroupItem/useGroupItem.ts +34 -34
  68. package/src/components/items/Item.tsx +267 -267
  69. package/src/components/items/LinkWrapper.tsx +39 -39
  70. package/src/components/items/RectangleItem/RectangleItem.tsx +184 -184
  71. package/src/components/items/RectangleItem/useRectangleItem.ts +89 -89
  72. package/src/components/items/RichTextItem/RichTextItem.tsx +112 -112
  73. package/src/components/items/RichTextItem/useRichTextItem.ts +12 -12
  74. package/src/components/items/RichTextWrapper.tsx +15 -15
  75. package/src/components/items/itemsMap.ts +28 -28
  76. package/src/components/items/useDraggable.ts +252 -252
  77. package/src/components/items/useItemAngle.ts +13 -13
  78. package/src/components/items/useItemArea.ts +35 -35
  79. package/src/components/items/useItemPointerEvents.ts +23 -23
  80. package/src/components/items/useItemScale.ts +17 -17
  81. package/src/components/items/useItemTriggers.ts +15 -15
  82. package/src/components/items/useRichTextItemValues.ts +66 -66
  83. package/src/components/items/useSizing.ts +25 -25
  84. package/src/components/items/useStickyItemTop.ts +21 -21
  85. package/src/components/useLayoutContext.ts +7 -7
  86. package/src/index.ts +24 -24
  87. package/src/interactions/InteractionsRegistry.ts +512 -512
  88. package/src/interactions/ItemInteractionCtrl.ts +78 -78
  89. package/src/interactions/getTransition.ts +27 -27
  90. package/src/interactions/types.ts +36 -36
  91. package/src/interactions/useItemInteractionCtrl.ts +18 -18
  92. package/src/provider/ArticleRectContext.ts +4 -4
  93. package/src/provider/CntrlContext.ts +5 -5
  94. package/src/provider/CntrlProvider.tsx +11 -11
  95. package/src/provider/CntrlSdkContext.ts +84 -84
  96. package/src/provider/CustomItemRegistry.ts +14 -14
  97. package/src/provider/CustomItemTypes.ts +3 -3
  98. package/src/provider/CustomSectionRegistry.ts +25 -25
  99. package/src/provider/InteractionsContext.tsx +73 -73
  100. package/src/provider/Keyframes.ts +11 -11
  101. package/src/provider/KeyframesContext.ts +5 -5
  102. package/src/provider/LayoutContext.ts +3 -3
  103. package/src/provider/WebGLContextManagerContext.tsx +4 -4
  104. package/src/provider/defaultContext.ts +8 -8
  105. package/src/provider/useCntrlContext.ts +8 -8
  106. package/src/styled-jsx.d.ts +1 -1
  107. package/src/utils/Animator/Animator.ts +570 -570
  108. package/src/utils/ArticleRectManager/ArticleRectObserver.ts +93 -93
  109. package/src/utils/ArticleRectManager/useArticleRectObserver.ts +17 -17
  110. package/src/utils/ArticleRectManager/useSectionRegistry.ts +11 -11
  111. package/src/utils/CSSNumeric.ts +19 -19
  112. package/src/utils/EventEmitter.ts +41 -41
  113. package/src/utils/RichTextConverter/RichTextConverter.tsx +342 -342
  114. package/src/utils/ScaleAnchorMap.ts +13 -13
  115. package/src/utils/StickyManager/StickyManager.ts +23 -23
  116. package/src/utils/Youtube/YouTubeIframeApiLoader.ts +58 -58
  117. package/src/utils/Youtube/YoutubeIframeApi.ts +101 -101
  118. package/src/utils/Youtube/useYouTubeIframeApi.ts +12 -12
  119. package/src/utils/areFillsVisible/areFillsVisible.test.ts +63 -63
  120. package/src/utils/areFillsVisible/areFillsVisible.ts +37 -37
  121. package/src/utils/binSearchInsertAt.ts +33 -33
  122. package/src/utils/castObject.ts +10 -10
  123. package/src/utils/checkOverflowClipSupport.ts +13 -13
  124. package/src/utils/domDfs.ts +11 -11
  125. package/src/utils/effects/useImageFx.ts +156 -156
  126. package/src/utils/effects/useVideoFx.ts +158 -158
  127. package/src/utils/getAnchoredItemTop.ts +12 -12
  128. package/src/utils/getCompoundBoundaryStyles.ts +60 -60
  129. package/src/utils/getFill.ts +46 -46
  130. package/src/utils/getFontFamilyValue.ts +3 -3
  131. package/src/utils/getInvertedRanges.ts +43 -43
  132. package/src/utils/getItemTopStyle.ts +39 -39
  133. package/src/utils/getStyleFromItemStateAndParams.ts +8 -8
  134. package/src/utils/getValidYoutubeUrl.ts +29 -29
  135. package/src/utils/isItemType.ts +5 -5
  136. package/src/utils/rangeMap.ts +13 -13
  137. 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,156 +1,156 @@
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
- }
11
-
12
- const PATTERN_URL = 'https://cdn.cntrl.site/client-app-files/texture2.png';
13
- const PATTERN_2_URL = 'https://cdn.cntrl.site/client-app-files/bayer16.png';
14
-
15
- export function useImageFx(
16
- canvas: HTMLCanvasElement | null | undefined,
17
- enabled: boolean,
18
- {
19
- imageUrl,
20
- fragmentShader,
21
- controlsValues
22
- }: FxParams,
23
- width: number,
24
- height: number
25
- ): boolean {
26
- const webGLContextManager = useContext(WebglContextManagerContext);
27
- const [isFXAllowed, setIsFXAllowed] = useState(true);
28
- const glRef = useRef<WebGL2RenderingContext | null>(null);
29
- const isRenderingRef = useRef(false);
30
- const frameRef = useRef<number>();
31
- const imageTextureManager = useMemo(() => {
32
- if (!imageUrl || !enabled) return;
33
- return new ImageTextureManager(imageUrl);
34
- }, [imageUrl, enabled]);
35
- const imageFx = useMemo<MediaEffect | undefined>(() => {
36
- if (!imageTextureManager) return;
37
- return new MediaEffect(
38
- imageTextureManager,
39
- PATTERN_URL,
40
- PATTERN_2_URL,
41
- fragmentShader!,
42
- {
43
- time: 0,
44
- cursor: [0, 0],
45
- ...controlsValues
46
- },
47
- width,
48
- height
49
- );
50
- }, [imageTextureManager, fragmentShader, width, height]);
51
-
52
- useEffect(() => {
53
- if (!imageFx || !controlsValues) return;
54
- for (const [key, value] of Object.entries(controlsValues)) {
55
- imageFx.setParam(key, value);
56
- }
57
- }, [imageFx, controlsValues]);
58
-
59
- useEffect(() => {
60
- if (!canvas || !imageFx) return;
61
- const handleMouseMove = (evt: MouseEvent) => {
62
- const rect = canvas.getBoundingClientRect();
63
- const x = rangeMap(evt.clientX, rect.left, rect.left + rect.width, 0, 1, true);
64
- const y = rangeMap(evt.clientY, rect.top, rect.top + rect.height, 0, 1, true);
65
- imageFx.setParam('cursor', [x, y]);
66
- };
67
- window.addEventListener('mousemove', handleMouseMove, { capture: true, passive: true });
68
- return () => {
69
- window.removeEventListener('mousemove', handleMouseMove);
70
- };
71
- }, [canvas, imageFx]);
72
-
73
- useEffect(() => {
74
- if (!enabled || !canvas || !imageFx) return;
75
-
76
- let time = 0;
77
-
78
- const startRendering = () => {
79
- if (isRenderingRef.current) return;
80
-
81
- const gl = webGLContextManager.getContext(canvas);
82
- if (!gl) {
83
- return;
84
- }
85
-
86
- glRef.current = gl;
87
- isRenderingRef.current = true;
88
-
89
- imageFx.prepare(gl);
90
-
91
- const renderFrame = () => {
92
- if (!isRenderingRef.current || !glRef.current) return;
93
-
94
- time += 0.1;
95
-
96
- webGLContextManager.updateContextSize(glRef.current, canvas.width, canvas.height);
97
-
98
- imageFx.setViewport(Math.floor(canvas.width), Math.floor(canvas.height));
99
- imageFx.setParam('time', time);
100
- try {
101
- imageFx.render(glRef.current);
102
- } catch {
103
- setIsFXAllowed(false);
104
- }
105
- webGLContextManager.renderToCanvas(glRef.current, canvas);
106
-
107
- frameRef.current = requestAnimationFrame(renderFrame);
108
- };
109
-
110
- frameRef.current = requestAnimationFrame(renderFrame);
111
- };
112
-
113
- const stopRendering = () => {
114
- if (!isRenderingRef.current) return;
115
-
116
- if (frameRef.current) {
117
- cancelAnimationFrame(frameRef.current);
118
- frameRef.current = undefined;
119
- }
120
-
121
- if (glRef.current) {
122
- webGLContextManager.releaseContext(canvas);
123
- glRef.current = null;
124
- }
125
-
126
- const ctx = canvas.getContext('2d');
127
- if (ctx) {
128
- ctx.clearRect(0, 0, canvas.width, canvas.height);
129
- }
130
-
131
- isRenderingRef.current = false;
132
- };
133
-
134
- const observer = new IntersectionObserver(
135
- ([entry]) => {
136
- if (entry.isIntersecting) {
137
- startRendering();
138
- } else {
139
- stopRendering();
140
- }
141
- },
142
- {
143
- threshold: 0,
144
- rootMargin: '75px'
145
- }
146
- );
147
-
148
- observer.observe(canvas);
149
-
150
- return () => {
151
- stopRendering();
152
- observer.disconnect();
153
- };
154
- }, [canvas, imageFx, enabled]);
155
- return isFXAllowed;
156
- }
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
+ }
11
+
12
+ const PATTERN_URL = 'https://cdn.cntrl.site/client-app-files/texture2.png';
13
+ const PATTERN_2_URL = 'https://cdn.cntrl.site/client-app-files/bayer16.png';
14
+
15
+ export function useImageFx(
16
+ canvas: HTMLCanvasElement | null | undefined,
17
+ enabled: boolean,
18
+ {
19
+ imageUrl,
20
+ fragmentShader,
21
+ controlsValues
22
+ }: FxParams,
23
+ width: number,
24
+ height: number
25
+ ): boolean {
26
+ const webGLContextManager = useContext(WebglContextManagerContext);
27
+ const [isFXAllowed, setIsFXAllowed] = useState(true);
28
+ const glRef = useRef<WebGL2RenderingContext | null>(null);
29
+ const isRenderingRef = useRef(false);
30
+ const frameRef = useRef<number>();
31
+ const imageTextureManager = useMemo(() => {
32
+ if (!imageUrl || !enabled) return;
33
+ return new ImageTextureManager(imageUrl);
34
+ }, [imageUrl, enabled]);
35
+ const imageFx = useMemo<MediaEffect | undefined>(() => {
36
+ if (!imageTextureManager) return;
37
+ return new MediaEffect(
38
+ imageTextureManager,
39
+ PATTERN_URL,
40
+ PATTERN_2_URL,
41
+ fragmentShader!,
42
+ {
43
+ time: 0,
44
+ cursor: [0, 0],
45
+ ...controlsValues
46
+ },
47
+ width,
48
+ height
49
+ );
50
+ }, [imageTextureManager, fragmentShader, width, height]);
51
+
52
+ useEffect(() => {
53
+ if (!imageFx || !controlsValues) return;
54
+ for (const [key, value] of Object.entries(controlsValues)) {
55
+ imageFx.setParam(key, value);
56
+ }
57
+ }, [imageFx, controlsValues]);
58
+
59
+ useEffect(() => {
60
+ if (!canvas || !imageFx) return;
61
+ const handleMouseMove = (evt: MouseEvent) => {
62
+ const rect = canvas.getBoundingClientRect();
63
+ const x = rangeMap(evt.clientX, rect.left, rect.left + rect.width, 0, 1, true);
64
+ const y = rangeMap(evt.clientY, rect.top, rect.top + rect.height, 0, 1, true);
65
+ imageFx.setParam('cursor', [x, y]);
66
+ };
67
+ window.addEventListener('mousemove', handleMouseMove, { capture: true, passive: true });
68
+ return () => {
69
+ window.removeEventListener('mousemove', handleMouseMove);
70
+ };
71
+ }, [canvas, imageFx]);
72
+
73
+ useEffect(() => {
74
+ if (!enabled || !canvas || !imageFx) return;
75
+
76
+ let time = 0;
77
+
78
+ const startRendering = () => {
79
+ if (isRenderingRef.current) return;
80
+
81
+ const gl = webGLContextManager.getContext(canvas);
82
+ if (!gl) {
83
+ return;
84
+ }
85
+
86
+ glRef.current = gl;
87
+ isRenderingRef.current = true;
88
+
89
+ imageFx.prepare(gl);
90
+
91
+ const renderFrame = () => {
92
+ if (!isRenderingRef.current || !glRef.current) return;
93
+
94
+ time += 0.1;
95
+
96
+ webGLContextManager.updateContextSize(glRef.current, canvas.width, canvas.height);
97
+
98
+ imageFx.setViewport(Math.floor(canvas.width), Math.floor(canvas.height));
99
+ imageFx.setParam('time', time);
100
+ try {
101
+ imageFx.render(glRef.current);
102
+ } catch {
103
+ setIsFXAllowed(false);
104
+ }
105
+ webGLContextManager.renderToCanvas(glRef.current, canvas);
106
+
107
+ frameRef.current = requestAnimationFrame(renderFrame);
108
+ };
109
+
110
+ frameRef.current = requestAnimationFrame(renderFrame);
111
+ };
112
+
113
+ const stopRendering = () => {
114
+ if (!isRenderingRef.current) return;
115
+
116
+ if (frameRef.current) {
117
+ cancelAnimationFrame(frameRef.current);
118
+ frameRef.current = undefined;
119
+ }
120
+
121
+ if (glRef.current) {
122
+ webGLContextManager.releaseContext(canvas);
123
+ glRef.current = null;
124
+ }
125
+
126
+ const ctx = canvas.getContext('2d');
127
+ if (ctx) {
128
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
129
+ }
130
+
131
+ isRenderingRef.current = false;
132
+ };
133
+
134
+ const observer = new IntersectionObserver(
135
+ ([entry]) => {
136
+ if (entry.isIntersecting) {
137
+ startRendering();
138
+ } else {
139
+ stopRendering();
140
+ }
141
+ },
142
+ {
143
+ threshold: 0,
144
+ rootMargin: '75px'
145
+ }
146
+ );
147
+
148
+ observer.observe(canvas);
149
+
150
+ return () => {
151
+ stopRendering();
152
+ observer.disconnect();
153
+ };
154
+ }, [canvas, imageFx, enabled]);
155
+ return isFXAllowed;
156
+ }