@cntrl-site/sdk-nextjs 1.8.39 → 1.8.40-alpha.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 (39) 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/lib/components/items/FileItem/ImageItem.js +46 -22
  6. package/lib/components/items/FileItem/VideoItem.js +45 -21
  7. package/lib/components/items/FileItem/useFileItem.js +12 -5
  8. package/lib/components/items/RectangleItem/RectangleItem.js +78 -22
  9. package/lib/components/items/RectangleItem/useRectangleItem.js +15 -8
  10. package/lib/interactions/CSSPropertyNameMap.js +18 -18
  11. package/lib/utils/Animator/Animator.js +171 -19
  12. package/lib/utils/areFillsVisible/areFillsVisible.js +23 -0
  13. package/lib/utils/getFill.js +43 -0
  14. package/package.json +2 -2
  15. package/src/components/items/CodeEmbedItem/CodeEmbedItem.tsx +1 -1
  16. package/src/components/items/ComponentItem/ComponentItem.tsx +1 -1
  17. package/src/components/items/CompoundItem/CompoundChild.tsx +2 -2
  18. package/src/components/items/CompoundItem/CompoundItem.tsx +1 -1
  19. package/src/components/items/CustomItem/CustomItem.tsx +1 -1
  20. package/src/components/items/EmbedVideoItem/VimeoEmbed.tsx +2 -2
  21. package/src/components/items/EmbedVideoItem/YoutubeEmbed.tsx +2 -2
  22. package/src/components/items/FileItem/ImageItem.tsx +60 -11
  23. package/src/components/items/FileItem/VideoItem.tsx +61 -12
  24. package/src/components/items/FileItem/useFileItem.ts +14 -6
  25. package/src/components/items/GroupItem/GroupItem.tsx +1 -1
  26. package/src/components/items/Item.tsx +2 -2
  27. package/src/components/items/RectangleItem/RectangleItem.tsx +126 -24
  28. package/src/components/items/RectangleItem/useRectangleItem.ts +17 -10
  29. package/src/components/items/RichTextItem/RichTextItem.tsx +5 -5
  30. package/src/interactions/CSSPropertyNameMap.ts +18 -19
  31. package/src/interactions/ItemInteractionCtrl.ts +4 -4
  32. package/src/interactions/types.ts +7 -7
  33. package/src/utils/Animator/Animator.ts +227 -27
  34. package/src/utils/areFillsVisible/areFillsVisible.test.ts +63 -0
  35. package/src/utils/areFillsVisible/areFillsVisible.ts +37 -0
  36. package/src/utils/getFill.ts +46 -0
  37. package/src/utils/getStyleFromItemStateAndParams.ts +2 -2
  38. package/src/provider/InteractionsContext.old.tsx +0 -66
  39. package/src/provider/InteractionsContext.test.tsx +0 -97
@@ -69,35 +69,27 @@ class Animator {
69
69
  top: (0, rangeMap_1.rangeMap)(pos, start.position, end.position, start.value.top, end.value.top, true)
70
70
  };
71
71
  }
