react-native-divkit 1.6.5 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/README.md +18 -15
  2. package/dist/DivKit.d.ts.map +1 -1
  3. package/dist/DivKit.js +115 -4
  4. package/dist/DivKit.js.map +1 -1
  5. package/dist/components/DivComponent.d.ts.map +1 -1
  6. package/dist/components/DivComponent.js +6 -2
  7. package/dist/components/DivComponent.js.map +1 -1
  8. package/dist/components/index.d.ts +4 -0
  9. package/dist/components/index.d.ts.map +1 -1
  10. package/dist/components/index.js +2 -0
  11. package/dist/components/index.js.map +1 -1
  12. package/dist/components/indicator/DivIndicator.d.ts +19 -0
  13. package/dist/components/indicator/DivIndicator.d.ts.map +1 -0
  14. package/dist/components/indicator/DivIndicator.js +112 -0
  15. package/dist/components/indicator/DivIndicator.js.map +1 -0
  16. package/dist/components/indicator/index.d.ts +3 -0
  17. package/dist/components/indicator/index.d.ts.map +1 -0
  18. package/dist/components/indicator/index.js +2 -0
  19. package/dist/components/indicator/index.js.map +1 -0
  20. package/dist/components/indicator/utils.d.ts +61 -0
  21. package/dist/components/indicator/utils.d.ts.map +1 -0
  22. package/dist/components/indicator/utils.js +104 -0
  23. package/dist/components/indicator/utils.js.map +1 -0
  24. package/dist/components/pager/DivPager.d.ts +22 -0
  25. package/dist/components/pager/DivPager.d.ts.map +1 -0
  26. package/dist/components/pager/DivPager.js +269 -0
  27. package/dist/components/pager/DivPager.js.map +1 -0
  28. package/dist/components/pager/index.d.ts +3 -0
  29. package/dist/components/pager/index.d.ts.map +1 -0
  30. package/dist/components/pager/index.js +2 -0
  31. package/dist/components/pager/index.js.map +1 -0
  32. package/dist/components/pager/utils.d.ts +96 -0
  33. package/dist/components/pager/utils.d.ts.map +1 -0
  34. package/dist/components/pager/utils.js +142 -0
  35. package/dist/components/pager/utils.js.map +1 -0
  36. package/dist/components/state/DivState.d.ts +11 -12
  37. package/dist/components/state/DivState.d.ts.map +1 -1
  38. package/dist/components/state/DivState.js +263 -35
  39. package/dist/components/state/DivState.js.map +1 -1
  40. package/dist/components/utilities/Background.d.ts.map +1 -1
  41. package/dist/components/utilities/Background.js +4 -3
  42. package/dist/components/utilities/Background.js.map +1 -1
  43. package/dist/components/utilities/Outer.d.ts.map +1 -1
  44. package/dist/components/utilities/Outer.js +175 -78
  45. package/dist/components/utilities/Outer.js.map +1 -1
  46. package/dist/context/DivStateScopeContext.d.ts +18 -0
  47. package/dist/context/DivStateScopeContext.d.ts.map +1 -0
  48. package/dist/context/DivStateScopeContext.js +7 -0
  49. package/dist/context/DivStateScopeContext.js.map +1 -0
  50. package/dist/context/PagerContext.d.ts +30 -0
  51. package/dist/context/PagerContext.d.ts.map +1 -0
  52. package/dist/context/PagerContext.js +76 -0
  53. package/dist/context/PagerContext.js.map +1 -0
  54. package/dist/context/index.d.ts +1 -0
  55. package/dist/context/index.d.ts.map +1 -1
  56. package/dist/context/index.js +1 -0
  57. package/dist/context/index.js.map +1 -1
  58. package/dist/hooks/useAppearanceTransition.d.ts +86 -0
  59. package/dist/hooks/useAppearanceTransition.d.ts.map +1 -0
  60. package/dist/hooks/useAppearanceTransition.js +490 -0
  61. package/dist/hooks/useAppearanceTransition.js.map +1 -0
  62. package/dist/hooks/useChangeBoundsTransition.d.ts +46 -0
  63. package/dist/hooks/useChangeBoundsTransition.d.ts.map +1 -0
  64. package/dist/hooks/useChangeBoundsTransition.js +151 -0
  65. package/dist/hooks/useChangeBoundsTransition.js.map +1 -0
  66. package/dist/utils/configureChangeBoundsLayout.d.ts +11 -0
  67. package/dist/utils/configureChangeBoundsLayout.d.ts.map +1 -0
  68. package/dist/utils/configureChangeBoundsLayout.js +65 -0
  69. package/dist/utils/configureChangeBoundsLayout.js.map +1 -0
  70. package/dist/utils/flattenTransition.d.ts +5 -0
  71. package/dist/utils/flattenTransition.d.ts.map +1 -0
  72. package/dist/utils/flattenTransition.js +27 -0
  73. package/dist/utils/flattenTransition.js.map +1 -0
  74. package/package.json +3 -1
  75. package/src/DivKit.tsx +131 -5
  76. package/src/components/DivComponent.tsx +8 -2
  77. package/src/components/README.md +59 -5
  78. package/src/components/index.ts +4 -0
  79. package/src/components/indicator/DivIndicator.tsx +175 -0
  80. package/src/components/indicator/index.ts +2 -0
  81. package/src/components/indicator/utils.ts +149 -0
  82. package/src/components/pager/DivPager.tsx +393 -0
  83. package/src/components/pager/index.ts +2 -0
  84. package/src/components/pager/utils.ts +214 -0
  85. package/src/components/state/DivState.tsx +308 -39
  86. package/src/components/utilities/Background.tsx +4 -3
  87. package/src/components/utilities/Outer.tsx +192 -75
  88. package/src/context/DivStateScopeContext.tsx +23 -0
  89. package/src/context/PagerContext.tsx +108 -0
  90. package/src/context/index.ts +8 -0
  91. package/src/hooks/useAppearanceTransition.ts +621 -0
  92. package/src/hooks/useChangeBoundsTransition.ts +193 -0
  93. package/src/types/indicator.d.ts +32 -0
  94. package/src/types/pager.d.ts +36 -0
  95. package/src/types/shape.d.ts +26 -0
  96. package/src/utils/configureChangeBoundsLayout.ts +74 -0
  97. package/src/utils/flattenTransition.ts +36 -0
