@cntrl-site/sdk-nextjs 1.6.11 → 1.6.12

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 (122) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +4 -4
  3. package/TODO.md +1 -1
  4. package/eslint.config.mjs +35 -35
  5. package/jest.config.js +6 -6
  6. package/lib/common/useItemFXData.js +7 -7
  7. package/lib/components/Article.js +5 -5
  8. package/lib/components/LayoutStyle.js +4 -4
  9. package/lib/components/Section/Section.js +14 -14
  10. package/lib/components/items/CodeEmbedItem/CodeEmbedItem.js +40 -29
  11. package/lib/components/items/CompoundItem/CompoundChild.js +20 -20
  12. package/lib/components/items/CompoundItem/CompoundItem.js +14 -14
  13. package/lib/components/items/CustomItem/CustomItem.js +9 -9
  14. package/lib/components/items/EmbedVideoItem/VimeoEmbed.js +23 -23
  15. package/lib/components/items/EmbedVideoItem/YoutubeEmbed.js +26 -26
  16. package/lib/components/items/FileItem/ImageItem.js +38 -38
  17. package/lib/components/items/FileItem/VideoItem.js +45 -45
  18. package/lib/components/items/GroupItem/GroupItem.js +13 -13
  19. package/lib/components/items/Item.js +30 -30
  20. package/lib/components/items/RectangleItem/RectangleItem.js +20 -20
  21. package/lib/components/items/RichTextItem/RichTextItem.js +21 -21
  22. package/lib/provider/CntrlSdkContext.js +8 -0
  23. package/lib/utils/RichTextConverter/RichTextConverter.js +21 -21
  24. package/package.json +57 -57
  25. package/src/common/useCurrentLayout.ts +52 -52
  26. package/src/common/useExemplary.ts +9 -9
  27. package/src/common/useItemFXData.ts +47 -47
  28. package/src/common/useKeyframeValue.ts +85 -85
  29. package/src/common/useRegisterResize.ts +17 -17
  30. package/src/components/Article.tsx +66 -66
  31. package/src/components/ArticleWrapper.tsx +21 -21
  32. package/src/components/Head.tsx +56 -56
  33. package/src/components/LayoutStyle.tsx +21 -21
  34. package/src/components/Page.tsx +33 -33
  35. package/src/components/ScrollPlaybackVideo.tsx +56 -56
  36. package/src/components/Section/Section.tsx +77 -77
  37. package/src/components/Section/useSectionColor.ts +19 -19
  38. package/src/components/Section/useSectionHeightMap.ts +19 -19
  39. package/src/components/items/CodeEmbedItem/CodeEmbedItem.tsx +132 -120
  40. package/src/components/items/CodeEmbedItem/useCodeEmbedItem.ts +36 -36
  41. package/src/components/items/CompoundItem/CompoundChild.tsx +150 -150
  42. package/src/components/items/CompoundItem/CompoundItem.tsx +71 -71
  43. package/src/components/items/CompoundItem/useCompoundItem.ts +21 -21
  44. package/src/components/items/CustomItem/CustomItem.tsx +47 -47
  45. package/src/components/items/EmbedVideoItem/VimeoEmbed.tsx +202 -202
  46. package/src/components/items/EmbedVideoItem/YoutubeEmbed.tsx +187 -187
  47. package/src/components/items/EmbedVideoItem/useEmbedVideoItem.ts +46 -46
  48. package/src/components/items/FileItem/ImageItem.tsx +156 -156
  49. package/src/components/items/FileItem/VideoItem.tsx +185 -185
  50. package/src/components/items/FileItem/useFileItem.ts +73 -73
  51. package/src/components/items/GroupItem/GroupItem.tsx +79 -79
  52. package/src/components/items/GroupItem/useGroupItem.ts +34 -34
  53. package/src/components/items/Item.tsx +251 -251
  54. package/src/components/items/LinkWrapper.tsx +39 -39
  55. package/src/components/items/RectangleItem/RectangleItem.tsx +95 -95
  56. package/src/components/items/RectangleItem/useRectangleItem.ts +82 -82
  57. package/src/components/items/RichTextItem/RichTextItem.tsx +105 -105
  58. package/src/components/items/RichTextItem/useRichTextItem.ts +12 -12
  59. package/src/components/items/RichTextWrapper.tsx +15 -15
  60. package/src/components/items/itemsMap.ts +26 -26
  61. package/src/components/items/useDraggable.ts +252 -252
  62. package/src/components/items/useItemAngle.ts +13 -13
  63. package/src/components/items/useItemArea.ts +35 -35
  64. package/src/components/items/useItemPointerEvents.ts +23 -23
  65. package/src/components/items/useItemScale.ts +17 -17
  66. package/src/components/items/useItemTriggers.ts +15 -15
  67. package/src/components/items/useRichTextItemValues.ts +65 -65
  68. package/src/components/items/useSizing.ts +25 -25
  69. package/src/components/items/useStickyItemTop.ts +21 -21
  70. package/src/components/useLayoutContext.ts +7 -7
  71. package/src/index.ts +24 -24
  72. package/src/interactions/CSSPropertyNameMap.ts +38 -38
  73. package/src/interactions/InteractionsRegistry.ts +275 -275
  74. package/src/interactions/ItemInteractionCtrl.ts +77 -77
  75. package/src/interactions/getTransition.ts +27 -27
  76. package/src/interactions/types.ts +36 -36
  77. package/src/interactions/useItemInteractionCtrl.ts +18 -18
  78. package/src/provider/ArticleRectContext.ts +4 -4
  79. package/src/provider/CntrlContext.ts +5 -5
  80. package/src/provider/CntrlProvider.tsx +11 -11
  81. package/src/provider/CntrlSdkContext.ts +69 -59
  82. package/src/provider/CustomItemRegistry.ts +14 -14
  83. package/src/provider/CustomItemTypes.ts +3 -3
  84. package/src/provider/CustomSectionRegistry.ts +25 -25
  85. package/src/provider/InteractionsContext.old.tsx +65 -65
  86. package/src/provider/InteractionsContext.test.tsx +97 -97
  87. package/src/provider/InteractionsContext.tsx +28 -28
  88. package/src/provider/Keyframes.ts +11 -11
  89. package/src/provider/KeyframesContext.ts +5 -5
  90. package/src/provider/LayoutContext.ts +3 -3
  91. package/src/provider/defaultContext.ts +8 -8
  92. package/src/provider/useCntrlContext.ts +8 -8
  93. package/src/styled-jsx.d.ts +1 -1
  94. package/src/utils/Animator/Animator.ts +371 -371
  95. package/src/utils/ArticleRectManager/ArticleRectObserver.ts +93 -93
  96. package/src/utils/ArticleRectManager/useArticleRectObserver.ts +17 -17
  97. package/src/utils/ArticleRectManager/useSectionRegistry.ts +11 -11
  98. package/src/utils/EventEmitter.ts +41 -41
  99. package/src/utils/RichTextConverter/RichTextConverter.tsx +326 -326
  100. package/src/utils/ScaleAnchorMap.ts +13 -13
  101. package/src/utils/StickyManager/StickyManager.ts +23 -23
  102. package/src/utils/Youtube/YouTubeIframeApiLoader.ts +58 -58
  103. package/src/utils/Youtube/YoutubeIframeApi.ts +101 -101
  104. package/src/utils/Youtube/useYouTubeIframeApi.ts +12 -12
  105. package/src/utils/binSearchInsertAt.ts +33 -33
  106. package/src/utils/castObject.ts +10 -10
  107. package/src/utils/effects/useImageFx.ts +113 -113
  108. package/src/utils/effects/useVideoFx.ts +116 -116
  109. package/src/utils/getAnchoredItemTop.ts +12 -12
  110. package/src/utils/getCompoundBondaryStyles.ts +62 -62
  111. package/src/utils/getFontFamilyValue.ts +3 -3
  112. package/src/utils/getInvertedRanges.ts +43 -43
  113. package/src/utils/getItemTopStyle.ts +14 -14
  114. package/src/utils/getStyleFromItemStateAndParams.ts +8 -8
  115. package/src/utils/getValidYoutubeUrl.ts +29 -29
  116. package/src/utils/isItemType.ts +6 -6
  117. package/src/utils/rangeMap.ts +13 -13
  118. package/src/utils/useElementRect.ts +23 -23
  119. package/.idea/modules.xml +0 -8
  120. package/.idea/sdk-nextjs.iml +0 -12
  121. package/.idea/vcs.xml +0 -6
  122. package/cntrl-site-sdk-nextjs-1.6.11-alpha.0.tgz +0 -0
