@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,6 +2,7 @@ import { KeyframeType, KeyframeValueMap } from '@cntrl-site/sdk';
2
2
  import { CntrlColor } from '@cntrl-site/color';
3
3
  import { binSearchInsertAt, createInsert } from '../binSearchInsertAt';
4
4
  import { rangeMap } from '../rangeMap';
5
+ import { FillLayer } from '@cntrl-site/sdk/dist/types/article/Item';
5
6
 
6
7
  export interface AnimationData<T extends KeyframeType> {
7
8
  position: number;
@@ -18,6 +19,12 @@ export type KeyframesMap = {
18
19
  [T in KeyframeType]: AnimationData<T>[];
19
20
  };
20
21
 
22
+ type ColorPoint = {
23
+ id: string;
24
+ value: string;
25
+ position: number;
26
+ };
27
+
21
28
  const compare = (lhs: { position: number }, rhs: { position: number }) => lhs.position - rhs.position;
22
29
  const insertBin = createInsert(binSearchInsertAt, compare);
23
30
 
@@ -93,40 +100,32 @@ export class Animator {
93
100
  };
94
101
  }
95
102
 
96
- getColor(
97
- values: KeyframeValueMap[KeyframeType.Color],
103
+ getBorderFill(
104
+ values: KeyframeValueMap[KeyframeType.BorderFill],
98
105
  pos: number
99
- ): KeyframeValueMap[KeyframeType.Color] {
100
- const keyframes = this.keyframesMap[KeyframeType.Color];
106
+ ): FillLayer[] {
107
+ const keyframes = this.keyframesMap[KeyframeType.BorderFill];
101
108
  if (!keyframes || !keyframes.length) return values;
102
109
  if (keyframes.length === 1) {
103
110
  const [keyframe] = keyframes;
104
- return {
105
- color: keyframe.value.color
106
- };
111
+ return keyframe.value;
107
112
  }
108
- const { start, end } = this.getStartEnd<KeyframeType.Color>(pos, keyframes);
109
- return {
110
- color: this.getRangeColor(start, end, pos)
111
- };
113
+ const { start, end } = this.getStartEnd<KeyframeType.BorderFill>(pos, keyframes);
114
+ return this.getRangeGradient(start, end, pos);
112
115
  }
113
116
 
114
- getBorderColor(
115
- values: KeyframeValueMap[KeyframeType.BorderColor],
117
+ getFill(
118
+ values: KeyframeValueMap[KeyframeType.Fill],
116
119
  pos: number
117
- ): KeyframeValueMap[KeyframeType.BorderColor] {
118
- const keyframes = this.keyframesMap[KeyframeType.BorderColor];
120
+ ): FillLayer[] {
121
+ const keyframes = this.keyframesMap[KeyframeType.Fill];
119
122
  if (!keyframes || !keyframes.length) return values;
120
123
  if (keyframes.length === 1) {
121
124
  const [keyframe] = keyframes;
122
- return {
123
- color: keyframe.value.color
124
- };
125
+ return keyframe.value;
125
126
  }
126
- const { start, end } = this.getStartEnd<KeyframeType.BorderColor>(pos, keyframes);
127
- return {
128
- color: this.getRangeColor(start, end, pos)
129
- };
127
+ const { start, end } = this.getStartEnd<KeyframeType.Fill>(pos, keyframes);
128
+ return this.getRangeGradient(start, end, pos);
130
129
  }
131
130
 
132
131
  getRadius(
@@ -338,8 +337,8 @@ export class Animator {
338
337
  }
339
338
 
340
339
  private getRangeColor(
341
- start: AnimationData<KeyframeType.Color | KeyframeType.BorderColor | KeyframeType.TextColor>,
342
- end: AnimationData<KeyframeType.Color | KeyframeType.BorderColor | KeyframeType.TextColor>,
340
+ start: AnimationData<KeyframeType.TextColor>,
341
+ end: AnimationData<KeyframeType.TextColor>,
343
342
  position: number
344
343
  ): string {
345
344
  const rangeAmount = rangeMap(position, start.position, end.position, 0, 1, true);
@@ -348,6 +347,207 @@ export class Animator {
348
347
  const mixedColor = startColor.mix(endColor, rangeAmount);
349
348
  return mixedColor.fmt('oklch');
350
349
  }
350
+
351
+ private getRangeGradient(
352
+ start: AnimationData<KeyframeType.Fill | KeyframeType.BorderFill>,
353
+ end: AnimationData<KeyframeType.Fill | KeyframeType.BorderFill>,
354
+ position: number
355
+ ): FillLayer[] {
356
+ const rangeAmount = rangeMap(position, start.position, end.position, 0, 1, true);
357
+ const allLayerIds = new Set([
358
+ ...start.value.map(layer => layer.id),
359
+ ...end.value.map(layer => layer.id)
360
+ ]);
361
+ const result: FillLayer[] = [];
362
+ for (const layerId of allLayerIds) {
363
+ const startLayers = start.value.find(layer => layer.id === layerId);
364
+ const endLayers = end.value.find(layer => layer.id === layerId);
365
+ const startLayersArray = Array.isArray(startLayers) ? startLayers : startLayers ? [startLayers] : [];
366
+ const endLayersArray = Array.isArray(endLayers) ? endLayers : endLayers ? [endLayers] : [];
367
+ if (startLayersArray.length > 0 && endLayersArray.length === 0) {
368
+ result.push(...startLayersArray);
369
+ continue;
370
+ }
371
+ if (startLayersArray.length === 0 && endLayersArray.length > 0) {
372
+ result.push(...endLayersArray);
373
+ continue;
374
+ }
375
+ if (startLayersArray.length > 0 && endLayersArray.length > 0) {
376
+ result.push(...this.interpolateFillLayerArray(startLayersArray, endLayersArray, rangeAmount));
377
+ }
378
+ }
379
+
380
+ return result;
381
+ }
382
+
383
+ private interpolateFillLayerArray(startLayers: FillLayer[], endLayers: FillLayer[], rangeAmount: number): FillLayer[] {
384
+ const startLayerMap = new Map(startLayers.map(layer => [layer.id, layer]));
385
+ const endLayerMap = new Map(endLayers.map(layer => [layer.id, layer]));
386
+ const allLayerIds = new Set([
387
+ ...startLayers.map(layer => layer.id),
388
+ ...endLayers.map(layer => layer.id)
389
+ ]);
390
+
391
+ const interpolatedLayers: FillLayer[] = [];
392
+
393
+ for (const layerId of allLayerIds) {
394
+ const startLayer = startLayerMap.get(layerId);
395
+ const endLayer = endLayerMap.get(layerId);
396
+ if (startLayer && !endLayer) {
397
+ interpolatedLayers.push(startLayer);
398
+ continue;
399
+ }
400
+ if (!startLayer && endLayer) {
401
+ interpolatedLayers.push(endLayer);
402
+ continue;
403
+ }
404
+ if (startLayer && endLayer) {
405
+ interpolatedLayers.push(this.interpolateFillLayer(startLayer, endLayer, rangeAmount));
406
+ }
407
+ }
408
+ const result: FillLayer[] = [];
409
+ for (const startLayer of startLayers) {
410
+ const interpolatedLayer = interpolatedLayers.find(layer => layer.id === startLayer.id);
411
+ if (interpolatedLayer) {
412
+ result.push(interpolatedLayer);
413
+ }
414
+ }
415
+ for (const endLayer of endLayers) {
416
+ if (!startLayerMap.has(endLayer.id)) {
417
+ const interpolatedLayer = interpolatedLayers.find(layer => layer.id === endLayer.id);
418
+ if (interpolatedLayer) {
419
+ result.push(interpolatedLayer);
420
+ }
421
+ }
422
+ }
423
+
424
+ return result;
425
+ }
426
+
427
+ private interpolateFillLayer(startLayer: FillLayer, endLayer: FillLayer, rangeAmount: number): FillLayer {
428
+ if (startLayer.type !== endLayer.type) {
429
+ return startLayer;
430
+ }
431
+
432
+ switch (startLayer.type) {
433
+ case 'solid': {
434
+ const endSolidLayer = endLayer as typeof startLayer;
435
+ const startColor = CntrlColor.parse(startLayer.value);
436
+ const endColor = CntrlColor.parse(endSolidLayer.value);
437
+ const mixedColor = startColor.mix(endColor, rangeAmount);
438
+ return {
439
+ ...startLayer,
440
+ value: mixedColor.fmt('oklch')
441
+ };
442
+ }
443
+
444
+ case 'linear-gradient': {
445
+ const endLinearLayer = endLayer as typeof startLayer;
446
+ const interpolatedColors = this.interpolateColorStops(startLayer.colors, endLinearLayer.colors, rangeAmount);
447
+ const angle = startLayer.angle + (endLinearLayer.angle - startLayer.angle) * rangeAmount;
448
+ const startPoint: [number, number] = [
449
+ startLayer.start[0] + (endLinearLayer.start[0] - startLayer.start[0]) * rangeAmount,
450
+ startLayer.start[1] + (endLinearLayer.start[1] - startLayer.start[1]) * rangeAmount
451
+ ];
452
+ const endPoint: [number, number] = [
453
+ startLayer.end[0] + (endLinearLayer.end[0] - startLayer.end[0]) * rangeAmount,
454
+ startLayer.end[1] + (endLinearLayer.end[1] - startLayer.end[1]) * rangeAmount
455
+ ];
456
+ return {
457
+ ...startLayer,
458
+ colors: interpolatedColors,
459
+ angle,
460
+ start: startPoint,
461
+ end: endPoint
462
+ };
463
+ }
464
+
465
+ case 'radial-gradient': {
466
+ const endRadialLayer = endLayer as typeof startLayer;
467
+ const interpolatedColors = this.interpolateColorStops(startLayer.colors, endRadialLayer.colors, rangeAmount);
468
+ const angle = startLayer.angle + (endRadialLayer.angle - startLayer.angle) * rangeAmount;
469
+ const center: [number, number] = [
470
+ startLayer.center[0] + (endRadialLayer.center[0] - startLayer.center[0]) * rangeAmount,
471
+ startLayer.center[1] + (endRadialLayer.center[1] - startLayer.center[1]) * rangeAmount
472
+ ];
473
+ const diameter = startLayer.diameter + (endRadialLayer.diameter - startLayer.diameter) * rangeAmount;
474
+ return {
475
+ ...startLayer,
476
+ colors: interpolatedColors,
477
+ angle,
478
+ center,
479
+ diameter
480
+ };
481
+ }
482
+
483
+ case 'conic-gradient': {
484
+ const endConicLayer = endLayer as typeof startLayer;
485
+ const interpolatedColors = this.interpolateColorStops(startLayer.colors, endConicLayer.colors, rangeAmount);
486
+ const angle = startLayer.angle + (endConicLayer.angle - startLayer.angle) * rangeAmount;
487
+ const center: [number, number] = [
488
+ startLayer.center[0] + (endConicLayer.center[0] - startLayer.center[0]) * rangeAmount,
489
+ startLayer.center[1] + (endConicLayer.center[1] - startLayer.center[1]) * rangeAmount
490
+ ];
491
+ return {
492
+ ...startLayer,
493
+ colors: interpolatedColors,
494
+ angle,
495
+ center
496
+ };
497
+ }
498
+
499
+ case 'image': {
500
+ const endImageLayer = endLayer as typeof startLayer;
501
+ const opacity = startLayer.opacity + (endImageLayer.opacity - startLayer.opacity) * rangeAmount;
502
+ const backgroundSize = startLayer.backgroundSize + (endImageLayer.backgroundSize - startLayer.backgroundSize) * rangeAmount;
503
+ return {
504
+ ...startLayer,
505
+ opacity,
506
+ backgroundSize
507
+ };
508
+ }
509
+
510
+ default:
511
+ return startLayer;
512
+ }
513
+ }
514
+
515
+ private interpolateColorStops(startColors: ColorPoint[], endColors: ColorPoint[], rangeAmount: number): ColorPoint[] {
516
+ const startColorMap = new Map(startColors.map(stop => [stop.id, stop]));
517
+ const endColorMap = new Map(endColors.map(stop => [stop.id, stop]));
518
+ const allStopIds = new Set([
519
+ ...startColors.map(stop => stop.id),
520
+ ...endColors.map(stop => stop.id)
521
+ ]);
522
+
523
+ const interpolatedStops: ColorPoint[] = [];
524
+
525
+ for (const stopId of allStopIds) {
526
+ const startStop = startColorMap.get(stopId);
527
+ const endStop = endColorMap.get(stopId);
528
+ if (startStop && !endStop) {
529
+ interpolatedStops.push(startStop);
530
+ continue;
531
+ }
532
+ if (!startStop && endStop) {
533
+ interpolatedStops.push(endStop);
534
+ continue;
535
+ }
536
+ if (startStop && endStop) {
537
+ const startColor = CntrlColor.parse(startStop.value);
538
+ const endColor = CntrlColor.parse(endStop.value);
539
+ const mixedColor = startColor.mix(endColor, rangeAmount);
540
+ const position = startStop.position + (endStop.position - startStop.position) * rangeAmount;
541
+
542
+ interpolatedStops.push({
543
+ id: stopId,
544
+ value: mixedColor.fmt('oklch'),
545
+ position
546
+ });
547
+ }
548
+ }
549
+ return interpolatedStops.sort((a, b) => a.position - b.position);
550
+ }
351
551
  }
352
552
 
353
553
  function createKeyframesMap(): KeyframesMap {
@@ -356,9 +556,8 @@ function createKeyframesMap(): KeyframesMap {
356
556
  [KeyframeType.Position]: [],
357
557
  [KeyframeType.BorderWidth]: [],
358
558
  [KeyframeType.BorderRadius]: [],
359
- [KeyframeType.Color]: [],
360
559
  [KeyframeType.Rotation]: [],
361
- [KeyframeType.BorderColor]: [],
560
+ [KeyframeType.BorderFill]: [],
362
561
  [KeyframeType.Opacity]: [],
363
562
  [KeyframeType.Scale]: [],
364
563
  [KeyframeType.Blur]: [],
@@ -366,6 +565,7 @@ function createKeyframesMap(): KeyframesMap {
366
565
  [KeyframeType.LetterSpacing]: [],
367
566
  [KeyframeType.WordSpacing]: [],
368
567
  [KeyframeType.TextColor]: [],
369
- [KeyframeType.FXParams]: []
568
+ [KeyframeType.FXParams]: [],
569
+ [KeyframeType.Fill]: []
370
570
  };
371
571
  }
@@ -0,0 +1,63 @@
1
+ import { areFillsVisible, FillLayer } from './areFillsVisible';
2
+
3
+ const solidFillVisible: FillLayer = {
4
+ type: 'solid',
5
+ value: 'rgba(0, 0, 0, 1)',
6
+ };
7
+
8
+ const linearFillVisible: FillLayer = {
9
+ type: 'linear-gradient',
10
+ colors: [{ value: 'rgba(0, 0, 0, 1)' }, { value: 'rgba(255, 255, 255, 1)' }],
11
+ };
12
+
13
+ const radialFillVisible: FillLayer = {
14
+ type: 'radial-gradient',
15
+ colors: [{ value: 'rgba(0, 0, 0, 1)' }, { value: 'rgba(255, 255, 255, 1)' }],
16
+ };
17
+
18
+ const conicFillVisible: FillLayer = {
19
+ type: 'conic-gradient',
20
+ colors: [{ value: 'rgba(0, 0, 0, 1)' }, { value: 'rgba(255, 255, 255, 1)' }],
21
+ };
22
+
23
+ const imageFillVisible: FillLayer = {
24
+ type: 'image',
25
+ opacity: 1,
26
+ };
27
+
28
+ const solidFillTransparent: FillLayer = {
29
+ type: 'solid',
30
+ value: 'rgba(0, 0, 0, 0)',
31
+ };
32
+
33
+ const linearFillTransparent: FillLayer = {
34
+ type: 'linear-gradient',
35
+ colors: [{ value: 'rgba(0, 0, 0, 0)' }, { value: 'rgba(255, 255, 255, 0)' }],
36
+ };
37
+
38
+ const radialFillTransparent: FillLayer = {
39
+ type: 'radial-gradient',
40
+ colors: [{ value: 'rgba(0, 0, 0, 0)' }, { value: 'rgba(255, 255, 255, 0)' }],
41
+ };
42
+
43
+ const conicFillTransparent: FillLayer = {
44
+ type: 'conic-gradient',
45
+ colors: [{ value: 'rgba(0, 0, 0, 0)' }, { value: 'rgba(255, 255, 255, 0)' }],
46
+ };
47
+
48
+ const imageFillTransparent: FillLayer = {
49
+ type: 'image',
50
+ opacity: 0,
51
+ };
52
+
53
+ describe('areFillsVisible', () => {
54
+ it('should return true if the fills are visible', () => {
55
+ expect(areFillsVisible([solidFillVisible, linearFillVisible, radialFillVisible, conicFillVisible, imageFillVisible])).toBe(true);
56
+ });
57
+ it('should return false if the fills are transparent', () => {
58
+ expect(areFillsVisible([solidFillTransparent, linearFillTransparent, radialFillTransparent, conicFillTransparent, imageFillTransparent])).toBe(false);
59
+ });
60
+ it('should return true if some fills are visible and some are transparent', () => {
61
+ expect(areFillsVisible([solidFillVisible, solidFillTransparent, linearFillVisible, linearFillTransparent, radialFillVisible, radialFillTransparent, conicFillVisible, conicFillTransparent, imageFillVisible, imageFillTransparent])).toBe(true);
62
+ });
63
+ });
@@ -0,0 +1,37 @@
1
+ import { CntrlColor } from '@cntrl-site/color';
2
+
3
+ export function areFillsVisible(fills: FillLayer[]) {
4
+ return fills.some(fill => {
5
+ switch (fill.type) {
6
+ case 'solid':
7
+ return CntrlColor.parse(fill.value).getAlpha() !== 0;
8
+ case 'linear-gradient':
9
+ return fill.colors.some(color => CntrlColor.parse(color.value).getAlpha() !== 0);
10
+ case 'radial-gradient':
11
+ return fill.colors.some(color => CntrlColor.parse(color.value).getAlpha() !== 0);
12
+ case 'conic-gradient':
13
+ return fill.colors.some(color => CntrlColor.parse(color.value).getAlpha() !== 0);
14
+ case 'image':
15
+ return fill.opacity !== 0;
16
+ default:
17
+ return true;
18
+ }
19
+ });
20
+ }
21
+
22
+ export type FillLayer = {
23
+ type: 'solid';
24
+ value: string;
25
+ } | {
26
+ type: 'linear-gradient';
27
+ colors: { value: string; }[];
28
+ } | {
29
+ type: 'radial-gradient';
30
+ colors: { value: string; }[];
31
+ } | {
32
+ type: 'conic-gradient';
33
+ colors: { value: string; }[];
34
+ } | {
35
+ type: 'image';
36
+ opacity: number;
37
+ }
@@ -0,0 +1,46 @@
1
+ import { FillLayer } from '@cntrl-site/sdk/dist/types/article/Item';
2
+
3
+ export function getFill(fill: FillLayer) {
4
+ if (fill.type === 'linear-gradient' && Array.isArray(fill.colors)) {
5
+ return `linear-gradient(${fill.angle}deg, ${fill.colors
6
+ .map(c => `${c.value} ${c.position}%`)
7
+ .join(', ')})`;
8
+ }
9
+
10
+ if (fill.type === 'radial-gradient' && Array.isArray(fill.colors)) {
11
+ const center = fill.center
12
+ ? `${fill.center[0] * 100}% ${fill.center[1] * 100}%`
13
+ : '50% 50%';
14
+ const diameter = fill.diameter;
15
+ return `radial-gradient(circle ${diameter * 100} at ${center}, ${fill.colors
16
+ .map(c => `${c.value} ${c.position}%`)
17
+ .join(', ')})`;
18
+ }
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 = (fill.angle ?? 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
+
32
+ return `conic-gradient(from ${angle + 90}deg at ${center}, ${colorsWithWrap
33
+ .map(c => `${c.value} ${c.position}%`)
34
+ .join(', ')})`;
35
+ }
36
+
37
+ if (fill.type === 'image' && fill.src) {
38
+ return `url(${fill.src})`;
39
+ }
40
+
41
+ if (fill.type === 'solid') {
42
+ return fill.value;
43
+ }
44
+
45
+ return 'transparent';
46
+ }
@@ -1,5 +1,5 @@
1
- export function getStyleFromItemStateAndParams<T extends string | number>(
2
- stateValue: string | number | undefined,
1
+ export function getStyleFromItemStateAndParams<T>(
2
+ stateValue: T | undefined,
3
3
  paramsValue: T | undefined
4
4
  ): T | undefined {
5
5
  return (stateValue as T) !== undefined
@@ -1,66 +0,0 @@
1
- import { createContext, FC, PropsWithChildren, useState } from 'react';
2
- import { Interaction, InteractionTrigger } from '@cntrl-site/sdk';
3
-
4
- const defaultState = {
5
- interactionsStatesMap: {},
6
- interactions: [],
7
- transitionTo: () => {},
8
- getItemTrigger: () => null
9
- };
10
-
11
- export const InteractionsContextOld = createContext<{
12
- interactionsStatesMap: StatesMap,
13
- interactions: Interaction[],
14
- transitionTo: (interactionId: string, stateId: string) => void,
15
- getItemTrigger: (itemId: string, triggerType: TriggerType) => Trigger | null
16
- }>(defaultState);
17
-
18
- interface Props {
19
- interactions: Interaction[];
20
- }
21
-
22
- export const InteractionsProvider: FC<PropsWithChildren<Props>> = ({ interactions, children }) => {
23
- const defaultStatesMap = interactions.reduce<Record<string, string>>((map, { id, startStateId }) => {
24
- map[id] = startStateId;
25
- return map;
26
- }, {});
27
- const [interactionsStatesMap, setInteractionsStatesMap] = useState(defaultStatesMap);
28
- const transitionTo = (interactionId: string, stateId: string) => {
29
- setInteractionsStatesMap((map) => ({ ...map, [interactionId]: stateId }));
30
- };
31
- const getItemTrigger = (itemId: string, triggerType: TriggerType): Trigger | null => {
32
- for (const interaction of interactions) {
33
- const activeStateId = interactionsStatesMap[interaction.id];
34
- const matchingTrigger = interaction.triggers.find((trigger) =>
35
- trigger.itemId === itemId
36
- && trigger.from === activeStateId
37
- && trigger.type === triggerType
38
- );
39
- if (matchingTrigger) {
40
- return {
41
- id: interaction.id,
42
- from: matchingTrigger.from,
43
- to: matchingTrigger.to,
44
- };
45
- }
46
- }
47
- return null;
48
- };
49
- return (
50
- <InteractionsContextOld.Provider value={{
51
- transitionTo,
52
- interactionsStatesMap,
53
- interactions,
54
- getItemTrigger
55
- }}
56
- >
57
- {children}
58
- </InteractionsContextOld.Provider>
59
- );
60
- };
61
-
62
- type StatesMap = Record<InteractionId, StateId>;
63
- type Trigger = { id: InteractionId, from: StateId, to: StateId };
64
- type TriggerType = InteractionTrigger['type'];
65
- type InteractionId = string;
66
- type StateId = string;
@@ -1,97 +0,0 @@
1
- import React from 'react';
2
- import { render, waitFor } from '@testing-library/react';
3
- import { InteractionsProvider, InteractionsContextOld } from './InteractionsContext.old';
4
- import { Interaction } from '@cntrl-site/sdk';
5
-
6
- describe('InteractionsProvider', () => {
7
- const interactions: Interaction[] = [
8
- {
9
- id: 'interaction1',
10
- startStateId: 'state1',
11
- triggers: [
12
- { itemId: 'item1', type: 'click', from: 'state1', to: 'state2' }
13
- ],
14
- states: [{ id: 'state1' }, { id: 'state2' }]
15
- },
16
- {
17
- id: 'interaction2',
18
- startStateId: 'state3',
19
- triggers: [
20
- { itemId: 'item2', type: 'hover-in', from: 'state3', to: 'state4' }
21
- ],
22
- states: [{ id: 'state3' }, { id: 'state4' }]
23
- }
24
- ];
25
-
26
- it('should generate correct default interactionsStatesMap', () => {
27
- let contextValue;
28
-
29
- render(
30
- <InteractionsProvider interactions={interactions}>
31
- <InteractionsContextOld.Consumer>
32
- {value => {
33
- contextValue = value;
34
- return null;
35
- }}
36
- </InteractionsContextOld.Consumer>
37
- </InteractionsProvider>
38
- );
39
-
40
- expect(contextValue!).toBeDefined();
41
- expect(contextValue!.interactionsStatesMap).toEqual({
42
- interaction1: 'state1',
43
- interaction2: 'state3'
44
- });
45
- });
46
-
47
- it('should correctly update interactionsStatesMap when transitionTo is called', async () => {
48
- let contextValue;
49
-
50
- render(
51
- <InteractionsProvider interactions={interactions}>
52
- <InteractionsContextOld.Consumer>
53
- {value => {
54
- contextValue = value;
55
- return null;
56
- }}
57
- </InteractionsContextOld.Consumer>
58
- </InteractionsProvider>
59
- );
60
-
61
- expect(contextValue!).toBeDefined();
62
- contextValue!.transitionTo('interaction1', 'state2');
63
-
64
- await waitFor(() => {
65
- expect(contextValue!.interactionsStatesMap['interaction1']).toBe('state2');
66
- });
67
- });
68
-
69
- it('should return the correct trigger using getItemTrigger', () => {
70
- let contextValue;
71
-
72
- render(
73
- <InteractionsProvider interactions={interactions}>
74
- <InteractionsContextOld.Consumer>
75
- {value => {
76
- contextValue = value;
77
- return null;
78
- }}
79
- </InteractionsContextOld.Consumer>
80
- </InteractionsProvider>
81
- );
82
-
83
- expect(contextValue!).toBeDefined();
84
-
85
- // Check the correct trigger is returned
86
- const trigger = contextValue!.getItemTrigger('item1', 'click');
87
- expect(trigger).toEqual({
88
- id: 'interaction1',
89
- from: 'state1',
90
- to: 'state2',
91
- });
92
-
93
- // Check that no trigger is returned when conditions don't match
94
- const noTrigger = contextValue!.getItemTrigger('item1', 'hover-on');
95
- expect(noTrigger).toBeNull();
96
- });
97
- });