72
- getColor(values, pos) {
73
- const keyframes = this.keyframesMap[sdk_1.KeyframeType.Color];
72
+ getBorderFill(values, pos) {
73
+ const keyframes = this.keyframesMap[sdk_1.KeyframeType.BorderFill];
74
74
  if (!keyframes || !keyframes.length)
75
75
  return values;
76
76
  if (keyframes.length === 1) {
77
77
  const [keyframe] = keyframes;
78
- return {
79
- color: keyframe.value.color
80
- };
78
+ return keyframe.value;
81
79
  }
82
80
  const { start, end } = this.getStartEnd(pos, keyframes);
83
- return {
84
- color: this.getRangeColor(start, end, pos)
85
- };
81
+ return this.getRangeGradient(start, end, pos);
86
82
  }
87
- getBorderColor(values, pos) {
88
- const keyframes = this.keyframesMap[sdk_1.KeyframeType.BorderColor];
83
+ getFill(values, pos) {
84
+ const keyframes = this.keyframesMap[sdk_1.KeyframeType.Fill];
89
85
  if (!keyframes || !keyframes.length)
90
86
  return values;
91
87
  if (keyframes.length === 1) {
92
88
  const [keyframe] = keyframes;
93
- return {
94
- color: keyframe.value.color
95
- };
89
+ return keyframe.value;
96
90
  }
97
91
  const { start, end } = this.getStartEnd(pos, keyframes);
98
- return {
99
- color: this.getRangeColor(start, end, pos)
100
- };
92
+ return this.getRangeGradient(start, end, pos);
101
93
  }
102
94
  getRadius(values, pos) {
103
95
  const keyframes = this.keyframesMap[sdk_1.KeyframeType.BorderRadius];
@@ -268,6 +260,166 @@ class Animator {
268
260
  const mixedColor = startColor.mix(endColor, rangeAmount);
269
261
  return mixedColor.fmt('oklch');
270
262
  }
263
+ getRangeGradient(start, end, position) {
264
+ const rangeAmount = (0, rangeMap_1.rangeMap)(position, start.position, end.position, 0, 1, true);
265
+ const allLayerIds = new Set([
266
+ ...start.value.map(layer => layer.id),
267
+ ...end.value.map(layer => layer.id)
268
+ ]);
269
+ const result = [];
270
+ for (const layerId of allLayerIds) {
271
+ const startLayers = start.value.find(layer => layer.id === layerId);
272
+ const endLayers = end.value.find(layer => layer.id === layerId);
273
+ const startLayersArray = Array.isArray(startLayers) ? startLayers : startLayers ? [startLayers] : [];
274
+ const endLayersArray = Array.isArray(endLayers) ? endLayers : endLayers ? [endLayers] : [];
275
+ if (startLayersArray.length > 0 && endLayersArray.length === 0) {
276
+ result.push(...startLayersArray);
277
+ continue;
278
+ }
279
+ if (startLayersArray.length === 0 && endLayersArray.length > 0) {
280
+ result.push(...endLayersArray);
281
+ continue;
282
+ }
283
+ if (startLayersArray.length > 0 && endLayersArray.length > 0) {
284
+ result.push(...this.interpolateFillLayerArray(startLayersArray, endLayersArray, rangeAmount));
285
+ }
286
+ }
287
+ return result;
288
+ }
289
+ interpolateFillLayerArray(startLayers, endLayers, rangeAmount) {
290
+ const startLayerMap = new Map(startLayers.map(layer => [layer.id, layer]));
291
+ const endLayerMap = new Map(endLayers.map(layer => [layer.id, layer]));
292
+ const allLayerIds = new Set([
293
+ ...startLayers.map(layer => layer.id),
294
+ ...endLayers.map(layer => layer.id)
295
+ ]);
296
+ const interpolatedLayers = [];
297
+ for (const layerId of allLayerIds) {
298
+ const startLayer = startLayerMap.get(layerId);
299
+ const endLayer = endLayerMap.get(layerId);
300
+ if (startLayer && !endLayer) {
301
+ interpolatedLayers.push(startLayer);
302
+ continue;
303
+ }
304
+ if (!startLayer && endLayer) {
305
+ interpolatedLayers.push(endLayer);
306
+ continue;
307
+ }
308
+ if (startLayer && endLayer) {
309
+ interpolatedLayers.push(this.interpolateFillLayer(startLayer, endLayer, rangeAmount));
310
+ }
311
+ }
312
+ const result = [];
313
+ for (const startLayer of startLayers) {
314
+ const interpolatedLayer = interpolatedLayers.find(layer => layer.id === startLayer.id);
315
+ if (interpolatedLayer) {
316
+ result.push(interpolatedLayer);
317
+ }
318
+ }
319
+ for (const endLayer of endLayers) {
320
+ if (!startLayerMap.has(endLayer.id)) {
321
+ const interpolatedLayer = interpolatedLayers.find(layer => layer.id === endLayer.id);
322
+ if (interpolatedLayer) {
323
+ result.push(interpolatedLayer);
324
+ }
325
+ }
326
+ }
327
+ return result;
328
+ }
329
+ interpolateFillLayer(startLayer, endLayer, rangeAmount) {
330
+ if (startLayer.type !== endLayer.type) {
331
+ return startLayer;
332
+ }
333
+ switch (startLayer.type) {
334
+ case 'solid': {
335
+ const endSolidLayer = endLayer;
336
+ const startColor = color_1.CntrlColor.parse(startLayer.value);
337
+ const endColor = color_1.CntrlColor.parse(endSolidLayer.value);
338
+ const mixedColor = startColor.mix(endColor, rangeAmount);
339
+ return Object.assign(Object.assign({}, startLayer), { value: mixedColor.fmt('oklch') });
340
+ }
341
+ case 'linear-gradient': {
342
+ const endLinearLayer = endLayer;
343
+ const interpolatedColors = this.interpolateColorStops(startLayer.colors, endLinearLayer.colors, rangeAmount);
344
+ const angle = startLayer.angle + (endLinearLayer.angle - startLayer.angle) * rangeAmount;
345
+ const startPoint = [
346
+ startLayer.start[0] + (endLinearLayer.start[0] - startLayer.start[0]) * rangeAmount,
347
+ startLayer.start[1] + (endLinearLayer.start[1] - startLayer.start[1]) * rangeAmount
348
+ ];
349
+ const endPoint = [
350
+ startLayer.end[0] + (endLinearLayer.end[0] - startLayer.end[0]) * rangeAmount,
351
+ startLayer.end[1] + (endLinearLayer.end[1] - startLayer.end[1]) * rangeAmount
352
+ ];
353
+ return Object.assign(Object.assign({}, startLayer), { colors: interpolatedColors, angle, start: startPoint, end: endPoint });
354
+ }
355
+ case 'radial-gradient': {
356
+ const endRadialLayer = endLayer;
357
+ const interpolatedColors = this.interpolateColorStops(startLayer.colors, endRadialLayer.colors, rangeAmount);
358
+ const angle = startLayer.angle + (endRadialLayer.angle - startLayer.angle) * rangeAmount;
359
+ const center = [
360
+ startLayer.center[0] + (endRadialLayer.center[0] - startLayer.center[0]) * rangeAmount,
361
+ startLayer.center[1] + (endRadialLayer.center[1] - startLayer.center[1]) * rangeAmount
362
+ ];
363
+ const diameter = startLayer.diameter + (endRadialLayer.diameter - startLayer.diameter) * rangeAmount;
364
+ return Object.assign(Object.assign({}, startLayer), { colors: interpolatedColors, angle,
365
+ center,
366
+ diameter });
367
+ }
368
+ case 'conic-gradient': {
369
+ const endConicLayer = endLayer;
370
+ const interpolatedColors = this.interpolateColorStops(startLayer.colors, endConicLayer.colors, rangeAmount);
371
+ const angle = startLayer.angle + (endConicLayer.angle - startLayer.angle) * rangeAmount;
372
+ const center = [
373
+ startLayer.center[0] + (endConicLayer.center[0] - startLayer.center[0]) * rangeAmount,
374
+ startLayer.center[1] + (endConicLayer.center[1] - startLayer.center[1]) * rangeAmount
375
+ ];
376
+ return Object.assign(Object.assign({}, startLayer), { colors: interpolatedColors, angle,
377
+ center });
378
+ }
379
+ case 'image': {
380
+ const endImageLayer = endLayer;
381
+ const opacity = startLayer.opacity + (endImageLayer.opacity - startLayer.opacity) * rangeAmount;
382
+ const backgroundSize = startLayer.backgroundSize + (endImageLayer.backgroundSize - startLayer.backgroundSize) * rangeAmount;
383
+ return Object.assign(Object.assign({}, startLayer), { opacity,
384
+ backgroundSize });
385
+ }
386
+ default:
387
+ return startLayer;
388
+ }
389
+ }
390
+ interpolateColorStops(startColors, endColors, rangeAmount) {
391
+ const startColorMap = new Map(startColors.map(stop => [stop.id, stop]));
392
+ const endColorMap = new Map(endColors.map(stop => [stop.id, stop]));
393
+ const allStopIds = new Set([
394
+ ...startColors.map(stop => stop.id),
395
+ ...endColors.map(stop => stop.id)
396
+ ]);
397
+ const interpolatedStops = [];
398
+ for (const stopId of allStopIds) {
399
+ const startStop = startColorMap.get(stopId);
400
+ const endStop = endColorMap.get(stopId);
401
+ if (startStop && !endStop) {
402
+ interpolatedStops.push(startStop);
403
+ continue;
404
+ }
405
+ if (!startStop && endStop) {
406
+ interpolatedStops.push(endStop);
407
+ continue;
408
+ }
409
+ if (startStop && endStop) {
410
+ const startColor = color_1.CntrlColor.parse(startStop.value);
411
+ const endColor = color_1.CntrlColor.parse(endStop.value);
412
+ const mixedColor = startColor.mix(endColor, rangeAmount);
413
+ const position = startStop.position + (endStop.position - startStop.position) * rangeAmount;
414
+ interpolatedStops.push({
415
+ id: stopId,
416
+ value: mixedColor.fmt('oklch'),
417
+ position
418
+ });
419
+ }
420
+ }
421
+ return interpolatedStops.sort((a, b) => a.position - b.position);
422
+ }
271
423
  }
272
424
  exports.Animator = Animator;
273
425
  function createKeyframesMap() {
@@ -276,9 +428,8 @@ function createKeyframesMap() {
276
428
  [sdk_1.KeyframeType.Position]: [],
277
429
  [sdk_1.KeyframeType.BorderWidth]: [],
278
430
  [sdk_1.KeyframeType.BorderRadius]: [],
279
- [sdk_1.KeyframeType.Color]: [],
280
431
  [sdk_1.KeyframeType.Rotation]: [],
281
- [sdk_1.KeyframeType.BorderColor]: [],
432
+ [sdk_1.KeyframeType.BorderFill]: [],
282
433
  [sdk_1.KeyframeType.Opacity]: [],
283
434
  [sdk_1.KeyframeType.Scale]: [],
284
435
  [sdk_1.KeyframeType.Blur]: [],
@@ -286,6 +437,7 @@ function createKeyframesMap() {
286
437
  [sdk_1.KeyframeType.LetterSpacing]: [],
287
438
  [sdk_1.KeyframeType.WordSpacing]: [],
288
439
  [sdk_1.KeyframeType.TextColor]: [],
289
- [sdk_1.KeyframeType.FXParams]: []
440
+ [sdk_1.KeyframeType.FXParams]: [],
441
+ [sdk_1.KeyframeType.Fill]: []
290
442
  };
291
443
  }
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.areFillsVisible = void 0;
4
+ const color_1 = require("@cntrl-site/color");
5
+ function areFillsVisible(fills) {
6
+ return fills.some(fill => {
7
+ switch (fill.type) {
8
+ case 'solid':
9
+ return color_1.CntrlColor.parse(fill.value).getAlpha() !== 0;
10
+ case 'linear-gradient':
11
+ return fill.colors.some(color => color_1.CntrlColor.parse(color.value).getAlpha() !== 0);
12
+ case 'radial-gradient':
13
+ return fill.colors.some(color => color_1.CntrlColor.parse(color.value).getAlpha() !== 0);
14
+ case 'conic-gradient':
15
+ return fill.colors.some(color => color_1.CntrlColor.parse(color.value).getAlpha() !== 0);
16
+ case 'image':
17
+ return fill.opacity !== 0;
18
+ default:
19
+ return true;
20
+ }
21
+ });
22
+ }
23
+ exports.areFillsVisible = areFillsVisible;
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getFill = void 0;
4
+ function getFill(fill) {
5
+ var _a;
6
+ if (fill.type === 'linear-gradient' && Array.isArray(fill.colors)) {
7
+ return `linear-gradient(${fill.angle}deg, ${fill.colors
8
+ .map(c => `${c.value} ${c.position}%`)
9
+ .join(', ')})`;
10
+ }
11
+ if (fill.type === 'radial-gradient' && Array.isArray(fill.colors)) {
12
+ const center = fill.center
13
+ ? `${fill.center[0] * 100}% ${fill.center[1] * 100}%`
14
+ : '50% 50%';
15
+ const diameter = fill.diameter;
16
+ return `radial-gradient(circle ${diameter * 100} at ${center}, ${fill.colors
17
+ .map(c => `${c.value} ${c.position}%`)
18
+ .join(', ')})`;
19
+ }
20
+ if (fill.type === 'conic-gradient' && Array.isArray(fill.colors)) {
21
+ const center = fill.center
22
+ ? `${fill.center[0] * 100}% ${fill.center[1] * 100}%`
23
+ : '50% 50%';
24
+ const angle = ((_a = fill.angle) !== null && _a !== void 0 ? _a : 0);
25
+ const hasMultipleStops = fill.colors.length > 1;
26
+ const lastStop = fill.colors[fill.colors.length - 1];
27
+ const shouldAppendWrapStop = hasMultipleStops && typeof lastStop.position === 'number' && lastStop.position < 100;
28
+ const colorsWithWrap = shouldAppendWrapStop
29
+ ? [...fill.colors, { value: fill.colors[0].value, position: 100 }]
30
+ : fill.colors;
31
+ return `conic-gradient(from ${angle + 90}deg at ${center}, ${colorsWithWrap
32
+ .map(c => `${c.value} ${c.position}%`)
33
+ .join(', ')})`;
34
+ }
35
+ if (fill.type === 'image' && fill.src) {
36
+ return `url(${fill.src})`;
37
+ }
38
+ if (fill.type === 'solid') {
39
+ return fill.value;
40
+ }
41
+ return 'transparent';
42
+ }
43
+ exports.getFill = getFill;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cntrl-site/sdk-nextjs",
3
- "version": "1.8.39",
3
+ "version": "1.8.40-alpha.0",
4
4
  "description": "SDK for Next.js",
5
5
  "author": "arsen@momdesign.nyc",
6
6
  "license": "MIT",
@@ -31,7 +31,7 @@
31
31
  "@antfu/eslint-config": "^3.8.0",
32
32
  "@cntrl-site/color": "^1.0.0",
33
33
  "@cntrl-site/effects": "^1.4.0",
34
- "@cntrl-site/sdk": "^1.23.0",
34
+ "@cntrl-site/sdk": "^1.23.1-alpha.1",
35
35
  "@types/vimeo__player": "^2.18.0",
36
36
  "@vimeo/player": "^2.25.0",
37
37
  "html-react-parser": "^3.0.1",
@@ -33,7 +33,7 @@ export const CodeEmbedItem: FC<ItemProps<TCodeEmbedItem>> = ({ item, sectionId,
33
33
  useRegisterResize(ref, onResize);
34
34
  const pos = stylesMap[anchor];
35
35
  const layoutValues: Record<string, any>[] = [item.area, item.layoutParams];
36
- const stateParams = interactionCtrl?.getState(['angle', 'blur', 'opacity']);
36
+ const stateParams = interactionCtrl?.getState<number>(['angle', 'blur', 'opacity']);
37
37
  const blur = (stateParams?.styles?.blur ?? itemBlur) as number;
38
38
  const opacity = stateParams?.styles?.opacity ?? itemOpacity;
39
39
  const angle = stateParams?.styles?.angle ?? itemAngle;
@@ -19,7 +19,7 @@ export const ComponentItem: FC<ItemProps<TComponentItem>> = ({ item, sectionId,
19
19
  const [ref, setRef] = useState<HTMLDivElement | null>(null);
20
20
  const { opacity: itemOpacity, blur: itemBlur } = useComponentItem(item, sectionId);
21
21
  useRegisterResize(ref, onResize);
22
- const stateParams = interactionCtrl?.getState(['opacity', 'angle', 'blur']);
22
+ const stateParams = interactionCtrl?.getState<number>(['opacity', 'angle', 'blur']);
23
23
  const angle = getStyleFromItemStateAndParams(stateParams?.styles?.angle, itemAngle);
24
24
  const opacity = getStyleFromItemStateAndParams(stateParams?.styles?.opacity, itemOpacity);
25
25
  const blur = getStyleFromItemStateAndParams(stateParams?.styles?.blur, itemBlur);
@@ -47,8 +47,8 @@ export const CompoundChild: FC<ChildItemProps> = ({ item, sectionId, isParentVis
47
47
  const itemScale = useItemScale(item, sectionId);
48
48
  const interactionCtrl = useItemInteractionCtrl(item.id);
49
49
  const triggers = useItemTriggers(interactionCtrl);
50
- const wrapperStateProps = interactionCtrl?.getState(['top', 'left', 'width', 'height']);
51
- const innerStateProps = interactionCtrl?.getState(['scale']);
50
+ const wrapperStateProps = interactionCtrl?.getState<number>(['top', 'left', 'width', 'height']);
51
+ const innerStateProps = interactionCtrl?.getState<number>(['scale']);
52
52
  const compoundSettings = layout && item.compoundSettings ? item.compoundSettings[layout] : undefined;
53
53
  const { width, height, top, left } = useItemArea(item, sectionId, {
54
54
  top: wrapperStateProps?.styles?.top as number,
@@ -19,7 +19,7 @@ export const CompoundItem: FC<ItemProps<TCompoundItem>> = ({ item, sectionId, on
19
19
  const layoutValues: Record<string, any>[] = [item.area, item.layoutParams];
20
20
  const [ref, setRef] = useState<HTMLDivElement | null>(null);
21
21
  useRegisterResize(ref, onResize);
22
- const stateParams = interactionCtrl?.getState(['opacity', 'angle']);
22
+ const stateParams = interactionCtrl?.getState<number>(['opacity', 'angle']);
23
23
  const angle = getStyleFromItemStateAndParams(stateParams?.styles?.angle, itemAngle);
24
24
  const opacity = getStyleFromItemStateAndParams(stateParams?.styles?.opacity, itemOpacity);
25
25
  const isInteractive = opacity !== 0 && opacity !== undefined;
@@ -14,7 +14,7 @@ export const CustomItem: FC<ItemProps<TCustomItem>> = ({ item, onResize, section
14
14
  const layoutValues: Record<string, any>[] = [item.area];
15
15
  const [ref, setRef] = useState<HTMLDivElement | null>(null);
16
16
  useRegisterResize(ref, onResize);
17
- const stateParams = interactionCtrl?.getState(['angle']);
17
+ const stateParams = interactionCtrl?.getState<number>(['angle']);
18
18
  const angle = stateParams?.styles?.angle ?? itemAngle;
19
19
  if (!component) return null;
20
20
  return (
@@ -31,8 +31,8 @@ export const VimeoEmbedItem: FC<ItemProps<TVimeoEmbedItem>> = ({ item, sectionId
31
31
  const [imgRef, setImgRef] = useState<HTMLImageElement | null>(null);
32
32
  const [isCoverVisible, setIsCoverVisible] = useState(false);
33
33
  const layoutValues: Record<string, any>[] = [item.area, item.layoutParams];
34
- const wrapperStateParams = interactionCtrl?.getState(['angle', 'blur', 'opacity']);
35
- const frameStateParams = interactionCtrl?.getState(['radius']);
34
+ const wrapperStateParams = interactionCtrl?.getState<number>(['angle', 'blur', 'opacity']);
35
+ const frameStateParams = interactionCtrl?.getState<number>(['radius']);
36
36
  const angle = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.angle, itemAngle);
37
37
  const blur = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.blur, itemBlur);
38
38
  const opacity = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.opacity, itemOpacity);
@@ -27,8 +27,8 @@ export const YoutubeEmbedItem: FC<ItemProps<TYoutubeEmbedItem>> = ({ item, secti
27
27
  const [isCoverVisible, setIsCoverVisible] = useState(false);
28
28
  const [imgRef, setImgRef] = useState<HTMLImageElement | null>(null);
29
29
  const layoutValues: Record<string, any>[] = [item.area, item.layoutParams];
30
- const wrapperStateParams = interactionCtrl?.getState(['angle', 'blur', 'opacity']);
31
- const frameStateParams = interactionCtrl?.getState(['radius']);
30
+ const wrapperStateParams = interactionCtrl?.getState<number>(['angle', 'blur', 'opacity']);
31
+ const frameStateParams = interactionCtrl?.getState<number>(['radius']);
32
32
  const angle = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.angle, itemAngle);
33
33
  const blur = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.blur, itemBlur);
34
34
  const opacity = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.opacity, itemOpacity);
@@ -13,6 +13,8 @@ import { useElementRect } from '../../../utils/useElementRect';
13
13
  import { useLayoutContext } from '../../useLayoutContext';
14
14
  import { getStyleFromItemStateAndParams } from '../../../utils/getStyleFromItemStateAndParams';
15
15
  import { useItemFXData } from '../../../common/useItemFXData';
16
+ import { getFill } from '../../../utils/getFill';
17
+ import { FillLayer } from '@cntrl-site/sdk/dist/types/article/Item';
16
18
 
17
19
  export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
18
20
  const id = useId();
@@ -22,7 +24,7 @@ export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize
22
24
  radius: itemRadius,
23
25
  strokeWidth: itemStrokeWidth,
24
26
  opacity: itemOpacity,
25
- strokeColor: itemStrokeColor,
27
+ strokeFill: itemStrokeFill,
26
28
  blur: itemBlur
27
29
  } = useFileItem(item, sectionId);
28
30
  const itemAngle = useItemAngle(item, sectionId);
@@ -38,8 +40,11 @@ export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize
38
40
  const exemplary = layouts?.find(l => l.id === layoutId)?.exemplary;
39
41
  const width = area && exemplary ? area.width * exemplary : 0;
40
42
  const height = area && exemplary ? area.height * exemplary : 0;
41
- const wrapperStateParams = interactionCtrl?.getState(['angle', 'opacity', 'blur']);
42
- const imgStateParams = interactionCtrl?.getState(['strokeWidth', 'radius', 'strokeColor']);
43
+ const wrapperStateParams = interactionCtrl?.getState<number>(['angle', 'opacity', 'blur']);
44
+ const imgStateParams = interactionCtrl?.getState<number>(['strokeWidth', 'radius']);
45
+ const stateStrokeFillParams = interactionCtrl?.getState<FillLayer[]>(['strokeFill']);
46
+ const stateStrokeFillLayers = stateStrokeFillParams?.styles?.strokeFill;
47
+ const strokeSolidTransition = stateStrokeFillParams?.transition ?? 'none';
43
48
 
44
49
  useEffect(() => {
45
50
  isInitialRef.current = false;
@@ -58,19 +63,20 @@ export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize
58
63
  const rect = useElementRect(wrapperRef);
59
64
  const rectWidth = Math.floor(rect?.width ?? 0);
60
65
  const rectHeight = Math.floor(rect?.height ?? 0);
61
- const borderColor = useMemo(() => {
62
- const borderColor = getStyleFromItemStateAndParams(imgStateParams?.styles?.strokeColor, itemStrokeColor);
63
- return borderColor ? CntrlColor.parse(borderColor) : undefined;
64
- }, [itemStrokeColor, imgStateParams?.styles?.strokeColor]);
65
66
  const radius = getStyleFromItemStateAndParams(imgStateParams?.styles?.radius, itemRadius);
66
67
  const strokeWidth = getStyleFromItemStateAndParams(imgStateParams?.styles?.strokeWidth, itemStrokeWidth);
67
68
  const angle = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.angle, itemAngle);
68
69
  const opacity = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.opacity, itemOpacity);
69
70
  const blur = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.blur, itemBlur);
71
+
72
+ const strokeValue = stateStrokeFillLayers
73
+ ? getStyleFromItemStateAndParams<FillLayer>(stateStrokeFillLayers[0], itemStrokeFill?.[0])
74
+ : itemStrokeFill?.[0];
75
+ const stroke = strokeValue
76
+ ? getFill(strokeValue) ?? 'transparent'
77
+ : 'transparent';
70
78
  const inlineStyles = {
71
- ...(borderColor ? { borderColor: `${borderColor.fmt('rgba')}` } : {}),
72
79
  ...(radius !== undefined ? { borderRadius: `${radius * 100}vw` } : {}),
73
- ...(strokeWidth !== undefined ? { borderWidth: `${strokeWidth * 100}vw` } : {}),
74
80
  transition: imgStateParams?.transition ?? 'none'
75
81
  };
76
82
  const isInteractive = opacity !== 0;
@@ -87,6 +93,14 @@ export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize
87
93
  ...(opacity !== undefined ? { opacity } : {}),
88
94
  ...(angle !== undefined ? { transform: `rotate(${angle}deg)` } : {}),
89
95
  ...(blur !== undefined ? { filter: `blur(${blur * 100}vw)` } : {}),
96
+ ...(strokeValue ? {
97
+ '--stroke-background': stroke,
98
+ ...(strokeValue.type === 'image' ? {
99
+ '--stroke-background-position': 'center',
100
+ '--stroke-background-size': strokeValue.behavior === 'repeat' ? `${strokeValue.backgroundSize}%` : strokeValue.behavior,
101
+ '--stroke-background-repeat': strokeValue.behavior === 'repeat' ? 'repeat' : 'no-repeat'
102
+ } : {})
103
+ } : {}),
90
104
  willChange: blur !== 0 && blur !== undefined ? 'transform' : 'unset',
91
105
  transition: wrapperStateParams?.transition ?? 'none'
92
106
  }}
@@ -109,6 +123,31 @@ export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize
109
123
  src={item.commonParams.url}
110
124
  />
111
125
  )}
126
+ <div
127
+ className={`image-border-${item.id}`}
128
+ style={{
129
+ position: 'absolute',
130
+ inset: 0,
131
+ borderRadius: 'inherit',
132
+ pointerEvents: 'none',
133
+ zIndex: 2,
134
+ WebkitMask: 'linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)',
135
+ WebkitMaskComposite: 'xor',
136
+ maskComposite: 'exclude',
137
+ ...(strokeWidth !== 0 && strokeValue ? {
138
+ ...(strokeWidth ? { padding: `${strokeWidth * 100}vw` } : {}),
139
+ ...(strokeValue.type === 'solid' ? { transition: strokeSolidTransition, background: stroke } : {}),
140
+ ...(strokeValue.type === 'image' ? {
141
+ backgroundPosition: 'center',
142
+ backgroundSize: strokeValue.behavior === 'repeat' ? `${strokeValue.backgroundSize}%` : strokeValue.behavior,
143
+ backgroundRepeat: strokeValue.behavior === 'repeat' ? 'repeat' : 'no-repeat'
144
+ } : {
145
+ background: stroke,
146
+ }
147
+ )
148
+ } : { background: stroke }),
149
+ }}
150
+ />
112
151
  </div>
113
152
  <JSXStyle id={id}>{`
114
153
  .image-wrapper-${item.id} {
@@ -145,9 +184,19 @@ export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize
145
184
  ${layoutParams.blur !== 0 ? 'will-change: transform;' : ''}
146
185
  }
147
186
  .image-${item.id} {
148
- border-color: ${CntrlColor.parse(layoutParams.strokeColor).fmt('rgba')};
149
187
  border-radius: ${layoutParams.radius * 100}vw;
150
- border-width: ${layoutParams.strokeWidth * 100}vw;
188
+ }
189
+ .image-border-${item.id} {
190
+ position: absolute;
191
+ inset: 0;
192
+ border-radius: inherit;
193
+ pointer-events: none;
194
+ z-index: 2;
195
+ -webkit-mask:
196
+ linear-gradient(#fff 0 0) content-box,
197
+ linear-gradient(#fff 0 0);
198
+ -webkit-mask-composite: xor;
199
+ mask-composite: exclude;
151
200
  }
152
201
  `);
153
202
  })}
@@ -14,6 +14,8 @@ import { getStyleFromItemStateAndParams } from '../../../utils/getStyleFromItemS
14
14
  import { useVideoFx } from '../../../utils/effects/useVideoFx';
15
15
  import { useElementRect } from '../../../utils/useElementRect';
16
16
  import { useItemFXData } from '../../../common/useItemFXData';
17
+ import { getFill } from '../../../utils/getFill';
18
+ import { FillLayer } from '@cntrl-site/sdk/dist/types/article/Item';
17
19
 
18
20
  export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
19
21
  const id = useId();
@@ -22,7 +24,7 @@ export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize
22
24
  const {
23
25
  radius: itemRadius,
24
26
  strokeWidth: itemStrokeWidth,
25
- strokeColor: itemStrokeColor,
27
+ strokeFill: itemStrokeFill,
26
28
  opacity: itemOpacity,
27
29
  blur: itemBlur
28
30
  } = useFileItem(item, sectionId);
@@ -48,17 +50,23 @@ export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize
48
50
  const scrollPlayback = layoutParams ? layoutParams.scrollPlayback : null;
49
51
  const layoutValues: Record<string, any>[] = [item.area, item.layoutParams];
50
52
  const hasScrollPlayback = scrollPlayback !== null;
51
- const wrapperStateParams = interactionCtrl?.getState(['angle', 'opacity', 'blur']);
52
- const videoStateParams = interactionCtrl?.getState(['strokeWidth', 'radius', 'strokeColor']);
53
- const borderColor = useMemo(() => {
54
- const strokeColor = getStyleFromItemStateAndParams(videoStateParams?.styles?.color, itemStrokeColor);
55
- return strokeColor ? CntrlColor.parse(strokeColor) : undefined;
56
- }, [videoStateParams?.styles?.color, itemStrokeColor]);
53
+ const wrapperStateParams = interactionCtrl?.getState<number>(['angle', 'opacity', 'blur']);
54
+ const videoStateParams = interactionCtrl?.getState<number>(['strokeWidth', 'radius']);
55
+ const stateStrokeFillParams = interactionCtrl?.getState<FillLayer[]>(['strokeFill']);
56
+ const stateStrokeFillLayers = stateStrokeFillParams?.styles?.strokeFill;
57
+ const strokeSolidTransition = stateStrokeFillParams?.transition ?? 'none';
57
58
  const angle = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.angle, itemAngle);
58
59
  const opacity = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.opacity, itemOpacity);
59
60
  const blur = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.blur, itemBlur);
60
61
  const strokeWidth = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.strokeWidth, itemStrokeWidth);
61
62
  const radius = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.radius, itemRadius);
