react-native-divkit 1.6.5 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/README.md +18 -15
  2. package/dist/DivKit.d.ts.map +1 -1
  3. package/dist/DivKit.js +115 -4
  4. package/dist/DivKit.js.map +1 -1
  5. package/dist/components/DivComponent.d.ts.map +1 -1
  6. package/dist/components/DivComponent.js +6 -2
  7. package/dist/components/DivComponent.js.map +1 -1
  8. package/dist/components/index.d.ts +4 -0
  9. package/dist/components/index.d.ts.map +1 -1
  10. package/dist/components/index.js +2 -0
  11. package/dist/components/index.js.map +1 -1
  12. package/dist/components/indicator/DivIndicator.d.ts +19 -0
  13. package/dist/components/indicator/DivIndicator.d.ts.map +1 -0
  14. package/dist/components/indicator/DivIndicator.js +112 -0
  15. package/dist/components/indicator/DivIndicator.js.map +1 -0
  16. package/dist/components/indicator/index.d.ts +3 -0
  17. package/dist/components/indicator/index.d.ts.map +1 -0
  18. package/dist/components/indicator/index.js +2 -0
  19. package/dist/components/indicator/index.js.map +1 -0
  20. package/dist/components/indicator/utils.d.ts +61 -0
  21. package/dist/components/indicator/utils.d.ts.map +1 -0
  22. package/dist/components/indicator/utils.js +104 -0
  23. package/dist/components/indicator/utils.js.map +1 -0
  24. package/dist/components/pager/DivPager.d.ts +22 -0
  25. package/dist/components/pager/DivPager.d.ts.map +1 -0
  26. package/dist/components/pager/DivPager.js +269 -0
  27. package/dist/components/pager/DivPager.js.map +1 -0
  28. package/dist/components/pager/index.d.ts +3 -0
  29. package/dist/components/pager/index.d.ts.map +1 -0
  30. package/dist/components/pager/index.js +2 -0
  31. package/dist/components/pager/index.js.map +1 -0
  32. package/dist/components/pager/utils.d.ts +96 -0
  33. package/dist/components/pager/utils.d.ts.map +1 -0
  34. package/dist/components/pager/utils.js +142 -0
  35. package/dist/components/pager/utils.js.map +1 -0
  36. package/dist/components/state/DivState.d.ts +11 -12
  37. package/dist/components/state/DivState.d.ts.map +1 -1
  38. package/dist/components/state/DivState.js +263 -35
  39. package/dist/components/state/DivState.js.map +1 -1
  40. package/dist/components/utilities/Background.d.ts.map +1 -1
  41. package/dist/components/utilities/Background.js +4 -3
  42. package/dist/components/utilities/Background.js.map +1 -1
  43. package/dist/components/utilities/Outer.d.ts.map +1 -1
  44. package/dist/components/utilities/Outer.js +175 -78
  45. package/dist/components/utilities/Outer.js.map +1 -1
  46. package/dist/context/DivStateScopeContext.d.ts +18 -0
  47. package/dist/context/DivStateScopeContext.d.ts.map +1 -0
  48. package/dist/context/DivStateScopeContext.js +7 -0
  49. package/dist/context/DivStateScopeContext.js.map +1 -0
  50. package/dist/context/PagerContext.d.ts +30 -0
  51. package/dist/context/PagerContext.d.ts.map +1 -0
  52. package/dist/context/PagerContext.js +76 -0
  53. package/dist/context/PagerContext.js.map +1 -0
  54. package/dist/context/index.d.ts +1 -0
  55. package/dist/context/index.d.ts.map +1 -1
  56. package/dist/context/index.js +1 -0
  57. package/dist/context/index.js.map +1 -1
  58. package/dist/hooks/useAppearanceTransition.d.ts +86 -0
  59. package/dist/hooks/useAppearanceTransition.d.ts.map +1 -0
  60. package/dist/hooks/useAppearanceTransition.js +490 -0
  61. package/dist/hooks/useAppearanceTransition.js.map +1 -0
  62. package/dist/hooks/useChangeBoundsTransition.d.ts +46 -0
  63. package/dist/hooks/useChangeBoundsTransition.d.ts.map +1 -0
  64. package/dist/hooks/useChangeBoundsTransition.js +151 -0
  65. package/dist/hooks/useChangeBoundsTransition.js.map +1 -0
  66. package/dist/utils/configureChangeBoundsLayout.d.ts +11 -0
  67. package/dist/utils/configureChangeBoundsLayout.d.ts.map +1 -0
  68. package/dist/utils/configureChangeBoundsLayout.js +65 -0
  69. package/dist/utils/configureChangeBoundsLayout.js.map +1 -0
  70. package/dist/utils/flattenTransition.d.ts +5 -0
  71. package/dist/utils/flattenTransition.d.ts.map +1 -0
  72. package/dist/utils/flattenTransition.js +27 -0
  73. package/dist/utils/flattenTransition.js.map +1 -0
  74. package/package.json +3 -1
  75. package/src/DivKit.tsx +131 -5
  76. package/src/components/DivComponent.tsx +8 -2
  77. package/src/components/README.md +59 -5
  78. package/src/components/index.ts +4 -0
  79. package/src/components/indicator/DivIndicator.tsx +175 -0
  80. package/src/components/indicator/index.ts +2 -0
  81. package/src/components/indicator/utils.ts +149 -0
  82. package/src/components/pager/DivPager.tsx +393 -0
  83. package/src/components/pager/index.ts +2 -0
  84. package/src/components/pager/utils.ts +214 -0
  85. package/src/components/state/DivState.tsx +308 -39
  86. package/src/components/utilities/Background.tsx +4 -3
  87. package/src/components/utilities/Outer.tsx +192 -75
  88. package/src/context/DivStateScopeContext.tsx +23 -0
  89. package/src/context/PagerContext.tsx +108 -0
  90. package/src/context/index.ts +8 -0
  91. package/src/hooks/useAppearanceTransition.ts +621 -0
  92. package/src/hooks/useChangeBoundsTransition.ts +193 -0
  93. package/src/types/indicator.d.ts +32 -0
  94. package/src/types/pager.d.ts +36 -0
  95. package/src/types/shape.d.ts +26 -0
  96. package/src/utils/configureChangeBoundsLayout.ts +74 -0
  97. package/src/utils/flattenTransition.ts +36 -0
@@ -0,0 +1,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,2 @@
1
+ export { DivIndicator } from './DivIndicator';
2
+ export type { DivIndicatorProps } from './DivIndicator';
@@ -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
+ }