@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
@@ -2,7 +2,15 @@ import { ImageItem, KeyframeType, VideoItem } from '@cntrl-site/sdk';
2
2
  import { useKeyframeValue } from '../../../common/useKeyframeValue';
3
3
  import { useLayoutContext } from '../../useLayoutContext';
4
4
 
5
- const DEFAULT_COLOR = 'rgba(0, 0, 0, 1)';
5
+ const DEFAULT_FILL = [
6
+ {
7
+ id: 'default',
8
+ type: 'solid' as const,
9
+ value: 'rgba(0, 0, 0, 1)',
10
+ blendMode: 'normal'
11
+ }
12
+ ];
13
+
6
14
  export function useFileItem(item: ImageItem | VideoItem, sectionId: string) {
7
15
  const layoutId = useLayoutContext();
8
16
  const radius = useKeyframeValue(
@@ -43,15 +51,15 @@ export function useFileItem(item: ImageItem | VideoItem, sectionId: string) {
43
51
  [layoutId]
44
52
  );
45
53
 
46
- const strokeColor = useKeyframeValue(
54
+ const strokeFill = useKeyframeValue(
47
55
  item,
48
- KeyframeType.BorderColor,
56
+ KeyframeType.BorderFill,
49
57
  (item, layoutId) => {
50
58
  if (!layoutId) return;
51
59
  const layoutParams = item.layoutParams[layoutId];
52
- return 'strokeColor' in layoutParams ? layoutParams.strokeColor : DEFAULT_COLOR;
60
+ return 'strokeFill' in layoutParams ? layoutParams.strokeFill : DEFAULT_FILL;
53
61
  },
54
- (animator, scroll, value) => value ? animator.getBorderColor({ color: value }, scroll).color : undefined,
62
+ (animator, scroll, value) => value ? animator.getBorderFill(value, scroll) : undefined,
55
63
  sectionId,
56
64
  [layoutId]
57
65
  );
@@ -69,5 +77,5 @@ export function useFileItem(item: ImageItem | VideoItem, sectionId: string) {
69
77
  [layoutId]
70
78
  );
71
79
 
72
- return { radius, strokeWidth, opacity, strokeColor, blur };
80
+ return { radius, strokeWidth, opacity, strokeFill, blur };
73
81
  }
@@ -19,7 +19,7 @@ export const GroupItem: FC<ItemProps<TGroupItem>> = ({ item, sectionId, onResize
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', '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);
@@ -82,8 +82,8 @@ export const Item: FC<ItemWrapperProps> = ({ item, sectionId, articleHeight, isP
82
82
  const triggers = useItemTriggers(interactionCtrl);
83
83
  const [position, setPosition] = useState({ x: 0, y: 0 });
84
84
  const [isDraggingActive, setIsDraggingActive] = useState(false);
85
- const wrapperStateProps = interactionCtrl?.getState(['top', 'left']);
86
- const innerStateProps = interactionCtrl?.getState(['width', 'height', 'scale']);
85
+ const wrapperStateProps = interactionCtrl?.getState<number>(['top', 'left']);
86
+ const innerStateProps = interactionCtrl?.getState<number>(['width', 'height', 'scale']);
87
87
  const { width, height, top, left } = useItemArea(item, sectionId, {
88
88
  top: wrapperStateProps?.styles?.top as number,
89
89
  left: wrapperStateProps?.styles?.left as number,
@@ -9,29 +9,30 @@ import { useItemAngle } from '../useItemAngle';
9
9
  import { useCntrlContext } from '../../../provider/useCntrlContext';
10
10
  import { useRegisterResize } from '../../../common/useRegisterResize';
11
11
  import { getStyleFromItemStateAndParams } from '../../../utils/getStyleFromItemStateAndParams';
12
+ import { FillLayer } from '@cntrl-site/sdk/dist/types/article/Item';
13
+ import { getFill } from '../../../utils/getFill';
14
+ import { areFillsVisible } from '../../../utils/areFillsVisible/areFillsVisible';
12
15
 
13
16
  export const RectangleItem: FC<ItemProps<TRectangleItem>> = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
14
17
  const id = useId();
15
18
  const { layouts } = useCntrlContext();
16
19
  const {
17
- fillColor: itemFillColor,
20
+ fill: itemFill,
18
21
  radius: itemRadius,
19
22
  strokeWidth: itemStrokeWidth,
20
- strokeColor: itemStrokeColor,
23
+ strokeFill: itemStrokeFill,
21
24
  blur: itemBlur,
22
25
  backdropBlur: itemBackdropBlur
23
26
  } = useRectangleItem(item, sectionId);
24
27
  const itemAngle = useItemAngle(item, sectionId);
25
- const stateParams = interactionCtrl?.getState(['angle', 'fillColor', 'strokeWidth', 'radius', 'strokeColor', 'blur', 'backdropBlur']);
28
+ const stateParams = interactionCtrl?.getState<number>(['angle', 'strokeWidth', 'radius', 'blur', 'backdropBlur']);
29
+ const stateFillParams = interactionCtrl?.getState<FillLayer[]>(['fill']);
30
+ const stateFillLayers = stateFillParams?.styles?.fill;
31
+ const solidTransition = stateFillParams?.transition ?? 'none';
32
+ const stateStrokeFillParams = interactionCtrl?.getState<FillLayer[]>(['strokeFill']);
33
+ const stateStrokeFillLayers = stateStrokeFillParams?.styles?.strokeFill;
34
+ const strokeSolidTransition = stateStrokeFillParams?.transition ?? 'none';
26
35
  const styles = stateParams?.styles ?? {};
27
- const backgroundColor = useMemo(() => {
28
- const fillColor = getStyleFromItemStateAndParams(styles?.fillColor, itemFillColor);
29
- return fillColor ? CntrlColor.parse(fillColor) : undefined;
30
- }, [itemFillColor, styles?.fillColor]);
31
- const borderColor = useMemo(() => {
32
- const strokeColor = getStyleFromItemStateAndParams(styles?.strokeColor, itemStrokeColor);
33
- return strokeColor ? CntrlColor.parse(strokeColor) : undefined;
34
- }, [itemStrokeColor, styles?.strokeColor]);
35
36
  const layoutValues: Record<string, any>[] = [item.area, item.layoutParams];
36
37
  const [ref, setRef] = useState<HTMLDivElement | null>(null);
37
38
  useRegisterResize(ref, onResize);
@@ -41,11 +42,18 @@ export const RectangleItem: FC<ItemProps<TRectangleItem>> = ({ item, sectionId,
41
42
  const angle = getStyleFromItemStateAndParams(styles?.angle, itemAngle);
42
43
  const blur = getStyleFromItemStateAndParams(styles?.blur, itemBlur);
43
44
  const backdropFilterValue = backdropBlur ? `blur(${backdropBlur * 100}vw)` : undefined;
44
- const isInteractive = backgroundColor?.getAlpha() !== 0 || (strokeWidth !== 0 && borderColor?.getAlpha() !== 0);
45
+ const isInteractive = areFillsVisible(stateFillLayers ?? itemFill ?? []) || (strokeWidth !== 0 && areFillsVisible(stateStrokeFillLayers ?? itemStrokeFill ?? []));
45
46
  useEffect(() => {
46
47
  onVisibilityChange?.(isInteractive);
47
48
  }, [isInteractive, onVisibilityChange]);
48
49
 
50
+ const strokeValue = stateStrokeFillLayers
51
+ ? getStyleFromItemStateAndParams<FillLayer>(stateStrokeFillLayers[0], itemStrokeFill?.[0])
52
+ : itemStrokeFill?.[0];
53
+ const stroke = strokeValue
54
+ ? getFill(strokeValue) ?? 'transparent'
55
+ : 'transparent';
56
+
49
57
  return (
50
58
  <LinkWrapper url={item.link?.url} target={item.link?.target}>
51
59
  <>
@@ -53,10 +61,15 @@ export const RectangleItem: FC<ItemProps<TRectangleItem>> = ({ item, sectionId,
53
61
  className={`rectangle-${item.id}`}
54
62
  ref={setRef}
55
63
  style={{
56
- ...(backgroundColor ? { backgroundColor: `${backgroundColor.fmt('rgba')}` } : {}),
57
- ...(borderColor ? { borderColor: `${borderColor.fmt('rgba')}` } : {}),
64
+ ...(strokeValue ? {
65
+ '--stroke-background': stroke,
66
+ ...(strokeValue.type === 'image' ? {
67
+ backgroundPosition: 'center',
68
+ backgroundSize: strokeValue.behavior === 'repeat' ? `${strokeValue.backgroundSize}%` : strokeValue.behavior,
69
+ backgroundRepeat: strokeValue.behavior === 'repeat' ? 'repeat' : 'no-repeat'
70
+ } : {})
71
+ } : {}),
58
72
  ...(radius !== undefined ? { borderRadius: `${radius * 100}vw` } : {}),
59
- ...(strokeWidth !== undefined ? { borderWidth: `${strokeWidth * 100}vw` } : {}),
60
73
  ...(angle !== undefined ? { transform: `rotate(${angle}deg)` } : {}),
61
74
  ...(blur !== undefined ? { filter: `blur(${blur * 100}vw)` } : {}),
62
75
  willChange: blur !== 0 && blur !== undefined ? 'transform' : 'unset',
@@ -66,32 +79,121 @@ export const RectangleItem: FC<ItemProps<TRectangleItem>> = ({ item, sectionId,
66
79
  ),
67
80
  transition: stateParams?.transition ?? 'none'
68
81
  }}
69
- />
82
+ >
83
+ <div
84
+ className={`rectangle-border-${item.id}`}
85
+ style={{
86
+ position: 'absolute',
87
+ inset: 0,
88
+ borderRadius: 'inherit',
89
+ pointerEvents: 'none',
90
+ zIndex: 2,
91
+ WebkitMask: 'linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)',
92
+ WebkitMaskComposite: 'xor',
93
+ maskComposite: 'exclude',
94
+ ...(strokeWidth !== 0 && strokeValue ? {
95
+ ...(strokeWidth ? { padding: `${strokeWidth * 100}vw` } : {}),
96
+ ...(strokeValue.type === 'solid' ? { transition: strokeSolidTransition, background: stroke } : {}),
97
+ ...(strokeValue.type === 'image' ? {
98
+ backgroundPosition: 'center',
99
+ backgroundSize: strokeValue.behavior === 'repeat' ? `${strokeValue.backgroundSize}%` : strokeValue.behavior,
100
+ backgroundRepeat: strokeValue.behavior === 'repeat' ? 'repeat' : 'no-repeat'
101
+ } : {
102
+ background: stroke,
103
+ }
104
+ )
105
+ } : { background: stroke }),
106
+ }}
107
+ />
108
+ {itemFill && itemFill.map((fill) => {
109
+ const stateFillLayer = stateFillLayers?.find((layer) => layer.id === fill.id);
110
+ const value = stateFillLayer
111
+ ? (getStyleFromItemStateAndParams<FillLayer>(stateFillLayer, fill) ?? fill)
112
+ : fill;
113
+ const background = value
114
+ ? getFill(value) ?? 'transparent'
115
+ : 'transparent';
116
+
117
+ return (
118
+ <Fill fill={value} itemId={item.id} background={background} solidTransition={solidTransition} />
119
+ );
120
+ })}
121
+ </div>
70
122
  <JSXStyle id={id}>{`
71
123
  .rectangle-${item.id} {
72
124
  position: absolute;
73
125
  width: 100%;
74
126
  height: 100%;
75
- border-style: solid;
76
127
  box-sizing: border-box;
77
- }
128
+ },
129
+ .image-fill-${item.id} {
130
+ position: absolute;
131
+ top: 0;
132
+ left: 0;
133
+ width: 100%;
134
+ height: 100%;
135
+ transform-origin: center center;
136
+ z-index: 1;
137
+ background-position: center;
138
+ },
78
139
  ${getLayoutStyles(layouts, layoutValues, ([area, layoutParams]) => {
79
- return (`
140
+ return (`
80
141
  .rectangle-${item.id} {
81
- background-color: ${CntrlColor.parse(layoutParams.fillColor).fmt('rgba')};
82
- border-color: ${CntrlColor.parse(layoutParams.strokeColor).fmt('rgba')};
83
- border-radius: ${layoutParams.radius * 100}vw;
84
- border-width: ${layoutParams.strokeWidth * 100}vw;
142
+ border-radius: ${layoutParams.radius * 100}vw
85
143
  transform: rotate(${area.angle}deg);
86
144
  filter: ${layoutParams.blur !== 0 ? `blur(${layoutParams.blur * 100}vw)` : 'unset'};
87
145
  ${layoutParams.blur !== 0 ? 'will-change: transform;' : ''}
88
146
  backdrop-filter: ${layoutParams.backdropBlur !== 0 ? `blur(${layoutParams.backdropBlur * 100}vw)` : 'unset'};
89
147
  -webkit-backdrop-filter: ${layoutParams.backdropBlur !== 0 ? `blur(${layoutParams.backdropBlur * 100}vw)` : 'unset'};
148
+ },
149
+ .rectangle-border-${item.id} {
150
+ position: absolute;
151
+ inset: 0;
152
+ border-radius: inherit;
153
+ pointer-events: none;
154
+ z-index: 2;
155
+ -webkit-mask:
156
+ linear-gradient(#fff 0 0) content-box,
157
+ linear-gradient(#fff 0 0);
158
+ -webkit-mask-composite: xor;
159
+ mask-composite: exclude;
90
160
  }
91
161
  `);
92
- })}
162
+ })}
93
163
  `}</JSXStyle>
94
164
  </>
95
165
  </LinkWrapper>
96
166
  );
97
167
  };
168
+
169
+ function Fill({ fill, itemId, background, solidTransition }: { fill: FillLayer; itemId: string; background: string; solidTransition: string; }) {
170
+ const isRotatedImage = fill.type === 'image' && fill.rotation && fill.rotation !== 0;
171
+
172
+ return (
173
+ <div
174
+ key={fill.id}
175
+ className={fill.type === 'image' ? `image-fill-${itemId}` : `fill-${itemId}`}
176
+ style={{
177
+ ...(fill.type === 'solid' ? { background, transition: solidTransition } : {}),
178
+ ...(fill.type === 'image'
179
+ ? {
180
+ transform: `rotate(${fill.rotation}deg)`,
181
+ backgroundImage: `url(${fill.src})`,
182
+ backgroundSize: fill.behavior === 'repeat' ? `${fill.backgroundSize}%` : fill.behavior,
183
+ backgroundRepeat: fill.behavior === 'repeat' ? 'repeat' : 'no-repeat',
184
+ opacity: fill.opacity
185
+ }
186
+ : { background }),
187
+ position: 'absolute',
188
+ mixBlendMode: fill.blendMode as any,
189
+ top: 0,
190
+ left: 0,
191
+ width: '100%',
192
+ height: '100%',
193
+ pointerEvents: 'none',
194
+ ...(isRotatedImage ? { overflow: 'hidden' } : {})
195
+ }}
196
+ >
197
+ </div>
198
+ );
199
+ };
@@ -2,7 +2,14 @@ import { KeyframeType, RectangleItem } from '@cntrl-site/sdk';
2
2
  import { useKeyframeValue } from '../../../common/useKeyframeValue';
3
3
  import { useLayoutContext } from '../../useLayoutContext';
4
4
 
5
- const defaultColor = 'rgba(0, 0, 0, 1)';
5
+ const defaultFill = [
6
+ {
7
+ id: 'default',
8
+ type: 'solid' as const,
9
+ value: 'rgba(0, 0, 0, 1)',
10
+ blendMode: 'normal'
11
+ }
12
+ ];
6
13
 
7
14
  export function useRectangleItem(item: RectangleItem, sectionId: string) {
8
15
  const layoutId = useLayoutContext();
@@ -30,27 +37,27 @@ export function useRectangleItem(item: RectangleItem, sectionId: string) {
30
37
  sectionId,
31
38
  [layoutId]
32
39
  );
33
- const fillColor = useKeyframeValue(
40
+ const fill = useKeyframeValue(
34
41
  item,
35
- KeyframeType.Color,
42
+ KeyframeType.Fill,
36
43
  (item, layoutId) => {
37
44
  if (!layoutId) return;
38
45
  const layoutParams = item.layoutParams[layoutId];
39
- return 'fillColor' in layoutParams ? layoutParams.fillColor : defaultColor;
46
+ return 'fill' in layoutParams ? layoutParams.fill : defaultFill;
40
47
  },
41
- (animator, scroll, value) => value ? animator.getColor({ color: value }, scroll).color : undefined,
48
+ (animator, scroll, value) => value ? animator.getFill(value, scroll) : undefined,
42
49
  sectionId,
43
50
  [layoutId]
44
51
  );
45
- const strokeColor = useKeyframeValue(
52
+ const strokeFill = useKeyframeValue(
46
53
  item,
47
- KeyframeType.BorderColor,
54
+ KeyframeType.BorderFill,
48
55
  (item, layoutId) => {
49
56
  if (!layoutId) return;
50
57
  const layoutParams = item.layoutParams[layoutId];
51
- return 'strokeColor' in layoutParams ? layoutParams.strokeColor : defaultColor;
58
+ return 'strokeFill' in layoutParams ? layoutParams.strokeFill : defaultFill;
52
59
  },
53
- (animator, scroll, value) => value ? animator.getBorderColor({ color: value }, scroll).color : undefined,
60
+ (animator, scroll, value) => value ? animator.getBorderFill(value, scroll) : undefined,
54
61
  sectionId,
55
62
  [layoutId]
56
63
  );
@@ -78,5 +85,5 @@ export function useRectangleItem(item: RectangleItem, sectionId: string) {
78
85
  sectionId,
79
86
  [layoutId]
80
87
  );
81
- return { fillColor, strokeWidth, radius, strokeColor, blur, backdropBlur };
88
+ return { fill, strokeWidth, radius, strokeFill, blur, backdropBlur };
82
89
  }
@@ -30,11 +30,11 @@ export const RichTextItem: FC<ItemProps<TRichTextItem>> = ({ item, sectionId, on
30
30
  const exemplary = useExemplary();
31
31
  const { layoutId } = useCurrentLayout();
32
32
  useRegisterResize(ref, onResize);
33
- const stateParams = interactionCtrl?.getState(['angle', 'blur', 'letterSpacing', 'wordSpacing', 'color']);
33
+ const stateParams = interactionCtrl?.getState<number | string>(['angle', 'blur', 'letterSpacing', 'wordSpacing', 'color']);
34
34
  const stateStyles = stateParams?.styles ?? {};
35
35
  const transition = stateParams?.transition ?? 'none';
36
36
  const textColor = useMemo(() => {
37
- const color = getStyleFromItemStateAndParams(stateParams?.styles?.color, itemColor);
37
+ const color = getStyleFromItemStateAndParams(stateParams?.styles?.color as string, itemColor);
38
38
  return color ? CntrlColor.parse(color) : undefined;
39
39
  }, [itemColor, stateStyles.color]);
40
40
  const angle = getStyleFromItemStateAndParams(stateStyles.angle, itemAngle);
@@ -60,11 +60,11 @@ export const RichTextItem: FC<ItemProps<TRichTextItem>> = ({ item, sectionId, on
60
60
  ref={setRef}
61
61
  className={`rich-text-wrapper-${item.id}`}
62
62
  style={{
63
- ...(blur !== undefined ? { filter: `blur(${blur * 100}vw)` } : {}),
63
+ ...(blur !== undefined ? { filter: `blur(${blur as number * 100}vw)` } : {}),
64
64
  ...(textColor ? { color: `${textColor.fmt('rgba')}` } : {}),
65
65
  ...(angle !== undefined ? { transform: `rotate(${angle}deg)` } : {}),
66
- ...(letterSpacing !== undefined ? { letterSpacing: `${letterSpacing * exemplary}px` } : {}),
67
- ...(wordSpacing !== undefined ? { wordSpacing: `${wordSpacing * exemplary}px` } : {}),
66
+ ...(letterSpacing !== undefined ? { letterSpacing: `${letterSpacing as number * exemplary}px` } : {}),
67
+ ...(wordSpacing !== undefined ? { wordSpacing: `${wordSpacing as number * exemplary}px` } : {}),
68
68
  ...(fontSize !== undefined ? { fontSize: `${Math.round(fontSize * exemplary)}px` } : {}),
69
69
  ...(lineHeight !== undefined ? { lineHeight: `${lineHeight * exemplary}px` } : {}),
70
70
  willChange: blur !== 0 && blur !== undefined ? 'transform' : 'unset',
@@ -1,37 +1,36 @@
1
1
  import { ArticleItemType, ItemState } from '@cntrl-site/sdk';
2
2
 
3
3
  export const CSSPropertyNameMap: Record<keyof ItemState<ArticleItemType>, string> = {
4
- 'width': 'width',
5
- 'height': 'height',
6
- 'top': 'top',
7
- 'left': 'left',
8
- 'scale': 'transform',
9
- 'angle': 'transform',
10
- 'opacity': 'opacity',
11
- 'radius': 'border-radius',
12
- 'strokeWidth': 'border-width',
13
- 'strokeColor': 'border-color',
14
- 'fillColor': 'background-color',
15
- 'blur': 'filter',
16
- 'backdropBlur': 'backdrop-filter',
17
- 'letterSpacing': 'letter-spacing',
18
- 'wordSpacing': 'word-spacing',
19
- 'color': 'color'
4
+ width: 'width',
5
+ height: 'height',
6
+ top: 'top',
7
+ left: 'left',
8
+ scale: 'transform',
9
+ angle: 'transform',
10
+ opacity: 'opacity',
11
+ radius: 'border-radius',
12
+ strokeWidth: 'border-width',
13
+ strokeFill: 'border-color',
14
+ fill: 'background',
15
+ blur: 'filter',
16
+ backdropBlur: 'backdrop-filter',
17
+ letterSpacing: 'letter-spacing',
18
+ wordSpacing: 'word-spacing',
19
+ color: 'color'
20
20
  };
21
21
 
22
22
  const PropertyNameCSSMap: Record<string, string[]> = {
23
23
  'transform': ['angle', 'scale'],
24
24
  'border-radius': ['radius'],
25
25
  'border-width': ['strokeWidth'],
26
- 'border-color': ['strokeColor'],
27
- 'background-color': ['fillColor'],
26
+ 'border-color': ['strokeFill'],
27
+ 'background': ['fill'],
28
28
  'filter': ['blur'],
29
29
  'backdrop-filter': ['backdrop-blur'],
30
30
  'letter-spacing': ['letterSpacing'],
31
31
  'word-spacing': ['wordSpacing'],
32
32
  };
33
33
 
34
-
35
34
  export function getStyleKeysFromCSSProperty(cssProp: string): string[] {
36
35
  const key = PropertyNameCSSMap[cssProp] ?? [cssProp];
37
36
  return key;
@@ -1,7 +1,7 @@
1
1
  import { InteractionsRegistryPort, ItemInteractionCtrl } from './types';
2
2
  import { getTransition } from './getTransition';
3
3
  import { getStyleKeysFromCSSProperty } from './CSSPropertyNameMap';
4
- import { InteractionItemTrigger } from '@cntrl-site/sdk';
4
+ import { FillLayer, InteractionItemTrigger } from '@cntrl-site/sdk';
5
5
 
6
6
  export class ItemInteractionController implements ItemInteractionCtrl {
7
7
  private transitionsInProgress: Set<string> = new Set();
@@ -15,12 +15,12 @@ export class ItemInteractionController implements ItemInteractionCtrl {
15
15
  this.registry.register(itemId, this);
16
16
  }
17
17
 
18
- getState(keys: string[]) {
18
+ getState<T>(keys: string[]) {
19
19
  const stateProps = this.registry.getStatePropsForItem(this.itemId);
20
- const styles = keys.reduce<Record<string, string | number>>((map, styleKey) => {
20
+ const styles = keys.reduce<Record<string, T>>((map, styleKey) => {
21
21
  const prop = stateProps[styleKey];
22
22
  if (prop?.value === undefined) return map;
23
- map[styleKey] = prop.value;
23
+ map[styleKey] = prop.value as T;
24
24
  return map;
25
25
  }, {});
26
26
  const transition = getTransition(stateProps, keys);
@@ -1,7 +1,7 @@
1
- import { ArticleItemType, InteractionItemTrigger, ItemState } from '@cntrl-site/sdk';
1
+ import { ArticleItemType, FillLayer, InteractionItemTrigger, ItemState } from '@cntrl-site/sdk';
2
2
 
3
3
  export interface ItemInteractionCtrl {
4
- getState(keys: string[]): StateCSSInfo;
4
+ getState<T>(keys: string[]): StateCSSInfo<T>;
5
5
  getHasTrigger(itemId: string, triggerType: InteractionItemTrigger['type']): boolean;
6
6
  sendTrigger(type: 'click' | 'hover-in' | 'hover-out'): void;
7
7
  handleTransitionEnd?: (styleKey: string) => void;
@@ -13,19 +13,19 @@ export interface ItemInteractionCtrl {
13
13
 
14
14
  export interface InteractionsRegistryPort {
15
15
  register(itemId: string, ctrl: ItemInteractionCtrl): void;
16
- getStatePropsForItem(itemId: string): StateProps;
16
+ getStatePropsForItem(itemId: string): StateProps<unknown>;
17
17
  getItemAvailableTriggers(itemId: string): Set<InteractionItemTrigger['type']>;
18
18
  notifyItemTrigger(itemId: string, type: TriggerType): void;
19
19
  notifyTransitionEnd(itemId: string): void;
20
20
  }
21
21
 
22
- type StateCSSInfo = {
23
- styles: Partial<Record<string, string | number>>;
22
+ type StateCSSInfo<T> = {
23
+ styles: Partial<Record<string, T>>;
24
24
  transition?: string;
25
25
  };
26
26
 
27
- type StateProps = Record<keyof ItemState<ArticleItemType>, {
28
- value?: string | number;
27
+ type StateProps<T> = Record<keyof ItemState<ArticleItemType>, {
28
+ value?: T;
29
29
  transition?: {
30
30
  timing: string;
31
31
  duration: number;