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.
- package/LICENSE +176 -0
- package/README.md +340 -0
- package/dist/DivKit.d.ts +68 -0
- package/dist/DivKit.d.ts.map +1 -0
- package/dist/DivKit.js +400 -0
- package/dist/DivKit.js.map +1 -0
- package/dist/actions/array.d.ts +8 -0
- package/dist/actions/array.d.ts.map +1 -0
- package/dist/actions/array.js +139 -0
- package/dist/actions/array.js.map +1 -0
- package/dist/actions/copyToClipboard.d.ts +22 -0
- package/dist/actions/copyToClipboard.d.ts.map +1 -0
- package/dist/actions/copyToClipboard.js +63 -0
- package/dist/actions/copyToClipboard.js.map +1 -0
- package/dist/actions/dict.d.ts +6 -0
- package/dist/actions/dict.d.ts.map +1 -0
- package/dist/actions/dict.js +58 -0
- package/dist/actions/dict.js.map +1 -0
- package/dist/actions/index.d.ts +11 -0
- package/dist/actions/index.d.ts.map +1 -0
- package/dist/actions/index.js +11 -0
- package/dist/actions/index.js.map +1 -0
- package/dist/actions/updateStructure.d.ts +6 -0
- package/dist/actions/updateStructure.d.ts.map +1 -0
- package/dist/actions/updateStructure.js +116 -0
- package/dist/actions/updateStructure.js.map +1 -0
- package/dist/components/DivComponent.d.ts +29 -0
- package/dist/components/DivComponent.d.ts.map +1 -0
- package/dist/components/DivComponent.js +62 -0
- package/dist/components/DivComponent.js.map +1 -0
- package/dist/components/container/DivContainer.d.ts +26 -0
- package/dist/components/container/DivContainer.d.ts.map +1 -0
- package/dist/components/container/DivContainer.js +172 -0
- package/dist/components/container/DivContainer.js.map +1 -0
- package/dist/components/container/index.d.ts +3 -0
- package/dist/components/container/index.d.ts.map +1 -0
- package/dist/components/container/index.js +2 -0
- package/dist/components/container/index.js.map +1 -0
- package/dist/components/image/DivImage.d.ts +29 -0
- package/dist/components/image/DivImage.d.ts.map +1 -0
- package/dist/components/image/DivImage.js +122 -0
- package/dist/components/image/DivImage.js.map +1 -0
- package/dist/components/image/index.d.ts +3 -0
- package/dist/components/image/index.d.ts.map +1 -0
- package/dist/components/image/index.js +2 -0
- package/dist/components/image/index.js.map +1 -0
- package/dist/components/index.d.ts +14 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +11 -0
- package/dist/components/index.js.map +1 -0
- package/dist/components/state/DivState.d.ts +26 -0
- package/dist/components/state/DivState.d.ts.map +1 -0
- package/dist/components/state/DivState.js +121 -0
- package/dist/components/state/DivState.js.map +1 -0
- package/dist/components/state/index.d.ts +3 -0
- package/dist/components/state/index.d.ts.map +1 -0
- package/dist/components/state/index.js +2 -0
- package/dist/components/state/index.js.map +1 -0
- package/dist/components/text/DivText.d.ts +28 -0
- package/dist/components/text/DivText.d.ts.map +1 -0
- package/dist/components/text/DivText.js +143 -0
- package/dist/components/text/DivText.js.map +1 -0
- package/dist/components/text/index.d.ts +3 -0
- package/dist/components/text/index.d.ts.map +1 -0
- package/dist/components/text/index.js +2 -0
- package/dist/components/text/index.js.map +1 -0
- package/dist/components/utilities/Outer.d.ts +17 -0
- package/dist/components/utilities/Outer.d.ts.map +1 -0
- package/dist/components/utilities/Outer.js +210 -0
- package/dist/components/utilities/Outer.js.map +1 -0
- package/dist/components/utilities/Unknown.d.ts +11 -0
- package/dist/components/utilities/Unknown.d.ts.map +1 -0
- package/dist/components/utilities/Unknown.js +50 -0
- package/dist/components/utilities/Unknown.js.map +1 -0
- package/dist/components/utilities/index.d.ts +5 -0
- package/dist/components/utilities/index.d.ts.map +1 -0
- package/dist/components/utilities/index.js +3 -0
- package/dist/components/utilities/index.js.map +1 -0
- package/dist/context/ActionContext.d.ts +25 -0
- package/dist/context/ActionContext.d.ts.map +1 -0
- package/dist/context/ActionContext.js +20 -0
- package/dist/context/ActionContext.js.map +1 -0
- package/dist/context/DivKitContext.d.ts +33 -0
- package/dist/context/DivKitContext.d.ts.map +1 -0
- package/dist/context/DivKitContext.js +14 -0
- package/dist/context/DivKitContext.js.map +1 -0
- package/dist/context/EnabledContext.d.ts +31 -0
- package/dist/context/EnabledContext.d.ts.map +1 -0
- package/dist/context/EnabledContext.js +31 -0
- package/dist/context/EnabledContext.js.map +1 -0
- package/dist/context/StateContext.d.ts +57 -0
- package/dist/context/StateContext.d.ts.map +1 -0
- package/dist/context/StateContext.js +20 -0
- package/dist/context/StateContext.js.map +1 -0
- package/dist/context/index.d.ts +9 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/context/index.js +9 -0
- package/dist/context/index.js.map +1 -0
- package/dist/expressions/bigint.d.ts +8 -0
- package/dist/expressions/bigint.d.ts.map +1 -0
- package/dist/expressions/bigint.js +31 -0
- package/dist/expressions/bigint.js.map +1 -0
- package/dist/expressions/const.d.ts +15 -0
- package/dist/expressions/const.d.ts.map +1 -0
- package/dist/expressions/const.js +15 -0
- package/dist/expressions/const.js.map +1 -0
- package/dist/expressions/eval.d.ts +77 -0
- package/dist/expressions/eval.d.ts.map +1 -0
- package/dist/expressions/eval.js +459 -0
- package/dist/expressions/eval.js.map +1 -0
- package/dist/expressions/expressions.d.ts +7 -0
- package/dist/expressions/expressions.d.ts.map +1 -0
- package/dist/expressions/expressions.js +3191 -0
- package/dist/expressions/expressions.js.map +1 -0
- package/dist/expressions/funcs/array.d.ts +2 -0
- package/dist/expressions/funcs/array.d.ts.map +1 -0
- package/dist/expressions/funcs/array.js +381 -0
- package/dist/expressions/funcs/array.js.map +1 -0
- package/dist/expressions/funcs/colors.d.ts +2 -0
- package/dist/expressions/funcs/colors.d.ts.map +1 -0
- package/dist/expressions/funcs/colors.js +75 -0
- package/dist/expressions/funcs/colors.js.map +1 -0
- package/dist/expressions/funcs/customFuncs.d.ts +8 -0
- package/dist/expressions/funcs/customFuncs.d.ts.map +1 -0
- package/dist/expressions/funcs/customFuncs.js +114 -0
- package/dist/expressions/funcs/customFuncs.js.map +1 -0
- package/dist/expressions/funcs/datetime.d.ts +2 -0
- package/dist/expressions/funcs/datetime.d.ts.map +1 -0
- package/dist/expressions/funcs/datetime.js +182 -0
- package/dist/expressions/funcs/datetime.js.map +1 -0
- package/dist/expressions/funcs/dict.d.ts +2 -0
- package/dist/expressions/funcs/dict.d.ts.map +1 -0
- package/dist/expressions/funcs/dict.js +170 -0
- package/dist/expressions/funcs/dict.js.map +1 -0
- package/dist/expressions/funcs/funcs.d.ts +80 -0
- package/dist/expressions/funcs/funcs.d.ts.map +1 -0
- package/dist/expressions/funcs/funcs.js +146 -0
- package/dist/expressions/funcs/funcs.js.map +1 -0
- package/dist/expressions/funcs/index.d.ts +2 -0
- package/dist/expressions/funcs/index.d.ts.map +1 -0
- package/dist/expressions/funcs/index.js +23 -0
- package/dist/expressions/funcs/index.js.map +1 -0
- package/dist/expressions/funcs/interval.d.ts +2 -0
- package/dist/expressions/funcs/interval.d.ts.map +1 -0
- package/dist/expressions/funcs/interval.js +61 -0
- package/dist/expressions/funcs/interval.js.map +1 -0
- package/dist/expressions/funcs/math.d.ts +2 -0
- package/dist/expressions/funcs/math.d.ts.map +1 -0
- package/dist/expressions/funcs/math.js +324 -0
- package/dist/expressions/funcs/math.js.map +1 -0
- package/dist/expressions/funcs/std.d.ts +2 -0
- package/dist/expressions/funcs/std.d.ts.map +1 -0
- package/dist/expressions/funcs/std.js +293 -0
- package/dist/expressions/funcs/std.js.map +1 -0
- package/dist/expressions/funcs/stored.d.ts +4 -0
- package/dist/expressions/funcs/stored.d.ts.map +1 -0
- package/dist/expressions/funcs/stored.js +62 -0
- package/dist/expressions/funcs/stored.js.map +1 -0
- package/dist/expressions/funcs/strings.d.ts +2 -0
- package/dist/expressions/funcs/strings.d.ts.map +1 -0
- package/dist/expressions/funcs/strings.js +158 -0
- package/dist/expressions/funcs/strings.js.map +1 -0
- package/dist/expressions/funcs/trigonometry.d.ts +2 -0
- package/dist/expressions/funcs/trigonometry.d.ts.map +1 -0
- package/dist/expressions/funcs/trigonometry.js +92 -0
- package/dist/expressions/funcs/trigonometry.js.map +1 -0
- package/dist/expressions/json.d.ts +18 -0
- package/dist/expressions/json.d.ts.map +1 -0
- package/dist/expressions/json.js +271 -0
- package/dist/expressions/json.js.map +1 -0
- package/dist/expressions/parserCache.d.ts +4 -0
- package/dist/expressions/parserCache.d.ts.map +1 -0
- package/dist/expressions/parserCache.js +23 -0
- package/dist/expressions/parserCache.js.map +1 -0
- package/dist/expressions/simpleUnescapeString.d.ts +2 -0
- package/dist/expressions/simpleUnescapeString.d.ts.map +1 -0
- package/dist/expressions/simpleUnescapeString.js +61 -0
- package/dist/expressions/simpleUnescapeString.js.map +1 -0
- package/dist/expressions/utils.d.ts +29 -0
- package/dist/expressions/utils.d.ts.map +1 -0
- package/dist/expressions/utils.js +236 -0
- package/dist/expressions/utils.js.map +1 -0
- package/dist/expressions/variable.d.ts +82 -0
- package/dist/expressions/variable.d.ts.map +1 -0
- package/dist/expressions/variable.js +337 -0
- package/dist/expressions/variable.js.map +1 -0
- package/dist/expressions/walk.d.ts +7 -0
- package/dist/expressions/walk.d.ts.map +1 -0
- package/dist/expressions/walk.js +39 -0
- package/dist/expressions/walk.js.map +1 -0
- package/dist/hooks/index.d.ts +8 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +11 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/useAction.d.ts +102 -0
- package/dist/hooks/useAction.d.ts.map +1 -0
- package/dist/hooks/useAction.js +116 -0
- package/dist/hooks/useAction.js.map +1 -0
- package/dist/hooks/useDerivedFromVars.d.ts +72 -0
- package/dist/hooks/useDerivedFromVars.d.ts.map +1 -0
- package/dist/hooks/useDerivedFromVars.js +100 -0
- package/dist/hooks/useDerivedFromVars.js.map +1 -0
- package/dist/hooks/useVariable.d.ts +86 -0
- package/dist/hooks/useVariable.d.ts.map +1 -0
- package/dist/hooks/useVariable.js +130 -0
- package/dist/hooks/useVariable.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/stores/createObservable.d.ts +38 -0
- package/dist/stores/createObservable.d.ts.map +1 -0
- package/dist/stores/createObservable.js +49 -0
- package/dist/stores/createObservable.js.map +1 -0
- package/dist/utils/applyTemplate.d.ts +8 -0
- package/dist/utils/applyTemplate.d.ts.map +1 -0
- package/dist/utils/applyTemplate.js +94 -0
- package/dist/utils/applyTemplate.js.map +1 -0
- package/dist/utils/correctColor.d.ts +18 -0
- package/dist/utils/correctColor.d.ts.map +1 -0
- package/dist/utils/correctColor.js +79 -0
- package/dist/utils/correctColor.js.map +1 -0
- package/dist/utils/escapeRegExp.d.ts +2 -0
- package/dist/utils/escapeRegExp.d.ts.map +1 -0
- package/dist/utils/escapeRegExp.js +4 -0
- package/dist/utils/escapeRegExp.js.map +1 -0
- package/dist/utils/formatDate.d.ts +6 -0
- package/dist/utils/formatDate.d.ts.map +1 -0
- package/dist/utils/formatDate.js +325 -0
- package/dist/utils/formatDate.js.map +1 -0
- package/dist/utils/padLeft.d.ts +2 -0
- package/dist/utils/padLeft.d.ts.map +1 -0
- package/dist/utils/padLeft.js +7 -0
- package/dist/utils/padLeft.js.map +1 -0
- package/dist/utils/uniq.d.ts +2 -0
- package/dist/utils/uniq.d.ts.map +1 -0
- package/dist/utils/uniq.js +4 -0
- package/dist/utils/uniq.js.map +1 -0
- package/dist/utils/wrapError.d.ts +10 -0
- package/dist/utils/wrapError.d.ts.map +1 -0
- package/dist/utils/wrapError.js +9 -0
- package/dist/utils/wrapError.js.map +1 -0
- package/package.json +58 -0
- package/src/DivKit.tsx +542 -0
- package/src/actions/array.ts +170 -0
- package/src/actions/copyToClipboard.ts +82 -0
- package/src/actions/dict.ts +71 -0
- package/src/actions/index.ts +11 -0
- package/src/actions/updateStructure.ts +134 -0
- package/src/components/DivComponent.tsx +75 -0
- package/src/components/README.md +230 -0
- package/src/components/container/DivContainer.tsx +222 -0
- package/src/components/container/index.ts +2 -0
- package/src/components/image/DivImage.tsx +172 -0
- package/src/components/image/index.ts +2 -0
- package/src/components/index.ts +20 -0
- package/src/components/state/DivState.tsx +146 -0
- package/src/components/state/index.ts +2 -0
- package/src/components/text/DivText.tsx +186 -0
- package/src/components/text/index.ts +2 -0
- package/src/components/utilities/Outer.tsx +239 -0
- package/src/components/utilities/README.md +175 -0
- package/src/components/utilities/Unknown.tsx +60 -0
- package/src/components/utilities/index.ts +4 -0
- package/src/context/ActionContext.tsx +37 -0
- package/src/context/DivKitContext.tsx +54 -0
- package/src/context/EnabledContext.tsx +50 -0
- package/src/context/StateContext.tsx +75 -0
- package/src/context/index.ts +33 -0
- package/src/expressions/ast.d.ts +101 -0
- package/src/expressions/bigint.ts +38 -0
- package/src/expressions/const.ts +16 -0
- package/src/expressions/eval.ts +669 -0
- package/src/expressions/expressions.peggy +235 -0
- package/src/expressions/expressions.ts +2854 -0
- package/src/expressions/funcs/array.ts +412 -0
- package/src/expressions/funcs/colors.ts +100 -0
- package/src/expressions/funcs/customFuncs.ts +139 -0
- package/src/expressions/funcs/datetime.ts +232 -0
- package/src/expressions/funcs/dict.ts +207 -0
- package/src/expressions/funcs/funcs.ts +323 -0
- package/src/expressions/funcs/index.ts +23 -0
- package/src/expressions/funcs/interval.ts +76 -0
- package/src/expressions/funcs/math.ts +395 -0
- package/src/expressions/funcs/std.ts +392 -0
- package/src/expressions/funcs/stored.ts +62 -0
- package/src/expressions/funcs/strings.ts +200 -0
- package/src/expressions/funcs/trigonometry.ts +108 -0
- package/src/expressions/json.ts +367 -0
- package/src/expressions/parserCache.ts +32 -0
- package/src/expressions/simpleUnescapeString.ts +57 -0
- package/src/expressions/utils.ts +271 -0
- package/src/expressions/variable.ts +429 -0
- package/src/expressions/walk.ts +43 -0
- package/src/hooks/README.md +265 -0
- package/src/hooks/index.ts +28 -0
- package/src/hooks/useAction.ts +152 -0
- package/src/hooks/useDerivedFromVars.ts +187 -0
- package/src/hooks/useVariable.ts +157 -0
- package/src/index.ts +97 -0
- package/src/stores/createObservable.ts +64 -0
- package/src/types/alignment.d.ts +13 -0
- package/src/types/background.d.ts +71 -0
- package/src/types/base.d.ts +224 -0
- package/src/types/border.d.ts +46 -0
- package/src/types/componentContext.d.ts +98 -0
- package/src/types/container.d.ts +40 -0
- package/src/types/edgeInserts.d.ts +9 -0
- package/src/types/general.d.ts +3 -0
- package/src/types/image.d.ts +33 -0
- package/src/types/imageScale.d.ts +1 -0
- package/src/types/layoutParams.d.ts +27 -0
- package/src/types/sizes.d.ts +37 -0
- package/src/types/state.d.ts +19 -0
- package/src/types/text.d.ts +126 -0
- package/src/utils/applyTemplate.ts +145 -0
- package/src/utils/correctColor.ts +102 -0
- package/src/utils/escapeRegExp.ts +3 -0
- package/src/utils/formatDate.ts +385 -0
- package/src/utils/padLeft.ts +6 -0
- package/src/utils/uniq.ts +3 -0
- 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,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,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
|
+
}
|