@@ -1,275 +1,275 @@
1
- import { InteractionsRegistryPort, ItemInteractionCtrl } from './types';
2
- import {
3
- Article,
4
- ArticleItemType,
5
- Interaction,
6
- InteractionTrigger,
7
- ItemAny,
8
- } from '@cntrl-site/sdk';
9
- import { isItemType } from '../utils/isItemType';
10
-
11
- export class InteractionsRegistry implements InteractionsRegistryPort {
12
- private ctrls: Map<ItemId, ItemInteractionCtrl> = new Map();
13
- private items: ItemAny[];
14
- private interactions: Interaction[];
15
- private stateItemsIdsMap: StateItemsIdsMap;
16
- private interactionStateMap: InteractionStateMap;
17
- private itemsStages: ItemStages;
18
- private activeStateIdInteractionIdMap: Record<StateId, InteractionId>;
19
-
20
- constructor(article: Article, private layoutId: string) {
21
- const { interactions } = article;
22
- this.items = this.unpackItems(article);
23
- const activeStatesIds = interactions.reduce<StateId[]>((map, inter) => {
24
- const activeStateId = inter.states.find((state) => state.id !== inter.startStateId)?.id!;
25
- map.push(activeStateId);
26
- return map;
27
- }, []);
28
- const interactionStateMap = interactions.reduce<InteractionStateMap>((map, { id, startStateId }) => {
29
- map[id] = startStateId;
30
- return map;
31
- }, {});
32
- this.activeStateIdInteractionIdMap = interactions.reduce<Record<StateId, InteractionId>>((map, interaction) => {
33
- const activeState = interaction.states.find((state) => state.id !== interaction.startStateId);
34
- if (activeState) {
35
- map[activeState.id] = interaction.id;
36
- }
37
- return map;
38
- }, {});
39
- const itemStages = this.getDefaultItemStages();
40
- const stateItemsIdsMap = activeStatesIds.reduce<StateItemsIdsMap>((map, stateId) => {
41
- map[stateId] = this.items
42
- .filter((item) => {
43
- const layoutStates = item.state[layoutId] ?? {};
44
- const state = layoutStates?.[stateId] ?? {};
45
- const hasKeys = Object.keys(state).length !== 0;
46
- return hasKeys;
47
- })
48
- .map((item) => item.id);
49
- return map;
50
- }, {});
51
- this.interactions = interactions;
52
- this.itemsStages = itemStages;
53
- this.stateItemsIdsMap = stateItemsIdsMap;
54
- this.interactionStateMap = interactionStateMap;
55
- }
56
-
57
- register(itemId: ItemId, ctrl: ItemInteractionCtrl) {
58
- this.ctrls.set(itemId, ctrl);
59
- }
60
-
61
- getStatePropsForItem(itemId: string) {
62
- const { items, layoutId } = this;
63
- const item = items.find((item) => item.id === itemId)!;
64
- const itemStages = this.itemsStages.filter((stage) => stage.itemId === itemId);
65
- itemStages.sort((a, b) => a.updated - b.updated);
66
- const itemStyles: StateProps = {};
67
- for (const stage of itemStages) {
68
- if (stage.type === 'active') {
69
- if (stage.isStartState) continue;
70
- const params = item.state[layoutId]?.[stage.stateId!] ?? {};
71
- for (const [key, stateDetails] of Object.entries(params)) {
72
- itemStyles[key] = {
73
- value: stateDetails.value
74
- };
75
- }
76
- }
77
- if (stage.type === 'transitioning') {
78
- const activeStateId = stage.direction === 'in' ? stage.to : stage.from;
79
- const params = item.state[layoutId]?.[activeStateId] ?? {};
80
- for (const [key, stateDetails] of Object.entries(params)) {
81
- itemStyles[key] = {
82
- value: stage.direction === 'in' ? stateDetails.value : itemStyles[key]?.value,
83
- transition: {
84
- timing: stateDetails[stage.direction].timing,
85
- duration: stateDetails[stage.direction].duration,
86
- delay: stateDetails[stage.direction].delay
87
- }
88
- };
89
- }
90
- }
91
- }
92
- return itemStyles;
93
- }
94
-
95
- getItemAvailableTriggers(itemId: string): Set<InteractionTrigger['type']> {
96
- const available = new Set<InteractionTrigger['type']>();
97
- const activeStates = Object.values(this.interactionStateMap);
98
- for (const interaction of this.interactions) {
99
- const { triggers } = interaction;
100
- for (const trigger of triggers) {
101
- if (trigger.itemId !== itemId) continue;
102
- if (activeStates.some((stateId) => trigger.from === stateId)) {
103
- available.add(trigger.type);
104
- }
105
- }
106
- }
107
- return available;
108
- }
109
-
110
- notifyTrigger(itemId: string, triggerType: TriggerType): void {
111
- const timestamp = Date.now();
112
- for (const interaction of this.interactions) {
113
- const currentStateId = this.getCurrentStateByInteractionId(interaction.id);
114
- const matchingTrigger = interaction.triggers.find((trigger) =>
115
- trigger.itemId === itemId
116
- && trigger.from === currentStateId
117
- && trigger.type === triggerType
118
- );
119
- if (!matchingTrigger) continue;
120
- const activeStateId = this.getActiveInteractionState(interaction.id);
121
- const isNewStateActive = matchingTrigger.to === activeStateId;
122
- this.setCurrentStateForInteraction(interaction.id, matchingTrigger.to);
123
- const transitioningItems = this.stateItemsIdsMap[activeStateId] ?? [];
124
- const state = interaction.states.find((state) => state.id === matchingTrigger.to);
125
- const actions = state?.actions ?? [];
126
- for (const action of actions) {
127
- const ctrl = this.ctrls.get(action.itemId);
128
- if (!ctrl) continue;
129
- ctrl.receiveAction(action.type);
130
- }
131
- this.itemsStages = this.itemsStages.map((stage) => {
132
- if (stage.interactionId !== interaction.id) return stage;
133
- return {
134
- itemId: stage.itemId,
135
- interactionId: stage.interactionId,
136
- type: 'transitioning',
137
- from: stage.type === 'transitioning' ? stage.to : stage.stateId!,
138
- to: matchingTrigger.to,
139
- direction: isNewStateActive ? 'in' : 'out',
140
- updated: timestamp
141
- };
142
- });
143
- const itemsToNotify = new Set<ItemId>(transitioningItems);
144
- for (const trigger of interaction.triggers) {
145
- itemsToNotify.add(trigger.itemId);
146
- }
147
- this.notifyItemCtrlsChange(Array.from(itemsToNotify));
148
- this.notifyTransitionStartForItems(transitioningItems, activeStateId);
149
- }
150
- }
151
-
152
- notifyTransitionStartForItems(itemsIds: string[], activeStateId: string) {
153
- for (const itemId of itemsIds) {
154
- const ctrl = this.ctrls.get(itemId);
155
- const item = this.items.find((item) => item.id === itemId)!;
156
- const keys = Object.keys(item.state[this.layoutId]?.[activeStateId] ?? {});
157
- ctrl?.handleTransitionStart?.(keys);
158
- }
159
- }
160
-
161
- notifyTransitionEnd(itemId: string): void {
162
- const timestamp = Date.now();
163
- this.itemsStages = this.itemsStages.map((stage) => {
164
- if (stage.itemId !== itemId || stage.type !== 'transitioning') return stage;
165
- return {
166
- itemId: itemId,
167
- interactionId: stage.interactionId,
168
- type: 'active',
169
- stateId: stage.to,
170
- isStartState: stage.direction === 'out',
171
- updated: timestamp
172
- };
173
- });
174
- this.ctrls.get(itemId)?.receiveChange();
175
- }
176
-
177
- private getCurrentStateByInteractionId(id: InteractionId): string {
178
- let state;
179
- for (const interactionId of Object.keys(this.interactionStateMap)) {
180
- if (id !== interactionId) continue;
181
- state = this.interactionStateMap[interactionId];
182
- }
183
- if (!state) throw new Error(`Failed to find current state for interaction w/ id="${id}"`);
184
- return state;
185
- }
186
-
187
- private setCurrentStateForInteraction(interactionId: InteractionId, stateId: StateId) {
188
- this.interactionStateMap = {
189
- ...this.interactionStateMap,
190
- [interactionId]: stateId
191
- };
192
- }
193
-
194
- private getActiveInteractionState(interactionId: InteractionId): string {
195
- const { interactions } = this;
196
- const interaction = interactions.find((interaction) => interaction.id === interactionId)!;
197
- return interaction.states.find(state => state.id !== interaction.startStateId)?.id!;
198
- }
199
-
200
- private notifyItemCtrlsChange(itemsIds: string[]) {
201
- for (const itemId of itemsIds) {
202
- this.ctrls.get(itemId)?.receiveChange();
203
- }
204
- }
205
-
206
- private unpackItems(article: Article): ItemAny[] {
207
- const itemsArr = [];
208
- for (const section of article.sections) {
209
- const items = this.getNestedItems(section.items);
210
- itemsArr.push(...items);
211
- }
212
- return itemsArr;
213
- }
214
-
215
- private getNestedItems(items: ItemAny[]): ItemAny[] {
216
- const allItems: ItemAny[] = [];
217
- for (const item of items) {
218
- if (isItemType(item, ArticleItemType.Group) || isItemType(item, ArticleItemType.Compound)) {
219
- const groupChildren = item?.items ?? [];
220
- const nestedItems = this.getNestedItems(groupChildren);
221
- allItems.push(...nestedItems);
222
- }
223
- allItems.push(item);
224
- }
225
- return allItems;
226
- }
227
-
228
- private getDefaultItemStages(): ItemStages {
229
- const timestamp = Date.now();
230
- const { items, layoutId } = this;
231
- const stages: ItemStages = [];
232
- for (const item of items) {
233
- const itemStatesMap = item.state[layoutId];
234
- if (!itemStatesMap) continue;
235
- for (const stateId of Object.keys(itemStatesMap)) {
236
- const interactionId = this.activeStateIdInteractionIdMap[stateId];
237
- if (!interactionId) continue;
238
- stages.push({
239
- itemId: item.id,
240
- interactionId,
241
- type: 'active',
242
- isStartState: true,
243
- updated: timestamp
244
- });
245
- }
246
- }
247
- return stages;
248
- }
249
- }
250
-
251
- type ItemStages = (TransitioningStage | ActiveStage)[];
252
- type TransitioningStage = {
253
- itemId: string;
254
- interactionId: string;
255
- type: 'transitioning';
256
- from: StateId;
257
- to: StateId;
258
- direction: 'in' | 'out';
259
- updated: number;
260
- };
261
- type ActiveStage = { type: 'active'; itemId: string; interactionId: string; stateId?: string; isStartState: boolean; updated: number; };
262
- type InteractionStateMap = Record<InteractionId, StateId>;
263
- type StateItemsIdsMap = Record<StateId, ItemId[]>;
264
- type TriggerType = InteractionTrigger['type'];
265
- type InteractionId = string;
266
- type StateId = string;
267
- type ItemId = string;
268
- type StateProps = Record<string, {
269
- value?: string | number;
270
- transition?: {
271
- timing: string;
272
- duration: number;
273
- delay: number;
274
- };
275
- }>;
1
+ import { InteractionsRegistryPort, ItemInteractionCtrl } from './types';
2
+ import {
3
+ Article,
4
+ ArticleItemType,
5
+ Interaction,
6
+ InteractionTrigger,
7
+ ItemAny,
8
+ } from '@cntrl-site/sdk';
9
+ import { isItemType } from '../utils/isItemType';
10
+
11
+ export class InteractionsRegistry implements InteractionsRegistryPort {
12
+ private ctrls: Map<ItemId, ItemInteractionCtrl> = new Map();
13
+ private items: ItemAny[];
14
+ private interactions: Interaction[];
15
+ private stateItemsIdsMap: StateItemsIdsMap;
16
+ private interactionStateMap: InteractionStateMap;
17
+ private itemsStages: ItemStages;
18
+ private activeStateIdInteractionIdMap: Record<StateId, InteractionId>;
19
+
20
+ constructor(article: Article, private layoutId: string) {
21
+ const { interactions } = article;
22
+ this.items = this.unpackItems(article);
23
+ const activeStatesIds = interactions.reduce<StateId[]>((map, inter) => {
24
+ const activeStateId = inter.states.find((state) => state.id !== inter.startStateId)?.id!;
25
+ map.push(activeStateId);
26
+ return map;
27
+ }, []);
28
+ const interactionStateMap = interactions.reduce<InteractionStateMap>((map, { id, startStateId }) => {
29
+ map[id] = startStateId;
30
+ return map;
31
+ }, {});
32
+ this.activeStateIdInteractionIdMap = interactions.reduce<Record<StateId, InteractionId>>((map, interaction) => {
33
+ const activeState = interaction.states.find((state) => state.id !== interaction.startStateId);
34
+ if (activeState) {
35
+ map[activeState.id] = interaction.id;
36
+ }
37
+ return map;
38
+ }, {});
39
+ const itemStages = this.getDefaultItemStages();
40
+ const stateItemsIdsMap = activeStatesIds.reduce<StateItemsIdsMap>((map, stateId) => {
41
+ map[stateId] = this.items
42
+ .filter((item) => {
43
+ const layoutStates = item.state[layoutId] ?? {};
44
+ const state = layoutStates?.[stateId] ?? {};
45
+ const hasKeys = Object.keys(state).length !== 0;
46
+ return hasKeys;
47
+ })
48
+ .map((item) => item.id);
49
+ return map;
50
+ }, {});
51
+ this.interactions = interactions;
52
+ this.itemsStages = itemStages;
53
+ this.stateItemsIdsMap = stateItemsIdsMap;
54
+ this.interactionStateMap = interactionStateMap;
55
+ }
56
+
57
+ register(itemId: ItemId, ctrl: ItemInteractionCtrl) {
58
+ this.ctrls.set(itemId, ctrl);
59
+ }
60
+
61
+ getStatePropsForItem(itemId: string) {
62
+ const { items, layoutId } = this;
63
+ const item = items.find((item) => item.id === itemId)!;
64
+ const itemStages = this.itemsStages.filter((stage) => stage.itemId === itemId);
65
+ itemStages.sort((a, b) => a.updated - b.updated);
66
+ const itemStyles: StateProps = {};
67
+ for (const stage of itemStages) {
68
+ if (stage.type === 'active') {
69
+ if (stage.isStartState) continue;
70
+ const params = item.state[layoutId]?.[stage.stateId!] ?? {};
71
+ for (const [key, stateDetails] of Object.entries(params)) {
72
+ itemStyles[key] = {
73
+ value: stateDetails.value
74
+ };
75
+ }
76
+ }
77
+ if (stage.type === 'transitioning') {
78
+ const activeStateId = stage.direction === 'in' ? stage.to : stage.from;
79
+ const params = item.state[layoutId]?.[activeStateId] ?? {};
80
+ for (const [key, stateDetails] of Object.entries(params)) {
81
+ itemStyles[key] = {
82
+ value: stage.direction === 'in' ? stateDetails.value : itemStyles[key]?.value,
83
+ transition: {
84
+ timing: stateDetails[stage.direction].timing,
85
+ duration: stateDetails[stage.direction].duration,
86
+ delay: stateDetails[stage.direction].delay
87
+ }
88
+ };
89
+ }
90
+ }
91
+ }
92
+ return itemStyles;
93
+ }
94
+
95
+ getItemAvailableTriggers(itemId: string): Set<InteractionTrigger['type']> {
96
+ const available = new Set<InteractionTrigger['type']>();
97
+ const activeStates = Object.values(this.interactionStateMap);
98
+ for (const interaction of this.interactions) {
99
+ const { triggers } = interaction;
100
+ for (const trigger of triggers) {
101
+ if (trigger.itemId !== itemId) continue;
102
+ if (activeStates.some((stateId) => trigger.from === stateId)) {
103
+ available.add(trigger.type);
104
+ }
105
+ }
106
+ }
107
+ return available;
108
+ }
109
+
110
+ notifyTrigger(itemId: string, triggerType: TriggerType): void {
111
+ const timestamp = Date.now();
112
+ for (const interaction of this.interactions) {
113
+ const currentStateId = this.getCurrentStateByInteractionId(interaction.id);
114
+ const matchingTrigger = interaction.triggers.find((trigger) =>
115
+ trigger.itemId === itemId
116
+ && trigger.from === currentStateId
117
+ && trigger.type === triggerType
118
+ );
119
+ if (!matchingTrigger) continue;
120
+ const activeStateId = this.getActiveInteractionState(interaction.id);
121
+ const isNewStateActive = matchingTrigger.to === activeStateId;
122
+ this.setCurrentStateForInteraction(interaction.id, matchingTrigger.to);
123
+ const transitioningItems = this.stateItemsIdsMap[activeStateId] ?? [];
124
+ const state = interaction.states.find((state) => state.id === matchingTrigger.to);
125
+ const actions = state?.actions ?? [];
126
+ for (const action of actions) {
127
+ const ctrl = this.ctrls.get(action.itemId);
128
+ if (!ctrl) continue;
129
+ ctrl.receiveAction(action.type);
130
+ }
131
+ this.itemsStages = this.itemsStages.map((stage) => {
132
+ if (stage.interactionId !== interaction.id) return stage;
133
+ return {
134
+ itemId: stage.itemId,
135
+ interactionId: stage.interactionId,
136
+ type: 'transitioning',
137
+ from: stage.type === 'transitioning' ? stage.to : stage.stateId!,
138
+ to: matchingTrigger.to,
139
+ direction: isNewStateActive ? 'in' : 'out',
140
+ updated: timestamp
141
+ };
142
+ });
143
+ const itemsToNotify = new Set<ItemId>(transitioningItems);
144
+ for (const trigger of interaction.triggers) {
145
+ itemsToNotify.add(trigger.itemId);
146
+ }
147
+ this.notifyItemCtrlsChange(Array.from(itemsToNotify));
148
+ this.notifyTransitionStartForItems(transitioningItems, activeStateId);
149
+ }
150
+ }
151
+
152
+ notifyTransitionStartForItems(itemsIds: string[], activeStateId: string) {
153
+ for (const itemId of itemsIds) {
154
+ const ctrl = this.ctrls.get(itemId);
155
+ const item = this.items.find((item) => item.id === itemId)!;
156
+ const keys = Object.keys(item.state[this.layoutId]?.[activeStateId] ?? {});
157
+ ctrl?.handleTransitionStart?.(keys);
158
+ }
159
+ }
160
+
161
+ notifyTransitionEnd(itemId: string): void {
162
+ const timestamp = Date.now();
163
+ this.itemsStages = this.itemsStages.map((stage) => {
164
+ if (stage.itemId !== itemId || stage.type !== 'transitioning') return stage;
165
+ return {
166
+ itemId: itemId,
167
+ interactionId: stage.interactionId,
168
+ type: 'active',
169
+ stateId: stage.to,
170
+ isStartState: stage.direction === 'out',
171
+ updated: timestamp
172
+ };
173
+ });
174
+ this.ctrls.get(itemId)?.receiveChange();
175
+ }
176
+
177
+ private getCurrentStateByInteractionId(id: InteractionId): string {
178
+ let state;
179
+ for (const interactionId of Object.keys(this.interactionStateMap)) {
180
+ if (id !== interactionId) continue;
181
+ state = this.interactionStateMap[interactionId];
182
+ }
183
+ if (!state) throw new Error(`Failed to find current state for interaction w/ id="${id}"`);
184
+ return state;
185
+ }
186
+
187
+ private setCurrentStateForInteraction(interactionId: InteractionId, stateId: StateId) {
188
+ this.interactionStateMap = {
189
+ ...this.interactionStateMap,
190
+ [interactionId]: stateId
191
+ };
192
+ }
193
+
194
+ private getActiveInteractionState(interactionId: InteractionId): string {
195
+ const { interactions } = this;
196
+ const interaction = interactions.find((interaction) => interaction.id === interactionId)!;
197
+ return interaction.states.find(state => state.id !== interaction.startStateId)?.id!;
198
+ }
199
+
200
+ private notifyItemCtrlsChange(itemsIds: string[]) {
201
+ for (const itemId of itemsIds) {
202
+ this.ctrls.get(itemId)?.receiveChange();
203
+ }
204
+ }
205
+
206
+ private unpackItems(article: Article): ItemAny[] {
207
+ const itemsArr = [];
208
+ for (const section of article.sections) {
209
+ const items = this.getNestedItems(section.items);
210
+ itemsArr.push(...items);
211
+ }
212
+ return itemsArr;
213
+ }
214
+
215
+ private getNestedItems(items: ItemAny[]): ItemAny[] {
216
+ const allItems: ItemAny[] = [];
217
+ for (const item of items) {
218
+ if (isItemType(item, ArticleItemType.Group) || isItemType(item, ArticleItemType.Compound)) {
219
+ const groupChildren = item?.items ?? [];
220
+ const nestedItems = this.getNestedItems(groupChildren);
221
+ allItems.push(...nestedItems);
222
+ }
223
+ allItems.push(item);
224
+ }
225
+ return allItems;
226
+ }
227
+
228
+ private getDefaultItemStages(): ItemStages {
229
+ const timestamp = Date.now();
230
+ const { items, layoutId } = this;
231
+ const stages: ItemStages = [];
232
+ for (const item of items) {
233
+ const itemStatesMap = item.state[layoutId];
234
+ if (!itemStatesMap) continue;
235
+ for (const stateId of Object.keys(itemStatesMap)) {
236
+ const interactionId = this.activeStateIdInteractionIdMap[stateId];
237
+ if (!interactionId) continue;
238
+ stages.push({
239
+ itemId: item.id,
240
+ interactionId,
241
+ type: 'active',
242
+ isStartState: true,
243
+ updated: timestamp
244
+ });
245
+ }
246
+ }
247
+ return stages;
248
+ }
249
+ }
250
+
251
+ type ItemStages = (TransitioningStage | ActiveStage)[];
252
+ type TransitioningStage = {
253
+ itemId: string;
254
+ interactionId: string;
255
+ type: 'transitioning';
256
+ from: StateId;
257
+ to: StateId;
258
+ direction: 'in' | 'out';
259
+ updated: number;
260
+ };
261
+ type ActiveStage = { type: 'active'; itemId: string; interactionId: string; stateId?: string; isStartState: boolean; updated: number; };
262
+ type InteractionStateMap = Record<InteractionId, StateId>;
263
+ type StateItemsIdsMap = Record<StateId, ItemId[]>;
264
+ type TriggerType = InteractionTrigger['type'];
265
+ type InteractionId = string;
266
+ type StateId = string;
267
+ type ItemId = string;
268
+ type StateProps = Record<string, {
269
+ value?: string | number;
270
+ transition?: {
271
+ timing: string;
272
+ duration: number;
273
+ delay: number;
274
+ };
275
+ }>;