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.
- package/README.md +18 -15
- package/dist/DivKit.d.ts.map +1 -1
- package/dist/DivKit.js +115 -4
- package/dist/DivKit.js.map +1 -1
- package/dist/components/DivComponent.d.ts.map +1 -1
- package/dist/components/DivComponent.js +6 -2
- package/dist/components/DivComponent.js.map +1 -1
- package/dist/components/index.d.ts +4 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +2 -0
- package/dist/components/index.js.map +1 -1
- package/dist/components/indicator/DivIndicator.d.ts +19 -0
- package/dist/components/indicator/DivIndicator.d.ts.map +1 -0
- package/dist/components/indicator/DivIndicator.js +112 -0
- package/dist/components/indicator/DivIndicator.js.map +1 -0
- package/dist/components/indicator/index.d.ts +3 -0
- package/dist/components/indicator/index.d.ts.map +1 -0
- package/dist/components/indicator/index.js +2 -0
- package/dist/components/indicator/index.js.map +1 -0
- package/dist/components/indicator/utils.d.ts +61 -0
- package/dist/components/indicator/utils.d.ts.map +1 -0
- package/dist/components/indicator/utils.js +104 -0
- package/dist/components/indicator/utils.js.map +1 -0
- package/dist/components/pager/DivPager.d.ts +22 -0
- package/dist/components/pager/DivPager.d.ts.map +1 -0
- package/dist/components/pager/DivPager.js +269 -0
- package/dist/components/pager/DivPager.js.map +1 -0
- package/dist/components/pager/index.d.ts +3 -0
- package/dist/components/pager/index.d.ts.map +1 -0
- package/dist/components/pager/index.js +2 -0
- package/dist/components/pager/index.js.map +1 -0
- package/dist/components/pager/utils.d.ts +96 -0
- package/dist/components/pager/utils.d.ts.map +1 -0
- package/dist/components/pager/utils.js +142 -0
- package/dist/components/pager/utils.js.map +1 -0
- 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 +175 -78
- 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/context/PagerContext.d.ts +30 -0
- package/dist/context/PagerContext.d.ts.map +1 -0
- package/dist/context/PagerContext.js +76 -0
- package/dist/context/PagerContext.js.map +1 -0
- package/dist/context/index.d.ts +1 -0
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js +1 -0
- package/dist/context/index.js.map +1 -1
- 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 +3 -1
- package/src/DivKit.tsx +131 -5
- package/src/components/DivComponent.tsx +8 -2
- package/src/components/README.md +59 -5
- package/src/components/index.ts +4 -0
- package/src/components/indicator/DivIndicator.tsx +175 -0
- package/src/components/indicator/index.ts +2 -0
- package/src/components/indicator/utils.ts +149 -0
- package/src/components/pager/DivPager.tsx +393 -0
- package/src/components/pager/index.ts +2 -0
- package/src/components/pager/utils.ts +214 -0
- package/src/components/state/DivState.tsx +308 -39
- package/src/components/utilities/Background.tsx +4 -3
- package/src/components/utilities/Outer.tsx +192 -75
- package/src/context/DivStateScopeContext.tsx +23 -0
- package/src/context/PagerContext.tsx +108 -0
- package/src/context/index.ts +8 -0
- package/src/hooks/useAppearanceTransition.ts +621 -0
- package/src/hooks/useChangeBoundsTransition.ts +193 -0
- package/src/types/indicator.d.ts +32 -0
- package/src/types/pager.d.ts +36 -0
- package/src/types/shape.d.ts +26 -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",
|
|
@@ -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
|
-
|
|
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
|
-
<
|
|
688
|
-
<
|
|
689
|
-
|
|
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':
|
package/src/components/README.md
CHANGED
|
@@ -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.
|
|
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`, `
|
|
107
|
-
- `slider
|
|
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
|
-
|
|
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
|
-
└──
|
|
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
|
package/src/components/index.ts
CHANGED
|
@@ -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';
|