@@ -0,0 +1,151 @@
1
+ import { useCallback, useMemo, useRef } from 'react';
2
+ import { Animated, Easing } from 'react-native';
3
+ import { flattenChangeTransition } from '../utils/flattenTransition';
4
+ function interpolationToEasing(interpolator) {
5
+ switch (interpolator) {
6
+ case 'linear': return Easing.linear;
7
+ case 'ease': return Easing.ease;
8
+ case 'ease_in': return Easing.in(Easing.ease);
9
+ case 'ease_out': return Easing.out(Easing.ease);
10
+ case 'ease_in_out': return Easing.inOut(Easing.ease);
11
+ case 'spring': return Easing.elastic(1);
12
+ default: return Easing.inOut(Easing.ease);
13
+ }
14
+ }
15
+ function normalize(transition) {
16
+ if (!transition)
17
+ return null;
18
+ const items = flattenChangeTransition(transition);
19
+ let longest = null;
20
+ let longestTotal = 0;
21
+ for (const it of items) {
22
+ const dur = Math.max(0, it.duration ?? 300);
23
+ const delay = Math.max(0, it.start_delay ?? 0);
24
+ if (dur + delay > longestTotal) {
25
+ longestTotal = dur + delay;
26
+ longest = it;
27
+ }
28
+ }
29
+ if (!longest || longestTotal === 0)
30
+ return null;
31
+ return {
32
+ duration: Math.max(0, longest.duration ?? 300),
33
+ delay: Math.max(0, longest.start_delay ?? 0),
34
+ easing: interpolationToEasing(longest.interpolator)
35
+ };
36
+ }
37
+ /**
38
+ * FLIP (First-Last-Invert-Play) hook for transition_change with custom cubic easing.
39
+ *
40
+ * On each layout change:
41
+ * 1. Capture previous (First) and new (Last) bounds via onLayout.
42
+ * 2. Set transform to translate(-dx, -dy) * scale(prevW/newW, prevH/newH) so the element
43
+ * visually stays at its old position/size (Invert).
44
+ * 3. Animate transform to identity over the spec duration (Play).
45
+ *
46
+ * Limitations:
47
+ * - onLayout reports coords relative to the parent. If the parent itself moves, we will
48
+ * see a position change but interpret it as our own movement — usually fine for items
49
+ * inside a stable container.
50
+ * - useNativeDriver is enabled (transform-only props), so the animation runs on the UI thread.
51
+ * - First layout is treated as the baseline and is not animated.
52
+ */
53
+ export function useChangeBoundsTransition(opts) {
54
+ const { transitionChange, suspended } = opts;
55
+ const spec = useMemo(() => normalize(transitionChange), [transitionChange]);
56
+ const translateX = useRef(new Animated.Value(0)).current;
57
+ const translateY = useRef(new Animated.Value(0)).current;
58
+ const scaleX = useRef(new Animated.Value(1)).current;
59
+ const scaleY = useRef(new Animated.Value(1)).current;
60
+ const prevLayoutRef = useRef(null);
61
+ const sizeRef = useRef(undefined);
62
+ const inFlightRef = useRef(null);
63
+ const onLayout = useCallback((e) => {
64
+ const { x, y, width, height } = e.nativeEvent.layout;
65
+ const prev = prevLayoutRef.current;
66
+ prevLayoutRef.current = { x, y, width, height };
67
+ sizeRef.current = { width, height };
68
+ if (!spec || suspended)
69
+ return;
70
+ if (!prev)
71
+ return; // baseline — nothing to invert from
72
+ if (prev.x === x && prev.y === y && prev.width === width && prev.height === height) {
73
+ return; // identical layout
74
+ }
75
+ if (width === 0 || height === 0)
76
+ return;
77
+ const sx = width > 0 ? prev.width / width : 1;
78
+ const sy = height > 0 ? prev.height / height : 1;
79
+ const prevCenterX = prev.x + prev.width / 2;
80
+ const prevCenterY = prev.y + prev.height / 2;
81
+ const nextCenterX = x + width / 2;
82
+ const nextCenterY = y + height / 2;
83
+ const dx = prevCenterX - nextCenterX;
84
+ const dy = prevCenterY - nextCenterY;
85
+ // Skip imperceptible movements
86
+ const SIGNIFICANT = 0.5;
87
+ const SCALE_EPS = 0.01;
88
+ if (Math.abs(dx) < SIGNIFICANT &&
89
+ Math.abs(dy) < SIGNIFICANT &&
90
+ Math.abs(sx - 1) < SCALE_EPS &&
91
+ Math.abs(sy - 1) < SCALE_EPS) {
92
+ return;
93
+ }
94
+ if (inFlightRef.current) {
95
+ inFlightRef.current.stop();
96
+ inFlightRef.current = null;
97
+ }
98
+ // Invert: snap to old position/size in transform space
99
+ translateX.setValue(dx);
100
+ translateY.setValue(dy);
101
+ scaleX.setValue(sx);
102
+ scaleY.setValue(sy);
103
+ // Play to identity
104
+ const comp = Animated.parallel([
105
+ Animated.timing(translateX, {
106
+ toValue: 0, duration: spec.duration, delay: spec.delay,
107
+ easing: spec.easing, useNativeDriver: true
108
+ }),
109
+ Animated.timing(translateY, {
110
+ toValue: 0, duration: spec.duration, delay: spec.delay,
111
+ easing: spec.easing, useNativeDriver: true
112
+ }),
113
+ Animated.timing(scaleX, {
114
+ toValue: 1, duration: spec.duration, delay: spec.delay,
115
+ easing: spec.easing, useNativeDriver: true
116
+ }),
117
+ Animated.timing(scaleY, {
118
+ toValue: 1, duration: spec.duration, delay: spec.delay,
119
+ easing: spec.easing, useNativeDriver: true
120
+ })
121
+ ]);
122
+ inFlightRef.current = comp;
123
+ comp.start(({ finished }) => {
124
+ if (inFlightRef.current === comp)
125
+ inFlightRef.current = null;
126
+ if (finished) {
127
+ translateX.setValue(0);
128
+ translateY.setValue(0);
129
+ scaleX.setValue(1);
130
+ scaleY.setValue(1);
131
+ }
132
+ });
133
+ }, [spec, suspended, translateX, translateY, scaleX, scaleY]);
134
+ const transform = useMemo(() => {
135
+ if (!spec)
136
+ return [];
137
+ return [
138
+ { translateX },
139
+ { translateY },
140
+ { scaleX },
141
+ { scaleY }
142
+ ];
143
+ }, [spec, translateX, translateY, scaleX, scaleY]);
144
+ return {
145
+ onLayout,
146
+ transform,
147
+ layoutWidth: sizeRef.current?.width,
148
+ layoutHeight: sizeRef.current?.height
149
+ };
150
+ }
151
+ //# sourceMappingURL=useChangeBoundsTransition.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useChangeBoundsTransition.js","sourceRoot":"","sources":["../../src/hooks/useChangeBoundsTransition.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { MaybeMissing } from '../expressions/json';
2
+ import type { TransitionChange } from '../types/base';
3
+ /**
4
+ * Triggers a smooth layout transition for the next render, based on a DivKit transition_change spec.
5
+ * Uses React Native's LayoutAnimation API (which respects duration and a coarse easing type,
6
+ * but not arbitrary cubic-bezier curves).
7
+ *
8
+ * Returns true if a transition was queued, false if there is no spec or duration is zero.
9
+ */
10
+ export declare function configureChangeBoundsLayout(transition: MaybeMissing<TransitionChange> | undefined): boolean;
11
+ //# sourceMappingURL=configureChangeBoundsLayout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configureChangeBoundsLayout.d.ts","sourceRoot":"","sources":["../../src/utils/configureChangeBoundsLayout.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AA6BtD;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CACvC,UAAU,EAAE,YAAY,CAAC,gBAAgB,CAAC,GAAG,SAAS,GACvD,OAAO,CAiCT"}
@@ -0,0 +1,65 @@
1
+ import { LayoutAnimation, Platform, UIManager } from 'react-native';
2
+ import { flattenChangeTransition } from './flattenTransition';
3
+ let layoutAnimationEnabled = false;
4
+ function enableLayoutAnimationIfNeeded() {
5
+ if (layoutAnimationEnabled)
6
+ return;
7
+ layoutAnimationEnabled = true;
8
+ if (Platform?.OS === 'android') {
9
+ UIManager.setLayoutAnimationEnabledExperimental?.(true);
10
+ }
11
+ }
12
+ function interpolatorToLAType(interp) {
13
+ switch (interp) {
14
+ case 'linear': return 'linear';
15
+ case 'ease': return 'easeInEaseOut';
16
+ case 'ease_in': return 'easeIn';
17
+ case 'ease_out': return 'easeOut';
18
+ case 'ease_in_out': return 'easeInEaseOut';
19
+ case 'spring': return 'spring';
20
+ default: return 'easeInEaseOut';
21
+ }
22
+ }
23
+ /**
24
+ * Triggers a smooth layout transition for the next render, based on a DivKit transition_change spec.
25
+ * Uses React Native's LayoutAnimation API (which respects duration and a coarse easing type,
26
+ * but not arbitrary cubic-bezier curves).
27
+ *
28
+ * Returns true if a transition was queued, false if there is no spec or duration is zero.
29
+ */
30
+ export function configureChangeBoundsLayout(transition) {
31
+ if (!transition)
32
+ return false;
33
+ const items = flattenChangeTransition(transition);
34
+ if (items.length === 0)
35
+ return false;
36
+ // Pick the longest duration (parallel composition); use the interpolator of that one.
37
+ let duration = 0;
38
+ let delayMax = 0;
39
+ let chosenInterp;
40
+ for (const it of items) {
41
+ const d = Math.max(0, it.duration ?? 300);
42
+ const delay = Math.max(0, it.start_delay ?? 0);
43
+ if (d > duration) {
44
+ duration = d;
45
+ chosenInterp = it.interpolator;
46
+ }
47
+ if (delay > delayMax)
48
+ delayMax = delay;
49
+ }
50
+ if (duration === 0)
51
+ return false;
52
+ const typeKey = interpolatorToLAType(chosenInterp);
53
+ const type = LayoutAnimation.Types[typeKey];
54
+ const property = LayoutAnimation.Properties.opacity;
55
+ const config = {
56
+ duration: duration + delayMax,
57
+ create: { type, property },
58
+ update: { type },
59
+ delete: { type, property }
60
+ };
61
+ enableLayoutAnimationIfNeeded();
62
+ LayoutAnimation.configureNext(config);
63
+ return true;
64
+ }
65
+ //# sourceMappingURL=configureChangeBoundsLayout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configureChangeBoundsLayout.js","sourceRoot":"","sources":["../../src/utils/configureChangeBoundsLayout.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAyB,QAAQ,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAI3F,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAI9D,IAAI,sBAAsB,GAAG,KAAK,CAAC;AAEnC,SAAS,6BAA6B;IAClC,IAAI,sBAAsB;QAAE,OAAO;IACnC,sBAAsB,GAAG,IAAI,CAAC;IAE9B,IAAI,QAAQ,EAAE,EAAE,KAAK,SAAS,EAAE,CAAC;QAC7B,SAAS,CAAC,qCAAqC,EAAE,CAAC,IAAI,CAAC,CAAC;IAC5D,CAAC;AACL,CAAC;AAED,SAAS,oBAAoB,CAAC,MAAiC;IAC3D,QAAQ,MAAM,EAAE,CAAC;QACb,KAAK,QAAQ,CAAC,CAAO,OAAO,QAAQ,CAAC;QACrC,KAAK,MAAM,CAAC,CAAS,OAAO,eAAe,CAAC;QAC5C,KAAK,SAAS,CAAC,CAAM,OAAO,QAAQ,CAAC;QACrC,KAAK,UAAU,CAAC,CAAK,OAAO,SAAS,CAAC;QACtC,KAAK,aAAa,CAAC,CAAE,OAAO,eAAe,CAAC;QAC5C,KAAK,QAAQ,CAAC,CAAO,OAAO,QAAQ,CAAC;QACrC,OAAO,CAAC,CAAa,OAAO,eAAe,CAAC;IAChD,CAAC;AACL,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,2BAA2B,CACvC,UAAsD;IAEtD,IAAI,CAAC,UAAU;QAAE,OAAO,KAAK,CAAC;IAC9B,MAAM,KAAK,GAAG,uBAAuB,CAAC,UAAU,CAAC,CAAC;IAClD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAErC,sFAAsF;IACtF,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,IAAI,YAAuC,CAAC;IAC5C,KAAK,MAAM,EAAE,IAAI,KAAK,EAAE,CAAC;QACrB,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAG,EAAU,CAAC,QAAQ,IAAI,GAAG,CAAC,CAAC;QACnD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAG,EAAU,CAAC,WAAW,IAAI,CAAC,CAAC,CAAC;QACxD,IAAI,CAAC,GAAG,QAAQ,EAAE,CAAC;YACf,QAAQ,GAAG,CAAC,CAAC;YACb,YAAY,GAAI,EAAU,CAAC,YAAyC,CAAC;QACzE,CAAC;QACD,IAAI,KAAK,GAAG,QAAQ;YAAE,QAAQ,GAAG,KAAK,CAAC;IAC3C,CAAC;IACD,IAAI,QAAQ,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAEjC,MAAM,OAAO,GAAG,oBAAoB,CAAC,YAAY,CAAC,CAAC;IACnD,MAAM,IAAI,GAAG,eAAe,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC5C,MAAM,QAAQ,GAAG,eAAe,CAAC,UAAU,CAAC,OAAO,CAAC;IAEpD,MAAM,MAAM,GAA0B;QAClC,QAAQ,EAAE,QAAQ,GAAG,QAAQ;QAC7B,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QAC1B,MAAM,EAAE,EAAE,IAAI,EAAE;QAChB,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;KAC7B,CAAC;IACF,6BAA6B,EAAE,CAAC;IAChC,eAAe,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;IACtC,OAAO,IAAI,CAAC;AAChB,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { MaybeMissing } from '../expressions/json';
2
+ import type { AnyTransition, AppearanceTransition, ChangeBoundsTransition, TransitionChange } from '../types/base';
3
+ export declare function flattenAppearanceTransition(transition: MaybeMissing<AppearanceTransition>): MaybeMissing<AnyTransition>[];
4
+ export declare function flattenChangeTransition(transition: MaybeMissing<TransitionChange>): MaybeMissing<ChangeBoundsTransition>[];
5
+ //# sourceMappingURL=flattenTransition.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flattenTransition.d.ts","sourceRoot":"","sources":["../../src/utils/flattenTransition.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEnH,wBAAgB,2BAA2B,CACvC,UAAU,EAAE,YAAY,CAAC,oBAAoB,CAAC,GAC/C,YAAY,CAAC,aAAa,CAAC,EAAE,CAa/B;AAED,wBAAgB,uBAAuB,CACnC,UAAU,EAAE,YAAY,CAAC,gBAAgB,CAAC,GAC3C,YAAY,CAAC,sBAAsB,CAAC,EAAE,CAaxC"}
@@ -0,0 +1,27 @@
1
+ export function flattenAppearanceTransition(transition) {
2
+ const res = [];
3
+ if (transition.type === 'set') {
4
+ const items = transition.items;
5
+ (items || []).forEach(item => {
6
+ res.push(...flattenAppearanceTransition(item));
7
+ });
8
+ }
9
+ else {
10
+ res.push(transition);
11
+ }
12
+ return res;
13
+ }
14
+ export function flattenChangeTransition(transition) {
15
+ const res = [];
16
+ if (transition.type === 'set') {
17
+ const items = transition.items;
18
+ (items || []).forEach(item => {
19
+ res.push(...flattenChangeTransition(item));
20
+ });
21
+ }
22
+ else {
23
+ res.push(transition);
24
+ }
25
+ return res;
26
+ }
27
+ //# sourceMappingURL=flattenTransition.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flattenTransition.js","sourceRoot":"","sources":["../../src/utils/flattenTransition.ts"],"names":[],"mappings":"AAGA,MAAM,UAAU,2BAA2B,CACvC,UAA8C;IAE9C,MAAM,GAAG,GAAkC,EAAE,CAAC;IAE9C,IAAK,UAAkB,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;QACrC,MAAM,KAAK,GAAI,UAAkB,CAAC,KAAyD,CAAC;QAC5F,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;YACzB,GAAG,CAAC,IAAI,CAAC,GAAG,2BAA2B,CAAC,IAAI,CAAC,CAAC,CAAC;QACnD,CAAC,CAAC,CAAC;IACP,CAAC;SAAM,CAAC;QACJ,GAAG,CAAC,IAAI,CAAC,UAAyC,CAAC,CAAC;IACxD,CAAC;IAED,OAAO,GAAG,CAAC;AACf,CAAC;AAED,MAAM,UAAU,uBAAuB,CACnC,UAA0C;IAE1C,MAAM,GAAG,GAA2C,EAAE,CAAC;IAEvD,IAAK,UAAkB,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;QACrC,MAAM,KAAK,GAAI,UAAkB,CAAC,KAAqD,CAAC;QACxF,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;YACzB,GAAG,CAAC,IAAI,CAAC,GAAG,uBAAuB,CAAC,IAAI,CAAC,CAAC,CAAC;QAC/C,CAAC,CAAC,CAAC;IACP,CAAC;SAAM,CAAC;QACJ,GAAG,CAAC,IAAI,CAAC,UAAkD,CAAC,CAAC;IACjE,CAAC;IAED,OAAO,GAAG,CAAC;AACf,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-divkit",
3
- "version": "1.6.5",
3
+ "version": "1.8.0",
4
4
  "description": "DivKit renderer for React Native - Server-driven UI framework",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -16,6 +16,7 @@
