react-native-divkit 0.1.0-alpha.1

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 (322) hide show
  1. package/LICENSE +176 -0
  2. package/README.md +340 -0
  3. package/dist/DivKit.d.ts +68 -0
  4. package/dist/DivKit.d.ts.map +1 -0
  5. package/dist/DivKit.js +400 -0
  6. package/dist/DivKit.js.map +1 -0
  7. package/dist/actions/array.d.ts +8 -0
  8. package/dist/actions/array.d.ts.map +1 -0
  9. package/dist/actions/array.js +139 -0
  10. package/dist/actions/array.js.map +1 -0
  11. package/dist/actions/copyToClipboard.d.ts +22 -0
  12. package/dist/actions/copyToClipboard.d.ts.map +1 -0
  13. package/dist/actions/copyToClipboard.js +63 -0
  14. package/dist/actions/copyToClipboard.js.map +1 -0
  15. package/dist/actions/dict.d.ts +6 -0
  16. package/dist/actions/dict.d.ts.map +1 -0
  17. package/dist/actions/dict.js +58 -0
  18. package/dist/actions/dict.js.map +1 -0
  19. package/dist/actions/index.d.ts +11 -0
  20. package/dist/actions/index.d.ts.map +1 -0
  21. package/dist/actions/index.js +11 -0
  22. package/dist/actions/index.js.map +1 -0
  23. package/dist/actions/updateStructure.d.ts +6 -0
  24. package/dist/actions/updateStructure.d.ts.map +1 -0
  25. package/dist/actions/updateStructure.js +116 -0
  26. package/dist/actions/updateStructure.js.map +1 -0
  27. package/dist/components/DivComponent.d.ts +29 -0
  28. package/dist/components/DivComponent.d.ts.map +1 -0
  29. package/dist/components/DivComponent.js +62 -0
  30. package/dist/components/DivComponent.js.map +1 -0
  31. package/dist/components/container/DivContainer.d.ts +26 -0
  32. package/dist/components/container/DivContainer.d.ts.map +1 -0
  33. package/dist/components/container/DivContainer.js +172 -0
  34. package/dist/components/container/DivContainer.js.map +1 -0
  35. package/dist/components/container/index.d.ts +3 -0
  36. package/dist/components/container/index.d.ts.map +1 -0
  37. package/dist/components/container/index.js +2 -0
  38. package/dist/components/container/index.js.map +1 -0
  39. package/dist/components/image/DivImage.d.ts +29 -0
  40. package/dist/components/image/DivImage.d.ts.map +1 -0
  41. package/dist/components/image/DivImage.js +122 -0
  42. package/dist/components/image/DivImage.js.map +1 -0
  43. package/dist/components/image/index.d.ts +3 -0
  44. package/dist/components/image/index.d.ts.map +1 -0
  45. package/dist/components/image/index.js +2 -0
  46. package/dist/components/image/index.js.map +1 -0
  47. package/dist/components/index.d.ts +14 -0
  48. package/dist/components/index.d.ts.map +1 -0
  49. package/dist/components/index.js +11 -0
  50. package/dist/components/index.js.map +1 -0
  51. package/dist/components/state/DivState.d.ts +26 -0
  52. package/dist/components/state/DivState.d.ts.map +1 -0
  53. package/dist/components/state/DivState.js +121 -0
  54. package/dist/components/state/DivState.js.map +1 -0
  55. package/dist/components/state/index.d.ts +3 -0
  56. package/dist/components/state/index.d.ts.map +1 -0
  57. package/dist/components/state/index.js +2 -0
  58. package/dist/components/state/index.js.map +1 -0
  59. package/dist/components/text/DivText.d.ts +28 -0
  60. package/dist/components/text/DivText.d.ts.map +1 -0
  61. package/dist/components/text/DivText.js +143 -0
  62. package/dist/components/text/DivText.js.map +1 -0
  63. package/dist/components/text/index.d.ts +3 -0
  64. package/dist/components/text/index.d.ts.map +1 -0
  65. package/dist/components/text/index.js +2 -0
  66. package/dist/components/text/index.js.map +1 -0
  67. package/dist/components/utilities/Outer.d.ts +17 -0
  68. package/dist/components/utilities/Outer.d.ts.map +1 -0
  69. package/dist/components/utilities/Outer.js +210 -0
  70. package/dist/components/utilities/Outer.js.map +1 -0
  71. package/dist/components/utilities/Unknown.d.ts +11 -0
  72. package/dist/components/utilities/Unknown.d.ts.map +1 -0
  73. package/dist/components/utilities/Unknown.js +50 -0
  74. package/dist/components/utilities/Unknown.js.map +1 -0
  75. package/dist/components/utilities/index.d.ts +5 -0
  76. package/dist/components/utilities/index.d.ts.map +1 -0
  77. package/dist/components/utilities/index.js +3 -0
  78. package/dist/components/utilities/index.js.map +1 -0
  79. package/dist/context/ActionContext.d.ts +25 -0
  80. package/dist/context/ActionContext.d.ts.map +1 -0
  81. package/dist/context/ActionContext.js +20 -0
  82. package/dist/context/ActionContext.js.map +1 -0
  83. package/dist/context/DivKitContext.d.ts +33 -0
  84. package/dist/context/DivKitContext.d.ts.map +1 -0
  85. package/dist/context/DivKitContext.js +14 -0
  86. package/dist/context/DivKitContext.js.map +1 -0
  87. package/dist/context/EnabledContext.d.ts +31 -0
  88. package/dist/context/EnabledContext.d.ts.map +1 -0
  89. package/dist/context/EnabledContext.js +31 -0
  90. package/dist/context/EnabledContext.js.map +1 -0
  91. package/dist/context/StateContext.d.ts +57 -0
  92. package/dist/context/StateContext.d.ts.map +1 -0
  93. package/dist/context/StateContext.js +20 -0
  94. package/dist/context/StateContext.js.map +1 -0
  95. package/dist/context/index.d.ts +9 -0
  96. package/dist/context/index.d.ts.map +1 -0
  97. package/dist/context/index.js +9 -0
  98. package/dist/context/index.js.map +1 -0
  99. package/dist/expressions/bigint.d.ts +8 -0
  100. package/dist/expressions/bigint.d.ts.map +1 -0
  101. package/dist/expressions/bigint.js +31 -0
  102. package/dist/expressions/bigint.js.map +1 -0
  103. package/dist/expressions/const.d.ts +15 -0
  104. package/dist/expressions/const.d.ts.map +1 -0
  105. package/dist/expressions/const.js +15 -0
  106. package/dist/expressions/const.js.map +1 -0
  107. package/dist/expressions/eval.d.ts +77 -0
  108. package/dist/expressions/eval.d.ts.map +1 -0
  109. package/dist/expressions/eval.js +459 -0
  110. package/dist/expressions/eval.js.map +1 -0
  111. package/dist/expressions/expressions.d.ts +7 -0
  112. package/dist/expressions/expressions.d.ts.map +1 -0
  113. package/dist/expressions/expressions.js +3191 -0
  114. package/dist/expressions/expressions.js.map +1 -0
  115. package/dist/expressions/funcs/array.d.ts +2 -0
  116. package/dist/expressions/funcs/array.d.ts.map +1 -0
  117. package/dist/expressions/funcs/array.js +381 -0
  118. package/dist/expressions/funcs/array.js.map +1 -0
  119. package/dist/expressions/funcs/colors.d.ts +2 -0
  120. package/dist/expressions/funcs/colors.d.ts.map +1 -0
  121. package/dist/expressions/funcs/colors.js +75 -0
  122. package/dist/expressions/funcs/colors.js.map +1 -0
  123. package/dist/expressions/funcs/customFuncs.d.ts +8 -0
  124. package/dist/expressions/funcs/customFuncs.d.ts.map +1 -0
  125. package/dist/expressions/funcs/customFuncs.js +114 -0
  126. package/dist/expressions/funcs/customFuncs.js.map +1 -0
  127. package/dist/expressions/funcs/datetime.d.ts +2 -0
  128. package/dist/expressions/funcs/datetime.d.ts.map +1 -0
  129. package/dist/expressions/funcs/datetime.js +182 -0
  130. package/dist/expressions/funcs/datetime.js.map +1 -0
  131. package/dist/expressions/funcs/dict.d.ts +2 -0
  132. package/dist/expressions/funcs/dict.d.ts.map +1 -0
  133. package/dist/expressions/funcs/dict.js +170 -0
  134. package/dist/expressions/funcs/dict.js.map +1 -0
  135. package/dist/expressions/funcs/funcs.d.ts +80 -0
  136. package/dist/expressions/funcs/funcs.d.ts.map +1 -0
  137. package/dist/expressions/funcs/funcs.js +146 -0
  138. package/dist/expressions/funcs/funcs.js.map +1 -0
  139. package/dist/expressions/funcs/index.d.ts +2 -0
  140. package/dist/expressions/funcs/index.d.ts.map +1 -0
  141. package/dist/expressions/funcs/index.js +23 -0
  142. package/dist/expressions/funcs/index.js.map +1 -0
  143. package/dist/expressions/funcs/interval.d.ts +2 -0
  144. package/dist/expressions/funcs/interval.d.ts.map +1 -0
  145. package/dist/expressions/funcs/interval.js +61 -0
  146. package/dist/expressions/funcs/interval.js.map +1 -0
  147. package/dist/expressions/funcs/math.d.ts +2 -0
  148. package/dist/expressions/funcs/math.d.ts.map +1 -0
  149. package/dist/expressions/funcs/math.js +324 -0
  150. package/dist/expressions/funcs/math.js.map +1 -0
  151. package/dist/expressions/funcs/std.d.ts +2 -0
  152. package/dist/expressions/funcs/std.d.ts.map +1 -0
  153. package/dist/expressions/funcs/std.js +293 -0
  154. package/dist/expressions/funcs/std.js.map +1 -0
  155. package/dist/expressions/funcs/stored.d.ts +4 -0
  156. package/dist/expressions/funcs/stored.d.ts.map +1 -0
  157. package/dist/expressions/funcs/stored.js +62 -0
  158. package/dist/expressions/funcs/stored.js.map +1 -0
  159. package/dist/expressions/funcs/strings.d.ts +2 -0
  160. package/dist/expressions/funcs/strings.d.ts.map +1 -0
  161. package/dist/expressions/funcs/strings.js +158 -0
  162. package/dist/expressions/funcs/strings.js.map +1 -0
  163. package/dist/expressions/funcs/trigonometry.d.ts +2 -0
  164. package/dist/expressions/funcs/trigonometry.d.ts.map +1 -0
  165. package/dist/expressions/funcs/trigonometry.js +92 -0
  166. package/dist/expressions/funcs/trigonometry.js.map +1 -0
  167. package/dist/expressions/json.d.ts +18 -0
  168. package/dist/expressions/json.d.ts.map +1 -0
  169. package/dist/expressions/json.js +271 -0
  170. package/dist/expressions/json.js.map +1 -0
  171. package/dist/expressions/parserCache.d.ts +4 -0
  172. package/dist/expressions/parserCache.d.ts.map +1 -0
  173. package/dist/expressions/parserCache.js +23 -0
  174. package/dist/expressions/parserCache.js.map +1 -0
  175. package/dist/expressions/simpleUnescapeString.d.ts +2 -0
  176. package/dist/expressions/simpleUnescapeString.d.ts.map +1 -0
  177. package/dist/expressions/simpleUnescapeString.js +61 -0
  178. package/dist/expressions/simpleUnescapeString.js.map +1 -0
  179. package/dist/expressions/utils.d.ts +29 -0
  180. package/dist/expressions/utils.d.ts.map +1 -0
  181. package/dist/expressions/utils.js +236 -0
  182. package/dist/expressions/utils.js.map +1 -0
  183. package/dist/expressions/variable.d.ts +82 -0
  184. package/dist/expressions/variable.d.ts.map +1 -0
  185. package/dist/expressions/variable.js +337 -0
  186. package/dist/expressions/variable.js.map +1 -0
  187. package/dist/expressions/walk.d.ts +7 -0
  188. package/dist/expressions/walk.d.ts.map +1 -0
  189. package/dist/expressions/walk.js +39 -0
  190. package/dist/expressions/walk.js.map +1 -0
  191. package/dist/hooks/index.d.ts +8 -0
  192. package/dist/hooks/index.d.ts.map +1 -0
  193. package/dist/hooks/index.js +11 -0
  194. package/dist/hooks/index.js.map +1 -0
  195. package/dist/hooks/useAction.d.ts +102 -0
  196. package/dist/hooks/useAction.d.ts.map +1 -0
  197. package/dist/hooks/useAction.js +116 -0
  198. package/dist/hooks/useAction.js.map +1 -0
  199. package/dist/hooks/useDerivedFromVars.d.ts +72 -0
  200. package/dist/hooks/useDerivedFromVars.d.ts.map +1 -0
  201. package/dist/hooks/useDerivedFromVars.js +100 -0
  202. package/dist/hooks/useDerivedFromVars.js.map +1 -0
  203. package/dist/hooks/useVariable.d.ts +86 -0
  204. package/dist/hooks/useVariable.d.ts.map +1 -0
  205. package/dist/hooks/useVariable.js +130 -0
  206. package/dist/hooks/useVariable.js.map +1 -0
  207. package/dist/index.d.ts +30 -0
  208. package/dist/index.d.ts.map +1 -0
  209. package/dist/index.js +28 -0
  210. package/dist/index.js.map +1 -0
  211. package/dist/stores/createObservable.d.ts +38 -0
  212. package/dist/stores/createObservable.d.ts.map +1 -0
  213. package/dist/stores/createObservable.js +49 -0
  214. package/dist/stores/createObservable.js.map +1 -0
  215. package/dist/utils/applyTemplate.d.ts +8 -0
  216. package/dist/utils/applyTemplate.d.ts.map +1 -0
  217. package/dist/utils/applyTemplate.js +94 -0
  218. package/dist/utils/applyTemplate.js.map +1 -0
  219. package/dist/utils/correctColor.d.ts +18 -0
  220. package/dist/utils/correctColor.d.ts.map +1 -0
  221. package/dist/utils/correctColor.js +79 -0
  222. package/dist/utils/correctColor.js.map +1 -0
  223. package/dist/utils/escapeRegExp.d.ts +2 -0
  224. package/dist/utils/escapeRegExp.d.ts.map +1 -0
  225. package/dist/utils/escapeRegExp.js +4 -0
  226. package/dist/utils/escapeRegExp.js.map +1 -0
  227. package/dist/utils/formatDate.d.ts +6 -0
  228. package/dist/utils/formatDate.d.ts.map +1 -0
  229. package/dist/utils/formatDate.js +325 -0
  230. package/dist/utils/formatDate.js.map +1 -0
  231. package/dist/utils/padLeft.d.ts +2 -0
  232. package/dist/utils/padLeft.d.ts.map +1 -0
  233. package/dist/utils/padLeft.js +7 -0
  234. package/dist/utils/padLeft.js.map +1 -0
  235. package/dist/utils/uniq.d.ts +2 -0
  236. package/dist/utils/uniq.d.ts.map +1 -0
  237. package/dist/utils/uniq.js +4 -0
  238. package/dist/utils/uniq.js.map +1 -0
  239. package/dist/utils/wrapError.d.ts +10 -0
  240. package/dist/utils/wrapError.d.ts.map +1 -0
  241. package/dist/utils/wrapError.js +9 -0
  242. package/dist/utils/wrapError.js.map +1 -0
  243. package/package.json +58 -0
  244. package/src/DivKit.tsx +542 -0
  245. package/src/actions/array.ts +170 -0
  246. package/src/actions/copyToClipboard.ts +82 -0
  247. package/src/actions/dict.ts +71 -0
  248. package/src/actions/index.ts +11 -0
  249. package/src/actions/updateStructure.ts +134 -0
  250. package/src/components/DivComponent.tsx +75 -0
  251. package/src/components/README.md +230 -0
  252. package/src/components/container/DivContainer.tsx +222 -0
  253. package/src/components/container/index.ts +2 -0
  254. package/src/components/image/DivImage.tsx +172 -0
  255. package/src/components/image/index.ts +2 -0
  256. package/src/components/index.ts +20 -0
  257. package/src/components/state/DivState.tsx +146 -0
  258. package/src/components/state/index.ts +2 -0
  259. package/src/components/text/DivText.tsx +186 -0
  260. package/src/components/text/index.ts +2 -0
  261. package/src/components/utilities/Outer.tsx +239 -0
  262. package/src/components/utilities/README.md +175 -0
  263. package/src/components/utilities/Unknown.tsx +60 -0
  264. package/src/components/utilities/index.ts +4 -0
  265. package/src/context/ActionContext.tsx +37 -0
  266. package/src/context/DivKitContext.tsx +54 -0
  267. package/src/context/EnabledContext.tsx +50 -0
  268. package/src/context/StateContext.tsx +75 -0
  269. package/src/context/index.ts +33 -0
  270. package/src/expressions/ast.d.ts +101 -0
  271. package/src/expressions/bigint.ts +38 -0
  272. package/src/expressions/const.ts +16 -0
  273. package/src/expressions/eval.ts +669 -0
  274. package/src/expressions/expressions.peggy +235 -0
  275. package/src/expressions/expressions.ts +2854 -0
  276. package/src/expressions/funcs/array.ts +412 -0
  277. package/src/expressions/funcs/colors.ts +100 -0
  278. package/src/expressions/funcs/customFuncs.ts +139 -0
  279. package/src/expressions/funcs/datetime.ts +232 -0
  280. package/src/expressions/funcs/dict.ts +207 -0
  281. package/src/expressions/funcs/funcs.ts +323 -0
  282. package/src/expressions/funcs/index.ts +23 -0
  283. package/src/expressions/funcs/interval.ts +76 -0
  284. package/src/expressions/funcs/math.ts +395 -0
  285. package/src/expressions/funcs/std.ts +392 -0
  286. package/src/expressions/funcs/stored.ts +62 -0
  287. package/src/expressions/funcs/strings.ts +200 -0
  288. package/src/expressions/funcs/trigonometry.ts +108 -0
  289. package/src/expressions/json.ts +367 -0
  290. package/src/expressions/parserCache.ts +32 -0
  291. package/src/expressions/simpleUnescapeString.ts +57 -0
  292. package/src/expressions/utils.ts +271 -0
  293. package/src/expressions/variable.ts +429 -0
  294. package/src/expressions/walk.ts +43 -0
  295. package/src/hooks/README.md +265 -0
  296. package/src/hooks/index.ts +28 -0
  297. package/src/hooks/useAction.ts +152 -0
  298. package/src/hooks/useDerivedFromVars.ts +187 -0
  299. package/src/hooks/useVariable.ts +157 -0
  300. package/src/index.ts +97 -0
  301. package/src/stores/createObservable.ts +64 -0
  302. package/src/types/alignment.d.ts +13 -0
  303. package/src/types/background.d.ts +71 -0
  304. package/src/types/base.d.ts +224 -0
  305. package/src/types/border.d.ts +46 -0
  306. package/src/types/componentContext.d.ts +98 -0
  307. package/src/types/container.d.ts +40 -0
  308. package/src/types/edgeInserts.d.ts +9 -0
  309. package/src/types/general.d.ts +3 -0
  310. package/src/types/image.d.ts +33 -0
  311. package/src/types/imageScale.d.ts +1 -0
  312. package/src/types/layoutParams.d.ts +27 -0
  313. package/src/types/sizes.d.ts +37 -0
  314. package/src/types/state.d.ts +19 -0
  315. package/src/types/text.d.ts +126 -0
  316. package/src/utils/applyTemplate.ts +145 -0
  317. package/src/utils/correctColor.ts +102 -0
  318. package/src/utils/escapeRegExp.ts +3 -0
  319. package/src/utils/formatDate.ts +385 -0
  320. package/src/utils/padLeft.ts +6 -0
  321. package/src/utils/uniq.ts +3 -0
  322. package/src/utils/wrapError.ts +21 -0
