@cntrl-site/sdk-nextjs 0.19.3 → 0.20.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.
@@ -0,0 +1,5 @@
1
+ <component name="ProjectCodeStyleConfiguration">
2
+ <state>
3
+ <option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
4
+ </state>
5
+ </component>
@@ -0,0 +1,15 @@
1
+ <component name="InspectionProjectProfileManager">
2
+ <profile version="1.0">
3
+ <option name="myName" value="Project Default" />
4
+ <inspection_tool class="HtmlUnknownAttribute" enabled="true" level="WARNING" enabled_by_default="true">
5
+ <option name="myValues">
6
+ <value>
7
+ <list size="1">
8
+ <item index="0" class="java.lang.String" itemvalue="jsx" />
9
+ </list>
10
+ </value>
11
+ </option>
12
+ <option name="myCustomValuesEnabled" value="true" />
13
+ </inspection_tool>
14
+ </profile>
15
+ </component>
Binary file
@@ -2,9 +2,9 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.LinkWrapper = void 0;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
- const LinkWrapper = ({ url, children }) => {
5
+ const LinkWrapper = ({ url, children, target }) => {
6
6
  const validUrl = url && buildValidUrl(url);
7
- return url ? ((0, jsx_runtime_1.jsx)("a", Object.assign({ href: validUrl, target: url.startsWith('/') || url.startsWith('#') ? '_self' : '_blank', rel: "noreferrer" }, { children: children }))) : ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: children }));
7
+ return url ? ((0, jsx_runtime_1.jsx)("a", Object.assign({ href: validUrl, target: target, rel: "noreferrer" }, { children: children }))) : ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: children }));
8
8
  };
9
9
  exports.LinkWrapper = LinkWrapper;
