@cntrl-site/sdk-nextjs 1.8.40 → 1.9.1

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 (134) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +4 -4
  3. package/TODO.md +1 -1
  4. package/cntrl-site-sdk-nextjs-1.8.40.tgz +0 -0
  5. package/eslint.config.mjs +35 -35
  6. package/jest.config.js +6 -6
  7. package/lib/common/useItemFXData.js +7 -7
  8. package/lib/components/Article.js +5 -5
  9. package/lib/components/LayoutStyle.js +4 -4
  10. package/lib/components/Section/Section.js +27 -27
  11. package/lib/components/items/CodeEmbedItem/CodeEmbedItem.js +36 -36
  12. package/lib/components/items/ComponentItem/ComponentItem.js +13 -13
  13. package/lib/components/items/CompoundItem/CompoundChild.js +20 -20
  14. package/lib/components/items/CompoundItem/CompoundItem.js +14 -14
  15. package/lib/components/items/CustomItem/CustomItem.js +9 -9
  16. package/lib/components/items/EmbedVideoItem/VimeoEmbed.js +24 -24
  17. package/lib/components/items/EmbedVideoItem/YoutubeEmbed.js +27 -27
  18. package/lib/components/items/FileItem/ImageItem.js +85 -58
  19. package/lib/components/items/FileItem/VideoItem.js +107 -80
  20. package/lib/components/items/FileItem/useFileItem.js +12 -5
  21. package/lib/components/items/GroupItem/GroupItem.js +14 -14
  22. package/lib/components/items/Item.js +30 -30
  23. package/lib/components/items/RectangleItem/RectangleItem.js +92 -36
  24. package/lib/components/items/RectangleItem/useRectangleItem.js +15 -8
  25. package/lib/components/items/RichTextItem/RichTextItem.js +22 -22
  26. package/lib/interactions/CSSPropertyNameMap.js +18 -18
  27. package/lib/utils/Animator/Animator.js +171 -19
  28. package/lib/utils/RichTextConverter/RichTextConverter.js +21 -21
  29. package/lib/utils/areFillsVisible/areFillsVisible.js +23 -0
  30. package/lib/utils/getFill.js +43 -0
  31. package/package.json +58 -57
  32. package/src/common/useCurrentLayout.ts +52 -52
  33. package/src/common/useExemplary.ts +9 -9
  34. package/src/common/useItemFXData.ts +47 -47
  35. package/src/common/useKeyframeValue.ts +85 -85
  36. package/src/common/useRegisterResize.ts +16 -16
  37. package/src/components/Article.tsx +73 -73
  38. package/src/components/ArticleWrapper.tsx +21 -21
  39. package/src/components/Head.tsx +56 -56
  40. package/src/components/LayoutStyle.tsx +21 -21
  41. package/src/components/Page.tsx +33 -33
  42. package/src/components/ScrollPlaybackVideo.tsx +56 -56
  43. package/src/components/Section/Section.tsx +127 -127
  44. package/src/components/Section/SectionImage.tsx +48 -48
  45. package/src/components/Section/SectionVideo.tsx +144 -144
  46. package/src/components/Section/useSectionColor.ts +19 -19
  47. package/src/components/Section/useSectionHeightMap.ts +19 -19
  48. package/src/components/items/CodeEmbedItem/CodeEmbedItem.tsx +145 -145
  49. package/src/components/items/CodeEmbedItem/useCodeEmbedItem.ts +36 -36
  50. package/src/components/items/ComponentItem/ComponentItem.tsx +67 -67
  51. package/src/components/items/ComponentItem/useComponentItem.ts +35 -35
  52. package/src/components/items/CompoundItem/CompoundChild.tsx +150 -150
  53. package/src/components/items/CompoundItem/CompoundItem.tsx +70 -70
  54. package/src/components/items/CompoundItem/useCompoundItem.ts +21 -21
  55. package/src/components/items/CustomItem/CustomItem.tsx +48 -48
  56. package/src/components/items/EmbedVideoItem/VimeoEmbed.tsx +206 -206
  57. package/src/components/items/EmbedVideoItem/YoutubeEmbed.tsx +193 -193
  58. package/src/components/items/EmbedVideoItem/useEmbedVideoItem.ts +46 -46
  59. package/src/components/items/FileItem/ImageItem.tsx +211 -158
  60. package/src/components/items/FileItem/VideoItem.tsx +328 -275
  61. package/src/components/items/FileItem/useFileItem.ts +81 -73
  62. package/src/components/items/GroupItem/GroupItem.tsx +81 -81
  63. package/src/components/items/GroupItem/useGroupItem.ts +34 -34
  64. package/src/components/items/Item.tsx +251 -251
  65. package/src/components/items/LinkWrapper.tsx +39 -39
  66. package/src/components/items/RectangleItem/RectangleItem.tsx +199 -97
  67. package/src/components/items/RectangleItem/useRectangleItem.ts +89 -82
  68. package/src/components/items/RichTextItem/RichTextItem.tsx +107 -107
  69. package/src/components/items/RichTextItem/useRichTextItem.ts +12 -12
  70. package/src/components/items/RichTextWrapper.tsx +15 -15
  71. package/src/components/items/itemsMap.ts +28 -28
  72. package/src/components/items/useDraggable.ts +252 -252
  73. package/src/components/items/useItemAngle.ts +13 -13
  74. package/src/components/items/useItemArea.ts +35 -35
  75. package/src/components/items/useItemPointerEvents.ts +23 -23
  76. package/src/components/items/useItemScale.ts +17 -17
  77. package/src/components/items/useItemTriggers.ts +15 -15
  78. package/src/components/items/useRichTextItemValues.ts +65 -65
  79. package/src/components/items/useSizing.ts +25 -25
  80. package/src/components/items/useStickyItemTop.ts +21 -21
  81. package/src/components/useLayoutContext.ts +7 -7
  82. package/src/index.ts +24 -24
  83. package/src/interactions/CSSPropertyNameMap.ts +37 -38
  84. package/src/interactions/InteractionsRegistry.ts +380 -380
  85. package/src/interactions/ItemInteractionCtrl.ts +78 -78
  86. package/src/interactions/getTransition.ts +27 -27
  87. package/src/interactions/types.ts +36 -36
  88. package/src/interactions/useItemInteractionCtrl.ts +18 -18
  89. package/src/provider/ArticleRectContext.ts +4 -4
  90. package/src/provider/CntrlContext.ts +5 -5
  91. package/src/provider/CntrlProvider.tsx +11 -11
  92. package/src/provider/CntrlSdkContext.ts +83 -83
  93. package/src/provider/CustomItemRegistry.ts +14 -14
  94. package/src/provider/CustomItemTypes.ts +3 -3
  95. package/src/provider/CustomSectionRegistry.ts +25 -25
  96. package/src/provider/InteractionsContext.tsx +59 -59
  97. package/src/provider/Keyframes.ts +11 -11
  98. package/src/provider/KeyframesContext.ts +5 -5
  99. package/src/provider/LayoutContext.ts +3 -3
  100. package/src/provider/WebGLContextManagerContext.tsx +4 -4
  101. package/src/provider/defaultContext.ts +8 -8
  102. package/src/provider/useCntrlContext.ts +8 -8
  103. package/src/styled-jsx.d.ts +1 -1
  104. package/src/utils/Animator/Animator.ts +571 -371
  105. package/src/utils/ArticleRectManager/ArticleRectObserver.ts +93 -93
  106. package/src/utils/ArticleRectManager/useArticleRectObserver.ts +17 -17
  107. package/src/utils/ArticleRectManager/useSectionRegistry.ts +11 -11
  108. package/src/utils/EventEmitter.ts +41 -41
  109. package/src/utils/RichTextConverter/RichTextConverter.tsx +326 -326
  110. package/src/utils/ScaleAnchorMap.ts +13 -13
  111. package/src/utils/StickyManager/StickyManager.ts +23 -23
  112. package/src/utils/Youtube/YouTubeIframeApiLoader.ts +58 -58
  113. package/src/utils/Youtube/YoutubeIframeApi.ts +101 -101
  114. package/src/utils/Youtube/useYouTubeIframeApi.ts +12 -12
  115. package/src/utils/areFillsVisible/areFillsVisible.test.ts +63 -0
  116. package/src/utils/areFillsVisible/areFillsVisible.ts +37 -0
  117. package/src/utils/binSearchInsertAt.ts +33 -33
  118. package/src/utils/castObject.ts +10 -10
  119. package/src/utils/checkOverflowClipSupport.ts +13 -13
  120. package/src/utils/effects/useImageFx.ts +156 -156
  121. package/src/utils/effects/useVideoFx.ts +158 -158
  122. package/src/utils/getAnchoredItemTop.ts +12 -12
  123. package/src/utils/getCompoundBoundaryStyles.ts +60 -60
  124. package/src/utils/getFill.ts +46 -0
  125. package/src/utils/getFontFamilyValue.ts +3 -3
  126. package/src/utils/getInvertedRanges.ts +43 -43
  127. package/src/utils/getItemTopStyle.ts +14 -14
  128. package/src/utils/getStyleFromItemStateAndParams.ts +8 -8
  129. package/src/utils/getValidYoutubeUrl.ts +29 -29
  130. package/src/utils/isItemType.ts +5 -5
  131. package/src/utils/rangeMap.ts +13 -13
  132. package/src/utils/useElementRect.ts +23 -23
  133. package/src/provider/InteractionsContext.old.tsx +0 -66
  134. package/src/provider/InteractionsContext.test.tsx +0 -97
