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.
- package/README.md +17 -16
- package/dist/DivKit.d.ts.map +1 -1
- package/dist/DivKit.js +109 -1
- package/dist/DivKit.js.map +1 -1
- package/dist/components/state/DivState.d.ts +11 -12
- package/dist/components/state/DivState.d.ts.map +1 -1
- package/dist/components/state/DivState.js +263 -35
- package/dist/components/state/DivState.js.map +1 -1
- package/dist/components/utilities/Background.d.ts.map +1 -1
- package/dist/components/utilities/Background.js +4 -3
- package/dist/components/utilities/Background.js.map +1 -1
- package/dist/components/utilities/Outer.d.ts.map +1 -1
- package/dist/components/utilities/Outer.js +172 -76
- package/dist/components/utilities/Outer.js.map +1 -1
- package/dist/context/DivStateScopeContext.d.ts +18 -0
- package/dist/context/DivStateScopeContext.d.ts.map +1 -0
- package/dist/context/DivStateScopeContext.js +7 -0
- package/dist/context/DivStateScopeContext.js.map +1 -0
- package/dist/hooks/useAppearanceTransition.d.ts +86 -0
- package/dist/hooks/useAppearanceTransition.d.ts.map +1 -0
- package/dist/hooks/useAppearanceTransition.js +490 -0
- package/dist/hooks/useAppearanceTransition.js.map +1 -0
- package/dist/hooks/useChangeBoundsTransition.d.ts +46 -0
- package/dist/hooks/useChangeBoundsTransition.d.ts.map +1 -0
- package/dist/hooks/useChangeBoundsTransition.js +151 -0
- package/dist/hooks/useChangeBoundsTransition.js.map +1 -0
- package/dist/utils/configureChangeBoundsLayout.d.ts +11 -0
- package/dist/utils/configureChangeBoundsLayout.d.ts.map +1 -0
- package/dist/utils/configureChangeBoundsLayout.js +65 -0
- package/dist/utils/configureChangeBoundsLayout.js.map +1 -0
- package/dist/utils/flattenTransition.d.ts +5 -0
- package/dist/utils/flattenTransition.d.ts.map +1 -0
- package/dist/utils/flattenTransition.js +27 -0
- package/dist/utils/flattenTransition.js.map +1 -0
- package/package.json +2 -1
- package/src/DivKit.tsx +125 -2
- package/src/components/state/DivState.tsx +308 -39
- package/src/components/utilities/Background.tsx +4 -3
- package/src/components/utilities/Outer.tsx +188 -73
- package/src/context/DivStateScopeContext.tsx +23 -0
- package/src/hooks/useAppearanceTransition.ts +621 -0
- package/src/hooks/useChangeBoundsTransition.ts +193 -0
- package/src/utils/configureChangeBoundsLayout.ts +74 -0
- 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.
|
|
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
|
-
|
|
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);
|