10
10
  function buildValidUrl(url) {
@@ -15,8 +15,10 @@ const useItemAngle_1 = require("../useItemAngle");
15
15
  const useCntrlContext_1 = require("../../provider/useCntrlContext");
16
16
  const HoverStyles_1 = require("../../utils/HoverStyles/HoverStyles");
17
17
  const useRegisterResize_1 = require("../../common/useRegisterResize");
18
+ const useImageFx_1 = require("../../utils/effects/useImageFx");
19
+ const useElementRect_1 = require("../../utils/useElementRect");
18
20
  const ImageItem = ({ item, sectionId, onResize }) => {
19
- var _a;
21
+ var _a, _b, _c, _d;
20
22
  const id = (0, react_1.useId)();
21
23
  const { layouts } = (0, useCntrlContext_1.useCntrlContext)();
22
24
  const { radius, strokeWidth, opacity, strokeColor, blur } = (0, useFileItem_1.useFileItem)(item, sectionId);
@@ -24,15 +26,25 @@ const ImageItem = ({ item, sectionId, onResize }) => {
24
26
  const borderColor = (0, react_1.useMemo)(() => color_1.CntrlColor.parse(strokeColor), [strokeColor]);
25
27
  const [ref, setRef] = (0, react_1.useState)(null);
26
28
  (0, useRegisterResize_1.useRegisterResize)(ref, onResize);
27
- return ((0, jsx_runtime_1.jsx)(LinkWrapper_1.LinkWrapper, Object.assign({ url: (_a = item.link) === null || _a === void 0 ? void 0 : _a.url }, { children: (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", Object.assign({ className: `image-wrapper-${item.id}`, ref: setRef, style: {
29
+ const { url, hasGLEffect, fragmentShader } = item.commonParams;
30
+ const fxCanvas = (0, react_1.useRef)(null);
31
+ (0, useImageFx_1.useImageFx)(fxCanvas.current, hasGLEffect !== null && hasGLEffect !== void 0 ? hasGLEffect : false, {
32
+ imageUrl: url,
33
+ fragmentShader
34
+ });
35
+ const rect = (0, useElementRect_1.useElementRect)(ref);
36
+ const rectWidth = Math.floor((_a = rect === null || rect === void 0 ? void 0 : rect.width) !== null && _a !== void 0 ? _a : 0);
37
+ const rectHeight = Math.floor((_b = rect === null || rect === void 0 ? void 0 : rect.height) !== null && _b !== void 0 ? _b : 0);
38
+ const inlineStyles = {
39
+ borderRadius: `${radius * 100}vw`,
40
+ borderWidth: `${strokeWidth * 100}vw`,
41
+ borderColor: `${borderColor.toCss()}`
42
+ };
43
+ return ((0, jsx_runtime_1.jsx)(LinkWrapper_1.LinkWrapper, Object.assign({ url: (_c = item.link) === null || _c === void 0 ? void 0 : _c.url, target: (_d = item.link) === null || _d === void 0 ? void 0 : _d.target }, { children: (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", Object.assign({ className: `image-wrapper-${item.id}`, ref: setRef, style: {
28
44
  opacity: `${opacity}`,
29
45
  transform: `rotate(${angle}deg)`,
30
46
  filter: `blur(${blur * 100}vw)`
31
- } }, { children: (0, jsx_runtime_1.jsx)("img", { className: `image image-${item.id}`, style: {
32
- borderRadius: `${radius * 100}vw`,
33
- borderWidth: `${strokeWidth * 100}vw`,
34
- borderColor: `${borderColor.toCss()}`
35
- }, src: item.commonParams.url }) })), (0, jsx_runtime_1.jsx)(style_1.default, Object.assign({ id: id }, { children: `
47
+ } }, { children: item.commonParams.hasGLEffect ? ((0, jsx_runtime_1.jsx)("canvas", { style: inlineStyles, ref: fxCanvas, className: "img-canvas", width: rectWidth, height: rectHeight })) : ((0, jsx_runtime_1.jsx)("img", { alt: "", className: `image image-${item.id}`, style: inlineStyles, src: item.commonParams.url })) })), (0, jsx_runtime_1.jsx)(style_1.default, Object.assign({ id: id }, { children: `
36
48
  @supports not (color: oklch(42% 0.3 90 / 1)) {
37
49
  .image-${item.id} {
38
50
  border-color: ${borderColor.fmt('rgba')};
@@ -53,6 +65,15 @@ const ImageItem = ({ item, sectionId, onResize }) => {
53
65
  pointer-events: none;
54
66
  border-style: solid;
55
67
  overflow: hidden;
68
+ box-sizing: border-box;
69
+ }
70
+ .img-canvas {
71
+ border: solid;
72
+ width: 100%;
73
+ height: 100%;
74
+ pointer-events: none;
75
+ border-width: 0;
76
+ box-sizing: border-box;
56
77
  }
57
78
  ${(0, sdk_1.getLayoutStyles)(layouts, [item.state.hover], ([hoverParams]) => {
58
79
  return (`
@@ -16,7 +16,7 @@ const useCntrlContext_1 = require("../../provider/useCntrlContext");
16
16
  const HoverStyles_1 = require("../../utils/HoverStyles/HoverStyles");
17
17
  const useRegisterResize_1 = require("../../common/useRegisterResize");
18
18
  const RectangleItem = ({ item, sectionId, onResize }) => {
19
- var _a;
19
+ var _a, _b;
20
20
  const id = (0, react_1.useId)();
21
21
  const { layouts } = (0, useCntrlContext_1.useCntrlContext)();
22
22
  const { fillColor, radius, strokeWidth, strokeColor, blur, backdropBlur } = (0, useRectangleItem_1.useRectangleItem)(item, sectionId);
@@ -26,7 +26,7 @@ const RectangleItem = ({ item, sectionId, onResize }) => {
26
26
  const [ref, setRef] = (0, react_1.useState)(null);
27
27
  (0, useRegisterResize_1.useRegisterResize)(ref, onResize);
28
28
  const backdropFilterValue = backdropBlur !== 0 ? `blur(${backdropBlur * 100}vw)` : 'unset';
29
- return ((0, jsx_runtime_1.jsx)(LinkWrapper_1.LinkWrapper, Object.assign({ url: (_a = item.link) === null || _a === void 0 ? void 0 : _a.url }, { children: (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { className: `rectangle-${item.id}`, ref: setRef, style: {
29
+ return ((0, jsx_runtime_1.jsx)(LinkWrapper_1.LinkWrapper, Object.assign({ url: (_a = item.link) === null || _a === void 0 ? void 0 : _a.url, target: (_b = item.link) === null || _b === void 0 ? void 0 : _b.target }, { children: (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { className: `rectangle-${item.id}`, ref: setRef, style: {
30
30
  backgroundColor: `${backgroundColor.toCss()}`,
31
31
  borderRadius: `${radius * 100}vw`,
32
32
  borderWidth: `${strokeWidth * 100}vw`,
@@ -16,7 +16,7 @@ const useCntrlContext_1 = require("../../provider/useCntrlContext");
16
16
  const HoverStyles_1 = require("../../utils/HoverStyles/HoverStyles");
17
17
  const useRegisterResize_1 = require("../../common/useRegisterResize");
18
18
  const VideoItem = ({ item, sectionId, onResize }) => {
19
- var _a;
19
+ var _a, _b;
20
20
  const id = (0, react_1.useId)();
21
21
  const { layouts } = (0, useCntrlContext_1.useCntrlContext)();
22
22
  const { radius, strokeWidth, strokeColor, opacity, blur } = (0, useFileItem_1.useFileItem)(item, sectionId);
@@ -24,7 +24,7 @@ const VideoItem = ({ item, sectionId, onResize }) => {
24
24
  const borderColor = (0, react_1.useMemo)(() => color_1.CntrlColor.parse(strokeColor), [strokeColor]);
25
25
  const [ref, setRef] = (0, react_1.useState)(null);
26
26
  (0, useRegisterResize_1.useRegisterResize)(ref, onResize);
27
- return ((0, jsx_runtime_1.jsxs)(LinkWrapper_1.LinkWrapper, Object.assign({ url: (_a = item.link) === null || _a === void 0 ? void 0 : _a.url }, { children: [(0, jsx_runtime_1.jsx)("div", Object.assign({ className: `video-wrapper-${item.id}`, ref: setRef, style: {
27
+ return ((0, jsx_runtime_1.jsxs)(LinkWrapper_1.LinkWrapper, Object.assign({ url: (_a = item.link) === null || _a === void 0 ? void 0 : _a.url, target: (_b = item.link) === null || _b === void 0 ? void 0 : _b.target }, { children: [(0, jsx_runtime_1.jsx)("div", Object.assign({ className: `video-wrapper-${item.id}`, ref: setRef, style: {
28
28
  opacity: `${opacity}`,
29
29
  transform: `rotate(${angle}deg)`,
30
30
  filter: `blur(${blur * 100}vw)`
@@ -16,7 +16,7 @@ const useCntrlContext_1 = require("../../provider/useCntrlContext");
16
16
  const HoverStyles_1 = require("../../utils/HoverStyles/HoverStyles");
17
17
  const useRegisterResize_1 = require("../../common/useRegisterResize");
18
18
  const VimeoEmbedItem = ({ item, sectionId, onResize }) => {
19
- var _a;
19
+ var _a, _b;
20
20
  const id = (0, react_1.useId)();
21
21
  const { layouts } = (0, useCntrlContext_1.useCntrlContext)();
22
22
  const { radius, blur } = (0, useEmbedVideoItem_1.useEmbedVideoItem)(item, sectionId);
@@ -40,7 +40,7 @@ const VimeoEmbedItem = ({ item, sectionId, onResize }) => {
40
40
  return validURL.href;
41
41
  };
42
42
  const validUrl = getValidVimeoUrl(url);
43
- return ((0, jsx_runtime_1.jsxs)(LinkWrapper_1.LinkWrapper, Object.assign({ url: (_a = item.link) === null || _a === void 0 ? void 0 : _a.url }, { children: [(0, jsx_runtime_1.jsx)("div", Object.assign({ className: `embed-video-wrapper-${item.id}`, ref: setRef, style: {
43
+ return ((0, jsx_runtime_1.jsxs)(LinkWrapper_1.LinkWrapper, Object.assign({ url: (_a = item.link) === null || _a === void 0 ? void 0 : _a.url, target: (_b = item.link) === null || _b === void 0 ? void 0 : _b.target }, { children: [(0, jsx_runtime_1.jsx)("div", Object.assign({ className: `embed-video-wrapper-${item.id}`, ref: setRef, style: {
44
44
  transform: `rotate(${angle}deg)`,
45
45
  filter: `blur(${blur * 100}vw)`
46
46
  }, onMouseEnter: () => {
@@ -17,7 +17,7 @@ const useCntrlContext_1 = require("../../provider/useCntrlContext");
17
17
  const useYouTubeIframeApi_1 = require("../../utils/Youtube/useYouTubeIframeApi");
18
18
  const useRegisterResize_1 = require("../../common/useRegisterResize");
19
19
  const YoutubeEmbedItem = ({ item, sectionId, onResize }) => {
20
- var _a;
20
+ var _a, _b;
21
21
  const id = (0, react_1.useId)();
22
22
  const { layouts } = (0, useCntrlContext_1.useCntrlContext)();
23
23
  const { play, controls, url } = item.commonParams;
@@ -56,7 +56,7 @@ const YoutubeEmbedItem = ({ item, sectionId, onResize }) => {
56
56
  (_a = placeholder.parentElement) === null || _a === void 0 ? void 0 : _a.removeChild(placeholder);
57
57
  };
58
58
  }, [YT, div]);
59
- return ((0, jsx_runtime_1.jsxs)(LinkWrapper_1.LinkWrapper, Object.assign({ url: (_a = item.link) === null || _a === void 0 ? void 0 : _a.url }, { children: [(0, jsx_runtime_1.jsx)("div", Object.assign({ className: `embed-youtube-video-wrapper-${item.id}`, onMouseEnter: () => {
59
+ return ((0, jsx_runtime_1.jsxs)(LinkWrapper_1.LinkWrapper, Object.assign({ url: (_a = item.link) === null || _a === void 0 ? void 0 : _a.url, target: (_b = item.link) === null || _b === void 0 ? void 0 : _b.target }, { children: [(0, jsx_runtime_1.jsx)("div", Object.assign({ className: `embed-youtube-video-wrapper-${item.id}`, onMouseEnter: () => {
60
60
  if (!player || play !== 'on-hover')
61
61
  return;
62
62
  player.playVideo();
@@ -4,6 +4,7 @@ exports.Animator = void 0;
4
4
  const sdk_1 = require("@cntrl-site/sdk");
5
5
  const color_1 = require("@cntrl-site/color");
6
6
  const binSearchInsertAt_1 = require("../binSearchInsertAt");
7
+ const rangeMap_1 = require("../rangeMap");
7
8
  const compare = (lhs, rhs) => lhs.position - rhs.position;
8
9
  const insertBin = (0, binSearchInsertAt_1.createInsert)(binSearchInsertAt_1.binSearchInsertAt, compare);
9
10
  class Animator {
@@ -28,8 +29,8 @@ class Animator {
28
29
  }
29
30
  const { start, end } = this.getStartEnd(pos, keyframes);
30
31
  return {
31
- width: rangeMap(pos, start.position, end.position, start.value.width, end.value.width, true),
32
- height: rangeMap(pos, start.position, end.position, start.value.height, end.value.height, true)
32
+ width: (0, rangeMap_1.rangeMap)(pos, start.position, end.position, start.value.width, end.value.width, true),
33
+ height: (0, rangeMap_1.rangeMap)(pos, start.position, end.position, start.value.height, end.value.height, true)
33
34
  };
34
35
  }
35
36
  getPositions(values, pos) {
@@ -45,8 +46,8 @@ class Animator {
45
46
  }
46
47
  const { start, end } = this.getStartEnd(pos, keyframes);
47
48
  return {
48
- left: rangeMap(pos, start.position, end.position, start.value.left, end.value.left, true),
49
- top: rangeMap(pos, start.position, end.position, start.value.top, end.value.top, true)
49
+ left: (0, rangeMap_1.rangeMap)(pos, start.position, end.position, start.value.left, end.value.left, true),
50
+ top: (0, rangeMap_1.rangeMap)(pos, start.position, end.position, start.value.top, end.value.top, true)
50
51
  };
51
52
  }
52
53
  getColor(values, pos) {
@@ -91,7 +92,7 @@ class Animator {
91
92
  }
92
93
  const { start, end } = this.getStartEnd(pos, keyframes);
93
94
  return {
94
- radius: rangeMap(pos, start.position, end.position, start.value.radius, end.value.radius, true)
95
+ radius: (0, rangeMap_1.rangeMap)(pos, start.position, end.position, start.value.radius, end.value.radius, true)
95
96
  };
96
97
  }
97
98
  getBorderWidth(values, pos) {
@@ -106,7 +107,7 @@ class Animator {
106
107
  }
107
108
  const { start, end } = this.getStartEnd(pos, keyframes);
108
109
  return {
109
- borderWidth: rangeMap(pos, start.position, end.position, start.value.borderWidth, end.value.borderWidth, true)
110
+ borderWidth: (0, rangeMap_1.rangeMap)(pos, start.position, end.position, start.value.borderWidth, end.value.borderWidth, true)
110
111
  };
111
112
  }
112
113
  getRotation(values, pos) {
@@ -121,7 +122,7 @@ class Animator {
121
122
  }
122
123
  const { start, end } = this.getStartEnd(pos, keyframes);
123
124
  return {
124
- angle: rangeMap(pos, start.position, end.position, start.value.angle, end.value.angle, true)
125
+ angle: (0, rangeMap_1.rangeMap)(pos, start.position, end.position, start.value.angle, end.value.angle, true)
125
126
  };
126
127
  }
127
128
  getOpacity(values, pos) {
@@ -136,7 +137,7 @@ class Animator {
136
137
  }
137
138
  const { start, end } = this.getStartEnd(pos, keyframes);
138
139
  return {
139
- opacity: rangeMap(pos, start.position, end.position, start.value.opacity, end.value.opacity, true)
140
+ opacity: (0, rangeMap_1.rangeMap)(pos, start.position, end.position, start.value.opacity, end.value.opacity, true)
140
141
  };
141
142
  }
142
143
  getScale(values, pos) {
@@ -151,7 +152,7 @@ class Animator {
151
152
  }
152
153
  const { start, end } = this.getStartEnd(pos, keyframes);
153
154
  return {
154
- scale: rangeMap(pos, start.position, end.position, start.value.scale, end.value.scale, true)
155
+ scale: (0, rangeMap_1.rangeMap)(pos, start.position, end.position, start.value.scale, end.value.scale, true)
155
156
  };
156
157
  }
157
158
  getBlur(values, pos) {
@@ -166,7 +167,7 @@ class Animator {
166
167
  }
167
168
  const { start, end } = this.getStartEnd(pos, keyframes);
168
169
  return {
169
- blur: rangeMap(pos, start.position, end.position, start.value.blur, end.value.blur, true)
170
+ blur: (0, rangeMap_1.rangeMap)(pos, start.position, end.position, start.value.blur, end.value.blur, true)
170
171
  };
171
172
  }
172
173
  getBackdropBlur(values, pos) {
@@ -181,7 +182,7 @@ class Animator {
181
182
  }
182
183
  const { start, end } = this.getStartEnd(pos, keyframes);
183
184
  return {
184
- backdropBlur: rangeMap(pos, start.position, end.position, start.value.backdropBlur, end.value.backdropBlur, true)
185
+ backdropBlur: (0, rangeMap_1.rangeMap)(pos, start.position, end.position, start.value.backdropBlur, end.value.backdropBlur, true)
185
186
  };
186
187
  }
187
188
  getTextColor(values, pos) {
@@ -211,7 +212,7 @@ class Animator {
211
212
  }
212
213
  const { start, end } = this.getStartEnd(pos, keyframes);
213
214
  return {
214
- letterSpacing: rangeMap(pos, start.position, end.position, start.value.letterSpacing, end.value.letterSpacing, true)
215
+ letterSpacing: (0, rangeMap_1.rangeMap)(pos, start.position, end.position, start.value.letterSpacing, end.value.letterSpacing, true)
215
216
  };
216
217
  }
217
218
  getWordSpacing(values, pos) {
@@ -226,7 +227,7 @@ class Animator {
226
227
  }
227
228
  const { start, end } = this.getStartEnd(pos, keyframes);
228
229
  return {
229
- wordSpacing: rangeMap(pos, start.position, end.position, start.value.wordSpacing, end.value.wordSpacing, true)
230
+ wordSpacing: (0, rangeMap_1.rangeMap)(pos, start.position, end.position, start.value.wordSpacing, end.value.wordSpacing, true)
230
231
  };
231
232
  }
232
233
  getStartEnd(position, keyframes) {
@@ -242,7 +243,7 @@ class Animator {
242
243
  }
243
244
  }
244
245
  getRangeColor(start, end, position) {
245
- const rangeAmount = rangeMap(position, start.position, end.position, 0, 1, true);
246
+ const rangeAmount = (0, rangeMap_1.rangeMap)(position, start.position, end.position, 0, 1, true);
246
247
  const startColor = color_1.CntrlColor.parse(start.value.color);
247
248
  const endColor = color_1.CntrlColor.parse(end.value.color);
248
249
  const mixedColor = startColor.mix(endColor, rangeAmount);
@@ -268,11 +269,3 @@ function createKeyframesMap() {
268
269
  [sdk_1.KeyframeType.TextColor]: []
269
270
  };
270
271
  }
271
- function rangeMap(n, start1, stop1, start2, stop2, withinBounds = false) {
272
- const mapped = (n - start1) / (stop1 - start1) * (stop2 - start2) + start2;
273
- if (withinBounds && n < start1)
274
- return start2;
275
- if (withinBounds && n > stop1)
276
- return stop2;
277
- return mapped;
278
- }
@@ -91,7 +91,7 @@ class RichTextConverter {
91
91
  offset = entity.end;
92
92
  }
93
93
  if (entity.link) {
94
- kids.push((0, jsx_runtime_1.jsx)(LinkWrapper_1.LinkWrapper, Object.assign({ url: entity.link }, { children: entityKids }), entity.start));
94
+ kids.push((0, jsx_runtime_1.jsx)(LinkWrapper_1.LinkWrapper, Object.assign({ url: entity.link, target: entity.target }, { children: entityKids }), entity.start));
95
95
  continue;
96
96
  }
97
97
  kids.push(...entityKids);
@@ -184,7 +184,7 @@ class RichTextConverter {
184
184
  const end = dividers[i + 1];
185
185
  const entity = entities.find(e => e.start === start);
186
186
  entitiesGroups.push(Object.assign({ stylesGroup: [], start,
187
- end }, (entity && { link: entity.data.url })));
187
+ end }, (entity && { link: entity.data.url, target: entity.data.target })));
188
188
  }
189
189
  return entitiesGroups;
190
190
  }
@@ -209,7 +209,7 @@ class RichTextConverter {
209
209
  const end = entityDividers[i + 1];
210
210
  const entity = entities.find(e => e.start === start);
211
211
  entitiesGroups.push(Object.assign({ stylesGroup: styleGroups.filter(s => s.start >= start && s.end <= end), start,
212
- end }, (entity && { link: entity.data.url })));
212
+ end }, (entity && { link: entity.data.url, target: entity.data.target })));
213
213
  }
214
214
  return entitiesGroups;
215
215
  }
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useImageFx = void 0;
4
+ const react_1 = require("react");
5
+ const effects_1 = require("@cntrl-site/effects");
6
+ const rangeMap_1 = require("../rangeMap");
7
+ function useImageFx(canvas, enabled, { imageUrl, fragmentShader }) {
8
+ const cursor = (0, react_1.useRef)([0, 0]);
9
+ const imageFx = (0, react_1.useMemo)(() => {
10
+ if (!imageUrl)
11
+ return undefined;
12
+ return new effects_1.ImageEffect(imageUrl, fragmentShader, { time: 0, cursor: cursor.current });
13
+ }, [imageUrl, fragmentShader]);
14
+ (0, react_1.useEffect)(() => {
15
+ if (!canvas || !imageFx)
16
+ return;
17
+ const handleMouseMove = (evt) => {
18
+ const rect = canvas.getBoundingClientRect();
19
+ const x = (0, rangeMap_1.rangeMap)(evt.clientX, rect.left, rect.left + rect.width, 0, 1);
20
+ const y = (0, rangeMap_1.rangeMap)(evt.clientY, rect.top, rect.top + rect.height, 0, 1);
21
+ imageFx.setParam('cursor', [x, y]);
22
+ };
23
+ window.addEventListener('mousemove', handleMouseMove, { capture: true, passive: true });
24
+ return () => {
25
+ window.removeEventListener('mousemove', handleMouseMove);
26
+ };
27
+ }, [canvas, imageFx]);
28
+ (0, react_1.useEffect)(() => {
29
+ const gl = canvas === null || canvas === void 0 ? void 0 : canvas.getContext('webgl2');
30
+ if (!enabled || !canvas || !gl || !imageFx)
31
+ return;
32
+ let running = false;
33
+ let time = 0;
34
+ let frame;
35
+ const renderFrame = () => {
36
+ time += 0.1;
37
+ imageFx.setViewport(Math.floor(canvas.width), Math.floor(canvas.height));
38
+ imageFx.setParam('time', time);
39
+ imageFx.render(gl);
40
+ frame = requestAnimationFrame(renderFrame);
41
+ };
42
+ const observer = new IntersectionObserver(([entry]) => {
43
+ if (entry.isIntersecting && !running) {
44
+ frame = requestAnimationFrame(renderFrame);
45
+ running = true;
46
+ return;
47
+ }
48
+ if (!entry.isIntersecting && running) {
49
+ window.cancelAnimationFrame(frame);
50
+ running = false;
51
+ }
52
+ });
53
+ observer.observe(canvas);
54
+ imageFx.prepare(gl);
55
+ return () => {
56
+ window.cancelAnimationFrame(frame);
57
+ observer.disconnect();
58
+ };
59
+ }, [canvas, imageFx, enabled]);
60
+ }
61
+ exports.useImageFx = useImageFx;
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.rangeMap = void 0;
4
+ function rangeMap(n, start1, stop1, start2, stop2, withinBounds = false) {
5
+ const mapped = (n - start1) / (stop1 - start1) * (stop2 - start2) + start2;
6
+ if (withinBounds && n < start1)
7
+ return start2;
8
+ if (withinBounds && n > stop1)
9
+ return stop2;
10
+ return mapped;
11
+ }
12
+ exports.rangeMap = rangeMap;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useElementRect = void 0;
4
+ const react_1 = require("react");
5
+ function useElementRect(element) {
6
+ const [rect, setRect] = (0, react_1.useState)(() => element === null || element === void 0 ? void 0 : element.getBoundingClientRect());
7
+ (0, react_1.useEffect)(() => {
8
+ if (!element)
9
+ return;
10
+ const observer = new ResizeObserver(entries => {
11
+ const el = entries.find(entry => entry.target === element);
12
+ if (!el) {
13
+ throw new Error('Element is not found in observed entries');
14
+ }
15
+ setRect(el.target.getBoundingClientRect());
16
+ });
17
+ observer.observe(element);
18
+ return () => {
19
+ observer.unobserve(element);
20
+ observer.disconnect();
21
+ };
22
+ }, [element]);
23
+ return rect;
24
+ }
25
+ exports.useElementRect = useElementRect;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cntrl-site/sdk-nextjs",
3
- "version": "0.19.3",
3
+ "version": "0.20.0",
4
4
  "description": "SDK for Next.js",
5
5
  "main": "lib/index.js",
6
6
  "types": "src/index.ts",
@@ -22,7 +22,8 @@
22
22
  "homepage": "https://github.com/cntrl-site/sdk-nextjs#readme",
23
23
  "dependencies": {
24
24
  "@cntrl-site/color": "^1.0.0",
25
- "@cntrl-site/sdk": "^1.4.0",
25
+ "@cntrl-site/effects": "^1.0.0",
26
+ "@cntrl-site/sdk": "^1.4.1",
26
27
  "@types/vimeo__player": "^2.18.0",
27
28
  "@vimeo/player": "^2.20.1",
28
29
  "html-react-parser": "^3.0.1",
@@ -3,14 +3,15 @@ import React, { ReactElement, ReactNode } from 'react';
3
3
  interface Props {
4
4
  url?: string;
5
5
  children: ReactElement | ReactNode[];
6
+ target?: string;
6
7
  }
7
8
 
8
- export const LinkWrapper: React.FC<Props> = ({ url, children }) => {
9
+ export const LinkWrapper: React.FC<Props> = ({ url, children, target }) => {
9
10
  const validUrl = url && buildValidUrl(url);
10
11
  return url ? (
11
12
  <a
12
13
  href={validUrl}
13
- target={url.startsWith('/') || url.startsWith('#') ? '_self' : '_blank'}
14
+ target={target}
14
15
  rel="noreferrer"
15
16
  >
16
17
  {children}
@@ -1,4 +1,4 @@
1
- import { FC, useId, useMemo, useState } from 'react';
1
+ import { FC, useId, useMemo, useRef, useState } from 'react';
2
2
  import JSXStyle from 'styled-jsx/style';
3
3
  import { CntrlColor } from '@cntrl-site/color';
4
4
  import { ArticleItemType, getLayoutStyles, ImageItem as TImageItem } from '@cntrl-site/sdk';
@@ -9,6 +9,8 @@ import { useItemAngle } from '../useItemAngle';
9
9
  import { useCntrlContext } from '../../provider/useCntrlContext';
10
10
  import { getHoverStyles, getTransitions } from '../../utils/HoverStyles/HoverStyles';
11
11
  import { useRegisterResize } from "../../common/useRegisterResize";
12
+ import { useImageFx } from '../../utils/effects/useImageFx';
13
+ import { useElementRect } from '../../utils/useElementRect';
12
14
 
13
15
  export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize }) => {
14
16
  const id = useId();
@@ -18,8 +20,22 @@ export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize
18
20
  const borderColor = useMemo(() => CntrlColor.parse(strokeColor), [strokeColor]);
19
21
  const [ref, setRef] = useState<HTMLDivElement | null>(null);
20
22
  useRegisterResize(ref, onResize);
23
+ const { url, hasGLEffect, fragmentShader } = item.commonParams;
24
+ const fxCanvas = useRef<HTMLCanvasElement | null>(null);
25
+ useImageFx(fxCanvas.current, hasGLEffect ?? false, {
26
+ imageUrl: url,
27
+ fragmentShader
28
+ });
29
+ const rect = useElementRect(ref);
30
+ const rectWidth = Math.floor(rect?.width ?? 0);
31
+ const rectHeight = Math.floor(rect?.height ?? 0);
32
+ const inlineStyles = {
33
+ borderRadius: `${radius * 100}vw`,
34
+ borderWidth: `${strokeWidth * 100}vw`,
35
+ borderColor: `${borderColor.toCss()}`
36
+ };
21
37
  return (
22
- <LinkWrapper url={item.link?.url}>
38
+ <LinkWrapper url={item.link?.url} target={item.link?.target}>
23
39
  <>
24
40
  <div
25
41
  className={`image-wrapper-${item.id}`}
@@ -30,15 +46,23 @@ export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize
30
46
  filter: `blur(${blur * 100}vw)`
31
47
  }}
32
48
  >
33
- <img
34
- className={`image image-${item.id}`}
35
- style={{
36
- borderRadius: `${radius * 100}vw`,
37
- borderWidth: `${strokeWidth * 100}vw`,
38
- borderColor: `${borderColor.toCss()}`
39
- }}
40
- src={item.commonParams.url}
41
- />
49
+ {item.commonParams.hasGLEffect ? (
50
+ <canvas
51
+ style={inlineStyles}
52
+ ref={fxCanvas}
53
+ className="img-canvas"
54
+ width={rectWidth}
55
+ height={rectHeight}
56
+ />
57
+ ) : (
58
+ <img
59
+ alt=""
60
+ className={`image image-${item.id}`}
61
+ style={inlineStyles}
62
+ src={item.commonParams.url}
63
+ />
64
+ )}
65
+
42
66
  </div>
43
67
  <JSXStyle id={id}>{`
44
68
  @supports not (color: oklch(42% 0.3 90 / 1)) {
@@ -61,6 +85,15 @@ export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize
61
85
  pointer-events: none;
62
86
  border-style: solid;
63
87
  overflow: hidden;
88
+ box-sizing: border-box;
89
+ }
90
+ .img-canvas {
91
+ border: solid;
92
+ width: 100%;
93
+ height: 100%;
94
+ pointer-events: none;
95
+ border-width: 0;
96
+ box-sizing: border-box;
64
97
  }
65
98
  ${getLayoutStyles(layouts, [item.state.hover], ([hoverParams]) => {
66
99
  return (`
@@ -22,7 +22,7 @@ export const RectangleItem: FC<ItemProps<TRectangleItem>> = ({ item, sectionId,
22
22
  const backdropFilterValue = backdropBlur !== 0 ? `blur(${backdropBlur * 100}vw)`: 'unset';
23
23
 
24
24
  return (
25
- <LinkWrapper url={item.link?.url}>
25
+ <LinkWrapper url={item.link?.url} target={item.link?.target}>
26
26
  <>
27
27
  <div
28
28
  className={`rectangle-${item.id}`}
@@ -19,7 +19,7 @@ export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize
19
19
  const [ref, setRef] = useState<HTMLDivElement | null>(null);
20
20
  useRegisterResize(ref, onResize);
21
21
  return (
22
- <LinkWrapper url={item.link?.url}>
22
+ <LinkWrapper url={item.link?.url} target={item.link?.target}>
23
23
  <div
24
24
  className={`video-wrapper-${item.id}`}
25
25
  ref={setRef}
@@ -37,7 +37,7 @@ export const VimeoEmbedItem: FC<ItemProps<TVimeoEmbedItem>> = ({ item, sectionId
37
37
  const validUrl = getValidVimeoUrl(url);
38
38
 
39
39
  return (
40
- <LinkWrapper url={item.link?.url}>
40
+ <LinkWrapper url={item.link?.url} target={item.link?.target}>
41
41
  <div
42
42
  className={`embed-video-wrapper-${item.id}`}
43
43
  ref={setRef}
@@ -52,7 +52,7 @@ export const YoutubeEmbedItem: FC<ItemProps<TYoutubeEmbedItem>> = ({ item, secti
52
52
  }, [YT, div]);
53
53
 
54
54
  return (
55
- <LinkWrapper url={item.link?.url}>
55
+ <LinkWrapper url={item.link?.url} target={item.link?.target}>
56
56
  <div
57
57
  className={`embed-youtube-video-wrapper-${item.id}`}
58
58
  onMouseEnter={() => {
@@ -1,6 +1,7 @@
1
1
  import { KeyframeType, KeyframeValueMap } from '@cntrl-site/sdk';
2
2
  import { CntrlColor } from '@cntrl-site/color';
3
3
  import { binSearchInsertAt, createInsert } from '../binSearchInsertAt';
4
+ import { rangeMap } from '../rangeMap';
4
5
 
5
6
  export interface AnimationData<T extends KeyframeType> {
6
7
  position: number;
@@ -348,17 +349,3 @@ function createKeyframesMap(): KeyframesMap {
348
349
  [KeyframeType.TextColor]: []
349
350
  };
350
351
  }
351
-
352
- function rangeMap(
353
- n: number,
354
- start1: number,
355
- stop1: number,
356
- start2: number,
357
- stop2: number,
358
- withinBounds: boolean = false
359
- ): number {
360
- const mapped = (n - start1) / (stop1 - start1) * (stop2 - start2) + start2;
361
- if (withinBounds && n < start1) return start2;
362
- if (withinBounds && n > stop1) return stop2;
363
- return mapped;
364
- }
@@ -21,6 +21,7 @@ interface StyleGroup {
21
21
 
22
22
  interface EntitiesGroup {
23
23
  link?: string;
24
+ target?: string;
24
25
  stylesGroup: StyleGroup[];
25
26
  start: number;
26
27
  end: number;
@@ -122,7 +123,7 @@ export class RichTextConverter {
122
123
  offset = entity.end;
123
124
  }
124
125
  if (entity.link) {
125
- kids.push(<LinkWrapper key={entity.start} url={entity.link} >{entityKids}</LinkWrapper>);
126
+ kids.push(<LinkWrapper key={entity.start} url={entity.link} target={entity.target}>{entityKids}</LinkWrapper>);
126
127
  continue;
127
128
  }
128
129
  kids.push(...entityKids);
@@ -218,7 +219,7 @@ export class RichTextConverter {
218
219
  stylesGroup: [],
219
220
  start,
220
221
  end,
221
- ...(entity && { link: entity.data.url })
222
+ ...(entity && { link: entity.data.url, target: entity.data.target })
222
223
  });
223
224
  }
224
225
  return entitiesGroups;
@@ -248,7 +249,7 @@ export class RichTextConverter {
248
249
  stylesGroup: styleGroups.filter(s => s.start >= start && s.end <= end),
249
250
  start,
250
251
  end,
251
- ...(entity && { link: entity.data.url })
252
+ ...(entity && { link: entity.data.url, target: entity.data.target })
252
253
  });
253
254
  }
254
255
 
@@ -0,0 +1,72 @@
1
+ import { useEffect, useMemo, useRef } from 'react';
2
+ import { ImageEffect } from '@cntrl-site/effects';
3
+ import { rangeMap } from '../rangeMap';
4
+
5
+ interface FxParams {
6
+ imageUrl?: string;
7
+ fragmentShader?: string;
8
+ }
9
+
10
+ export function useImageFx(
11
+ canvas: HTMLCanvasElement | null | undefined,
12
+ enabled: boolean,
13
+ {
14
+ imageUrl,
15
+ fragmentShader
16
+ }: FxParams
17
+ ): void {
18
+ const cursor = useRef<[number, number]>([0, 0]);
19
+ const imageFx = useMemo<ImageEffect | undefined>(() => {
20
+ if (!imageUrl) return undefined;
21
+ return new ImageEffect(imageUrl, fragmentShader!, { time: 0, cursor: cursor.current });
22
+ }, [imageUrl, fragmentShader]);
23
+
24
+ useEffect(() => {
25
+ if (!canvas || !imageFx) return;
26
+ const handleMouseMove = (evt: MouseEvent) => {
27
+ const rect = canvas.getBoundingClientRect();
28
+ const x = rangeMap(evt.clientX, rect.left, rect.left + rect.width, 0, 1);
29
+ const y = rangeMap(evt.clientY, rect.top, rect.top + rect.height, 0, 1);
30
+ imageFx.setParam('cursor', [x, y]);
31
+ };
32
+ window.addEventListener('mousemove', handleMouseMove, { capture: true, passive: true });
33
+ return () => {
34
+ window.removeEventListener('mousemove', handleMouseMove);
35
+ };
36
+ }, [canvas, imageFx]);
37
+
38
+ useEffect(() => {
39
+ const gl = canvas?.getContext('webgl2');
40
+ if (!enabled || !canvas || !gl || !imageFx) return;
41
+ let running = false;
42
+ let time = 0;
43
+ let frame: number;
44
+
45
+ const renderFrame = () => {
46
+ time += 0.1;
47
+ imageFx.setViewport(Math.floor(canvas.width), Math.floor(canvas.height));
48
+ imageFx.setParam('time', time);
49
+ imageFx.render(gl);
50
+ frame = requestAnimationFrame(renderFrame);
51
+ };
52
+
53
+ const observer = new IntersectionObserver(([entry]) => {
54
+ if (entry.isIntersecting && !running) {
55
+ frame = requestAnimationFrame(renderFrame);
56
+ running = true;
57
+ return;
58
+ }
59
+ if (!entry.isIntersecting && running) {
60
+ window.cancelAnimationFrame(frame);
61
+ running = false;
62
+ }
63
+ });
64
+ observer.observe(canvas);
65
+ imageFx.prepare(gl);
66
+
67
+ return () => {
68
+ window.cancelAnimationFrame(frame);
69
+ observer.disconnect();
70
+ };
71
+ }, [canvas, imageFx, enabled]);
72
+ }
@@ -0,0 +1,13 @@
1
+ export function rangeMap(
2
+ n: number,
3
+ start1: number,
4
+ stop1: number,
5
+ start2: number,
6
+ stop2: number,
7
+ withinBounds: boolean = false
8
+ ): number {
9
+ const mapped = (n - start1) / (stop1 - start1) * (stop2 - start2) + start2;
10
+ if (withinBounds && n < start1) return start2;
11
+ if (withinBounds && n > stop1) return stop2;
12
+ return mapped;
13
+ }
@@ -0,0 +1,23 @@
1
+ import { useEffect, useState } from 'react';
2
+
3
+ export function useElementRect(element: HTMLElement | null): DOMRect | undefined {
4
+ const [rect, setRect] = useState<DOMRect | undefined>(() => element?.getBoundingClientRect());
5
+
6
+ useEffect(() => {
7
+ if (!element) return;
8
+ const observer = new ResizeObserver(entries => {
9
+ const el = entries.find(entry => entry.target === element);
10
+ if (!el) {
11
+ throw new Error('Element is not found in observed entries');
12
+ }
13
+ setRect(el.target.getBoundingClientRect());
14
+ });
15
+ observer.observe(element);
16
+ return () => {
17
+ observer.unobserve(element);
18
+ observer.disconnect();
19
+ };
20
+ }, [element]);
21
+
22
+ return rect;
23
+ }
Binary file