@@ -1,60 +1,60 @@
1
- import { AreaAnchor, DimensionMode } from '@cntrl-site/sdk';
2
-
3
- const positionMatrix: Record<AreaAnchor, [number, number]> = {
4
- [AreaAnchor.TopLeft]: [0, 0],
5
- [AreaAnchor.TopCenter]: [0, 0.5],
6
- [AreaAnchor.TopRight]: [0, 1],
7
- [AreaAnchor.MiddleLeft]: [0.5, 0],
8
- [AreaAnchor.MiddleCenter]: [0.5, 0.5],
9
- [AreaAnchor.MiddleRight]: [0.5, 1],
10
- [AreaAnchor.BottomLeft]: [1, 0],
11
- [AreaAnchor.BottomCenter]: [1, 0.5],
12
- [AreaAnchor.BottomRight]: [1, 1]
13
- };
14
-
15
- export function getCompoundHeight(compoundSettings: CompoundSettings | undefined, height: number | undefined) {
16
- if (height === undefined || !compoundSettings) return;
17
- if (compoundSettings.heightMode === 'relative') {
18
- return `${height * 100}%`;
19
- }
20
- return `${height * 100}vw`;
21
- }
22
-
23
- export function getCompoundWidth(compoundSettings: CompoundSettings | undefined, width: number | undefined, isRichText: boolean, exemplary?: number) {
24
- if (width === undefined || !compoundSettings) return;
25
- if (compoundSettings.widthMode === 'relative') {
26
- return `${width * 100}%`;
27
- }
28
- return isRichText && exemplary ? `${width * exemplary}px` : `${width * 100}vw`;
29
- }
30
-
31
- export function getCompoundTop(compoundSettings: CompoundSettings | undefined, top: number | undefined) {
32
- if (top === undefined || !compoundSettings) return;
33
- if (compoundSettings.heightMode === 'relative') {
34
- return `${top * 100}%`;
35
- }
36
- const [ky] = positionMatrix[compoundSettings.positionAnchor];
37
- return `calc(${ky * 100}% + ${top * 100}vw)`;
38
- }
39
-
40
- export function getCompoundLeft(compoundSettings: CompoundSettings | undefined, left: number | undefined) {
41
- if (left === undefined || !compoundSettings) return;
42
- if (compoundSettings.widthMode === 'relative') {
43
- return `${left * 100}%`;
44
- }
45
- const [_, kx] = positionMatrix[compoundSettings.positionAnchor];
46
- return `calc(${kx * 100}% + ${left * 100}vw)`;
47
- }
48
-
49
- export function getCompoundTransform(compoundSettings: CompoundSettings | undefined) {
50
- if (!compoundSettings) return 'unset';
51
- const areaAnchor = compoundSettings.positionAnchor as AreaAnchor;
52
- const [ky, kx] = positionMatrix[areaAnchor];
53
- return `translate(${kx * (-100)}%, ${ky * (-100)}%)`;
54
- }
55
-
56
- type CompoundSettings = {
57
- positionAnchor: AreaAnchor;
58
- widthMode: DimensionMode;
59
- heightMode: DimensionMode;
60
- };
1
+ import { AreaAnchor, DimensionMode } from '@cntrl-site/sdk';
2
+
3
+ const positionMatrix: Record<AreaAnchor, [number, number]> = {
4
+ [AreaAnchor.TopLeft]: [0, 0],
5
+ [AreaAnchor.TopCenter]: [0, 0.5],
6
+ [AreaAnchor.TopRight]: [0, 1],
7
+ [AreaAnchor.MiddleLeft]: [0.5, 0],
8
+ [AreaAnchor.MiddleCenter]: [0.5, 0.5],
9
+ [AreaAnchor.MiddleRight]: [0.5, 1],
10
+ [AreaAnchor.BottomLeft]: [1, 0],
11
+ [AreaAnchor.BottomCenter]: [1, 0.5],
12
+ [AreaAnchor.BottomRight]: [1, 1]
13
+ };
14
+
15
+ export function getCompoundHeight(compoundSettings: CompoundSettings | undefined, height: number | undefined) {
16
+ if (height === undefined || !compoundSettings) return;
17
+ if (compoundSettings.heightMode === 'relative') {
18
+ return `${height * 100}%`;
19
+ }
20
+ return `${height * 100}vw`;
21
+ }
22
+
23
+ export function getCompoundWidth(compoundSettings: CompoundSettings | undefined, width: number | undefined, isRichText: boolean, exemplary?: number) {
24
+ if (width === undefined || !compoundSettings) return;
25
+ if (compoundSettings.widthMode === 'relative') {
26
+ return `${width * 100}%`;
27
+ }
28
+ return isRichText && exemplary ? `${width * exemplary}px` : `${width * 100}vw`;
29
+ }
30
+
31
+ export function getCompoundTop(compoundSettings: CompoundSettings | undefined, top: number | undefined) {
32
+ if (top === undefined || !compoundSettings) return;
33
+ if (compoundSettings.heightMode === 'relative') {
34
+ return `${top * 100}%`;
35
+ }
36
+ const [ky] = positionMatrix[compoundSettings.positionAnchor];
37
+ return `calc(${ky * 100}% + ${top * 100}vw)`;
38
+ }
39
+
40
+ export function getCompoundLeft(compoundSettings: CompoundSettings | undefined, left: number | undefined) {
41
+ if (left === undefined || !compoundSettings) return;
42
+ if (compoundSettings.widthMode === 'relative') {
43
+ return `${left * 100}%`;
44
+ }
45
+ const [_, kx] = positionMatrix[compoundSettings.positionAnchor];
46
+ return `calc(${kx * 100}% + ${left * 100}vw)`;
47
+ }
48
+
49
+ export function getCompoundTransform(compoundSettings: CompoundSettings | undefined) {
50
+ if (!compoundSettings) return 'unset';
51
+ const areaAnchor = compoundSettings.positionAnchor as AreaAnchor;
52
+ const [ky, kx] = positionMatrix[areaAnchor];
53
+ return `translate(${kx * (-100)}%, ${ky * (-100)}%)`;
54
+ }
55
+
56
+ type CompoundSettings = {
57
+ positionAnchor: AreaAnchor;
58
+ widthMode: DimensionMode;
59
+ heightMode: DimensionMode;
60
+ };
@@ -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,3 +1,3 @@
1
- export function getFontFamilyValue(value: string | undefined) {
2
- return value && value.includes('"') ? value : `"${value}"`;
3
- }
1
+ export function getFontFamilyValue(value: string | undefined) {
2
+ return value && value.includes('"') ? value : `"${value}"`;
3
+ }
@@ -1,43 +1,43 @@
1
- interface Range {
2
- start: number;
3
- end: number;
4
- }
5
-
6
- /**
7
- * @param length
8
- * @param ranges - SORTED array of ranges [start, end)
9
- */
10
-
11
- export function getInvertedRanges(length: number, ranges: Range[]): Range[] {
12
- if (ranges.length === 0) {
13
- return [{
14
- start: 0,
15
- end: length
16
- }];
17
- }
18
- const inverted: Range[] = [];
19
- if (ranges[0].start > 0) {
20
- inverted.push({
21
- start: 0,
22
- end: ranges[0].start
23
- });
24
- }
25
- for (let i = 0; i < ranges.length - 1; i += 1) {
26
- const left = ranges[i];
27
- const right = ranges[i + 1];
28
- if (right.start - left.end > 0) {
29
- inverted.push({
30
- start: left.end,
31
- end: right.start
32
- });
33
- }
34
- }
35
- const lastRange = ranges[ranges.length - 1];
36
- if (lastRange.end < length) {
37
- inverted.push({
38
- start: lastRange.end,
39
- end: length
40
- });
41
- }
42
- return inverted;
43
- }
1
+ interface Range {
2
+ start: number;
3
+ end: number;
4
+ }
5
+
6
+ /**
7
+ * @param length
8
+ * @param ranges - SORTED array of ranges [start, end)
9
+ */
10
+
11
+ export function getInvertedRanges(length: number, ranges: Range[]): Range[] {
12
+ if (ranges.length === 0) {
13
+ return [{
14
+ start: 0,
15
+ end: length
16
+ }];
17
+ }
18
+ const inverted: Range[] = [];
19
+ if (ranges[0].start > 0) {
20
+ inverted.push({
21
+ start: 0,
22
+ end: ranges[0].start
23
+ });
24
+ }
25
+ for (let i = 0; i < ranges.length - 1; i += 1) {
26
+ const left = ranges[i];
27
+ const right = ranges[i + 1];
28
+ if (right.start - left.end > 0) {
29
+ inverted.push({
30
+ start: left.end,
31
+ end: right.start
32
+ });
33
+ }
34
+ }
35
+ const lastRange = ranges[ranges.length - 1];
36
+ if (lastRange.end < length) {
37
+ inverted.push({
38
+ start: lastRange.end,
39
+ end: length
40
+ });
41
+ }
42
+ return inverted;
43
+ }
@@ -1,14 +1,14 @@
1
- import { AnchorSide } from '@cntrl-site/sdk';
2
-
3
- export function getItemTopStyle(top: number, anchorSide?: AnchorSide) {
4
- const defaultValue = `${top * 100}vw`;
5
- if (!anchorSide) return defaultValue;
6
- switch (anchorSide) {
7
- case AnchorSide.Top:
8
- return defaultValue;
9
- case AnchorSide.Center:
10
- return `calc(50% + ${top * 100}vw)`;
11
- case AnchorSide.Bottom:
12
- return `calc(100% + ${top * 100}vw)`;
13
- }
14
- }
1
+ import { AnchorSide } from '@cntrl-site/sdk';
2
+
3
+ export function getItemTopStyle(top: number, anchorSide?: AnchorSide) {
4
+ const defaultValue = `${top * 100}vw`;
5
+ if (!anchorSide) return defaultValue;
6
+ switch (anchorSide) {
7
+ case AnchorSide.Top:
8
+ return defaultValue;
9
+ case AnchorSide.Center:
10
+ return `calc(50% + ${top * 100}vw)`;
11
+ case AnchorSide.Bottom:
12
+ return `calc(100% + ${top * 100}vw)`;
13
+ }
14
+ }
@@ -1,8 +1,8 @@
1
- export function getStyleFromItemStateAndParams<T extends string | number>(
2
- stateValue: string | number | undefined,
3
- paramsValue: T | undefined
4
- ): T | undefined {
5
- return (stateValue as T) !== undefined
6
- ? stateValue as T
7
- : paramsValue;
8
- }
1
+ export function getStyleFromItemStateAndParams<T>(
2
+ stateValue: T | undefined,
3
+ paramsValue: T | undefined
4
+ ): T | undefined {
5
+ return (stateValue as T) !== undefined
6
+ ? stateValue as T
7
+ : paramsValue;
8
+ }
@@ -1,29 +1,29 @@
1
- enum Allowed {
2
- Tiny = 'youtu.be',
3
- Full = 'www.youtube.com'
4
- }
5
-
6
- export function getYoutubeId({ hostname, pathname, search }: URL): string {
7
- if (hostname !== Allowed.Full && hostname !== Allowed.Tiny) {
8
- throw new Error(`Cannot get valid YouTube ID from "${hostname}" - address is not whitelisted`);
9
- }
10
- switch (hostname) {
11
- case Allowed.Tiny:
12
- return pathname.replace('/', '');
13
- case Allowed.Full:
14
- if (pathname.startsWith('/shorts/')) {
15
- const shortsId = pathname.split('/shorts/')[1];
16
- if (!shortsId) {
17
- throw new Error('Cannot get valid YouTube Shorts ID from the pathname.');
18
- }
19
- return shortsId;
20
- } else {
21
- const searchParams = new URLSearchParams(search);
22
- const id = searchParams.get('v');
23
- if (!id) {
24
- throw new Error('Cannot get valid YouTube ID from search params.');
25
- }
26
- return id;
27
- }
28
- }
29
- }
1
+ enum Allowed {
2
+ Tiny = 'youtu.be',
3
+ Full = 'www.youtube.com'
4
+ }
5
+
6
+ export function getYoutubeId({ hostname, pathname, search }: URL): string {
7
+ if (hostname !== Allowed.Full && hostname !== Allowed.Tiny) {
8
+ throw new Error(`Cannot get valid YouTube ID from "${hostname}" - address is not whitelisted`);
9
+ }
10
+ switch (hostname) {
11
+ case Allowed.Tiny:
12
+ return pathname.replace('/', '');
13
+ case Allowed.Full:
14
+ if (pathname.startsWith('/shorts/')) {
15
+ const shortsId = pathname.split('/shorts/')[1];
16
+ if (!shortsId) {
17
+ throw new Error('Cannot get valid YouTube Shorts ID from the pathname.');
18
+ }
19
+ return shortsId;
20
+ } else {
21
+ const searchParams = new URLSearchParams(search);
22
+ const id = searchParams.get('v');
23
+ if (!id) {
24
+ throw new Error('Cannot get valid YouTube ID from search params.');
25
+ }
26
+ return id;
27
+ }
28
+ }
29
+ }
@@ -1,5 +1,5 @@
1
- import { ArticleItemType, ItemAny, Item as TItem } from '@cntrl-site/sdk';
2
-
3
- export function isItemType<T extends ArticleItemType>(item: ItemAny, itemType: T): item is TItem<T> {
4
- return item.type === itemType;
5
- }
1
+ import { ArticleItemType, ItemAny, Item as TItem } from '@cntrl-site/sdk';
2
+
3
+ export function isItemType<T extends ArticleItemType>(item: ItemAny, itemType: T): item is TItem<T> {
4
+ return item.type === itemType;
5
+ }
@@ -1,13 +1,13 @@
1
- export function rangeMap(
2
- n: number,
3
- start1: number,
4
- stop1: number,
5
- start2: number,
6
- stop2: number,
7
- withinBounds: boolean = false
8
- ): number {
9
- const mapped = (n - start1) / (stop1 - start1) * (stop2 - start2) + start2;
10
- if (withinBounds && n < start1) return start2;
11
- if (withinBounds && n > stop1) return stop2;
12
- return mapped;
13
- }
1
+ export function rangeMap(
2
+ n: number,
3
+ start1: number,
4
+ stop1: number,
5
+ start2: number,
6
+ stop2: number,
7
+ withinBounds: boolean = false
8
+ ): number {
9
+ const mapped = (n - start1) / (stop1 - start1) * (stop2 - start2) + start2;
10
+ if (withinBounds && n < start1) return start2;
11
+ if (withinBounds && n > stop1) return stop2;
12
+ return mapped;
13
+ }
@@ -1,23 +1,23 @@
1
- import { useEffect, useState } from 'react';
2
-
3
- export function useElementRect(element: HTMLElement | null): DOMRect | undefined {
4
- const [rect, setRect] = useState<DOMRect | undefined>(() => element?.getBoundingClientRect());
5
-
6
- useEffect(() => {
7
- if (!element) return;
8
- const observer = new ResizeObserver(entries => {
9
- const el = entries.find(entry => entry.target === element);
10
- if (!el) {
11
- throw new Error('Element is not found in observed entries');
12
- }
13
- setRect(el.target.getBoundingClientRect());
14
- });
15
- observer.observe(element);
16
- return () => {
17
- observer.unobserve(element);
18
- observer.disconnect();
19
- };
20
- }, [element]);
21
-
22
- return rect;
23
- }
1
+ import { useEffect, useState } from 'react';
2
+
3
+ export function useElementRect(element: HTMLElement | null): DOMRect | undefined {
4
+ const [rect, setRect] = useState<DOMRect | undefined>(() => element?.getBoundingClientRect());
5
+
6
+ useEffect(() => {
7
+ if (!element) return;
8
+ const observer = new ResizeObserver(entries => {
9
+ const el = entries.find(entry => entry.target === element);
10
+ if (!el) {
11
+ throw new Error('Element is not found in observed entries');
12
+ }
13
+ setRect(el.target.getBoundingClientRect());
14
+ });
15
+ observer.observe(element);
16
+ return () => {
17
+ observer.unobserve(element);
18
+ observer.disconnect();
19
+ };
20
+ }, [element]);
21
+
22
+ return rect;
23
+ }
@@ -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
- });