react-native-divkit 1.7.0 → 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 (44) hide show
  1. package/README.md +17 -16
  2. package/dist/DivKit.d.ts.map +1 -1
  3. package/dist/DivKit.js +109 -1
  4. package/dist/DivKit.js.map +1 -1
  5. package/dist/components/state/DivState.d.ts +11 -12
  6. package/dist/components/state/DivState.d.ts.map +1 -1
  7. package/dist/components/state/DivState.js +263 -35
  8. package/dist/components/state/DivState.js.map +1 -1
  9. package/dist/components/utilities/Background.d.ts.map +1 -1
  10. package/dist/components/utilities/Background.js +4 -3
  11. package/dist/components/utilities/Background.js.map +1 -1
  12. package/dist/components/utilities/Outer.d.ts.map +1 -1
  13. package/dist/components/utilities/Outer.js +172 -76
  14. package/dist/components/utilities/Outer.js.map +1 -1
  15. package/dist/context/DivStateScopeContext.d.ts +18 -0
  16. package/dist/context/DivStateScopeContext.d.ts.map +1 -0
  17. package/dist/context/DivStateScopeContext.js +7 -0
  18. package/dist/context/DivStateScopeContext.js.map +1 -0
  19. package/dist/hooks/useAppearanceTransition.d.ts +86 -0
  20. package/dist/hooks/useAppearanceTransition.d.ts.map +1 -0
  21. package/dist/hooks/useAppearanceTransition.js +490 -0
  22. package/dist/hooks/useAppearanceTransition.js.map +1 -0
  23. package/dist/hooks/useChangeBoundsTransition.d.ts +46 -0
  24. package/dist/hooks/useChangeBoundsTransition.d.ts.map +1 -0
  25. package/dist/hooks/useChangeBoundsTransition.js +151 -0
  26. package/dist/hooks/useChangeBoundsTransition.js.map +1 -0
  27. package/dist/utils/configureChangeBoundsLayout.d.ts +11 -0
  28. package/dist/utils/configureChangeBoundsLayout.d.ts.map +1 -0
  29. package/dist/utils/configureChangeBoundsLayout.js +65 -0
  30. package/dist/utils/configureChangeBoundsLayout.js.map +1 -0
  31. package/dist/utils/flattenTransition.d.ts +5 -0
  32. package/dist/utils/flattenTransition.d.ts.map +1 -0
  33. package/dist/utils/flattenTransition.js +27 -0
  34. package/dist/utils/flattenTransition.js.map +1 -0
  35. package/package.json +2 -1
  36. package/src/DivKit.tsx +125 -2
  37. package/src/components/state/DivState.tsx +308 -39
  38. package/src/components/utilities/Background.tsx +4 -3
  39. package/src/components/utilities/Outer.tsx +188 -73
  40. package/src/context/DivStateScopeContext.tsx +23 -0
  41. package/src/hooks/useAppearanceTransition.ts +621 -0
  42. package/src/hooks/useChangeBoundsTransition.ts +193 -0
  43. package/src/utils/configureChangeBoundsLayout.ts +74 -0
  44. 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.7.0",
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",
@@ -51,6 +51,7 @@
51
51
  "@typescript-eslint/eslint-plugin": "^5.59.0",
52
52
  "@typescript-eslint/parser": "^5.59.0",
53
53
  "eslint": "^8.47.0",
54
+ "eslint-plugin-react-hooks": "^4.6.2",
54
55
  "jest": "^29.0.0",
55
56
  "react": "19.2.4",
56
57
  "react-native": "0.83.1",
package/src/DivKit.tsx CHANGED
@@ -20,7 +20,7 @@
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';
@@ -40,6 +40,7 @@ import { updateStructure } from './actions/updateStructure';
40
40
  import { applySetStateAction, type ActionSetStateCompat } from './actions/setState';
41
41
  import { evalExpression } from './expressions/eval';
42
42
  import { parse } from './expressions/expressions';
43
+ import { prepareVars } from './expressions/json';
43
44
  import { getUrlSchema } from './utils/url';
44
45
 
45
46
  /**
@@ -249,6 +250,14 @@ export function DivKit({
249
250
  return `${key}_${componentIdCounter.current++}`;
250
251
  }, []);
251
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
+
252
261
  // Variable management
253
262
  const getVariable = useCallback(
254
263
  (name: string): Variable | undefined => {
@@ -432,7 +441,24 @@ export function DivKit({
432
441
  const name = params.get('name');
433
442
  const value = params.get('value');
434
443
  if (name && value !== null) {
435
- 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
+ }
436
462
  } else {
437
463
  logError(
438
464
  wrapError(new Error('Incorrect set_variable_action'), {
@@ -464,6 +490,103 @@ export function DivKit({
464
490
  [variables, logError, onStat, onCustomAction, setVariable]
465
491
  );
466
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
+
467
590
  // Component registration
468
591
  const registerComponent = useCallback((_componentId: string, context: ComponentContext): void => {
469
592
  componentsMap.current.set(context.id, context);