@@ -0,0 +1,146 @@
1
+ import React, { useState, useEffect, useMemo } from 'react';
2
+ import { View } from 'react-native';
3
+ import type { ComponentContext } from '../../types/componentContext';
4
+ import type { DivStateData, State } from '../../types/state';
5
+ import { Outer } from '../utilities/Outer';
6
+ import { useStateContext } from '../../context/StateContext';
7
+ import { useDivKitContext } from '../../context/DivKitContext';
8
+ import { wrapError } from '../../utils/wrapError';
9
+
10
+ export interface DivStateProps {
11
+ componentContext: ComponentContext<DivStateData>;
12
+ }
13
+
14
+ /**
15
+ * DivState component - renders different content based on state
16
+ * MVP implementation with basic features:
17
+ * - State selection by state_id
18
+ * - Default state
19
+ * - State switching via actions (set_state)
20
+ * - State registration in StateContext
21
+ * - State variable binding (state_id_variable)
22
+ *
23
+ * Deferred for post-MVP:
24
+ * - Transition animations (in/out/change)
25
+ * - Animation timing and interpolation
26
+ * - Clip to bounds
27
+ * - Advanced state management
28
+ * - Multiple concurrent state transitions
29
+ *
30
+ * Based on Web State.svelte
31
+ */
32
+ export function DivState({ componentContext }: DivStateProps) {
33
+ const { json } = componentContext;
34
+ const { getVariable } = useDivKitContext();
35
+ const { registerState } = useStateContext();
36
+
37
+ // Get state ID for registration
38
+ const stateId = json.div_id || json.id;
39
+
40
+ // Find default state
41
+ const defaultStateId = useMemo(() => {
42
+ if (json.default_state_id) {
43
+ return json.default_state_id;
44
+ }
45
+ // If no default, use first state
46
+ if (json.states && json.states.length > 0) {
47
+ return json.states[0].state_id;
48
+ }
49
+ return undefined;
50
+ }, [json.default_state_id, json.states]);
51
+
52
+ // State management
53
+ const [currentStateId, setCurrentStateId] = useState<string | undefined>(defaultStateId);
54
+
55
+ // Handle state_id_variable (two-way binding)
56
+ const stateVariableName = json.state_id_variable;
57
+ const stateVariable = stateVariableName ? getVariable(stateVariableName) : undefined;
58
+
59
+ // Sync with state variable
60
+ useEffect(() => {
61
+ if (stateVariable) {
62
+ // Subscribe to variable changes
63
+ const unsubscribe = stateVariable.subscribe((value: unknown) => {
64
+ if (typeof value === 'string' && value !== currentStateId) {
65
+ setCurrentStateId(value);
66
+ }
67
+ });
68
+
69
+ return unsubscribe;
70
+ }
71
+ return undefined;
72
+ }, [stateVariable, currentStateId]);
73
+
74
+ // Update variable when state changes
75
+ useEffect(() => {
76
+ if (stateVariable && currentStateId) {
77
+ const currentValue = stateVariable.getValue();
78
+ if (currentValue !== currentStateId) {
79
+ stateVariable.setValue(currentStateId);
80
+ }
81
+ }
82
+ }, [stateVariable, currentStateId]);
83
+
84
+ // Register state in context for set_state action
85
+ useEffect(() => {
86
+ if (stateId) {
87
+ const unregister = registerState(stateId, async (newStateId: string) => {
88
+ setCurrentStateId(newStateId);
89
+ return undefined;
90
+ });
91
+ return unregister;
92
+ }
93
+ return undefined;
94
+ }, [stateId, registerState]);
95
+
96
+ // Validate states
97
+ useEffect(() => {
98
+ if (!json.states || json.states.length === 0) {
99
+ componentContext.logError(wrapError(new Error('Empty "states" prop for div "state"')));
100
+ }
101
+ if (!stateId) {
102
+ componentContext.logError(wrapError(new Error('Missing "id" prop for div "state"')));
103
+ }
104
+ }, [json.states, stateId, componentContext]);
105
+
106
+ // Find current state
107
+ const currentState = useMemo((): State | undefined => {
108
+ if (!json.states) return undefined;
109
+ const found = json.states.find(s => s.state_id === currentStateId);
110
+ if (!found || !found.state_id) return undefined;
111
+ return found as State;
112
+ }, [json.states, currentStateId]);
113
+
114
+ // Create child context for current state
115
+ const childContext = useMemo(() => {
116
+ if (!currentState?.div) return undefined;
117
+
118
+ return componentContext.produceChildContext(currentState.div, {
119
+ path: currentStateId,
120
+ });
121
+ }, [currentState, currentStateId, componentContext]);
122
+
123
+ // Render current state
124
+ const renderContent = () => {
125
+ if (!currentState?.div || !childContext) {
126
+ return null;
127
+ }
128
+
129
+ // Import DivComponent dynamically to avoid circular dependency
130
+ const DivComponent = require('../DivComponent').DivComponent;
131
+
132
+ return (
133
+ <DivComponent
134
+ componentContext={childContext}
135
+ />
136
+ );
137
+ };
138
+
139
+ return (
140
+ <Outer componentContext={componentContext}>
141
+ <View>
142
+ {renderContent()}
143
+ </View>
144
+ </Outer>
145
+ );
146
+ }
@@ -0,0 +1,2 @@
1
+ export { DivState } from './DivState';
2
+ export type { DivStateProps } from './DivState';
@@ -0,0 +1,186 @@
1
+ import React, { useMemo } from 'react';
2
+ import { Text, TextStyle } from 'react-native';
3
+ import type { ComponentContext } from '../../types/componentContext';
4
+ import type { DivTextData, FontWeight, Truncate } from '../../types/text';
5
+ import { Outer } from '../utilities/Outer';
6
+ import { useDerivedFromVarsSimple } from '../../hooks/useDerivedFromVars';
7
+ import { useDivKitContext } from '../../context/DivKitContext';
8
+
9
+ export interface DivTextProps {
10
+ componentContext: ComponentContext<DivTextData>;
11
+ }
12
+
13
+ /**
14
+ * DivText component - renders text with styling
15
+ * MVP implementation with basic features:
16
+ * - Text rendering with variable substitution
17
+ * - Font styling (size, weight, color, family)
18
+ * - Text alignment (horizontal & vertical)
19
+ * - Max lines with ellipsize
20
+ * - Line height, letter spacing
21
+ * - Text decorations (underline, strikethrough)
22
+ *
23
+ * Deferred for post-MVP:
24
+ * - Text ranges (nested styling)
25
+ * - Text images
26
+ * - Text gradients
27
+ * - Text shadows
28
+ * - Auto ellipsize
29
+ * - Selectable text with custom actions
30
+ *
31
+ * Based on Web Text.svelte
32
+ */
33
+ export function DivText({ componentContext }: DivTextProps) {
34
+ const { direction } = useDivKitContext();
35
+ const { json, variables } = componentContext;
36
+
37
+ // Reactive properties - use hooks for properties that may contain variables
38
+ const text = useDerivedFromVarsSimple<string>(
39
+ json.text || '',
40
+ variables || new Map()
41
+ );
42
+
43
+ const fontSize = useDerivedFromVarsSimple<number>(
44
+ json.font_size || 12,
45
+ variables || new Map()
46
+ );
47
+
48
+ const textColor = useDerivedFromVarsSimple<string>(
49
+ json.text_color || '#000000',
50
+ variables || new Map()
51
+ );
52
+
53
+ const textAlignmentHorizontal = useDerivedFromVarsSimple(
54
+ json.text_alignment_horizontal || 'start',
55
+ variables || new Map()
56
+ );
57
+
58
+ const maxLines = useDerivedFromVarsSimple<number | undefined>(
59
+ json.max_lines,
60
+ variables || new Map()
61
+ );
62
+
63
+ // Build text style
64
+ const textStyle = useMemo((): TextStyle => {
65
+ const style: TextStyle = {};
66
+
67
+ // Font size
68
+ if (fontSize) {
69
+ style.fontSize = fontSize;
70
+ }
71
+
72
+ // Text color
73
+ if (textColor) {
74
+ style.color = textColor;
75
+ }
76
+
77
+ // Font weight
78
+ if (json.font_weight) {
79
+ const weightMap: Record<FontWeight, TextStyle['fontWeight']> = {
80
+ 'light': '300',
81
+ 'regular': '400',
82
+ 'medium': '500',
83
+ 'bold': '700'
84
+ };
85
+ style.fontWeight = weightMap[json.font_weight] || '400';
86
+ } else if (json.font_weight_value) {
87
+ // Custom weight value (100-900)
88
+ const weight = Math.max(100, Math.min(900, json.font_weight_value));
89
+ style.fontWeight = String(weight) as TextStyle['fontWeight'];
90
+ }
91
+
92
+ // Font family
93
+ if (json.font_family) {
94
+ style.fontFamily = json.font_family;
95
+ }
96
+
97
+ // Line height
98
+ if (json.line_height && fontSize) {
99
+ // DivKit line_height is in pixels, React Native expects ratio or pixels
100
+ // Convert to ratio: line_height / font_size
101
+ style.lineHeight = json.line_height;
102
+ }
103
+
104
+ // Letter spacing
105
+ if (json.letter_spacing !== undefined) {
106
+ style.letterSpacing = json.letter_spacing;
107
+ }
108
+
109
+ // Text alignment
110
+ const alignValue = textAlignmentHorizontal;
111
+ if (alignValue === 'start') {
112
+ style.textAlign = direction === 'rtl' ? 'right' : 'left';
113
+ } else if (alignValue === 'end') {
114
+ style.textAlign = direction === 'rtl' ? 'left' : 'right';
115
+ } else if (alignValue === 'center') {
116
+ style.textAlign = 'center';
117
+ } else if (alignValue === 'left' || alignValue === 'right') {
118
+ style.textAlign = alignValue;
119
+ }
120
+
121
+ // Text decoration - underline
122
+ if (json.underline === 'single') {
123
+ style.textDecorationLine = 'underline';
124
+ }
125
+
126
+ // Text decoration - strike (strikethrough)
127
+ if (json.strike === 'single') {
128
+ if (style.textDecorationLine === 'underline') {
129
+ style.textDecorationLine = 'underline line-through';
130
+ } else {
131
+ style.textDecorationLine = 'line-through';
132
+ }
133
+ }
134
+
135
+ // Font feature settings (advanced typography)
136
+ if (json.font_feature_settings) {
137
+ // React Native supports fontVariant for some features
138
+ // For full support, this may require native modules
139
+ // MVP: basic support
140
+ style.fontVariant = json.font_feature_settings.split(',').map(s => s.trim()) as any;
141
+ }
142
+
143
+ return style;
144
+ }, [
145
+ fontSize,
146
+ textColor,
147
+ textAlignmentHorizontal,
148
+ json.font_weight,
149
+ json.font_weight_value,
150
+ json.font_family,
151
+ json.line_height,
152
+ json.letter_spacing,
153
+ json.underline,
154
+ json.strike,
155
+ json.font_feature_settings,
156
+ direction
157
+ ]);
158
+
159
+ // Determine numberOfLines prop
160
+ const numberOfLines = maxLines && maxLines > 0 ? maxLines : undefined;
161
+
162
+ // Ellipsize mode
163
+ const ellipsizeMode = useMemo(() => {
164
+ const truncate = json.truncate as Truncate | undefined;
165
+ if (truncate === 'end' || numberOfLines !== undefined) {
166
+ return 'tail';
167
+ }
168
+ return undefined;
169
+ }, [json.truncate, numberOfLines]);
170
+
171
+ // Vertical alignment is handled by Outer component via alignment props
172
+ // For text, we primarily care about horizontal alignment which is in textStyle
173
+
174
+ return (
175
+ <Outer componentContext={componentContext}>
176
+ <Text
177
+ style={textStyle}
178
+ numberOfLines={numberOfLines}
179
+ ellipsizeMode={ellipsizeMode}
180
+ allowFontScaling={false} // DivKit has fixed sizes
181
+ >
182
+ {text}
183
+ </Text>
184
+ </Outer>
185
+ );
186
+ }
@@ -0,0 +1,2 @@
1
+ export { DivText } from './DivText';
2
+ export type { DivTextProps } from './DivText';
@@ -0,0 +1,239 @@
1
+ import React, { ReactNode, useMemo } from 'react';
2
+ import { View, Pressable, ViewStyle, StyleSheet } from 'react-native';
3
+ import type { ComponentContext } from '../../types/componentContext';
4
+ import type { DivBaseData } from '../../types/base';
5
+ import type { Visibility } from '../../types/base';
6
+ import type { FixedSize, MatchParentSize } from '../../types/sizes';
7
+ import type { MaybeMissing } from '../../expressions/json';
8
+ import { useDerivedFromVarsSimple } from '../../hooks/useDerivedFromVars';
9
+ import { useActionHandler, useHasActions } from '../../hooks/useAction';
10
+ import { useDivKitContext } from '../../context/DivKitContext';
11
+
12
+ export interface OuterProps<T extends DivBaseData = DivBaseData> {
13
+ componentContext: ComponentContext<T>;
14
+ children: ReactNode;
15
+ style?: ViewStyle;
16
+ }
17
+
18
+ /**
19
+ * Outer component - base wrapper for all DivKit components
20
+ * Handles visibility, sizing, padding, margins, background, borders, and actions
21
+ *
22
+ * Based on Web Outer.svelte but simplified for React Native MVP
23
+ */
24
+ export function Outer<T extends DivBaseData = DivBaseData>({
25
+ componentContext,
26
+ children,
27
+ style: customStyle
28
+ }: OuterProps<T>) {
29
+ const { direction } = useDivKitContext();
30
+ const { json, variables } = componentContext;
31
+
32
+ // Only use reactive hooks for truly dynamic properties (visibility, alpha)
33
+ // For MVP, other properties are read directly from JSON (can be enhanced later)
34
+ const visibility = useDerivedFromVarsSimple<Visibility>(
35
+ json.visibility || 'visible',
36
+ variables || new Map()
37
+ );
38
+ const alpha = useDerivedFromVarsSimple<number>(
39
+ json.alpha !== undefined ? json.alpha : 1,
40
+ variables || new Map()
41
+ );
42
+
43
+ // Extract properties directly from JSON for MVP (non-reactive)
44
+ const paddings = json.paddings;
45
+ const margins = json.margins;
46
+ const background = json.background;
47
+ const border = json.border;
48
+ const width = json.width;
49
+ const height = json.height;
50
+
51
+ // Actions - use type assertion for now (will be refined in component implementations)
52
+ const jsonAny = json as any;
53
+ const actions = jsonAny.actions || (jsonAny.action ? [jsonAny.action] : []);
54
+ const hasActions = useHasActions(actions);
55
+ const handlePress = useActionHandler(actions, { componentContext });
56
+
57
+ // Early return for gone visibility
58
+ if (visibility === 'gone') {
59
+ return null;
60
+ }
61
+
62
+ // Build styles
63
+ const containerStyle = useMemo(() => {
64
+ const styles: ViewStyle = {};
65
+
66
+ // Visibility (invisible = opacity 0, but still takes space)
67
+ if (visibility === 'invisible') {
68
+ styles.opacity = 0;
69
+ } else if (typeof alpha === 'number' && alpha !== 1) {
70
+ styles.opacity = Math.max(0, Math.min(1, alpha));
71
+ }
72
+
73
+ // Width
74
+ if (width) {
75
+ const widthVal = width as MaybeMissing<any>;
76
+ if (widthVal.type === 'fixed') {
77
+ styles.width = (widthVal as FixedSize).value;
78
+ } else if (widthVal.type === 'match_parent') {
79
+ styles.width = '100%';
80
+ styles.flexGrow = (widthVal as MatchParentSize).weight || 1;
81
+ } else if (widthVal.type === 'wrap_content') {
82
+ styles.alignSelf = 'flex-start';
83
+ // React Native default is wrap_content-like for View
84
+ }
85
+ } else {
86
+ // Default: match_parent
87
+ styles.width = '100%';
88
+ styles.flexGrow = 1;
89
+ }
90
+
91
+ // Height
92
+ if (height) {
93
+ const heightVal = height as MaybeMissing<any>;
94
+ if (heightVal.type === 'fixed') {
95
+ styles.height = (heightVal as FixedSize).value;
96
+ } else if (heightVal.type === 'match_parent') {
97
+ styles.height = '100%';
98
+ styles.flexGrow = (heightVal as MatchParentSize).weight || 1;
99
+ }
100
+ // wrap_content is default in React Native
101
+ }
102
+
103
+ // Paddings
104
+ if (paddings) {
105
+ const p = paddings as any;
106
+ if (p.top !== undefined) styles.paddingTop = p.top;
107
+ if (p.bottom !== undefined) styles.paddingBottom = p.bottom;
108
+
109
+ // Handle RTL for start/end
110
+ if (direction === 'rtl') {
111
+ if (p.start !== undefined) styles.paddingRight = p.start;
112
+ if (p.end !== undefined) styles.paddingLeft = p.end;
113
+ } else {
114
+ if (p.start !== undefined) styles.paddingLeft = p.start;
115
+ if (p.end !== undefined) styles.paddingRight = p.end;
116
+ }
117
+
118
+ // Fallback to left/right if start/end not provided
119
+ if (p.left !== undefined && p.start === undefined) {
120
+ styles.paddingLeft = p.left;
121
+ }
122
+ if (p.right !== undefined && p.end === undefined) {
123
+ styles.paddingRight = p.right;
124
+ }
125
+ }
126
+
127
+ // Margins
128
+ if (margins) {
129
+ const m = margins as any;
130
+ if (m.top !== undefined) styles.marginTop = m.top;
131
+ if (m.bottom !== undefined) styles.marginBottom = m.bottom;
132
+
133
+ // Handle RTL for start/end
134
+ if (direction === 'rtl') {
135
+ if (m.start !== undefined) styles.marginRight = m.start;
136
+ if (m.end !== undefined) styles.marginLeft = m.end;
137
+ } else {
138
+ if (m.start !== undefined) styles.marginLeft = m.start;
139
+ if (m.end !== undefined) styles.marginRight = m.end;
140
+ }
141
+
142
+ // Fallback to left/right
143
+ if (m.left !== undefined && m.start === undefined) {
144
+ styles.marginLeft = m.left;
145
+ }
146
+ if (m.right !== undefined && m.end === undefined) {
147
+ styles.marginRight = m.right;
148
+ }
149
+ }
150
+
151
+ // Background (MVP: only solid colors)
152
+ if (background && Array.isArray(background)) {
153
+ const bg = background as any[];
154
+ const solidBg = bg.find((b: any) => b?.type === 'solid');
155
+ if (solidBg && solidBg.color) {
156
+ styles.backgroundColor = solidBg.color;
157
+ }
158
+ }
159
+
160
+ // Border
161
+ if (border) {
162
+ const b = border as any;
163
+ if (b.stroke) {
164
+ const strokeWidth = b.stroke.width || 1;
165
+ const strokeColor = b.stroke.color || '#000000';
166
+ styles.borderWidth = strokeWidth;
167
+ styles.borderColor = strokeColor;
168
+ styles.borderStyle = b.stroke.style?.type === 'dashed' ? 'dashed' : 'solid';
169
+ }
170
+
171
+ // Border radius
172
+ if (b.corner_radius !== undefined) {
173
+ styles.borderRadius = b.corner_radius;
174
+ } else if (b.corners_radius) {
175
+ // React Native supports individual corners
176
+ const corners = b.corners_radius;
177
+ if (corners['top-left'] !== undefined) {
178
+ styles.borderTopLeftRadius = corners['top-left'];
179
+ }
180
+ if (corners['top-right'] !== undefined) {
181
+ styles.borderTopRightRadius = corners['top-right'];
182
+ }
183
+ if (corners['bottom-left'] !== undefined) {
184
+ styles.borderBottomLeftRadius = corners['bottom-left'];
185
+ }
186
+ if (corners['bottom-right'] !== undefined) {
187
+ styles.borderBottomRightRadius = corners['bottom-right'];
188
+ }
189
+ }
190
+
191
+ // Shadow (box-shadow equivalent)
192
+ if (b.has_shadow) {
193
+ const shadow = b.shadow;
194
+ if (shadow) {
195
+ styles.shadowColor = shadow.color || '#000000';
196
+ styles.shadowOffset = {
197
+ width: shadow.offset?.x?.value || 0,
198
+ height: shadow.offset?.y?.value || 2
199
+ };
200
+ styles.shadowOpacity = shadow.alpha !== undefined ? shadow.alpha : 0.18;
201
+ styles.shadowRadius = shadow.blur || 2;
202
+ // Android elevation
203
+ styles.elevation = 3;
204
+ } else {
205
+ // Default shadow
206
+ styles.shadowColor = '#000000';
207
+ styles.shadowOffset = { width: 0, height: 1 };
208
+ styles.shadowOpacity = 0.18;
209
+ styles.shadowRadius = 2;
210
+ styles.elevation = 2;
211
+ }
212
+ }
213
+ }
214
+
215
+ return styles;
216
+ }, [visibility, alpha, width, height, paddings, margins, background, border, direction]);
217
+
218
+ const finalStyle = useMemo(() => {
219
+ return StyleSheet.flatten([containerStyle, customStyle]);
220
+ }, [containerStyle, customStyle]);
221
+
222
+ // Render with or without Pressable based on actions
223
+ if (hasActions) {
224
+ return (
225
+ <Pressable
226
+ onPress={handlePress}
227
+ style={finalStyle}
228
+ >
229
+ {children}
230
+ </Pressable>
231
+ );
232
+ }
233
+
234
+ return (
235
+ <View style={finalStyle}>
236
+ {children}
237
+ </View>
238
+ );
239
+ }