63
+
64
+ const strokeValue = stateStrokeFillLayers
65
+ ? getStyleFromItemStateAndParams<FillLayer>(stateStrokeFillLayers[0], itemStrokeFill?.[0])
66
+ : itemStrokeFill?.[0];
67
+ const stroke = strokeValue
68
+ ? getFill(strokeValue) ?? 'transparent'
69
+ : 'transparent';
62
70
  useEffect(() => {
63
71
  isInitialRef.current = false;
64
72
  }, []);
@@ -76,8 +84,6 @@ export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize
76
84
  useRegisterResize(ref, onResize);
77
85
  const inlineStyles = {
78
86
  ...(radius !== undefined ? { borderRadius: `${radius * 100}vw` } : {}),
79
- ...(strokeWidth !== undefined ? { borderWidth: `${strokeWidth * 100}vw` } : {}),
80
- ...(borderColor ? { borderColor: `${borderColor.toCss()}` } : {}),
81
87
  transition: videoStateParams?.transition ?? 'none'
82
88
  };
83
89
  const isInteractive = opacity !== 0;
@@ -112,6 +118,14 @@ export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize
112
118
  ...(opacity !== undefined ? { opacity } : {}),
113
119
  ...(angle !== undefined ? { transform: `rotate(${angle}deg)` } : {}),
