@cntrl-site/sdk-nextjs 1.8.40 → 1.9.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 +49 -22
  6. package/lib/components/items/FileItem/VideoItem.js +48 -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 +63 -10
  23. package/src/components/items/FileItem/VideoItem.tsx +64 -11
  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.40",
3
+ "version": "1.9.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.2",
34
+ "@cntrl-site/sdk": "^1.24.0",
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,17 +63,19 @@ 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
80
  ...(strokeWidth !== undefined ? { borderWidth: `${strokeWidth * 100}vw` } : {}),
74
81
  transition: imgStateParams?.transition ?? 'none'
@@ -87,6 +94,14 @@ export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize
87
94
  ...(opacity !== undefined ? { opacity } : {}),
88
95
  ...(angle !== undefined ? { transform: `rotate(${angle}deg)` } : {}),
89
96
  ...(blur !== undefined ? { filter: `blur(${blur * 100}vw)` } : {}),
97
+ ...(strokeValue ? {
98
+ '--stroke-background': stroke,
99
+ ...(strokeValue.type === 'image' ? {
100
+ '--stroke-background-position': 'center',
101
+ '--stroke-background-size': strokeValue.behavior === 'repeat' ? `${strokeValue.backgroundSize}%` : strokeValue.behavior,
102
+ '--stroke-background-repeat': strokeValue.behavior === 'repeat' ? 'repeat' : 'no-repeat'
103
+ } : {})
104
+ } : {}),
90
105
  willChange: blur !== 0 && blur !== undefined ? 'transform' : 'unset',
91
106
  transition: wrapperStateParams?.transition ?? 'none'
92
107
  }}
@@ -109,6 +124,31 @@ export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize
109
124
  src={item.commonParams.url}
110
125
  />
111
126
  )}
127
+ <div
128
+ className={`image-border-${item.id}`}
129
+ style={{
130
+ position: 'absolute',
131
+ inset: 0,
132
+ borderRadius: 'inherit',
133
+ pointerEvents: 'none',
134
+ zIndex: 2,
135
+ WebkitMask: 'linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)',
136
+ WebkitMaskComposite: 'xor',
137
+ maskComposite: 'exclude',
138
+ ...(strokeWidth !== 0 && strokeValue ? {
139
+ ...(strokeWidth ? { padding: `${strokeWidth * 100}vw` } : {}),
140
+ ...(strokeValue.type === 'solid' ? { transition: strokeSolidTransition, background: stroke } : {}),
141
+ ...(strokeValue.type === 'image' ? {
142
+ backgroundPosition: 'center',
143
+ backgroundSize: strokeValue.behavior === 'repeat' ? `${strokeValue.backgroundSize}%` : strokeValue.behavior,
144
+ backgroundRepeat: strokeValue.behavior === 'repeat' ? 'repeat' : 'no-repeat'
145
+ } : {
146
+ background: stroke,
147
+ }
148
+ )
149
+ } : { background: stroke }),
150
+ }}
151
+ />
112
152
  </div>
113
153
  <JSXStyle id={id}>{`
114
154
  .image-wrapper-${item.id} {
@@ -145,9 +185,22 @@ export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize
145
185
  ${layoutParams.blur !== 0 ? 'will-change: transform;' : ''}
146
186
  }
147
187
  .image-${item.id} {
148
- border-color: ${CntrlColor.parse(layoutParams.strokeColor).fmt('rgba')};
149
- border-radius: ${layoutParams.radius * 100}vw;
188
+ border: solid;
189
+ border-color: transparent;
150
190
  border-width: ${layoutParams.strokeWidth * 100}vw;
191
+ border-radius: ${layoutParams.radius * 100}vw;
192
+ }
193
+ .image-border-${item.id} {
194
+ position: absolute;
195
+ inset: 0;
196
+ border-radius: inherit;
197
+ pointer-events: none;
198
+ z-index: 2;
199
+ -webkit-mask:
200
+ linear-gradient(#fff 0 0) content-box,
201
+ linear-gradient(#fff 0 0);
202
+ -webkit-mask-composite: xor;
203
+ mask-composite: exclude;
151
204
  }
152
205
  `);
153
206
  })}
@@ -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
  }, []);
