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,175 @@
|
|
|
1
|
+
import React, { useEffect, useMemo, useRef, useState, useCallback } from 'react';
|
|
2
|
+
import { View, ScrollView, Pressable, StyleSheet, ViewStyle } from 'react-native';
|
|
3
|
+
import type { ComponentContext } from '../../types/componentContext';
|
|
4
|
+
import type { DivIndicatorData } from '../../types/indicator';
|
|
5
|
+
import type { PagerData } from '../../types/componentContext';
|
|
6
|
+
import { Outer } from '../utilities/Outer';
|
|
7
|
+
import { useDerivedFromVarsSimple } from '../../hooks/useDerivedFromVars';
|
|
8
|
+
import { usePagerContextOptional } from '../../context/PagerContext';
|
|
9
|
+
import { buildDotStyles, resolvePlacement } from './utils';
|
|
10
|
+
|
|
11
|
+
export interface DivIndicatorProps {
|
|
12
|
+
componentContext: ComponentContext<DivIndicatorData>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* DivIndicator — page-position dots for a DivPager.
|
|
17
|
+
*
|
|
18
|
+
* Based on Web Indicator.svelte. Adapted for React Native:
|
|
19
|
+
* - Subscribes to a pager via PagerContext (registered by DivPager).
|
|
20
|
+
* - Computes active/inactive dot styles from active_shape/inactive_shape, or
|
|
21
|
+
* falls back to legacy `shape` + `active_item_size` + colors.
|
|
22
|
+
* - Tap on a dot scrolls the bound pager to that page.
|
|
23
|
+
* - Items_placement supports 'default' (space_between_centers) and 'stretch'
|
|
24
|
+
* (item_spacing). For unknown placements falls back to default.
|
|
25
|
+
*/
|
|
26
|
+
export function DivIndicator({ componentContext }: DivIndicatorProps) {
|
|
27
|
+
const pagerCtx = usePagerContextOptional();
|
|
28
|
+
const { json, variables } = componentContext;
|
|
29
|
+
|
|
30
|
+
const shapeJson = useDerivedFromVarsSimple<any>(json.shape, variables || new Map());
|
|
31
|
+
const activeShapeJson = useDerivedFromVarsSimple<any>(
|
|
32
|
+
json.active_shape,
|
|
33
|
+
variables || new Map()
|
|
34
|
+
);
|
|
35
|
+
const inactiveShapeJson = useDerivedFromVarsSimple<any>(
|
|
36
|
+
json.inactive_shape,
|
|
37
|
+
variables || new Map()
|
|
38
|
+
);
|
|
39
|
+
const activeColor = useDerivedFromVarsSimple<string | undefined>(
|
|
40
|
+
json.active_item_color,
|
|
41
|
+
variables || new Map()
|
|
42
|
+
);
|
|
43
|
+
const inactiveColor = useDerivedFromVarsSimple<string | undefined>(
|
|
44
|
+
json.inactive_item_color,
|
|
45
|
+
variables || new Map()
|
|
46
|
+
);
|
|
47
|
+
const activeItemSize = useDerivedFromVarsSimple<number | undefined>(
|
|
48
|
+
json.active_item_size,
|
|
49
|
+
variables || new Map()
|
|
50
|
+
);
|
|
51
|
+
const spaceBetweenCenters = useDerivedFromVarsSimple<any>(
|
|
52
|
+
json.space_between_centers,
|
|
53
|
+
variables || new Map()
|
|
54
|
+
);
|
|
55
|
+
const itemsPlacement = useDerivedFromVarsSimple<any>(
|
|
56
|
+
json.items_placement,
|
|
57
|
+
variables || new Map()
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const { active: activeStyle, inactive: inactiveStyle } = useMemo(
|
|
61
|
+
() =>
|
|
62
|
+
buildDotStyles({
|
|
63
|
+
activeShape: activeShapeJson,
|
|
64
|
+
inactiveShape: inactiveShapeJson,
|
|
65
|
+
legacyShape: shapeJson,
|
|
66
|
+
activeColor,
|
|
67
|
+
inactiveColor,
|
|
68
|
+
activeItemSize
|
|
69
|
+
}),
|
|
70
|
+
[activeShapeJson, inactiveShapeJson, shapeJson, activeColor, inactiveColor, activeItemSize]
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const { placement, gap, stretchSpacing, maxVisible } = useMemo(
|
|
74
|
+
() =>
|
|
75
|
+
resolvePlacement({
|
|
76
|
+
itemsPlacement,
|
|
77
|
+
spaceBetweenCenters,
|
|
78
|
+
inactiveWidth: inactiveStyle.width
|
|
79
|
+
}),
|
|
80
|
+
[itemsPlacement, spaceBetweenCenters, inactiveStyle.width]
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const [pagerData, setPagerData] = useState<PagerData | null>(null);
|
|
84
|
+
const scrollerRef = useRef<ScrollView>(null);
|
|
85
|
+
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
if (!pagerCtx) return;
|
|
88
|
+
const unsubscribe = pagerCtx.listenPager(json.pager_id, (data: PagerData) => {
|
|
89
|
+
setPagerData(data);
|
|
90
|
+
});
|
|
91
|
+
return unsubscribe;
|
|
92
|
+
}, [pagerCtx, json.pager_id]);
|
|
93
|
+
|
|
94
|
+
const onItemPress = useCallback(
|
|
95
|
+
(index: number) => {
|
|
96
|
+
if (!pagerData) return;
|
|
97
|
+
if (index !== pagerData.currentItem) {
|
|
98
|
+
pagerData.scrollToPagerItem(index);
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
[pagerData]
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
if (!pagerData || pagerData.size <= 1) {
|
|
105
|
+
// Match Web: indicator only shown when size > 1, but Outer still applies
|
|
106
|
+
// sizing/margins. Render an empty Outer so layout (height/margins) holds.
|
|
107
|
+
return <Outer componentContext={componentContext}>{null}</Outer>;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const renderDot = (index: number) => {
|
|
111
|
+
const isActive = index === pagerData.currentItem;
|
|
112
|
+
const dot = isActive ? activeStyle : inactiveStyle;
|
|
113
|
+
const dotStyle: ViewStyle = {
|
|
114
|
+
width: dot.width,
|
|
115
|
+
height: dot.height,
|
|
116
|
+
borderRadius: dot.borderRadius,
|
|
117
|
+
backgroundColor: dot.background
|
|
118
|
+
};
|
|
119
|
+
const wrapperStyle: ViewStyle = {
|
|
120
|
+
justifyContent: 'center',
|
|
121
|
+
alignItems: 'center',
|
|
122
|
+
// reserve room for the larger active dot so the row height stays stable
|
|
123
|
+
width: placement === 'stretch' ? undefined : Math.max(activeStyle.width, inactiveStyle.width),
|
|
124
|
+
height: Math.max(activeStyle.height, inactiveStyle.height),
|
|
125
|
+
marginLeft: index === 0 ? 0 : placement === 'stretch' ? stretchSpacing : gap / 2,
|
|
126
|
+
marginRight:
|
|
127
|
+
index === pagerData.size - 1 ? 0 : placement === 'stretch' ? stretchSpacing : gap / 2,
|
|
128
|
+
flex: placement === 'stretch' ? 1 : undefined,
|
|
129
|
+
maxWidth: placement === 'stretch' && pagerData.size > maxVisible ? undefined : undefined
|
|
130
|
+
};
|
|
131
|
+
return (
|
|
132
|
+
<Pressable
|
|
133
|
+
key={`indicator-dot-${index}`}
|
|
134
|
+
onPress={() => onItemPress(index)}
|
|
135
|
+
style={wrapperStyle}
|
|
136
|
+
>
|
|
137
|
+
<View style={dotStyle} />
|
|
138
|
+
</Pressable>
|
|
139
|
+
);
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const dots = [];
|
|
143
|
+
for (let i = 0; i < pagerData.size; i++) dots.push(renderDot(i));
|
|
144
|
+
|
|
145
|
+
const rowStyle: ViewStyle = {
|
|
146
|
+
flexDirection: 'row',
|
|
147
|
+
alignItems: 'center',
|
|
148
|
+
justifyContent: placement === 'stretch' ? 'space-between' : 'center'
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
<Outer componentContext={componentContext}>
|
|
153
|
+
<ScrollView
|
|
154
|
+
ref={scrollerRef}
|
|
155
|
+
horizontal
|
|
156
|
+
showsHorizontalScrollIndicator={false}
|
|
157
|
+
contentContainerStyle={styles.scrollContent}
|
|
158
|
+
style={styles.scroller}
|
|
159
|
+
>
|
|
160
|
+
<View style={rowStyle}>{dots}</View>
|
|
161
|
+
</ScrollView>
|
|
162
|
+
</Outer>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const styles = StyleSheet.create({
|
|
167
|
+
scroller: {
|
|
168
|
+
alignSelf: 'stretch'
|
|
169
|
+
},
|
|
170
|
+
scrollContent: {
|
|
171
|
+
flexGrow: 1,
|
|
172
|
+
justifyContent: 'center',
|
|
173
|
+
alignItems: 'center'
|
|
174
|
+
}
|
|
175
|
+
});
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers for DivIndicator. Extracted for unit testing.
|
|
3
|
+
*/
|
|
4
|
+
import { correctColor } from '../../utils/correctColor';
|
|
5
|
+
|
|
6
|
+
export interface DotStyle {
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
borderRadius: number;
|
|
10
|
+
background: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const DEFAULT_ACTIVE: DotStyle = {
|
|
14
|
+
width: 13,
|
|
15
|
+
height: 13,
|
|
16
|
+
borderRadius: 6.5,
|
|
17
|
+
background: '#ffdc60'
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const DEFAULT_INACTIVE: DotStyle = {
|
|
21
|
+
width: 10,
|
|
22
|
+
height: 10,
|
|
23
|
+
borderRadius: 5,
|
|
24
|
+
background: '#33919cb5'
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
interface ShapeLike {
|
|
28
|
+
type?: string;
|
|
29
|
+
item_width?: { value?: number };
|
|
30
|
+
item_height?: { value?: number };
|
|
31
|
+
corner_radius?: { value?: number };
|
|
32
|
+
radius?: { value?: number };
|
|
33
|
+
background_color?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Convert a Shape (rounded_rectangle | circle) into a DotStyle.
|
|
38
|
+
* Falls back to `base` when the shape is missing or unsupported.
|
|
39
|
+
*/
|
|
40
|
+
export function shapeToDot(shape: unknown, fallbackColor: string, base: DotStyle): DotStyle {
|
|
41
|
+
if (!shape) return base;
|
|
42
|
+
const s = shape as ShapeLike;
|
|
43
|
+
if (s.type === 'rounded_rectangle') {
|
|
44
|
+
const w = s.item_width?.value ?? base.width;
|
|
45
|
+
const h = s.item_height?.value ?? base.height;
|
|
46
|
+
const r = s.corner_radius?.value ?? Math.min(w, h) / 2;
|
|
47
|
+
return {
|
|
48
|
+
width: w,
|
|
49
|
+
height: h,
|
|
50
|
+
borderRadius: r,
|
|
51
|
+
background: correctColor(s.background_color, 1, fallbackColor)
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
if (s.type === 'circle') {
|
|
55
|
+
const r = s.radius?.value ?? base.width / 2;
|
|
56
|
+
return {
|
|
57
|
+
width: r * 2,
|
|
58
|
+
height: r * 2,
|
|
59
|
+
borderRadius: r,
|
|
60
|
+
background: correctColor(s.background_color, 1, fallbackColor)
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return base;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface BuildDotStylesArgs {
|
|
67
|
+
activeShape?: unknown;
|
|
68
|
+
inactiveShape?: unknown;
|
|
69
|
+
legacyShape?: unknown;
|
|
70
|
+
activeColor?: string;
|
|
71
|
+
inactiveColor?: string;
|
|
72
|
+
activeItemSize?: number;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Compute final active/inactive dot styles. Mirrors Indicator.svelte:
|
|
77
|
+
* - explicit active_shape/inactive_shape take precedence
|
|
78
|
+
* - else legacy `shape` + `active_item_size` + colors generates both
|
|
79
|
+
* - else defaults
|
|
80
|
+
*/
|
|
81
|
+
export function buildDotStyles(args: BuildDotStylesArgs): { active: DotStyle; inactive: DotStyle } {
|
|
82
|
+
const { activeShape, inactiveShape, legacyShape, activeColor, inactiveColor, activeItemSize } = args;
|
|
83
|
+
let inactive: DotStyle = { ...DEFAULT_INACTIVE };
|
|
84
|
+
let active: DotStyle = { ...DEFAULT_ACTIVE };
|
|
85
|
+
|
|
86
|
+
if (activeShape) {
|
|
87
|
+
active = shapeToDot(activeShape, active.background, active);
|
|
88
|
+
}
|
|
89
|
+
if (inactiveShape) {
|
|
90
|
+
inactive = shapeToDot(inactiveShape, inactive.background, inactive);
|
|
91
|
+
}
|
|
92
|
+
if (!activeShape && !inactiveShape && legacyShape) {
|
|
93
|
+
const sizeMul = typeof activeItemSize === 'number' && activeItemSize > 0 ? activeItemSize : 1.3;
|
|
94
|
+
inactive = shapeToDot(legacyShape, inactive.background, inactive);
|
|
95
|
+
inactive.background = correctColor(inactiveColor, 1, inactive.background);
|
|
96
|
+
const activeBg = correctColor(activeColor, 1, active.background);
|
|
97
|
+
active = {
|
|
98
|
+
width: inactive.width * sizeMul,
|
|
99
|
+
height: inactive.height * sizeMul,
|
|
100
|
+
borderRadius: inactive.borderRadius * sizeMul,
|
|
101
|
+
background: activeBg
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
return { active, inactive };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export type IndicatorPlacement = 'default' | 'stretch';
|
|
108
|
+
|
|
109
|
+
export interface ResolvePlacementArgs {
|
|
110
|
+
itemsPlacement?: { type?: string; space_between_centers?: { value?: number }; item_spacing?: { value?: number }; max_visible_items?: number };
|
|
111
|
+
spaceBetweenCenters?: { value?: number };
|
|
112
|
+
inactiveWidth: number;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface ResolvedPlacement {
|
|
116
|
+
placement: IndicatorPlacement;
|
|
117
|
+
gap: number;
|
|
118
|
+
stretchSpacing: number;
|
|
119
|
+
maxVisible: number;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Resolve which placement mode to use and the spacing parameters.
|
|
124
|
+
* - stretch: equal spacing across the full width, item_spacing px between dots.
|
|
125
|
+
* - default: gap = space_between_centers - inactiveWidth.
|
|
126
|
+
*/
|
|
127
|
+
export function resolvePlacement(args: ResolvePlacementArgs): ResolvedPlacement {
|
|
128
|
+
const { itemsPlacement, spaceBetweenCenters, inactiveWidth } = args;
|
|
129
|
+
|
|
130
|
+
if (itemsPlacement && itemsPlacement.type === 'stretch') {
|
|
131
|
+
return {
|
|
132
|
+
placement: 'stretch',
|
|
133
|
+
gap: 0,
|
|
134
|
+
stretchSpacing: itemsPlacement.item_spacing?.value ?? 5,
|
|
135
|
+
maxVisible: itemsPlacement.max_visible_items ?? 10
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
let center = spaceBetweenCenters?.value;
|
|
139
|
+
if (itemsPlacement && itemsPlacement.type === 'default') {
|
|
140
|
+
center = itemsPlacement.space_between_centers?.value ?? center;
|
|
141
|
+
}
|
|
142
|
+
const c = typeof center === 'number' && center >= 0 ? center : 15;
|
|
143
|
+
return {
|
|
144
|
+
placement: 'default',
|
|
145
|
+
gap: Math.max(0, c - inactiveWidth),
|
|
146
|
+
stretchSpacing: 0,
|
|
147
|
+
maxVisible: 10
|
|
148
|
+
};
|
|
149
|
+
}
|