114
120
  ...(blur !== undefined ? { filter: `blur(${blur * 100}vw)` } : {}),
121
+ ...(strokeValue ? {
122
+ '--stroke-background': stroke,
123
+ ...(strokeValue.type === 'image' ? {
124
+ '--stroke-background-position': 'center',
125
+ '--stroke-background-size': strokeValue.behavior === 'repeat' ? `${strokeValue.backgroundSize}%` : strokeValue.behavior,
126
+ '--stroke-background-repeat': strokeValue.behavior === 'repeat' ? 'repeat' : 'no-repeat'
127
+ } : {})
128
+ } : {}),
115
129
  willChange: blur !== 0 && blur !== undefined ? 'transform' : 'unset',
116
130
  transition: wrapperStateParams?.transition ?? 'none'
117
131
  }}
@@ -203,6 +217,31 @@ export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize
203
217
  ))}
204
218
  </>
205
219
  )}
220
+ <div
221
+ className={`video-border-${item.id}`}
222
+ style={{
223
+ position: 'absolute',
224
+ inset: 0,
225
+ borderRadius: 'inherit',
226
+ pointerEvents: 'none',
227
+ zIndex: 2,
228
+ WebkitMask: 'linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)',
229
+ WebkitMaskComposite: 'xor',
230
+ maskComposite: 'exclude',
231
+ ...(strokeWidth !== 0 && strokeValue ? {
232
+ ...(strokeWidth ? { padding: `${strokeWidth * 100}vw` } : {}),
233
+ ...(strokeValue.type === 'solid' ? { transition: strokeSolidTransition, background: stroke } : {}),
234
+ ...(strokeValue.type === 'image' ? {
235
+ backgroundPosition: 'center',
236
+ backgroundSize: strokeValue.behavior === 'repeat' ? `${strokeValue.backgroundSize}%` : strokeValue.behavior,
237
+ backgroundRepeat: strokeValue.behavior === 'repeat' ? 'repeat' : 'no-repeat'
238
+ } : {
239
+ background: stroke,
240
+ }
241
+ )
242
+ } : { background: stroke }),
243
+ }}
244
+ />
206
245
  </div>
