@cntrl-site/sdk-nextjs 1.8.36-alpha.0 → 1.8.36
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/lib/components/items/CodeEmbedItem/CodeEmbedItem.js +1 -0
- package/lib/components/items/ComponentItem/ComponentItem.js +1 -0
- package/lib/components/items/EmbedVideoItem/VimeoEmbed.js +1 -0
- package/lib/components/items/EmbedVideoItem/YoutubeEmbed.js +1 -0
- package/lib/components/items/FileItem/ImageItem.js +1 -0
- package/lib/components/items/FileItem/VideoItem.js +1 -0
- package/lib/components/items/GroupItem/GroupItem.js +1 -0
- package/lib/components/items/Item.js +1 -1
- package/lib/components/items/RectangleItem/RectangleItem.js +1 -0
- package/lib/components/items/RichTextItem/RichTextItem.js +1 -0
- package/lib/interactions/InteractionsRegistry.js +111 -5
- package/lib/interactions/ItemInteractionCtrl.js +1 -1
- package/lib/provider/InteractionsContext.js +19 -0
- package/package.json +2 -2
- package/src/components/ScrollPlaybackVideo.tsx +1 -1
- package/src/components/items/CodeEmbedItem/CodeEmbedItem.tsx +1 -0
- package/src/components/items/ComponentItem/ComponentItem.tsx +1 -0
- package/src/components/items/EmbedVideoItem/VimeoEmbed.tsx +1 -0
- package/src/components/items/EmbedVideoItem/YoutubeEmbed.tsx +1 -0
- package/src/components/items/FileItem/ImageItem.tsx +1 -0
- package/src/components/items/FileItem/VideoItem.tsx +1 -0
- package/src/components/items/GroupItem/GroupItem.tsx +1 -0
- package/src/components/items/Item.tsx +1 -1
- package/src/components/items/RectangleItem/RectangleItem.tsx +1 -0
- package/src/components/items/RichTextItem/RichTextItem.tsx +1 -0
- package/src/interactions/InteractionsRegistry.ts +106 -9
- package/src/interactions/ItemInteractionCtrl.ts +4 -3
- package/src/interactions/types.ts +4 -4
- package/src/provider/InteractionsContext.tsx +22 -1
|
@@ -40,6 +40,7 @@ const GroupItem = ({ item, sectionId, onResize, interactionCtrl, onVisibilityCha
|
|
|
40
40
|
width: 100%;
|
|
41
41
|
height: 100%;
|
|
42
42
|
box-sizing: border-box;
|
|
43
|
+
will-change: transform;
|
|
43
44
|
}
|
|
44
45
|
${(0, sdk_1.getLayoutStyles)(layouts, layoutValues, ([area, layoutParams]) => {
|
|
45
46
|
return (`
|
|
@@ -53,7 +53,7 @@ const Item = ({ item, sectionId, articleHeight, isParentVisible = true, isInGrou
|
|
|
53
53
|
const [position, setPosition] = (0, react_1.useState)({ x: 0, y: 0 });
|
|
54
54
|
const [isDraggingActive, setIsDraggingActive] = (0, react_1.useState)(false);
|
|
55
55
|
const wrapperStateProps = interactionCtrl === null || interactionCtrl === void 0 ? void 0 : interactionCtrl.getState(['top', 'left']);
|
|
56
|
-
const innerStateProps = interactionCtrl === null || interactionCtrl === void 0 ? void 0 : interactionCtrl.getState(['width', 'height', 'scale'
|
|
56
|
+
const innerStateProps = interactionCtrl === null || interactionCtrl === void 0 ? void 0 : interactionCtrl.getState(['width', 'height', 'scale']);
|
|
57
57
|
const { width, height, top, left } = (0, useItemArea_1.useItemArea)(item, sectionId, {
|
|
58
58
|
top: (_b = wrapperStateProps === null || wrapperStateProps === void 0 ? void 0 : wrapperStateProps.styles) === null || _b === void 0 ? void 0 : _b.top,
|
|
59
59
|
left: (_c = wrapperStateProps === null || wrapperStateProps === void 0 ? void 0 : wrapperStateProps.styles) === null || _c === void 0 ? void 0 : _c.left,
|
|
@@ -62,6 +62,7 @@ const RectangleItem = ({ item, sectionId, onResize, interactionCtrl, onVisibilit
|
|
|
62
62
|
border-radius: ${layoutParams.radius * 100}vw;
|
|
63
63
|
border-width: ${layoutParams.strokeWidth * 100}vw;
|
|
64
64
|
transform: rotate(${area.angle}deg);
|
|
65
|
+
will-change: transform;
|
|
65
66
|
filter: ${layoutParams.blur !== 0 ? `blur(${layoutParams.blur * 100}vw)` : 'unset'};
|
|
66
67
|
backdrop-filter: ${layoutParams.backdropBlur !== 0 ? `blur(${layoutParams.backdropBlur * 100}vw)` : 'unset'};
|
|
67
68
|
-webkit-backdrop-filter: ${layoutParams.backdropBlur !== 0 ? `blur(${layoutParams.backdropBlur * 100}vw)` : 'unset'};
|
|
@@ -68,6 +68,7 @@ const RichTextItem = ({ item, sectionId, onResize, interactionCtrl, onVisibility
|
|
|
68
68
|
font-variant: ${layoutParams.fontVariant};
|
|
69
69
|
color: ${color.fmt('rgba')};
|
|
70
70
|
transform: rotate(${area.angle}deg);
|
|
71
|
+
will-change: transform;
|
|
71
72
|
filter: ${layoutParams.blur !== 0 ? `blur(${layoutParams.blur * 100}vw)` : 'unset'};
|
|
72
73
|
text-transform: ${layoutParams.textTransform};
|
|
73
74
|
}
|
|
@@ -29,7 +29,6 @@ class InteractionsRegistry {
|
|
|
29
29
|
}
|
|
30
30
|
return map;
|
|
31
31
|
}, {});
|
|
32
|
-
const itemStages = this.getDefaultItemStages();
|
|
33
32
|
const stateItemsIdsMap = activeStatesIds.reduce((map, stateId) => {
|
|
34
33
|
map[stateId] = this.items
|
|
35
34
|
.filter((item) => {
|
|
@@ -42,9 +41,10 @@ class InteractionsRegistry {
|
|
|
42
41
|
return map;
|
|
43
42
|
}, {});
|
|
44
43
|
this.interactions = interactions;
|
|
45
|
-
this.itemsStages = itemStages;
|
|
46
44
|
this.stateItemsIdsMap = stateItemsIdsMap;
|
|
47
45
|
this.interactionStateMap = interactionStateMap;
|
|
46
|
+
const itemStages = this.getDefaultItemStages();
|
|
47
|
+
this.itemsStages = itemStages;
|
|
48
48
|
}
|
|
49
49
|
register(itemId, ctrl) {
|
|
50
50
|
this.ctrls.set(itemId, ctrl);
|
|
@@ -93,6 +93,8 @@ class InteractionsRegistry {
|
|
|
93
93
|
for (const interaction of this.interactions) {
|
|
94
94
|
const { triggers } = interaction;
|
|
95
95
|
for (const trigger of triggers) {
|
|
96
|
+
if (!('itemId' in trigger))
|
|
97
|
+
continue;
|
|
96
98
|
if (trigger.itemId !== itemId)
|
|
97
99
|
continue;
|
|
98
100
|
if (activeStates.includes(trigger.from)) {
|
|
@@ -102,12 +104,112 @@ class InteractionsRegistry {
|
|
|
102
104
|
}
|
|
103
105
|
return available;
|
|
104
106
|
}
|
|
105
|
-
|
|
107
|
+
notifyLoad() {
|
|
108
|
+
var _a, _b;
|
|
109
|
+
const timestamp = Date.now();
|
|
110
|
+
for (const interaction of this.interactions) {
|
|
111
|
+
const currentStateId = this.getCurrentStateByInteractionId(interaction.id);
|
|
112
|
+
const matchingTrigger = interaction.triggers.find(trigger => 'position' in trigger && trigger.position === 0 && trigger.from === currentStateId);
|
|
113
|
+
if (!matchingTrigger)
|
|
114
|
+
continue;
|
|
115
|
+
const activeStateId = this.getActiveInteractionState(interaction.id);
|
|
116
|
+
const isNewStateActive = matchingTrigger.to === activeStateId;
|
|
117
|
+
this.setCurrentStateForInteraction(interaction.id, matchingTrigger.to);
|
|
118
|
+
const transitioningItems = (_a = this.stateItemsIdsMap[activeStateId]) !== null && _a !== void 0 ? _a : [];
|
|
119
|
+
const state = interaction.states.find((state) => state.id === matchingTrigger.to);
|
|
120
|
+
const actions = (_b = state === null || state === void 0 ? void 0 : state.actions) !== null && _b !== void 0 ? _b : [];
|
|
121
|
+
for (const action of actions) {
|
|
122
|
+
const ctrl = this.ctrls.get(action.itemId);
|
|
123
|
+
if (!ctrl)
|
|
124
|
+
continue;
|
|
125
|
+
ctrl.receiveAction(action.type);
|
|
126
|
+
}
|
|
127
|
+
this.itemsStages = this.itemsStages.map((stage) => {
|
|
128
|
+
if (stage.interactionId !== interaction.id)
|
|
129
|
+
return stage;
|
|
130
|
+
return {
|
|
131
|
+
itemId: stage.itemId,
|
|
132
|
+
interactionId: stage.interactionId,
|
|
133
|
+
type: 'transitioning',
|
|
134
|
+
from: stage.type === 'transitioning' ? stage.to : stage.stateId,
|
|
135
|
+
to: matchingTrigger.to,
|
|
136
|
+
direction: isNewStateActive ? 'in' : 'out',
|
|
137
|
+
updated: timestamp
|
|
138
|
+
};
|
|
139
|
+
});
|
|
140
|
+
const itemsToNotify = new Set(transitioningItems);
|
|
141
|
+
for (const trigger of interaction.triggers) {
|
|
142
|
+
if (!('itemId' in trigger))
|
|
143
|
+
continue;
|
|
144
|
+
itemsToNotify.add(trigger.itemId);
|
|
145
|
+
}
|
|
146
|
+
this.notifyItemCtrlsChange(Array.from(itemsToNotify));
|
|
147
|
+
this.notifyTransitionStartForItems(transitioningItems, activeStateId);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
notifyScroll(position) {
|
|
151
|
+
var _a, _b, _c;
|
|
152
|
+
const timestamp = Date.now();
|
|
153
|
+
for (const interaction of this.interactions) {
|
|
154
|
+
const currentStateId = this.getCurrentStateByInteractionId(interaction.id);
|
|
155
|
+
const activeStateId = (_a = interaction.states.find((state) => state.id !== interaction.startStateId)) === null || _a === void 0 ? void 0 : _a.id;
|
|
156
|
+
const matchingTrigger = interaction.triggers.find((trigger) => {
|
|
157
|
+
if (!('position' in trigger) || trigger.position === 0)
|
|
158
|
+
return false;
|
|
159
|
+
const triggerPosition = trigger.position * window.innerWidth;
|
|
160
|
+
const isScrolledPastTrigger = triggerPosition < position;
|
|
161
|
+
if (!isScrolledPastTrigger && !trigger.isReverse)
|
|
162
|
+
return false;
|
|
163
|
+
const stateId = isScrolledPastTrigger ? trigger.from : trigger.to;
|
|
164
|
+
return stateId === currentStateId;
|
|
165
|
+
});
|
|
166
|
+
if (!matchingTrigger || !('position' in matchingTrigger) || !activeStateId)
|
|
167
|
+
continue;
|
|
168
|
+
const triggerPosition = matchingTrigger.position * window.innerWidth;
|
|
169
|
+
const isScrolledPastTrigger = triggerPosition < position;
|
|
170
|
+
const targetStateId = isScrolledPastTrigger ? matchingTrigger.to : matchingTrigger.from;
|
|
171
|
+
this.setCurrentStateForInteraction(interaction.id, targetStateId);
|
|
172
|
+
const transitioningItems = (_b = this.stateItemsIdsMap[activeStateId]) !== null && _b !== void 0 ? _b : [];
|
|
173
|
+
const state = interaction.states.find((state) => state.id === targetStateId);
|
|
174
|
+
const actions = (_c = state === null || state === void 0 ? void 0 : state.actions) !== null && _c !== void 0 ? _c : [];
|
|
175
|
+
for (const action of actions) {
|
|
176
|
+
const ctrl = this.ctrls.get(action.itemId);
|
|
177
|
+
if (!ctrl)
|
|
178
|
+
continue;
|
|
179
|
+
ctrl.receiveAction(action.type);
|
|
180
|
+
}
|
|
181
|
+
const itemsStages = this.itemsStages.map((stage) => {
|
|
182
|
+
if (stage.interactionId !== interaction.id)
|
|
183
|
+
return stage;
|
|
184
|
+
const newStage = {
|
|
185
|
+
itemId: stage.itemId,
|
|
186
|
+
interactionId: stage.interactionId,
|
|
187
|
+
type: 'transitioning',
|
|
188
|
+
from: stage.type === 'transitioning' ? stage.to : stage.stateId,
|
|
189
|
+
to: targetStateId,
|
|
190
|
+
direction: targetStateId === activeStateId ? 'in' : 'out',
|
|
191
|
+
updated: timestamp
|
|
192
|
+
};
|
|
193
|
+
return newStage;
|
|
194
|
+
});
|
|
195
|
+
this.itemsStages = itemsStages;
|
|
196
|
+
const itemsToNotify = new Set(transitioningItems);
|
|
197
|
+
for (const trigger of interaction.triggers) {
|
|
198
|
+
if (!('itemId' in trigger))
|
|
199
|
+
continue;
|
|
200
|
+
itemsToNotify.add(trigger.itemId);
|
|
201
|
+
}
|
|
202
|
+
this.notifyItemCtrlsChange(Array.from(itemsToNotify));
|
|
203
|
+
this.notifyTransitionStartForItems(transitioningItems, activeStateId);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
notifyItemTrigger(itemId, triggerType) {
|
|
106
207
|
var _a, _b;
|
|
107
208
|
const timestamp = Date.now();
|
|
108
209
|
for (const interaction of this.interactions) {
|
|
109
210
|
const currentStateId = this.getCurrentStateByInteractionId(interaction.id);
|
|
110
|
-
const matchingTrigger = interaction.triggers.find((trigger) =>
|
|
211
|
+
const matchingTrigger = interaction.triggers.find((trigger) => 'itemId' in trigger
|
|
212
|
+
&& trigger.itemId === itemId
|
|
111
213
|
&& trigger.from === currentStateId
|
|
112
214
|
&& trigger.type === triggerType);
|
|
113
215
|
if (!matchingTrigger)
|
|
@@ -139,6 +241,8 @@ class InteractionsRegistry {
|
|
|
139
241
|
});
|
|
140
242
|
const itemsToNotify = new Set(transitioningItems);
|
|
141
243
|
for (const trigger of interaction.triggers) {
|
|
244
|
+
if (!('itemId' in trigger))
|
|
245
|
+
continue;
|
|
142
246
|
itemsToNotify.add(trigger.itemId);
|
|
143
247
|
}
|
|
144
248
|
this.notifyItemCtrlsChange(Array.from(itemsToNotify));
|
|
@@ -223,6 +327,7 @@ class InteractionsRegistry {
|
|
|
223
327
|
return allItems;
|
|
224
328
|
}
|
|
225
329
|
getDefaultItemStages() {
|
|
330
|
+
var _a;
|
|
226
331
|
const timestamp = Date.now();
|
|
227
332
|
const { items } = this;
|
|
228
333
|
const stages = [];
|
|
@@ -239,7 +344,8 @@ class InteractionsRegistry {
|
|
|
239
344
|
interactionId,
|
|
240
345
|
type: 'active',
|
|
241
346
|
isStartState: true,
|
|
242
|
-
updated: timestamp
|
|
347
|
+
updated: timestamp,
|
|
348
|
+
stateId: (_a = this.interactions.find((interaction) => interaction.id === interactionId)) === null || _a === void 0 ? void 0 : _a.startStateId
|
|
243
349
|
});
|
|
244
350
|
}
|
|
245
351
|
}
|
|
@@ -5,14 +5,33 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
5
5
|
const react_1 = require("react");
|
|
6
6
|
const InteractionsRegistry_1 = require("../interactions/InteractionsRegistry");
|
|
7
7
|
const useCurrentLayout_1 = require("../common/useCurrentLayout");
|
|
8
|
+
const ArticleRectContext_1 = require("./ArticleRectContext");
|
|
8
9
|
exports.InteractionsContext = (0, react_1.createContext)(undefined);
|
|
9
10
|
const InteractionsProvider = ({ article, children }) => {
|
|
10
11
|
const { layoutId } = (0, useCurrentLayout_1.useCurrentLayout)();
|
|
12
|
+
const articleRectObserver = (0, react_1.useContext)(ArticleRectContext_1.ArticleRectContext);
|
|
11
13
|
const registry = (0, react_1.useMemo)(() => {
|
|
12
14
|
if (!layoutId)
|
|
13
15
|
return;
|
|
14
16
|
return new InteractionsRegistry_1.InteractionsRegistry(article, layoutId);
|
|
15
17
|
}, [layoutId]);
|
|
18
|
+
(0, react_1.useEffect)(() => {
|
|
19
|
+
if (!registry || !articleRectObserver)
|
|
20
|
+
return;
|
|
21
|
+
const handleScroll = () => {
|
|
22
|
+
const scrollY = window.scrollY;
|
|
23
|
+
registry.notifyScroll(scrollY);
|
|
24
|
+
};
|
|
25
|
+
return articleRectObserver.on('scroll', handleScroll);
|
|
26
|
+
}, [registry, articleRectObserver]);
|
|
27
|
+
const notifyLoad = (0, react_1.useCallback)(() => {
|
|
28
|
+
if (!registry)
|
|
29
|
+
return;
|
|
30
|
+
registry.notifyLoad();
|
|
31
|
+
}, [registry]);
|
|
32
|
+
(0, react_1.useEffect)(() => {
|
|
33
|
+
notifyLoad();
|
|
34
|
+
}, [notifyLoad]);
|
|
16
35
|
return ((0, jsx_runtime_1.jsx)(exports.InteractionsContext.Provider, { value: registry, children: children }));
|
|
17
36
|
};
|
|
18
37
|
exports.InteractionsProvider = InteractionsProvider;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cntrl-site/sdk-nextjs",
|
|
3
|
-
"version": "1.8.36
|
|
3
|
+
"version": "1.8.36",
|
|
4
4
|
"description": "SDK for Next.js",
|
|
5
5
|
"author": "arsen@momdesign.nyc",
|
|
6
6
|
"license": "MIT",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"@antfu/eslint-config": "^3.8.0",
|
|
32
32
|
"@cntrl-site/color": "^1.0.0",
|
|
33
33
|
"@cntrl-site/effects": "^1.4.0",
|
|
34
|
-
"@cntrl-site/sdk": "^1.
|
|
34
|
+
"@cntrl-site/sdk": "^1.23.0",
|
|
35
35
|
"@types/vimeo__player": "^2.18.0",
|
|
36
36
|
"@vimeo/player": "^2.25.0",
|
|
37
37
|
"html-react-parser": "^3.0.1",
|
|
@@ -24,7 +24,7 @@ export const ScrollPlaybackVideo: FC<Props> = ({ sectionId, src, playbackParams,
|
|
|
24
24
|
const scrollPos = articleRectObserver.getSectionScroll(sectionId);
|
|
25
25
|
const time = rangeMap(scrollPos, playbackParams.from, playbackParams.to, 0, 1, true);
|
|
26
26
|
setTime(toFixed(time));
|
|
27
|
-
})
|
|
27
|
+
});
|
|
28
28
|
}, [playbackParams?.from, playbackParams?.to, time]);
|
|
29
29
|
|
|
30
30
|
const scrollVideoManager = useMemo<ScrollPlaybackVideoManager | null>(() => {
|
|
@@ -83,7 +83,7 @@ export const Item: FC<ItemWrapperProps> = ({ item, sectionId, articleHeight, isP
|
|
|
83
83
|
const [position, setPosition] = useState({ x: 0, y: 0 });
|
|
84
84
|
const [isDraggingActive, setIsDraggingActive] = useState(false);
|
|
85
85
|
const wrapperStateProps = interactionCtrl?.getState(['top', 'left']);
|
|
86
|
-
const innerStateProps = interactionCtrl?.getState(['width', 'height', 'scale'
|
|
86
|
+
const innerStateProps = interactionCtrl?.getState(['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,
|
|
@@ -82,6 +82,7 @@ export const RectangleItem: FC<ItemProps<TRectangleItem>> = ({ item, sectionId,
|
|
|
82
82
|
border-radius: ${layoutParams.radius * 100}vw;
|
|
83
83
|
border-width: ${layoutParams.strokeWidth * 100}vw;
|
|
84
84
|
transform: rotate(${area.angle}deg);
|
|
85
|
+
will-change: transform;
|
|
85
86
|
filter: ${layoutParams.blur !== 0 ? `blur(${layoutParams.blur * 100}vw)` : 'unset'};
|
|
86
87
|
backdrop-filter: ${layoutParams.backdropBlur !== 0 ? `blur(${layoutParams.backdropBlur * 100}vw)` : 'unset'};
|
|
87
88
|
-webkit-backdrop-filter: ${layoutParams.backdropBlur !== 0 ? `blur(${layoutParams.backdropBlur * 100}vw)` : 'unset'};
|
|
@@ -89,6 +89,7 @@ export const RichTextItem: FC<ItemProps<TRichTextItem>> = ({ item, sectionId, on
|
|
|
89
89
|
font-variant: ${layoutParams.fontVariant};
|
|
90
90
|
color: ${color.fmt('rgba')};
|
|
91
91
|
transform: rotate(${area.angle}deg);
|
|
92
|
+
will-change: transform;
|
|
92
93
|
filter: ${layoutParams.blur !== 0 ? `blur(${layoutParams.blur * 100}vw)` : 'unset'};
|
|
93
94
|
text-transform: ${layoutParams.textTransform};
|
|
94
95
|
}
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
Article,
|
|
4
4
|
ArticleItemType,
|
|
5
5
|
Interaction,
|
|
6
|
-
|
|
6
|
+
InteractionItemTrigger,
|
|
7
7
|
ItemAny,
|
|
8
8
|
} from '@cntrl-site/sdk';
|
|
9
9
|
import { isItemType } from '../utils/isItemType';
|
|
@@ -39,7 +39,6 @@ export class InteractionsRegistry implements InteractionsRegistryPort {
|
|
|
39
39
|
}
|
|
40
40
|
return map;
|
|
41
41
|
}, {});
|
|
42
|
-
const itemStages = this.getDefaultItemStages();
|
|
43
42
|
const stateItemsIdsMap = activeStatesIds.reduce<StateItemsIdsMap>((map, stateId) => {
|
|
44
43
|
map[stateId] = this.items
|
|
45
44
|
.filter((item) => {
|
|
@@ -51,9 +50,10 @@ export class InteractionsRegistry implements InteractionsRegistryPort {
|
|
|
51
50
|
return map;
|
|
52
51
|
}, {});
|
|
53
52
|
this.interactions = interactions;
|
|
54
|
-
this.itemsStages = itemStages;
|
|
55
53
|
this.stateItemsIdsMap = stateItemsIdsMap;
|
|
56
54
|
this.interactionStateMap = interactionStateMap;
|
|
55
|
+
const itemStages = this.getDefaultItemStages();
|
|
56
|
+
this.itemsStages = itemStages;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
register(itemId: ItemId, ctrl: ItemInteractionCtrl) {
|
|
@@ -96,12 +96,13 @@ export class InteractionsRegistry implements InteractionsRegistryPort {
|
|
|
96
96
|
return itemStyles;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
getItemAvailableTriggers(itemId: string): Set<
|
|
100
|
-
const available = new Set<
|
|
99
|
+
getItemAvailableTriggers(itemId: string): Set<InteractionItemTrigger['type']> {
|
|
100
|
+
const available = new Set<InteractionItemTrigger['type']>();
|
|
101
101
|
const activeStates = Object.values(this.interactionStateMap);
|
|
102
102
|
for (const interaction of this.interactions) {
|
|
103
103
|
const { triggers } = interaction;
|
|
104
104
|
for (const trigger of triggers) {
|
|
105
|
+
if (!('itemId' in trigger)) continue;
|
|
105
106
|
if (trigger.itemId !== itemId) continue;
|
|
106
107
|
if (activeStates.includes(trigger.from)) {
|
|
107
108
|
available.add(trigger.type);
|
|
@@ -111,12 +112,106 @@ export class InteractionsRegistry implements InteractionsRegistryPort {
|
|
|
111
112
|
return available;
|
|
112
113
|
}
|
|
113
114
|
|
|
114
|
-
|
|
115
|
+
notifyLoad() {
|
|
116
|
+
const timestamp = Date.now();
|
|
117
|
+
for (const interaction of this.interactions) {
|
|
118
|
+
const currentStateId = this.getCurrentStateByInteractionId(interaction.id);
|
|
119
|
+
const matchingTrigger = interaction.triggers.find(trigger =>
|
|
120
|
+
'position' in trigger && trigger.position === 0 && trigger.from === currentStateId
|
|
121
|
+
);
|
|
122
|
+
if (!matchingTrigger) continue;
|
|
123
|
+
const activeStateId = this.getActiveInteractionState(interaction.id);
|
|
124
|
+
const isNewStateActive = matchingTrigger.to === activeStateId;
|
|
125
|
+
this.setCurrentStateForInteraction(interaction.id, matchingTrigger.to);
|
|
126
|
+
const transitioningItems = this.stateItemsIdsMap[activeStateId] ?? [];
|
|
127
|
+
const state = interaction.states.find((state) => state.id === matchingTrigger.to);
|
|
128
|
+
const actions = state?.actions ?? [];
|
|
129
|
+
for (const action of actions) {
|
|
130
|
+
const ctrl = this.ctrls.get(action.itemId);
|
|
131
|
+
if (!ctrl) continue;
|
|
132
|
+
ctrl.receiveAction(action.type);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
this.itemsStages = this.itemsStages.map((stage) => {
|
|
136
|
+
if (stage.interactionId !== interaction.id) return stage;
|
|
137
|
+
return {
|
|
138
|
+
itemId: stage.itemId,
|
|
139
|
+
interactionId: stage.interactionId,
|
|
140
|
+
type: 'transitioning',
|
|
141
|
+
from: stage.type === 'transitioning' ? stage.to : stage.stateId!,
|
|
142
|
+
to: matchingTrigger.to,
|
|
143
|
+
direction: isNewStateActive ? 'in' : 'out',
|
|
144
|
+
updated: timestamp
|
|
145
|
+
};
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const itemsToNotify = new Set<ItemId>(transitioningItems);
|
|
149
|
+
for (const trigger of interaction.triggers) {
|
|
150
|
+
if (!('itemId' in trigger)) continue;
|
|
151
|
+
itemsToNotify.add(trigger.itemId);
|
|
152
|
+
}
|
|
153
|
+
this.notifyItemCtrlsChange(Array.from(itemsToNotify));
|
|
154
|
+
this.notifyTransitionStartForItems(transitioningItems, activeStateId);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
notifyScroll(position: number) {
|
|
159
|
+
const timestamp = Date.now();
|
|
160
|
+
for (const interaction of this.interactions) {
|
|
161
|
+
const currentStateId = this.getCurrentStateByInteractionId(interaction.id);
|
|
162
|
+
const activeStateId = interaction.states.find((state) => state.id !== interaction.startStateId)?.id;
|
|
163
|
+
const matchingTrigger = interaction.triggers.find((trigger) => {
|
|
164
|
+
if (!('position' in trigger) || trigger.position === 0) return false;
|
|
165
|
+
const triggerPosition = trigger.position * window.innerWidth;
|
|
166
|
+
const isScrolledPastTrigger = triggerPosition < position;
|
|
167
|
+
if (!isScrolledPastTrigger && !trigger.isReverse) return false;
|
|
168
|
+
const stateId = isScrolledPastTrigger ? trigger.from : trigger.to;
|
|
169
|
+
return stateId === currentStateId;
|
|
170
|
+
});
|
|
171
|
+
if (!matchingTrigger || !('position' in matchingTrigger) || !activeStateId) continue;
|
|
172
|
+
const triggerPosition = matchingTrigger.position * window.innerWidth;
|
|
173
|
+
const isScrolledPastTrigger = triggerPosition < position;
|
|
174
|
+
const targetStateId = isScrolledPastTrigger ? matchingTrigger.to : matchingTrigger.from;
|
|
175
|
+
this.setCurrentStateForInteraction(interaction.id, targetStateId);
|
|
176
|
+
const transitioningItems = this.stateItemsIdsMap[activeStateId] ?? [];
|
|
177
|
+
const state = interaction.states.find((state) => state.id === targetStateId);
|
|
178
|
+
const actions = state?.actions ?? [];
|
|
179
|
+
for (const action of actions) {
|
|
180
|
+
const ctrl = this.ctrls.get(action.itemId);
|
|
181
|
+
if (!ctrl) continue;
|
|
182
|
+
ctrl.receiveAction(action.type);
|
|
183
|
+
}
|
|
184
|
+
const itemsStages = this.itemsStages.map((stage) => {
|
|
185
|
+
if (stage.interactionId !== interaction.id) return stage;
|
|
186
|
+
const newStage = {
|
|
187
|
+
itemId: stage.itemId,
|
|
188
|
+
interactionId: stage.interactionId,
|
|
189
|
+
type: 'transitioning' as const,
|
|
190
|
+
from: stage.type === 'transitioning' ? stage.to : stage.stateId!,
|
|
191
|
+
to: targetStateId,
|
|
192
|
+
direction: targetStateId === activeStateId ? 'in' as const : 'out' as const,
|
|
193
|
+
updated: timestamp
|
|
194
|
+
};
|
|
195
|
+
return newStage;
|
|
196
|
+
});
|
|
197
|
+
this.itemsStages = itemsStages;
|
|
198
|
+
const itemsToNotify = new Set<ItemId>(transitioningItems);
|
|
199
|
+
for (const trigger of interaction.triggers) {
|
|
200
|
+
if (!('itemId' in trigger)) continue;
|
|
201
|
+
itemsToNotify.add(trigger.itemId);
|
|
202
|
+
}
|
|
203
|
+
this.notifyItemCtrlsChange(Array.from(itemsToNotify));
|
|
204
|
+
this.notifyTransitionStartForItems(transitioningItems, activeStateId);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
notifyItemTrigger(itemId: string, triggerType: TriggerType): void {
|
|
115
209
|
const timestamp = Date.now();
|
|
116
210
|
for (const interaction of this.interactions) {
|
|
117
211
|
const currentStateId = this.getCurrentStateByInteractionId(interaction.id);
|
|
118
212
|
const matchingTrigger = interaction.triggers.find((trigger) =>
|
|
119
|
-
|
|
213
|
+
'itemId' in trigger
|
|
214
|
+
&& trigger.itemId === itemId
|
|
120
215
|
&& trigger.from === currentStateId
|
|
121
216
|
&& trigger.type === triggerType
|
|
122
217
|
);
|
|
@@ -146,6 +241,7 @@ export class InteractionsRegistry implements InteractionsRegistryPort {
|
|
|
146
241
|
});
|
|
147
242
|
const itemsToNotify = new Set<ItemId>(transitioningItems);
|
|
148
243
|
for (const trigger of interaction.triggers) {
|
|
244
|
+
if (!('itemId' in trigger)) continue;
|
|
149
245
|
itemsToNotify.add(trigger.itemId);
|
|
150
246
|
}
|
|
151
247
|
this.notifyItemCtrlsChange(Array.from(itemsToNotify));
|
|
@@ -248,7 +344,8 @@ export class InteractionsRegistry implements InteractionsRegistryPort {
|
|
|
248
344
|
interactionId,
|
|
249
345
|
type: 'active',
|
|
250
346
|
isStartState: true,
|
|
251
|
-
updated: timestamp
|
|
347
|
+
updated: timestamp,
|
|
348
|
+
stateId: this.interactions.find((interaction) => interaction.id === interactionId)?.startStateId
|
|
252
349
|
});
|
|
253
350
|
}
|
|
254
351
|
}
|
|
@@ -269,7 +366,7 @@ type TransitioningStage = {
|
|
|
269
366
|
type ActiveStage = { type: 'active'; itemId: string; interactionId: string; stateId?: string; isStartState: boolean; updated: number; };
|
|
270
367
|
type InteractionStateMap = Record<InteractionId, StateId>;
|
|
271
368
|
type StateItemsIdsMap = Record<StateId, ItemId[]>;
|
|
272
|
-
type TriggerType =
|
|
369
|
+
type TriggerType = InteractionItemTrigger['type'];
|
|
273
370
|
type InteractionId = string;
|
|
274
371
|
type StateId = string;
|
|
275
372
|
type ItemId = string;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { InteractionsRegistryPort, ItemInteractionCtrl } from './types';
|
|
2
2
|
import { getTransition } from './getTransition';
|
|
3
3
|
import { getStyleKeysFromCSSProperty } from './CSSPropertyNameMap';
|
|
4
|
-
import {
|
|
4
|
+
import { InteractionItemTrigger } from '@cntrl-site/sdk';
|
|
5
5
|
|
|
6
6
|
export class ItemInteractionController implements ItemInteractionCtrl {
|
|
7
7
|
private transitionsInProgress: Set<string> = new Set();
|
|
8
8
|
private actionReceiver: ((type: 'play' | 'pause') => void) | undefined;
|
|
9
|
+
|
|
9
10
|
constructor(
|
|
10
11
|
private itemId: string,
|
|
11
12
|
private registry: InteractionsRegistryPort,
|
|
@@ -29,13 +30,13 @@ export class ItemInteractionController implements ItemInteractionCtrl {
|
|
|
29
30
|
};
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
getHasTrigger(itemId: string, triggerType:
|
|
33
|
+
getHasTrigger(itemId: string, triggerType: InteractionItemTrigger['type']): boolean {
|
|
33
34
|
const triggers = this.registry.getItemAvailableTriggers(itemId);
|
|
34
35
|
return triggers.has(triggerType);
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
sendTrigger(type: 'click' | 'hover-in' | 'hover-out') {
|
|
38
|
-
this.registry.
|
|
39
|
+
this.registry.notifyItemTrigger(this.itemId, type);
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
receiveAction(type: 'play' | 'pause') {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { ArticleItemType,
|
|
1
|
+
import { ArticleItemType, InteractionItemTrigger, ItemState } from '@cntrl-site/sdk';
|
|
2
2
|
|
|
3
3
|
export interface ItemInteractionCtrl {
|
|
4
4
|
getState(keys: string[]): StateCSSInfo;
|
|
5
|
-
getHasTrigger(itemId: string, triggerType:
|
|
5
|
+
getHasTrigger(itemId: string, triggerType: InteractionItemTrigger['type']): boolean;
|
|
6
6
|
sendTrigger(type: 'click' | 'hover-in' | 'hover-out'): void;
|
|
7
7
|
handleTransitionEnd?: (styleKey: string) => void;
|
|
8
8
|
handleTransitionStart?: (styleKeys: string[]) => void;
|
|
@@ -14,8 +14,8 @@ export interface ItemInteractionCtrl {
|
|
|
14
14
|
export interface InteractionsRegistryPort {
|
|
15
15
|
register(itemId: string, ctrl: ItemInteractionCtrl): void;
|
|
16
16
|
getStatePropsForItem(itemId: string): StateProps;
|
|
17
|
-
getItemAvailableTriggers(itemId: string): Set<
|
|
18
|
-
|
|
17
|
+
getItemAvailableTriggers(itemId: string): Set<InteractionItemTrigger['type']>;
|
|
18
|
+
notifyItemTrigger(itemId: string, type: TriggerType): void;
|
|
19
19
|
notifyTransitionEnd(itemId: string): void;
|
|
20
20
|
}
|
|
21
21
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { createContext, FC, PropsWithChildren, useContext, useMemo } from 'react';
|
|
1
|
+
import { createContext, FC, PropsWithChildren, useCallback, useContext, useEffect, useMemo } from 'react';
|
|
2
2
|
import { InteractionsRegistry } from '../interactions/InteractionsRegistry';
|
|
3
3
|
import { Article } from '@cntrl-site/sdk';
|
|
4
4
|
import { useCurrentLayout } from '../common/useCurrentLayout';
|
|
5
|
+
import { ArticleRectContext } from './ArticleRectContext';
|
|
5
6
|
|
|
6
7
|
export const InteractionsContext = createContext<InteractionsRegistry | undefined>(undefined);
|
|
7
8
|
|
|
@@ -11,10 +12,30 @@ interface Props {
|
|
|
11
12
|
|
|
12
13
|
export const InteractionsProvider: FC<PropsWithChildren<Props>> = ({ article, children }) => {
|
|
13
14
|
const { layoutId } = useCurrentLayout();
|
|
15
|
+
const articleRectObserver = useContext(ArticleRectContext);
|
|
14
16
|
const registry = useMemo(() => {
|
|
15
17
|
if (!layoutId) return;
|
|
16
18
|
return new InteractionsRegistry(article, layoutId);
|
|
17
19
|
}, [layoutId]);
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (!registry || !articleRectObserver) return;
|
|
23
|
+
const handleScroll = () => {
|
|
24
|
+
const scrollY = window.scrollY;
|
|
25
|
+
registry.notifyScroll(scrollY);
|
|
26
|
+
};
|
|
27
|
+
return articleRectObserver.on('scroll', handleScroll);
|
|
28
|
+
}, [registry, articleRectObserver]);
|
|
29
|
+
|
|
30
|
+
const notifyLoad = useCallback(() => {
|
|
31
|
+
if (!registry) return;
|
|
32
|
+
registry.notifyLoad();
|
|
33
|
+
}, [registry]);
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
notifyLoad();
|
|
37
|
+
}, [notifyLoad]);
|
|
38
|
+
|
|
18
39
|
return (
|
|
19
40
|
<InteractionsContext.Provider value={registry}>
|
|
20
41
|
{children}
|