@cntrl-site/sdk-nextjs 1.8.13 → 1.8.15

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 (126) 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 +11 -8
  8. package/lib/components/LayoutStyle.js +4 -4
  9. package/lib/components/Section/Section.js +43 -43
  10. package/lib/components/items/CodeEmbedItem/CodeEmbedItem.js +35 -35
  11. package/lib/components/items/ComponentItem/ComponentItem.js +12 -12
  12. package/lib/components/items/CompoundItem/CompoundChild.js +20 -20
  13. package/lib/components/items/CompoundItem/CompoundItem.js +14 -14
  14. package/lib/components/items/CustomItem/CustomItem.js +9 -9
  15. package/lib/components/items/EmbedVideoItem/VimeoEmbed.js +24 -24
  16. package/lib/components/items/EmbedVideoItem/YoutubeEmbed.js +27 -27
  17. package/lib/components/items/FileItem/ImageItem.js +39 -39
  18. package/lib/components/items/FileItem/VideoItem.js +46 -46
  19. package/lib/components/items/GroupItem/GroupItem.js +13 -13
  20. package/lib/components/items/Item.js +30 -30
  21. package/lib/components/items/RectangleItem/RectangleItem.js +21 -21
  22. package/lib/components/items/RichTextItem/RichTextItem.js +22 -22
  23. package/lib/provider/WebGLContextManagerContext.js +6 -0
  24. package/lib/utils/RichTextConverter/RichTextConverter.js +21 -21
  25. package/lib/utils/effects/useImageFx.js +56 -29
  26. package/lib/utils/effects/useVideoFx.js +56 -29
  27. package/package.json +57 -57
  28. package/src/common/useCurrentLayout.ts +52 -52
  29. package/src/common/useExemplary.ts +9 -9
  30. package/src/common/useItemFXData.ts +47 -47
  31. package/src/common/useKeyframeValue.ts +85 -85
  32. package/src/common/useRegisterResize.ts +16 -16
  33. package/src/components/Article.tsx +73 -66
  34. package/src/components/ArticleWrapper.tsx +21 -21
  35. package/src/components/Head.tsx +56 -56
  36. package/src/components/LayoutStyle.tsx +21 -21
  37. package/src/components/Page.tsx +33 -33
  38. package/src/components/ScrollPlaybackVideo.tsx +56 -56
  39. package/src/components/Section/Section.tsx +164 -164
  40. package/src/components/Section/useSectionColor.ts +19 -19
  41. package/src/components/Section/useSectionHeightMap.ts +19 -19
  42. package/src/components/items/CodeEmbedItem/CodeEmbedItem.tsx +137 -137
  43. package/src/components/items/CodeEmbedItem/useCodeEmbedItem.ts +36 -36
  44. package/src/components/items/ComponentItem/ComponentItem.tsx +65 -65
  45. package/src/components/items/ComponentItem/useComponentItem.ts +35 -35
  46. package/src/components/items/CompoundItem/CompoundChild.tsx +150 -150
  47. package/src/components/items/CompoundItem/CompoundItem.tsx +70 -70
  48. package/src/components/items/CompoundItem/useCompoundItem.ts +21 -21
  49. package/src/components/items/CustomItem/CustomItem.tsx +48 -48
  50. package/src/components/items/EmbedVideoItem/VimeoEmbed.tsx +202 -202
  51. package/src/components/items/EmbedVideoItem/YoutubeEmbed.tsx +187 -187
  52. package/src/components/items/EmbedVideoItem/useEmbedVideoItem.ts +46 -46
  53. package/src/components/items/FileItem/ImageItem.tsx +156 -156
  54. package/src/components/items/FileItem/VideoItem.tsx +185 -185
  55. package/src/components/items/FileItem/useFileItem.ts +73 -73
  56. package/src/components/items/GroupItem/GroupItem.tsx +79 -79
  57. package/src/components/items/GroupItem/useGroupItem.ts +34 -34
  58. package/src/components/items/Item.tsx +251 -251
  59. package/src/components/items/LinkWrapper.tsx +39 -39
  60. package/src/components/items/RectangleItem/RectangleItem.tsx +95 -95
  61. package/src/components/items/RectangleItem/useRectangleItem.ts +82 -82
  62. package/src/components/items/RichTextItem/RichTextItem.tsx +105 -105
  63. package/src/components/items/RichTextItem/useRichTextItem.ts +12 -12
  64. package/src/components/items/RichTextWrapper.tsx +15 -15
  65. package/src/components/items/itemsMap.ts +28 -28
  66. package/src/components/items/useDraggable.ts +252 -252
  67. package/src/components/items/useItemAngle.ts +13 -13
  68. package/src/components/items/useItemArea.ts +35 -35
  69. package/src/components/items/useItemPointerEvents.ts +23 -23
  70. package/src/components/items/useItemScale.ts +17 -17
  71. package/src/components/items/useItemTriggers.ts +15 -15
  72. package/src/components/items/useRichTextItemValues.ts +65 -65
  73. package/src/components/items/useSizing.ts +25 -25
  74. package/src/components/items/useStickyItemTop.ts +21 -21
  75. package/src/components/useLayoutContext.ts +7 -7
  76. package/src/index.ts +24 -24
  77. package/src/interactions/CSSPropertyNameMap.ts +38 -38
  78. package/src/interactions/InteractionsRegistry.ts +283 -283
  79. package/src/interactions/ItemInteractionCtrl.ts +77 -77
  80. package/src/interactions/getTransition.ts +27 -27
  81. package/src/interactions/types.ts +36 -36
  82. package/src/interactions/useItemInteractionCtrl.ts +18 -18
  83. package/src/provider/ArticleRectContext.ts +4 -4
  84. package/src/provider/CntrlContext.ts +5 -5
  85. package/src/provider/CntrlProvider.tsx +11 -11
  86. package/src/provider/CntrlSdkContext.ts +83 -83
  87. package/src/provider/CustomItemRegistry.ts +14 -14
  88. package/src/provider/CustomItemTypes.ts +3 -3
  89. package/src/provider/CustomSectionRegistry.ts +25 -25
  90. package/src/provider/InteractionsContext.old.tsx +66 -66
  91. package/src/provider/InteractionsContext.test.tsx +97 -97
  92. package/src/provider/InteractionsContext.tsx +28 -28
  93. package/src/provider/Keyframes.ts +11 -11
  94. package/src/provider/KeyframesContext.ts +5 -5
  95. package/src/provider/LayoutContext.ts +3 -3
  96. package/src/provider/WebGLContextManagerContext.tsx +4 -0
  97. package/src/provider/defaultContext.ts +8 -8
  98. package/src/provider/useCntrlContext.ts +8 -8
  99. package/src/styled-jsx.d.ts +1 -1
  100. package/src/utils/Animator/Animator.ts +371 -371
  101. package/src/utils/ArticleRectManager/ArticleRectObserver.ts +93 -93
  102. package/src/utils/ArticleRectManager/useArticleRectObserver.ts +17 -17
  103. package/src/utils/ArticleRectManager/useSectionRegistry.ts +11 -11
  104. package/src/utils/EventEmitter.ts +41 -41
  105. package/src/utils/RichTextConverter/RichTextConverter.tsx +326 -326
  106. package/src/utils/ScaleAnchorMap.ts +13 -13
  107. package/src/utils/StickyManager/StickyManager.ts +23 -23
  108. package/src/utils/Youtube/YouTubeIframeApiLoader.ts +58 -58
  109. package/src/utils/Youtube/YoutubeIframeApi.ts +101 -101
  110. package/src/utils/Youtube/useYouTubeIframeApi.ts +12 -12
  111. package/src/utils/binSearchInsertAt.ts +33 -33
  112. package/src/utils/castObject.ts +10 -10
  113. package/src/utils/effects/useImageFx.ts +156 -113
  114. package/src/utils/effects/useVideoFx.ts +158 -116
  115. package/src/utils/getAnchoredItemTop.ts +12 -12
  116. package/src/utils/getCompoundBoundaryStyles.ts +60 -60
  117. package/src/utils/getFontFamilyValue.ts +3 -3
  118. package/src/utils/getInvertedRanges.ts +43 -43
  119. package/src/utils/getItemTopStyle.ts +14 -14
  120. package/src/utils/getStyleFromItemStateAndParams.ts +8 -8
  121. package/src/utils/getValidYoutubeUrl.ts +29 -29
  122. package/src/utils/isItemType.ts +5 -5
  123. package/src/utils/rangeMap.ts +13 -13
  124. package/src/utils/useElementRect.ts +23 -23
  125. package/cntrl-site-sdk-nextjs-1.8.12.tgz +0 -0
  126. package/cntrl-site-sdk-nextjs-1.8.13.tgz +0 -0