16
16
  "test": "jest",
17
17
  "test:snapshot": "jest --config jest.snapshot.config.js",
18
18
  "test:snapshot:update": "jest --config jest.snapshot.config.js -u",
19
+ "test:integration": "jest --config jest.integration.config.js",
19
20
  "lint": "eslint src --ext .ts,.tsx",
20
21
  "typecheck": "tsc --noEmit"
21
22
  },
@@ -50,6 +51,7 @@
50
51
  "@typescript-eslint/eslint-plugin": "^5.59.0",
51
52
  "@typescript-eslint/parser": "^5.59.0",
52
53
  "eslint": "^8.47.0",
54
+ "eslint-plugin-react-hooks": "^4.6.2",
53
55
  "jest": "^29.0.0",
54
56
  "react": "19.2.4",
55
57
  "react-native": "0.83.1",
package/src/DivKit.tsx CHANGED
@@ -20,13 +20,14 @@
20
20
 
21
21
  import React, { useMemo, useCallback, useRef, useEffect } from 'react';
22
22
  import { View, type ViewStyle } from 'react-native';
23
- import type { Action, DivJson, DivVariable, Direction } from '../typings/common';
23
+ import type { Action, DivJson, DivVariable, Direction, VariableTrigger } from '../typings/common';
24
24
  import type { DivBaseData } from './types/base';