207
246
  <JSXStyle id={id}>{`
208
247
  .video-wrapper-${item.id} {
@@ -240,7 +279,7 @@ export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize
240
279
  border-style: solid;
241
280
  }
242
281
  .video-${item.id} {
243
- border-color: ${itemStrokeColor};
282
+ border-radius: ${radius !== undefined ? `${radius * 100}vw` : '0'};
244
283
  }
245
284
  .video-playback-wrapper {
246
285
  display: flex;
@@ -263,9 +302,19 @@ export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize
263
302
  ${layoutParams.blur !== 0 ? 'will-change: transform;' : ''}
264
303
  }
265
304
  .video-${item.id} {
266
- border-color: ${CntrlColor.parse(layoutParams.strokeColor).fmt('rgba')};
267
305
  border-radius: ${layoutParams.radius * 100}vw;
268
- border-width: ${layoutParams.strokeWidth * 100}vw;
306
+ }
307
+ .video-border-${item.id} {
308
+ position: absolute;
309
+ inset: 0;
310
+ border-radius: inherit;
311
+ pointer-events: none;
312
+ z-index: 2;
313
+ -webkit-mask:
314
+ linear-gradient(#fff 0 0) content-box,
315
+ linear-gradient(#fff 0 0);
316
+ -webkit-mask-composite: xor;
317
+ mask-composite: exclude;
269
318
  }
270
319
  `);
271
320
  })}