@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.
- package/.idea/codeStyles/codeStyleConfig.xml +5 -0
- package/.idea/modules.xml +8 -0
- package/.idea/sdk-nextjs.iml +12 -0
- package/.idea/vcs.xml +6 -0
- package/lib/components/items/FileItem/ImageItem.js +49 -22
- package/lib/components/items/FileItem/VideoItem.js +48 -21
- package/lib/components/items/FileItem/useFileItem.js +12 -5
- package/lib/components/items/RectangleItem/RectangleItem.js +78 -22
- package/lib/components/items/RectangleItem/useRectangleItem.js +15 -8
- package/lib/interactions/CSSPropertyNameMap.js +18 -18
- package/lib/utils/Animator/Animator.js +171 -19
- package/lib/utils/areFillsVisible/areFillsVisible.js +23 -0
- package/lib/utils/getFill.js +43 -0
- package/package.json +2 -2
- package/src/components/items/CodeEmbedItem/CodeEmbedItem.tsx +1 -1
- package/src/components/items/ComponentItem/ComponentItem.tsx +1 -1
- package/src/components/items/CompoundItem/CompoundChild.tsx +2 -2
- package/src/components/items/CompoundItem/CompoundItem.tsx +1 -1
- package/src/components/items/CustomItem/CustomItem.tsx +1 -1
- package/src/components/items/EmbedVideoItem/VimeoEmbed.tsx +2 -2
- package/src/components/items/EmbedVideoItem/YoutubeEmbed.tsx +2 -2
- package/src/components/items/FileItem/ImageItem.tsx +63 -10
- package/src/components/items/FileItem/VideoItem.tsx +64 -11
- package/src/components/items/FileItem/useFileItem.ts +14 -6
- package/src/components/items/GroupItem/GroupItem.tsx +1 -1
- package/src/components/items/Item.tsx +2 -2
- package/src/components/items/RectangleItem/RectangleItem.tsx +126 -24
- package/src/components/items/RectangleItem/useRectangleItem.ts +17 -10
- package/src/components/items/RichTextItem/RichTextItem.tsx +5 -5
- package/src/interactions/CSSPropertyNameMap.ts +18 -19
- package/src/interactions/ItemInteractionCtrl.ts +4 -4
- package/src/interactions/types.ts +7 -7
- package/src/utils/Animator/Animator.ts +227 -27
- package/src/utils/areFillsVisible/areFillsVisible.test.ts +63 -0
- package/src/utils/areFillsVisible/areFillsVisible.ts +37 -0
- package/src/utils/getFill.ts +46 -0
- package/src/utils/getStyleFromItemStateAndParams.ts +2 -2
- package/src/provider/InteractionsContext.old.tsx +0 -66
- 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
|
-
|
|
97
|
-
values: KeyframeValueMap[KeyframeType.
|
|
103
|
+
getBorderFill(
|
|
104
|
+
values: KeyframeValueMap[KeyframeType.BorderFill],
|
|
98
105
|
pos: number
|
|
99
|
-
):
|
|
100
|
-
const keyframes = this.keyframesMap[KeyframeType.
|
|
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.
|
|
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
|
-
|
|
115
|
-
values: KeyframeValueMap[KeyframeType.
|
|
117
|
+
getFill(
|
|
118
|
+
values: KeyframeValueMap[KeyframeType.Fill],
|
|
116
119
|
pos: number
|
|
117
|
-
):
|
|
118
|
-
const keyframes = this.keyframesMap[KeyframeType.
|
|
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.
|
|
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.
|
|
342
|
-
end: AnimationData<KeyframeType.
|
|
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.
|
|
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
|
|
2
|
-
stateValue:
|
|
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
|
-
});
|