25
25
  import type { ComponentContext } from './types/componentContext';
26
26
  import type { MaybeMissing } from './expressions/json';
27
27
  import { DivKitContext, type DivKitContextValue, type TypefaceProvider } from './context/DivKitContext';
28
28
  import { ActionContext, type ActionContextValue } from './context/ActionContext';
29
29
  import { StateContext, type StateContextValue, type StateSetter } from './context/StateContext';
30
+ import { PagerProvider } from './context/PagerContext';
30
31
  import { DivComponent } from './components/DivComponent';
31
32
  import { createVariable, Variable, type VariableType } from './expressions/variable';
32
33
  import { GlobalVariablesController } from './expressions/globalVariablesController';
@@ -39,6 +40,7 @@ import { updateStructure } from './actions/updateStructure';
39
40
  import { applySetStateAction, type ActionSetStateCompat } from './actions/setState';
40
41
  import { evalExpression } from './expressions/eval';
41
42
  import { parse } from './expressions/expressions';
43
+ import { prepareVars } from './expressions/json';
42
44
  import { getUrlSchema } from './utils/url';
43
45
 
44
46
  /**
@@ -248,6 +250,14 @@ export function DivKit({
248
250
  return `${key}_${componentIdCounter.current++}`;
249
251
  }, []);
250
252
 
253
+ // Card-level variable_triggers (data.card.variable_triggers).
254
+ // We can't subscribe here yet — execAnyActions is defined below; the actual subscription
255
+ // happens in a useEffect after execAnyActions is in scope.
256
+ const variableTriggers = useMemo<VariableTrigger[] | undefined>(() => {
257
+ const raw = data.card?.variable_triggers;
258
+ return Array.isArray(raw) ? (raw as VariableTrigger[]) : undefined;
259
+ }, [data]);
260
+
251
261
  // Variable management
252
262
  const getVariable = useCallback(
253
263
  (name: string): Variable | undefined => {
@@ -431,7 +441,24 @@ export function DivKit({
431
441
  const name = params.get('name');
432
442
  const value = params.get('value');
433
443
  if (name && value !== null) {
434
- setVariable(name, value);
444
+ const variableInstance = variables.get(name);
445
+ if (variableInstance) {
446
+ try {
447
+ variableInstance.set(value);
448
+ } catch (err) {
449
+ logError(
450
+ wrapError(err as Error, {
451
+ additional: { variable: name, value }
452
+ })
453
+ );
454
+ }
455
+ } else {
456
+ logError(
457
+ wrapError(new Error('Cannot find variable'), {
458
+ additional: { name }
459
+ })
460
+ );
461
+ }
435
462
  } else {
436
463
  logError(
437
464
  wrapError(new Error('Incorrect set_variable_action'), {
@@ -463,6 +490,103 @@ export function DivKit({
463
490
  [variables, logError, onStat, onCustomAction, setVariable]
464
491
  );
465
492
 
493
+ // Subscribe card-level variable_triggers (по образцу Web Root.svelte processVariableTriggers).
494
+ // on_condition — actions fire only on false→true transition.
495
+ // on_variable — actions fire every time used variables change while condition is true.
496
+ useEffect(() => {
497
+ if (!variableTriggers || variableTriggers.length === 0) {
498
+ return;
499
+ }
500
+
501
+ const cleanups: Array<() => void> = [];
502
+
503
+ for (const trigger of variableTriggers) {
504
+ if (typeof trigger.condition !== 'string') {
505
+ logError(wrapError(new Error('variable_trigger has a condition that is not a string'), {
506
+ additional: { condition: trigger.condition as unknown as string }
507
+ }));
508
+ continue;
509
+ }
510
+ if (!Array.isArray(trigger.actions)) {
511
+ logError(wrapError(new Error('variable_trigger has no actions'), {
512
+ additional: { condition: trigger.condition }
513
+ }));
514
+ continue;
515
+ }
516
+ const mode = trigger.mode || 'on_condition';
517
+ if (mode !== 'on_variable' && mode !== 'on_condition') {
518
+ logError(wrapError(new Error('variable_trigger has an unsupported mode'), {
519
+ additional: { mode }
520
+ }));
521
+ continue;
522
+ }
523
+
524
+ const prepared = prepareVars(
525
+ { condition: trigger.condition },
526
+ logError,
527
+ undefined,
528
+ 0
529
+ );
530
+
531
+ if (prepared.vars.length === 0) {
532
+ logError(wrapError(new Error('variable_trigger must have variables in the condition'), {
533
+ additional: { condition: trigger.condition }
534
+ }));
535
+ continue;
536
+ }
537
+
538
+ const evaluate = (): boolean => {
539
+ const { result } = prepared.applyVars(variables, undefined, false);
540
+ const value = (result as { condition?: unknown } | undefined)?.condition;
541
+ if (value === undefined) {
542
+ return false;
543
+ }
544
+ // Boolean expressions stringify to '0'/'1' via prepareVars.
545
+ if (value === '1' || value === 1 || value === true) return true;
546
+ if (value === '0' || value === 0 || value === false) return false;
547
+ return Boolean(value);
548
+ };
549
+
550
+ let prevConditionResult = false;
551
+ let initialized = false;
552
+
553
+ const onChange = () => {
554
+ if (!initialized) return;
555
+ const cond = evaluate();
556
+ if (cond && (mode === 'on_variable' || prevConditionResult === false)) {
557
+ prevConditionResult = cond;
558
+ execAnyActions(trigger.actions, { processUrls: true });
559
+ } else {
560
+ prevConditionResult = cond;
561
+ }
562
+ };
563
+
564
+ const unsubs: Array<() => void> = [];
565
+ for (const varName of prepared.vars) {
566
+ const variable = variables.get(varName);
567
+ if (!variable) continue;
568
+ unsubs.push(variable.subscribe(onChange));
569
+ }
570
+
571
+ // After all immediate subscribe-callbacks have fired (and been ignored),
572
+ // run initial evaluation explicitly — matches Web first-emit behavior.
573
+ initialized = true;
574
+ const initialCond = evaluate();
575
+ if (initialCond) {
576
+ prevConditionResult = initialCond;
577
+ execAnyActions(trigger.actions, { processUrls: true });
578
+ }
579
+
580
+ cleanups.push(() => {
581
+ unsubs.forEach(u => u());
582
+ });
583
+ }
584
+
585
+ return () => {
586
+ cleanups.forEach(c => c());
587
+ };
588
+ }, [variableTriggers, variables, execAnyActions, logError]);
589
+
466
590
  // Component registration
467
591
  const registerComponent = useCallback((_componentId: string, context: ComponentContext): void => {
468
592
  componentsMap.current.set(context.id, context);
@@ -684,9 +808,11 @@ export function DivKit({
684
808
  <DivKitContext.Provider value={divKitContextValue}>
685
809
  <ActionContext.Provider value={actionContextValue}>
686
810
  <StateContext.Provider value={stateContextValue}>
687
- <View style={style}>
688
- <DivComponent componentContext={rootComponentContext} />
689
- </View>
811
+ <PagerProvider>
812
+ <View style={style}>
813
+ <DivComponent componentContext={rootComponentContext} />
814
+ </View>
815
+ </PagerProvider>
690
816
  </StateContext.Provider>
691
817
  </ActionContext.Provider>
692
818
  </DivKitContext.Provider>
@@ -5,6 +5,8 @@ import { DivText } from './text';
5
5
  import { DivContainer } from './container';
6
6
  import { DivImage } from './image';
7
7
  import { DivState } from './state';
8
+ import { DivPager } from './pager';
9
+ import { DivIndicator } from './indicator';
8
10
  import { Unknown } from './utilities/Unknown';
9
11
 
10
12
  export interface DivComponentProps {
@@ -54,12 +56,16 @@ export function DivComponent({ componentContext }: DivComponentProps) {
54
56
  case 'state':
55
57
  return <DivState componentContext={componentContext as any} />;
56
58
 
59
+ case 'pager':
60
+ return <DivPager componentContext={componentContext as any} />;
61
+
62
+ case 'indicator':
63
+ return <DivIndicator componentContext={componentContext as any} />;
64
+
57
65
  // Future components (post-MVP)
58
66
  case 'gallery':
59
- case 'pager':
60
67
  case 'tabs':
61
68
  case 'slider':
62
- case 'indicator':
63
69
  case 'input':
64
70
  case 'select':
65
71
  case 'switch':
@@ -90,7 +90,44 @@ State management component for switching between different UI states.
90
90
  - Advanced state management
91
91
  - Multiple concurrent state transitions
92
92
 
93
- ### 5. DivComponent (`DivComponent.tsx`)
93
+ ### 5. DivPager (`pager/DivPager.tsx`)
94
+
95
+ Horizontal/vertical pager with snap-to-page scrolling, ported from
96
+ `Pager.svelte`.
97
+
98
+ **Features:**
99
+
100
+ - ✅ `ScrollView` + `snapToInterval` snap-to-page behaviour
101
+ - ✅ `layout_mode`: `percentage`, `neighbour_page_width` (fixed), `wrap_content`
102
+ - ✅ `orientation`: horizontal / vertical
103
+ - ✅ `infinite_scroll` (duplicate-edge trick with silent re-snap)
104
+ - ✅ `default_item`, `restrict_parent_scroll`
105
+ - ✅ `item_spacing`, `item_builder`
106
+ - ✅ Exposes pager state to `DivIndicator` via `PagerContext`
107
+ (`registerPager` / `listenPager` / `scrollToItem`)
108
+
109
+ **Deferred:**
110
+
111
+ - `page_transformation` (parallax / depth)
112
+
113
+ ### 6. DivIndicator (`indicator/DivIndicator.tsx`)
114
+
115
+ Page-position dots for a `DivPager`, ported from `Indicator.svelte`.
116
+
117
+ **Features:**
118
+
119
+ - ✅ Subscription to a pager via `PagerContext` (`pager_id`)
120
+ - ✅ Active/inactive dot styles from `active_shape` / `inactive_shape`
121
+ - ✅ Legacy `shape` + `active_item_size` + colors fallback
122
+ - ✅ Tap-to-scroll: `Pressable` → `pagerCtx.scrollToItem(index)`
123
+ - ✅ `items_placement`: `default` (space_between_centers) and `stretch`
124
+ - ✅ Internal `ScrollView` for overflow (many pages)
125
+
126
+ **Deferred:**
127
+
128
+ - Animated transitions between dot states (positions snap; no interpolation)
129
+
130
+ ### 7. DivComponent (`DivComponent.tsx`)
94
131
 
95
132
  Universal component router that dispatches to appropriate component based on type.
96
133
 
@@ -100,11 +137,13 @@ Universal component router that dispatches to appropriate component based on typ
100
137
  - `container` → DivContainer
101
138
  - `image` / `gif` → DivImage
102
139
  - `state` → DivState
140
+ - `pager` → DivPager
141
+ - `indicator` → DivIndicator
103
142
 
104
143
  **Deferred Types:**
105
144
 
106
- - `gallery`, `pager`, `tabs`
107
- - `slider`, `indicator`
145
+ - `gallery`, `tabs`
146
+ - `slider`
108
147
  - `input`, `select`, `switch`
109
148
  - `video`, `custom`
110
149
  - `separator`, `grid`
@@ -141,9 +180,16 @@ DivComponent (router)
141
180
  │ └── DivComponent (recursive for children)
142
181
  ├── DivImage
143
182
  │ └── Outer
144
- └── DivState
183
+ ├── DivState
184
+ │ ├── Outer
185
+ │ └── DivComponent (recursive for active state)
186
+ ├── DivPager
187
+ │ ├── Outer
188
+ │ ├── ScrollView (snap-to-page)
189
+ │ └── DivComponent (recursive for each page)
190
+ └── DivIndicator
145
191
  ├── Outer
146
- └── DivComponent (recursive for active state)
192
+ └── PagerContext (subscription to bound pager)
147
193
  ```
148
194
 
149
195
  ## Integration with Context System
@@ -220,6 +266,14 @@ src/components/
220
266
  ├── state/
221
267
  │ ├── DivState.tsx # State component
222
268
  │ └── index.ts
269
+ ├── pager/
270
+ │ ├── DivPager.tsx # Pager component
271
+ │ ├── utils.ts # layout_mode + infinite-scroll helpers
272
+ │ └── index.ts
273
+ ├── indicator/
274
+ │ ├── DivIndicator.tsx # Indicator component
275
+ │ ├── utils.ts # dot styles / placement
276
+ │ └── index.ts
223
277
  ├── utilities/
224
278
  │ ├── Outer.tsx # Base wrapper
225
279
  │ ├── Unknown.tsx # Fallback
@@ -7,6 +7,8 @@ export { DivText } from './text';
7
7
  export { DivContainer } from './container';
8
8
  export { DivImage } from './image';
9
9
  export { DivState } from './state';
10
+ export { DivPager } from './pager';
11
+ export { DivIndicator } from './indicator';
10
12
 
11
13
  // Utilities
12
14
  export { Outer } from './utilities/Outer';
@@ -17,4 +19,6 @@ export type { DivTextProps } from './text';
17
19
  export type { DivContainerProps } from './container';
18
20
  export type { DivImageProps } from './image';
19
21
  export type { DivStateProps } from './state';
22
+ export type { DivPagerProps } from './pager';
23
+ export type { DivIndicatorProps } from './indicator';
20
24
  export type { OuterProps } from './utilities/Outer';