@@ -1,371 +1,371 @@
1
- import { KeyframeType, KeyframeValueMap } from '@cntrl-site/sdk';
2
- import { CntrlColor } from '@cntrl-site/color';
3
- import { binSearchInsertAt, createInsert } from '../binSearchInsertAt';
4
- import { rangeMap } from '../rangeMap';
5
-
6
- export interface AnimationData<T extends KeyframeType> {
7
- position: number;
8
- value: KeyframeValueMap[T];
9
- type: T;
10
- }
11
-
12
- interface PositionKeyframes<T extends KeyframeType> {
13
- start: AnimationData<T>;
14
- end: AnimationData<T>;
15
- }
16
-
17
- export type KeyframesMap = {
18
- [T in KeyframeType]: AnimationData<T>[];
19
- };
20
-
21
- const compare = (lhs: { position: number }, rhs: { position: number }) => lhs.position - rhs.position;
22
- const insertBin = createInsert(binSearchInsertAt, compare);
23
-
24
- export class Animator {
25
- private static pushKeyframeToMap<T extends KeyframeType>(keyframe: AnimationData<T>, map: KeyframesMap): void {
26
- insertBin(map[keyframe.type], keyframe);
27
- }
28
-
29
- private keyframesMap: KeyframesMap = createKeyframesMap();
30
- constructor(
31
- private keyframes: AnimationData<KeyframeType>[]
32
- ) {
33
- this.fillKeyframesMap();
34
- }
35
-
36
- getFXParams(
37
- valuesMap: Record<string, number>,
38
- pos: number
39
- ): KeyframeValueMap[KeyframeType.FXParams] {
40
- const keyframes = this.keyframesMap[KeyframeType.FXParams];
41
- if (!keyframes.length) return valuesMap;
42
- if (keyframes.length === 1) {
43
- const [keyframe] = keyframes;
44
- return Object.entries(valuesMap).reduce<Record<string, number>>((acc, [key, fallbackValue]) => {
45
- acc[key] = keyframe.value[key] ?? fallbackValue;
46
- return acc;
47
- }, {});
48
- }
49
- const { start, end } = this.getStartEnd<KeyframeType.FXParams>(pos, keyframes);
50
- return Object.entries(valuesMap).reduce<Record<string, number>>((acc, [key, fallbackValue]) => {
51
- acc[key] = rangeMap(pos, start.position, end.position, start.value[key] ?? fallbackValue, end.value[key] ?? fallbackValue, true);
52
- return acc;
53
- }, {});
54
- }
55
-
56
- getDimensions(
57
- values: KeyframeValueMap[KeyframeType.Dimensions],
58
- pos: number
59
- ): KeyframeValueMap[KeyframeType.Dimensions] {
60
- const keyframes = this.keyframesMap[KeyframeType.Dimensions];
61
- if (!keyframes || !keyframes.length) return values;
62
- if (keyframes.length === 1) {
63
- const [keyframe] = keyframes;
64
- return {
65
- width: keyframe.value.width,
66
- height: keyframe.value.height
67
- };
68
- }
69
- const { start, end } = this.getStartEnd<KeyframeType.Dimensions>(pos, keyframes);
70
- return {
71
- width: rangeMap(pos, start.position, end.position, start.value.width, end.value.width, true),
72
- height: rangeMap(pos, start.position, end.position, start.value.height, end.value.height, true)
73
- };
74
- }
75
-
76
- getPositions(
77
- values: KeyframeValueMap[KeyframeType.Position],
78
- pos: number
79
- ): KeyframeValueMap[KeyframeType.Position] {
80
- const keyframes = this.keyframesMap[KeyframeType.Position];
81
- if (!keyframes || !keyframes.length) return values;
82
- if (keyframes.length === 1) {
83
- const [keyframe] = keyframes;
84
- return {
85
- left: keyframe.value.left,
86
- top: keyframe.value.top
87
- };
88
- }
89
- const { start, end } = this.getStartEnd<KeyframeType.Position>(pos, keyframes);
90
- return {
91
- left: rangeMap(pos, start.position, end.position, start.value.left, end.value.left, true),
92
- top: rangeMap(pos, start.position, end.position, start.value.top, end.value.top, true)
93
- };
94
- }
95
-
96
- getColor(
97
- values: KeyframeValueMap[KeyframeType.Color],
98
- pos: number
99
- ): KeyframeValueMap[KeyframeType.Color] {
100
- const keyframes = this.keyframesMap[KeyframeType.Color];
101
- if (!keyframes || !keyframes.length) return values;
102
- if (keyframes.length === 1) {
103
- const [keyframe] = keyframes;
104
- return {
105
- color: keyframe.value.color
106
- };
107
- }
108
- const { start, end } = this.getStartEnd<KeyframeType.Color>(pos, keyframes);
109
- return {
110
- color: this.getRangeColor(start, end, pos)
111
- };
112
- }
113
-
114
- getBorderColor(
115
- values: KeyframeValueMap[KeyframeType.BorderColor],
116
- pos: number
117
- ): KeyframeValueMap[KeyframeType.BorderColor] {
118
- const keyframes = this.keyframesMap[KeyframeType.BorderColor];
119
- if (!keyframes || !keyframes.length) return values;
120
- if (keyframes.length === 1) {
121
- const [keyframe] = keyframes;
122
- return {
123
- color: keyframe.value.color
124
- };
125
- }
126
- const { start, end } = this.getStartEnd<KeyframeType.BorderColor>(pos, keyframes);
127
- return {
128
- color: this.getRangeColor(start, end, pos)
129
- };
130
- }
131
-
132
- getRadius(
133
- values: KeyframeValueMap[KeyframeType.BorderRadius],
134
- pos: number
135
- ): KeyframeValueMap[KeyframeType.BorderRadius] {
136
- const keyframes = this.keyframesMap[KeyframeType.BorderRadius];
137
- if (!keyframes || !keyframes.length) return values;
138
- if (keyframes.length === 1) {
139
- const [keyframe] = keyframes;
140
- return {
141
- radius: keyframe.value.radius
142
- };
143
- }
144
- const { start, end } = this.getStartEnd<KeyframeType.BorderRadius>(pos, keyframes);
145
- return {
146
- radius: rangeMap(pos, start.position, end.position, start.value.radius, end.value.radius, true)
147
- };
148
- }
149
-
150
- getBorderWidth(
151
- values: KeyframeValueMap[KeyframeType.BorderWidth],
152
- pos: number
153
- ): KeyframeValueMap[KeyframeType.BorderWidth] {
154
- const keyframes = this.keyframesMap[KeyframeType.BorderWidth];
155
- if (!keyframes || !keyframes.length) return values;
156
- if (keyframes.length === 1) {
157
- const [keyframe] = keyframes;
158
- return {
159
- borderWidth: keyframe.value.borderWidth
160
- };
161
- }
162
- const { start, end } = this.getStartEnd<KeyframeType.BorderWidth>(pos, keyframes);
163
- return {
164
- borderWidth: rangeMap(pos, start.position, end.position, start.value.borderWidth, end.value.borderWidth, true)
165
- };
166
- }
167
-
168
- getRotation(
169
- values: KeyframeValueMap[KeyframeType.Rotation],
170
- pos: number
171
- ): KeyframeValueMap[KeyframeType.Rotation] {
172
- const keyframes = this.keyframesMap[KeyframeType.Rotation];
173
- if (!keyframes || !keyframes.length) return values;
174
- if (keyframes.length === 1) {
175
- const [keyframe] = keyframes;
176
- return {
177
- angle: keyframe.value.angle
178
- };
179
- }
180
- const { start, end } = this.getStartEnd<KeyframeType.Rotation>(pos, keyframes);
181
- return {
182
- angle: rangeMap(pos, start.position, end.position, start.value.angle, end.value.angle, true)
183
- };
184
- }
185
-
186
- getOpacity(
187
- values: KeyframeValueMap[KeyframeType.Opacity],
188
- pos: number
189
- ): KeyframeValueMap[KeyframeType.Opacity] {
190
- const keyframes = this.keyframesMap[KeyframeType.Opacity];
191
- if (!keyframes || !keyframes.length) return values;
192
- if (keyframes.length === 1) {
193
- const [keyframe] = keyframes;
194
- return {
195
- opacity: keyframe.value.opacity
196
- };
197
- }
198
- const { start, end } = this.getStartEnd<KeyframeType.Opacity>(pos, keyframes);
199
- return {
200
- opacity: rangeMap(pos, start.position, end.position, start.value.opacity, end.value.opacity, true)
201
- };
202
- }
203
-
204
- getScale(
205
- values: KeyframeValueMap[KeyframeType.Scale],
206
- pos: number
207
- ): KeyframeValueMap[KeyframeType.Scale] {
208
- const keyframes = this.keyframesMap[KeyframeType.Scale];
209
- if (!keyframes || !keyframes.length) return values;
210
- if (keyframes.length === 1) {
211
- const [keyframe] = keyframes;
212
- return {
213
- scale: keyframe.value.scale
214
- };
215
- }
216
- const { start, end } = this.getStartEnd<KeyframeType.Scale>(pos, keyframes);
217
- return {
218
- scale: rangeMap(pos, start.position, end.position, start.value.scale, end.value.scale, true)
219
- };
220
- }
221
-
222
- getBlur(
223
- values: KeyframeValueMap[KeyframeType.Blur],
224
- pos: number
225
- ): KeyframeValueMap[KeyframeType.Blur] {
226
- const keyframes = this.keyframesMap[KeyframeType.Blur];
227
- if (!keyframes || !keyframes.length) return values;
228
- if (keyframes.length === 1) {
229
- const [keyframe] = keyframes;
230
- return {
231
- blur: keyframe.value.blur
232
- };
233
- }
234
- const { start, end } = this.getStartEnd<KeyframeType.Blur>(pos, keyframes);
235
- return {
236
- blur: rangeMap(pos, start.position, end.position, start.value.blur, end.value.blur, true)
237
- };
238
- }
239
-
240
- getBackdropBlur(
241
- values: KeyframeValueMap[KeyframeType.BackdropBlur],
242
- pos: number
243
- ): KeyframeValueMap[KeyframeType.BackdropBlur] {
244
- const keyframes = this.keyframesMap[KeyframeType.BackdropBlur];
245
- if (!keyframes || !keyframes.length) return values;
246
- if (keyframes.length === 1) {
247
- const [keyframe] = keyframes;
248
- return {
249
- backdropBlur: keyframe.value.backdropBlur
250
- };
251
- }
252
- const { start, end } = this.getStartEnd<KeyframeType.BackdropBlur>(pos, keyframes);
253
- return {
254
- backdropBlur: rangeMap(pos, start.position, end.position, start.value.backdropBlur, end.value.backdropBlur, true)
255
- };
256
- }
257
-
258
- getTextColor(
259
- values: KeyframeValueMap[KeyframeType.TextColor],
260
- pos: number
261
- ): KeyframeValueMap[KeyframeType.TextColor] {
262
- const keyframes = this.keyframesMap[KeyframeType.TextColor];
263
- if (!keyframes || !keyframes.length) return values;
264
- if (keyframes.length === 1) {
265
- const [keyframe] = keyframes;
266
- return {
267
- color: keyframe.value.color
268
- };
269
- }
270
- const { start, end } = this.getStartEnd<KeyframeType.TextColor>(pos, keyframes);
271
- return {
272
- color: this.getRangeColor(start, end, pos)
273
- };
274
- }
275
-
276
- getLetterSpacing(
277
- values: KeyframeValueMap[KeyframeType.LetterSpacing],
278
- pos: number
279
- ): KeyframeValueMap[KeyframeType.LetterSpacing] {
280
- const keyframes = this.keyframesMap[KeyframeType.LetterSpacing];
281
- if (!keyframes || !keyframes.length) return values;
282
- if (keyframes.length === 1) {
283
- const [keyframe] = keyframes;
284
- return {
285
- letterSpacing: keyframe.value.letterSpacing
286
- };
287
- }
288
- const { start, end } = this.getStartEnd<KeyframeType.LetterSpacing>(pos, keyframes);
289
- return {
290
- letterSpacing: rangeMap(
291
- pos,
292
- start.position,
293
- end.position,
294
- start.value.letterSpacing,
295
- end.value.letterSpacing,
296
- true
297
- )
298
- };
299
- }
300
-
301
- getWordSpacing(
302
- values: KeyframeValueMap[KeyframeType.WordSpacing],
303
- pos: number
304
- ): KeyframeValueMap[KeyframeType.WordSpacing] {
305
- const keyframes = this.keyframesMap[KeyframeType.WordSpacing];
306
- if (!keyframes || !keyframes.length) return values;
307
- if (keyframes.length === 1) {
308
- const [keyframe] = keyframes;
309
- return {
310
- wordSpacing: keyframe.value.wordSpacing
311
- };
312
- }
313
- const { start, end } = this.getStartEnd<KeyframeType.WordSpacing>(pos, keyframes);
314
- return {
315
- wordSpacing: rangeMap(
316
- pos,
317
- start.position,
318
- end.position,
319
- start.value.wordSpacing,
320
- end.value.wordSpacing,
321
- true
322
- )
323
- };
324
- }
325
-
326
- getStartEnd<T extends KeyframeType>(position: number, keyframes: AnimationData<T>[]): PositionKeyframes<T> {
327
- const index = binSearchInsertAt(keyframes, { position }, compare);
328
- const end = index === keyframes.length ? index - 1 : index;
329
- const start = end === 0 ? end : end - 1;
330
- return { start: keyframes[start], end: keyframes[end] };
331
- }
332
-
333
- private fillKeyframesMap() {
334
- this.keyframesMap = createKeyframesMap();
335
- for (const keyframe of this.keyframes) {
336
- Animator.pushKeyframeToMap(keyframe, this.keyframesMap);
337
- }
338
- }
339
-
340
- private getRangeColor(
341
- start: AnimationData<KeyframeType.Color | KeyframeType.BorderColor | KeyframeType.TextColor>,
342
- end: AnimationData<KeyframeType.Color | KeyframeType.BorderColor | KeyframeType.TextColor>,
343
- position: number
344
- ): string {
345
- const rangeAmount = rangeMap(position, start.position, end.position, 0, 1, true);
346
- const startColor = CntrlColor.parse(start.value.color);
347
- const endColor = CntrlColor.parse(end.value.color);
348
- const mixedColor = startColor.mix(endColor, rangeAmount);
349
- return mixedColor.fmt('oklch');
350
- }
351
- }
352
-
353
- function createKeyframesMap(): KeyframesMap {
354
- return {
355
- [KeyframeType.Dimensions]: [],
356
- [KeyframeType.Position]: [],
357
- [KeyframeType.BorderWidth]: [],
358
- [KeyframeType.BorderRadius]: [],
359
- [KeyframeType.Color]: [],
360
- [KeyframeType.Rotation]: [],
361
- [KeyframeType.BorderColor]: [],
362
- [KeyframeType.Opacity]: [],
363
- [KeyframeType.Scale]: [],
364
- [KeyframeType.Blur]: [],
365
- [KeyframeType.BackdropBlur]: [],
366
- [KeyframeType.LetterSpacing]: [],
367
- [KeyframeType.WordSpacing]: [],
368
- [KeyframeType.TextColor]: [],
369
- [KeyframeType.FXParams]: []
370
- };
371
- }
1
+ import { KeyframeType, KeyframeValueMap } from '@cntrl-site/sdk';
2
+ import { CntrlColor } from '@cntrl-site/color';
3
+ import { binSearchInsertAt, createInsert } from '../binSearchInsertAt';
4
+ import { rangeMap } from '../rangeMap';
5
+
6
+ export interface AnimationData<T extends KeyframeType> {
7
+ position: number;
8
+ value: KeyframeValueMap[T];
9
+ type: T;
10
+ }
11
+
12
+ interface PositionKeyframes<T extends KeyframeType> {
13
+ start: AnimationData<T>;
14
+ end: AnimationData<T>;
15
+ }
16
+
17
+ export type KeyframesMap = {
18
+ [T in KeyframeType]: AnimationData<T>[];
19
+ };
20
+
21
+ const compare = (lhs: { position: number }, rhs: { position: number }) => lhs.position - rhs.position;
22
+ const insertBin = createInsert(binSearchInsertAt, compare);
23
+
24
+ export class Animator {
25
+ private static pushKeyframeToMap<T extends KeyframeType>(keyframe: AnimationData<T>, map: KeyframesMap): void {
26
+ insertBin(map[keyframe.type], keyframe);
27
+ }
28
+
29
+ private keyframesMap: KeyframesMap = createKeyframesMap();
30
+ constructor(
31
+ private keyframes: AnimationData<KeyframeType>[]
32
+ ) {
33
+ this.fillKeyframesMap();
34
+ }
35
+
36
+ getFXParams(
37
+ valuesMap: Record<string, number>,
38
+ pos: number
39
+ ): KeyframeValueMap[KeyframeType.FXParams] {
40
+ const keyframes = this.keyframesMap[KeyframeType.FXParams];
41
+ if (!keyframes.length) return valuesMap;
42
+ if (keyframes.length === 1) {
43
+ const [keyframe] = keyframes;
44
+ return Object.entries(valuesMap).reduce<Record<string, number>>((acc, [key, fallbackValue]) => {
45
+ acc[key] = keyframe.value[key] ?? fallbackValue;
46
+ return acc;
47
+ }, {});
48
+ }
49
+ const { start, end } = this.getStartEnd<KeyframeType.FXParams>(pos, keyframes);
50
+ return Object.entries(valuesMap).reduce<Record<string, number>>((acc, [key, fallbackValue]) => {
51
+ acc[key] = rangeMap(pos, start.position, end.position, start.value[key] ?? fallbackValue, end.value[key] ?? fallbackValue, true);
52
+ return acc;
53
+ }, {});
54
+ }
55
+
56
+ getDimensions(
57
+ values: KeyframeValueMap[KeyframeType.Dimensions],
58
+ pos: number
59
+ ): KeyframeValueMap[KeyframeType.Dimensions] {
60
+ const keyframes = this.keyframesMap[KeyframeType.Dimensions];
61
+ if (!keyframes || !keyframes.length) return values;
62
+ if (keyframes.length === 1) {
63
+ const [keyframe] = keyframes;
64
+ return {
65
+ width: keyframe.value.width,
66
+ height: keyframe.value.height
67
+ };
68
+ }
69
+ const { start, end } = this.getStartEnd<KeyframeType.Dimensions>(pos, keyframes);
70
+ return {
71
+ width: rangeMap(pos, start.position, end.position, start.value.width, end.value.width, true),
72
+ height: rangeMap(pos, start.position, end.position, start.value.height, end.value.height, true)
73
+ };
74
+ }
75
+
76
+ getPositions(
77
+ values: KeyframeValueMap[KeyframeType.Position],
78
+ pos: number
79
+ ): KeyframeValueMap[KeyframeType.Position] {
80
+ const keyframes = this.keyframesMap[KeyframeType.Position];
81
+ if (!keyframes || !keyframes.length) return values;
82
+ if (keyframes.length === 1) {
83
+ const [keyframe] = keyframes;
84
+ return {
85
+ left: keyframe.value.left,
86
+ top: keyframe.value.top
87
+ };
88
+ }
89
+ const { start, end } = this.getStartEnd<KeyframeType.Position>(pos, keyframes);
90
+ return {
91
+ left: rangeMap(pos, start.position, end.position, start.value.left, end.value.left, true),
92
+ top: rangeMap(pos, start.position, end.position, start.value.top, end.value.top, true)
93
+ };
94
+ }
95
+
96
+ getColor(
97
+ values: KeyframeValueMap[KeyframeType.Color],
98
+ pos: number
99
+ ): KeyframeValueMap[KeyframeType.Color] {
100
+ const keyframes = this.keyframesMap[KeyframeType.Color];
101
+ if (!keyframes || !keyframes.length) return values;
102
+ if (keyframes.length === 1) {
103
+ const [keyframe] = keyframes;
104
+ return {
105
+ color: keyframe.value.color
106
+ };
107
+ }
108
+ const { start, end } = this.getStartEnd<KeyframeType.Color>(pos, keyframes);
109
+ return {
110
+ color: this.getRangeColor(start, end, pos)
111
+ };
112
+ }
113
+
114
+ getBorderColor(
115
+ values: KeyframeValueMap[KeyframeType.BorderColor],
116
+ pos: number
117
+ ): KeyframeValueMap[KeyframeType.BorderColor] {
118
+ const keyframes = this.keyframesMap[KeyframeType.BorderColor];
119
+ if (!keyframes || !keyframes.length) return values;
120
+ if (keyframes.length === 1) {
121
+ const [keyframe] = keyframes;
122
+ return {
123
+ color: keyframe.value.color
124
+ };
125
+ }
126
+ const { start, end } = this.getStartEnd<KeyframeType.BorderColor>(pos, keyframes);
127
+ return {
128
+ color: this.getRangeColor(start, end, pos)
129
+ };
130
+ }
131
+
132
+ getRadius(
133
+ values: KeyframeValueMap[KeyframeType.BorderRadius],
134
+ pos: number
135
+ ): KeyframeValueMap[KeyframeType.BorderRadius] {
136
+ const keyframes = this.keyframesMap[KeyframeType.BorderRadius];
137
+ if (!keyframes || !keyframes.length) return values;
138
+ if (keyframes.length === 1) {
139
+ const [keyframe] = keyframes;
140
+ return {
141
+ radius: keyframe.value.radius
142
+ };
143
+ }
144
+ const { start, end } = this.getStartEnd<KeyframeType.BorderRadius>(pos, keyframes);
145
+ return {
146
+ radius: rangeMap(pos, start.position, end.position, start.value.radius, end.value.radius, true)
147
+ };
148
+ }
149
+
150
+ getBorderWidth(
151
+ values: KeyframeValueMap[KeyframeType.BorderWidth],
152
+ pos: number
153
+ ): KeyframeValueMap[KeyframeType.BorderWidth] {
154
+ const keyframes = this.keyframesMap[KeyframeType.BorderWidth];
155
+ if (!keyframes || !keyframes.length) return values;
156
+ if (keyframes.length === 1) {
157
+ const [keyframe] = keyframes;
158
+ return {
159
+ borderWidth: keyframe.value.borderWidth
160
+ };
161
+ }
162
+ const { start, end } = this.getStartEnd<KeyframeType.BorderWidth>(pos, keyframes);
163
+ return {
164
+ borderWidth: rangeMap(pos, start.position, end.position, start.value.borderWidth, end.value.borderWidth, true)
165
+ };
166
+ }
167
+
168
+ getRotation(
169
+ values: KeyframeValueMap[KeyframeType.Rotation],
170
+ pos: number
171
+ ): KeyframeValueMap[KeyframeType.Rotation] {
172
+ const keyframes = this.keyframesMap[KeyframeType.Rotation];
173
+ if (!keyframes || !keyframes.length) return values;
174
+ if (keyframes.length === 1) {
175
+ const [keyframe] = keyframes;
176
+ return {
177
+ angle: keyframe.value.angle
178
+ };
179
+ }
180
+ const { start, end } = this.getStartEnd<KeyframeType.Rotation>(pos, keyframes);
181
+ return {
182
+ angle: rangeMap(pos, start.position, end.position, start.value.angle, end.value.angle, true)
183
+ };
184
+ }
185
+
186
+ getOpacity(
187
+ values: KeyframeValueMap[KeyframeType.Opacity],
188
+ pos: number
189
+ ): KeyframeValueMap[KeyframeType.Opacity] {
190
+ const keyframes = this.keyframesMap[KeyframeType.Opacity];
191
+ if (!keyframes || !keyframes.length) return values;
192
+ if (keyframes.length === 1) {
193
+ const [keyframe] = keyframes;
194
+ return {
195
+ opacity: keyframe.value.opacity
196
+ };
197
+ }
198
+ const { start, end } = this.getStartEnd<KeyframeType.Opacity>(pos, keyframes);
199
+ return {
200
+ opacity: rangeMap(pos, start.position, end.position, start.value.opacity, end.value.opacity, true)
201
+ };
202
+ }
203
+
204
+ getScale(
205
+ values: KeyframeValueMap[KeyframeType.Scale],
206
+ pos: number
207
+ ): KeyframeValueMap[KeyframeType.Scale] {
208
+ const keyframes = this.keyframesMap[KeyframeType.Scale];
209
+ if (!keyframes || !keyframes.length) return values;
210
+ if (keyframes.length === 1) {
211
+ const [keyframe] = keyframes;
212
+ return {
213
+ scale: keyframe.value.scale
214
+ };
215
+ }
216
+ const { start, end } = this.getStartEnd<KeyframeType.Scale>(pos, keyframes);
217
+ return {
218
+ scale: rangeMap(pos, start.position, end.position, start.value.scale, end.value.scale, true)
219
+ };
220
+ }
221
+
222
+ getBlur(
223
+ values: KeyframeValueMap[KeyframeType.Blur],
224
+ pos: number
225
+ ): KeyframeValueMap[KeyframeType.Blur] {
226
+ const keyframes = this.keyframesMap[KeyframeType.Blur];
227
+ if (!keyframes || !keyframes.length) return values;
228
+ if (keyframes.length === 1) {
229
+ const [keyframe] = keyframes;
230
+ return {
231
+ blur: keyframe.value.blur
232
+ };
233
+ }
234
+ const { start, end } = this.getStartEnd<KeyframeType.Blur>(pos, keyframes);
235
+ return {
236
+ blur: rangeMap(pos, start.position, end.position, start.value.blur, end.value.blur, true)
237
+ };
238
+ }
239
+
240
+ getBackdropBlur(
241
+ values: KeyframeValueMap[KeyframeType.BackdropBlur],
242
+ pos: number
243
+ ): KeyframeValueMap[KeyframeType.BackdropBlur] {
244
+ const keyframes = this.keyframesMap[KeyframeType.BackdropBlur];
245
+ if (!keyframes || !keyframes.length) return values;
246
+ if (keyframes.length === 1) {
247
+ const [keyframe] = keyframes;
248
+ return {
249
+ backdropBlur: keyframe.value.backdropBlur
250
+ };
251
+ }
252
+ const { start, end } = this.getStartEnd<KeyframeType.BackdropBlur>(pos, keyframes);
253
+ return {
254
+ backdropBlur: rangeMap(pos, start.position, end.position, start.value.backdropBlur, end.value.backdropBlur, true)
255
+ };
256
+ }
257
+
258
+ getTextColor(
259
+ values: KeyframeValueMap[KeyframeType.TextColor],
260
+ pos: number
261
+ ): KeyframeValueMap[KeyframeType.TextColor] {
262
+ const keyframes = this.keyframesMap[KeyframeType.TextColor];
263
+ if (!keyframes || !keyframes.length) return values;
264
+ if (keyframes.length === 1) {
265
+ const [keyframe] = keyframes;
266
+ return {
267
+ color: keyframe.value.color
268
+ };
269
+ }
270
+ const { start, end } = this.getStartEnd<KeyframeType.TextColor>(pos, keyframes);
271
+ return {
272
+ color: this.getRangeColor(start, end, pos)
273
+ };
274
+ }
275
+
276
+ getLetterSpacing(
277
+ values: KeyframeValueMap[KeyframeType.LetterSpacing],
278
+ pos: number
279
+ ): KeyframeValueMap[KeyframeType.LetterSpacing] {
280
+ const keyframes = this.keyframesMap[KeyframeType.LetterSpacing];
281
+ if (!keyframes || !keyframes.length) return values;
282
+ if (keyframes.length === 1) {
283
+ const [keyframe] = keyframes;
284
+ return {
285
+ letterSpacing: keyframe.value.letterSpacing
286
+ };
287
+ }
288
+ const { start, end } = this.getStartEnd<KeyframeType.LetterSpacing>(pos, keyframes);
289
+ return {
290
+ letterSpacing: rangeMap(
291
+ pos,
292
+ start.position,
293
+ end.position,
294
+ start.value.letterSpacing,
295
+ end.value.letterSpacing,
296
+ true
297
+ )
298
+ };
299
+ }
300
+
301
+ getWordSpacing(
302
+ values: KeyframeValueMap[KeyframeType.WordSpacing],
303
+ pos: number
304
+ ): KeyframeValueMap[KeyframeType.WordSpacing] {
305
+ const keyframes = this.keyframesMap[KeyframeType.WordSpacing];
306
+ if (!keyframes || !keyframes.length) return values;
307
+ if (keyframes.length === 1) {
308
+ const [keyframe] = keyframes;
309
+ return {
310
+ wordSpacing: keyframe.value.wordSpacing
311
+ };
312
+ }
313
+ const { start, end } = this.getStartEnd<KeyframeType.WordSpacing>(pos, keyframes);
314
+ return {
315
+ wordSpacing: rangeMap(
316
+ pos,
317
+ start.position,
318
+ end.position,
319
+ start.value.wordSpacing,
320
+ end.value.wordSpacing,
321
+ true
322
+ )
323
+ };
324
+ }
325
+
326
+ getStartEnd<T extends KeyframeType>(position: number, keyframes: AnimationData<T>[]): PositionKeyframes<T> {
327
+ const index = binSearchInsertAt(keyframes, { position }, compare);
328
+ const end = index === keyframes.length ? index - 1 : index;
329
+ const start = end === 0 ? end : end - 1;
330
+ return { start: keyframes[start], end: keyframes[end] };
331
+ }
332
+
333
+ private fillKeyframesMap() {
334
+ this.keyframesMap = createKeyframesMap();
335
+ for (const keyframe of this.keyframes) {
336
+ Animator.pushKeyframeToMap(keyframe, this.keyframesMap);
337
+ }
338
+ }
339
+
340
+ private getRangeColor(
341
+ start: AnimationData<KeyframeType.Color | KeyframeType.BorderColor | KeyframeType.TextColor>,
342
+ end: AnimationData<KeyframeType.Color | KeyframeType.BorderColor | KeyframeType.TextColor>,
343
+ position: number
344
+ ): string {
345
+ const rangeAmount = rangeMap(position, start.position, end.position, 0, 1, true);
346
+ const startColor = CntrlColor.parse(start.value.color);
347
+ const endColor = CntrlColor.parse(end.value.color);
348
+ const mixedColor = startColor.mix(endColor, rangeAmount);
349
+ return mixedColor.fmt('oklch');
350
+ }
351
+ }
352
+
353
+ function createKeyframesMap(): KeyframesMap {
354
+ return {
355
+ [KeyframeType.Dimensions]: [],
356
+ [KeyframeType.Position]: [],
357
+ [KeyframeType.BorderWidth]: [],
358
+ [KeyframeType.BorderRadius]: [],
359
+ [KeyframeType.Color]: [],
360
+ [KeyframeType.Rotation]: [],
361
+ [KeyframeType.BorderColor]: [],
362
+ [KeyframeType.Opacity]: [],
363
+ [KeyframeType.Scale]: [],
364
+ [KeyframeType.Blur]: [],
365
+ [KeyframeType.BackdropBlur]: [],
366
+ [KeyframeType.LetterSpacing]: [],
367
+ [KeyframeType.WordSpacing]: [],
368
+ [KeyframeType.TextColor]: [],
369
+ [KeyframeType.FXParams]: []
370
+ };
371
+ }