@@ -77,7 +85,6 @@ export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize
77
85
  const inlineStyles = {
78
86
  ...(radius !== undefined ? { borderRadius: `${radius * 100}vw` } : {}),
79
87
  ...(strokeWidth !== undefined ? { borderWidth: `${strokeWidth * 100}vw` } : {}),
80
- ...(borderColor ? { borderColor: `${borderColor.toCss()}` } : {}),
81
88
  transition: videoStateParams?.transition ?? 'none'
82
89
  };
83
90
  const isInteractive = opacity !== 0;
@@ -112,6 +119,14 @@ export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize
112
119
  ...(opacity !== undefined ? { opacity } : {}),
113
120
  ...(angle !== undefined ? { transform: `rotate(${angle}deg)` } : {}),
114
121
  ...(blur !== undefined ? { filter: `blur(${blur * 100}vw)` } : {}),
122
+ ...(strokeValue ? {
123
+ '--stroke-background': stroke,
124
+ ...(strokeValue.type === 'image' ? {
125
+ '--stroke-background-position': 'center',
126
+ '--stroke-background-size': strokeValue.behavior === 'repeat' ? `${strokeValue.backgroundSize}%` : strokeValue.behavior,
127
+ '--stroke-background-repeat': strokeValue.behavior === 'repeat' ? 'repeat' : 'no-repeat'
128
+ } : {})
129
+ } : {}),
115
130
  willChange: blur !== 0 && blur !== undefined ? 'transform' : 'unset',
116
131
  transition: wrapperStateParams?.transition ?? 'none'
117
132
  }}
@@ -203,6 +218,31 @@ export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize
203
218
  ))}
204
219
  </>
205
220
  )}
221
+ <div
222
+ className={`video-border-${item.id}`}
223
+ style={{
224
+ position: 'absolute',
225
+ inset: 0,
226
+ borderRadius: 'inherit',
227
+ pointerEvents: 'none',
228
+ zIndex: 2,
229
+ WebkitMask: 'linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)',
230
+ WebkitMaskComposite: 'xor',
231
+ maskComposite: 'exclude',
232
+ ...(strokeWidth !== 0 && strokeValue ? {
233
+ ...(strokeWidth ? { padding: `${strokeWidth * 100}vw` } : {}),
234
+ ...(strokeValue.type === 'solid' ? { transition: strokeSolidTransition, background: stroke } : {}),
235
+ ...(strokeValue.type === 'image' ? {
236
+ backgroundPosition: 'center',
237
+ backgroundSize: strokeValue.behavior === 'repeat' ? `${strokeValue.backgroundSize}%` : strokeValue.behavior,
238
+ backgroundRepeat: strokeValue.behavior === 'repeat' ? 'repeat' : 'no-repeat'
239
+ } : {
240
+ background: stroke,
241
+ }
242
+ )
243
+ } : { background: stroke }),
244
+ }}
245
+ />
206
246
  </div>
207
247
  <JSXStyle id={id}>{`
208
248
  .video-wrapper-${item.id} {
@@ -240,7 +280,7 @@ export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize
240
280
  border-style: solid;
241
281
  }
242
282
  .video-${item.id} {
243
- border-color: ${itemStrokeColor};
283
+ border-radius: ${radius !== undefined ? `${radius * 100}vw` : '0'};
244
284
  }
245
285
  .video-playback-wrapper {
246
286
  display: flex;
@@ -263,9 +303,22 @@ export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize
263
303
  ${layoutParams.blur !== 0 ? 'will-change: transform;' : ''}
264
304
  }
265
305
  .video-${item.id} {
266
- border-color: ${CntrlColor.parse(layoutParams.strokeColor).fmt('rgba')};
267
- border-radius: ${layoutParams.radius * 100}vw;
306
+ border: solid;
307
+ border-color: transparent;
268
308
  border-width: ${layoutParams.strokeWidth * 100}vw;
309
+ border-radius: ${layoutParams.radius * 100}vw;
310
+ }
311
+ .video-border-${item.id} {
312
+ position: absolute;
313
+ inset: 0;
314
+ border-radius: inherit;
315
+ pointer-events: none;
316
+ z-index: 2;
317
+ -webkit-mask:
318
+ linear-gradient(#fff 0 0) content-box,
319
+ linear-gradient(#fff 0 0);
320
+ -webkit-mask-composite: xor;
321
+ mask-composite: exclude;
269
322
  }
270
323
  `);
271
324
  })}