flowboard-react 0.2.0 → 0.4.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 (45) hide show
  1. package/README.md +15 -2
  2. package/lib/module/Flowboard.js +50 -11
  3. package/lib/module/Flowboard.js.map +1 -1
  4. package/lib/module/FlowboardProvider.js +18 -14
  5. package/lib/module/FlowboardProvider.js.map +1 -1
  6. package/lib/module/components/FlowboardFlow.js +70 -37
  7. package/lib/module/components/FlowboardFlow.js.map +1 -1
  8. package/lib/module/components/FlowboardRenderer.js +622 -105
  9. package/lib/module/components/FlowboardRenderer.js.map +1 -1
  10. package/lib/module/core/assetPreloader.js +20 -18
  11. package/lib/module/core/assetPreloader.js.map +1 -1
  12. package/lib/module/core/resolverService.js +31 -1
  13. package/lib/module/core/resolverService.js.map +1 -1
  14. package/lib/module/index.js +1 -0
  15. package/lib/module/index.js.map +1 -1
  16. package/lib/module/types/react-native-peers.d.js +2 -0
  17. package/lib/module/types/react-native-peers.d.js.map +1 -0
  18. package/lib/module/utils/flowboardUtils.js +20 -14
  19. package/lib/module/utils/flowboardUtils.js.map +1 -1
  20. package/lib/typescript/src/Flowboard.d.ts +5 -1
  21. package/lib/typescript/src/Flowboard.d.ts.map +1 -1
  22. package/lib/typescript/src/FlowboardProvider.d.ts.map +1 -1
  23. package/lib/typescript/src/components/FlowboardFlow.d.ts +1 -2
  24. package/lib/typescript/src/components/FlowboardFlow.d.ts.map +1 -1
  25. package/lib/typescript/src/components/FlowboardRenderer.d.ts.map +1 -1
  26. package/lib/typescript/src/core/assetPreloader.d.ts.map +1 -1
  27. package/lib/typescript/src/core/resolverService.d.ts +6 -0
  28. package/lib/typescript/src/core/resolverService.d.ts.map +1 -1
  29. package/lib/typescript/src/index.d.ts +3 -1
  30. package/lib/typescript/src/index.d.ts.map +1 -1
  31. package/lib/typescript/src/types/flowboard.d.ts +4 -0
  32. package/lib/typescript/src/types/flowboard.d.ts.map +1 -1
  33. package/lib/typescript/src/utils/flowboardUtils.d.ts +6 -0
  34. package/lib/typescript/src/utils/flowboardUtils.d.ts.map +1 -1
  35. package/package.json +4 -3
  36. package/src/Flowboard.ts +86 -16
  37. package/src/FlowboardProvider.tsx +20 -16
  38. package/src/components/FlowboardFlow.tsx +89 -49
  39. package/src/components/FlowboardRenderer.tsx +771 -98
  40. package/src/core/assetPreloader.ts +21 -32
  41. package/src/core/resolverService.ts +42 -2
  42. package/src/index.tsx +3 -0
  43. package/src/types/flowboard.ts +5 -0
  44. package/src/types/react-native-peers.d.ts +106 -0
  45. package/src/utils/flowboardUtils.ts +28 -14
@@ -9,17 +9,21 @@ import LottieView from 'lottie-react-native';
9
9
  import MaskInput from 'react-native-mask-input';
10
10
  import Svg, { Circle, G, Line, Polygon, Text as SvgText } from 'react-native-svg';
11
11
  import PagerView from 'react-native-pager-view';
12
- import { insetsToStyle, parseAlignment, parseColor, parseCrossAlignment, parseDimension, parseFlexAlignment, parseFontWeight, parseInsets, parseResizeMode, parseTextAlign, parseTextDecoration, resolveNumericValue, resolveText } from "../utils/flowboardUtils.js";
12
+ import { insetsToStyle, insetsToMarginStyle, parseAlignment, parseColor, parseCrossAlignment, parseDimension, parseFlexAlignment, parseFontWeight, parseInsets, parseResizeMode, parseTextAlign, parseTextDecoration, resolveNumericValue, resolveText } from "../utils/flowboardUtils.js";
13
13
  import { resolveFontAwesomeIcon, FontAwesome6 } from "../core/fontAwesome.js";
14
14
  import { useSliderRegistry } from "./widgets/sliderRegistry.js";
15
15
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
16
16
  const styles = StyleSheet.create({
17
17
  root: {
18
- flex: 1
18
+ flex: 1,
19
+ backgroundColor: '#ffffff'
19
20
  },
20
21
  background: {
21
22
  ...StyleSheet.absoluteFillObject
22
23
  },
24
+ whiteBg: {
25
+ backgroundColor: '#ffffff'
26
+ },
23
27
  safeArea: {
24
28
  flex: 1
25
29
  },
@@ -48,26 +52,32 @@ export default function FlowboardRenderer(props) {
48
52
  const scrollable = screenData.scrollable === true;
49
53
  const safeArea = screenData.safeArea !== false;
50
54
  const padding = parseInsets(screenData.padding);
51
- const paddingStyle = insetsToStyle(padding);
55
+ const contentPaddingStyle = insetsToStyle(padding);
56
+ const progressPaddingStyle = {
57
+ paddingTop: padding.top,
58
+ paddingRight: padding.right,
59
+ paddingLeft: padding.left
60
+ };
61
+ const rootCrossAxisAlignment = screenData.crossAxisAlignment ?? screenData.crossAxis;
52
62
  const content = /*#__PURE__*/_jsxs(View, {
53
63
  style: {
54
64
  flex: 1
55
65
  },
56
66
  children: [showProgress && /*#__PURE__*/_jsx(View, {
57
- style: [paddingStyle, {
67
+ style: [progressPaddingStyle, {
58
68
  paddingBottom: 8
59
69
  }],
60
70
  children: renderProgressBar(currentIndex, totalScreens, progressColor, progressThickness, progressRadius, progressStyle)
61
71
  }), scrollable ? /*#__PURE__*/_jsx(ScrollView, {
62
72
  contentContainerStyle: {
63
- ...paddingStyle,
73
+ ...contentPaddingStyle,
64
74
  paddingTop: showProgress ? 0 : padding.top
65
75
  },
66
76
  children: /*#__PURE__*/_jsx(View, {
67
77
  style: {
68
78
  flexGrow: 1,
69
79
  justifyContent: parseFlexAlignment(screenData.mainAxisAlignment),
70
- alignItems: parseCrossAlignment(screenData.crossAxisAlignment)
80
+ alignItems: parseRootCrossAlignment(rootCrossAxisAlignment)
71
81
  },
72
82
  children: childrenData.map((child, index) => buildWidget(child, {
73
83
  onAction,
@@ -82,10 +92,10 @@ export default function FlowboardRenderer(props) {
82
92
  }) : /*#__PURE__*/_jsx(View, {
83
93
  style: {
84
94
  flex: 1,
85
- ...paddingStyle,
95
+ ...contentPaddingStyle,
86
96
  paddingTop: showProgress ? 0 : padding.top,
87
97
  justifyContent: parseFlexAlignment(screenData.mainAxisAlignment),
88
- alignItems: parseCrossAlignment(screenData.crossAxisAlignment)
98
+ alignItems: parseRootCrossAlignment(rootCrossAxisAlignment)
89
99
  },
90
100
  children: childrenData.map((child, index) => buildWidget(child, {
91
101
  onAction,
@@ -102,15 +112,18 @@ export default function FlowboardRenderer(props) {
102
112
  style: styles.root,
103
113
  children: [renderBackground(backgroundData, bgColorCode), safeArea ? /*#__PURE__*/_jsx(SafeAreaView, {
104
114
  style: styles.safeArea,
115
+ mode: "padding",
116
+ edges: ['top', 'right', 'bottom', 'left'],
105
117
  children: content
106
118
  }) : content]
107
119
  });
108
120
  }
109
121
  function renderBackground(bgData, legacyColorCode) {
110
122
  if (!bgData) {
123
+ const fallbackBackground = parseColor(legacyColorCode);
111
124
  return /*#__PURE__*/_jsx(View, {
112
125
  style: [styles.background, {
113
- backgroundColor: parseColor(legacyColorCode)
126
+ backgroundColor: fallbackBackground === 'transparent' ? 'rgba(255,255,255,1)' : fallbackBackground
114
127
  }]
115
128
  });
116
129
  }
@@ -136,25 +149,27 @@ function renderBackground(bgData, legacyColorCode) {
136
149
  case 'image':
137
150
  {
138
151
  const url = bgData.url;
139
- if (!url) return null;
140
- return /*#__PURE__*/_jsx(Image, {
141
- source: {
142
- uri: url
143
- },
144
- style: styles.background,
145
- resizeMode: fit
152
+ if (!hasImageUri(url)) {
153
+ return /*#__PURE__*/_jsx(View, {
154
+ style: [styles.background, styles.whiteBg]
155
+ });
156
+ }
157
+ return /*#__PURE__*/_jsx(BackgroundImageLayer, {
158
+ uri: String(url),
159
+ fit: fit
146
160
  });
147
161
  }
148
162
  case 'asset':
149
163
  {
150
164
  const path = bgData.path;
151
- if (!path) return null;
152
- return /*#__PURE__*/_jsx(Image, {
153
- source: {
154
- uri: path
155
- },
156
- style: styles.background,
157
- resizeMode: fit
165
+ if (!hasImageUri(path)) {
166
+ return /*#__PURE__*/_jsx(View, {
167
+ style: [styles.background, styles.whiteBg]
168
+ });
169
+ }
170
+ return /*#__PURE__*/_jsx(BackgroundImageLayer, {
171
+ uri: String(path),
172
+ fit: fit
158
173
  });
159
174
  }
160
175
  case 'color':
@@ -175,6 +190,47 @@ function renderBackground(bgData, legacyColorCode) {
175
190
  }]
176
191
  });
177
192
  }
193
+ function BackgroundImageLayer({
194
+ uri,
195
+ fit
196
+ }) {
197
+ const [hasError, setHasError] = React.useState(false);
198
+ const opacity = React.useRef(new Animated.Value(0)).current;
199
+ React.useEffect(() => {
200
+ opacity.setValue(0);
201
+ }, [uri, opacity]);
202
+ const fadeIn = React.useCallback(() => {
203
+ Animated.timing(opacity, {
204
+ toValue: 1,
205
+ duration: 160,
206
+ useNativeDriver: true
207
+ }).start();
208
+ }, [opacity]);
209
+ if (hasError) {
210
+ return /*#__PURE__*/_jsx(View, {
211
+ style: [styles.background, styles.whiteBg]
212
+ });
213
+ }
214
+ return /*#__PURE__*/_jsx(Animated.Image, {
215
+ source: {
216
+ uri
217
+ },
218
+ style: [styles.background, {
219
+ opacity
220
+ }],
221
+ resizeMode: fit,
222
+ onLoad: fadeIn,
223
+ onError: () => setHasError(true)
224
+ });
225
+ }
226
+ function hasImageUri(value) {
227
+ if (typeof value !== 'string') return false;
228
+ return value.trim().length > 0;
229
+ }
230
+ function isNetworkLikeUri(value) {
231
+ if (!hasImageUri(value)) return false;
232
+ return /^(https?:|file:|content:|data:)/i.test(value.trim());
233
+ }
178
234
  function renderProgressBar(currentIndex, totalScreens, color, thickness, radius, style) {
179
235
  if (style === 'dotted') {
180
236
  return /*#__PURE__*/_jsx(View, {
@@ -282,8 +338,8 @@ function buildWidget(json, params) {
282
338
  case 'container':
283
339
  {
284
340
  const gradient = parseGradient(props.background);
285
- const width = normalizeDimension(parseDimension(props.width));
286
- const height = normalizeDimension(parseDimension(props.height));
341
+ const width = normalizeDimension(parseLayoutDimension(props.width));
342
+ const height = normalizeDimension(parseLayoutDimension(props.height));
287
343
  const borderRadius = props.borderRadius ? Number(props.borderRadius) : undefined;
288
344
  const borderColor = props.borderColor ? parseColor(props.borderColor) : undefined;
289
345
  const borderWidth = borderColor ? 1 : 0;
@@ -398,8 +454,8 @@ function buildWidget(json, params) {
398
454
  {
399
455
  const gradient = parseGradient(props.background);
400
456
  const borderRadius = Number(props.borderRadius ?? 10);
401
- const width = normalizeDimension(parseDimension(props.width)) ?? '100%';
402
- const height = normalizeDimension(parseDimension(props.height)) ?? 50;
457
+ const width = normalizeDimension(parseLayoutDimension(props.width)) ?? '100%';
458
+ const height = normalizeDimension(parseLayoutDimension(props.height)) ?? 50;
403
459
  const label = props.label ?? 'Button';
404
460
  const textStyle = getTextStyle({
405
461
  ...props,
@@ -469,8 +525,8 @@ function buildWidget(json, params) {
469
525
  {
470
526
  node = /*#__PURE__*/_jsx(View, {
471
527
  style: {
472
- height: normalizeDimension(parseDimension(props.height)),
473
- width: normalizeDimension(parseDimension(props.width))
528
+ height: normalizeDimension(parseLayoutDimension(props.height)),
529
+ width: normalizeDimension(parseLayoutDimension(props.width))
474
530
  }
475
531
  });
476
532
  break;
@@ -481,15 +537,13 @@ function buildWidget(json, params) {
481
537
  ...params,
482
538
  allowFlexExpansion: true
483
539
  }) : null;
484
- if (params.allowFlexExpansion) {
485
- return /*#__PURE__*/_jsx(View, {
486
- style: {
487
- flex: 1
488
- },
489
- children: expandedChild
490
- });
491
- }
492
- return expandedChild;
540
+ node = params.allowFlexExpansion ? /*#__PURE__*/_jsx(View, {
541
+ style: {
542
+ flex: 1
543
+ },
544
+ children: expandedChild
545
+ }) : expandedChild;
546
+ break;
493
547
  }
494
548
  case 'padding':
495
549
  {
@@ -527,7 +581,48 @@ function buildWidget(json, params) {
527
581
  params.onInputChange(id, value);
528
582
  }
529
583
  },
530
- onAction: params.onAction,
584
+ onAction: (actionName, payload) => {
585
+ if (actionName === 'next' && params.onInputChange && id && Object.prototype.hasOwnProperty.call(payload ?? {}, 'selectedValue')) {
586
+ params.onInputChange(id, payload?.selectedValue);
587
+ }
588
+ params.onAction(actionName, {
589
+ ...(payload ?? {}),
590
+ ...(id ? {
591
+ fieldId: id
592
+ } : {})
593
+ });
594
+ },
595
+ buildWidget: widgetJson => buildWidget(widgetJson, {
596
+ ...params,
597
+ allowFlexExpansion: true
598
+ })
599
+ });
600
+ break;
601
+ }
602
+ case 'wheel_picker':
603
+ {
604
+ node = /*#__PURE__*/_jsx(WheelPicker, {
605
+ properties: props,
606
+ options: Array.isArray(json.options) ? json.options : [],
607
+ itemTemplate: json.itemTemplate ?? props.itemTemplate,
608
+ formData: params.formData,
609
+ initialValue: params.formData[id ?? ''],
610
+ onChanged: value => {
611
+ if (params.onInputChange && id) {
612
+ params.onInputChange(id, value);
613
+ }
614
+ },
615
+ onAction: (actionName, payload) => {
616
+ if (actionName === 'next' && params.onInputChange && id && Object.prototype.hasOwnProperty.call(payload ?? {}, 'selectedValue')) {
617
+ params.onInputChange(id, payload?.selectedValue);
618
+ }
619
+ params.onAction(actionName, {
620
+ ...(payload ?? {}),
621
+ ...(id ? {
622
+ fieldId: id
623
+ } : {})
624
+ });
625
+ },
531
626
  buildWidget: widgetJson => buildWidget(widgetJson, {
532
627
  ...params,
533
628
  allowFlexExpansion: true
@@ -538,30 +633,23 @@ function buildWidget(json, params) {
538
633
  case 'image':
539
634
  {
540
635
  const source = props.source;
541
- const width = normalizeDimension(parseDimension(props.width, true));
542
- const height = normalizeDimension(parseDimension(props.height, true));
636
+ const width = normalizeDimension(parseLayoutDimension(props.width, true));
637
+ const height = normalizeDimension(parseLayoutDimension(props.height, true));
543
638
  if (source === 'asset') {
544
- node = /*#__PURE__*/_jsx(Image, {
545
- source: {
546
- uri: props.path
547
- },
548
- style: {
549
- width,
550
- height
551
- },
552
- resizeMode: parseResizeMode(props.fit)
639
+ if (!hasImageUri(props.path)) return null;
640
+ node = /*#__PURE__*/_jsx(SduiImage, {
641
+ uri: String(props.path),
642
+ fit: parseResizeMode(props.fit),
643
+ width: width,
644
+ height: height
553
645
  });
554
646
  } else if (source === 'network') {
555
- if (!props.url) return null;
556
- node = /*#__PURE__*/_jsx(Image, {
557
- source: {
558
- uri: props.url
559
- },
560
- style: {
561
- width,
562
- height
563
- },
564
- resizeMode: parseResizeMode(props.fit)
647
+ if (!isNetworkLikeUri(props.url)) return null;
648
+ node = /*#__PURE__*/_jsx(SduiImage, {
649
+ uri: String(props.url),
650
+ fit: parseResizeMode(props.fit),
651
+ width: width,
652
+ height: height
565
653
  });
566
654
  } else {
567
655
  node = null;
@@ -571,8 +659,8 @@ function buildWidget(json, params) {
571
659
  case 'lottie':
572
660
  {
573
661
  const source = props.source;
574
- const width = normalizeDimension(parseDimension(props.width, true));
575
- const height = normalizeDimension(parseDimension(props.height, true));
662
+ const width = normalizeDimension(parseLayoutDimension(props.width, true));
663
+ const height = normalizeDimension(parseLayoutDimension(props.height, true));
576
664
  const loop = props.loop === true;
577
665
  if (source === 'asset') {
578
666
  node = /*#__PURE__*/_jsx(LottieView, {
@@ -661,7 +749,7 @@ function buildWidget(json, params) {
661
749
  autoPlay: props.autoPlay === true,
662
750
  autoPlayDelay: Number(props.autoPlayDelay ?? 3000),
663
751
  loop: props.loop === true,
664
- height: parseDimension(props.height),
752
+ height: parseLayoutDimension(props.height),
665
753
  physics: props.physics,
666
754
  children: (childrenJson ?? []).map((child, index) => /*#__PURE__*/_jsx(View, {
667
755
  style: {
@@ -751,13 +839,13 @@ function buildWidget(json, params) {
751
839
  }
752
840
  case 'positioned':
753
841
  {
754
- const left = parseDimension(props.left);
755
- const top = parseDimension(props.top);
756
- const right = parseDimension(props.right);
757
- const bottom = parseDimension(props.bottom);
758
- const width = parseDimension(props.width);
759
- const height = parseDimension(props.height);
760
- return /*#__PURE__*/_jsx(View, {
842
+ const left = parseLayoutDimension(props.left);
843
+ const top = parseLayoutDimension(props.top);
844
+ const right = parseLayoutDimension(props.right);
845
+ const bottom = parseLayoutDimension(props.bottom);
846
+ const width = parseLayoutDimension(props.width);
847
+ const height = parseLayoutDimension(props.height);
848
+ node = /*#__PURE__*/_jsx(View, {
761
849
  style: {
762
850
  position: 'absolute',
763
851
  left,
@@ -771,6 +859,7 @@ function buildWidget(json, params) {
771
859
  ...params
772
860
  }) : null
773
861
  });
862
+ break;
774
863
  }
775
864
  default:
776
865
  node = null;
@@ -780,7 +869,7 @@ function buildWidget(json, params) {
780
869
  if (marginVal !== undefined && marginVal !== null) {
781
870
  const marginInsets = parseInsets(marginVal);
782
871
  node = /*#__PURE__*/_jsx(View, {
783
- style: insetsToStyle(marginInsets),
872
+ style: insetsToMarginStyle(marginInsets),
784
873
  children: node
785
874
  });
786
875
  } else if (type !== 'container' && type !== 'padding' && props.padding) {
@@ -796,13 +885,13 @@ function buildWidget(json, params) {
796
885
  shouldExpand = true;
797
886
  }
798
887
  if (props.height !== undefined) {
799
- const heightVal = parseDimension(props.height);
888
+ const heightVal = parseLayoutDimension(props.height);
800
889
  if (heightVal === Number.POSITIVE_INFINITY) {
801
890
  shouldExpand = true;
802
891
  }
803
892
  }
804
893
  if (shouldExpand) {
805
- return /*#__PURE__*/_jsx(View, {
894
+ node = /*#__PURE__*/_jsx(View, {
806
895
  style: {
807
896
  flex: 1
808
897
  },
@@ -810,6 +899,17 @@ function buildWidget(json, params) {
810
899
  });
811
900
  }
812
901
  }
902
+ if (params.key !== undefined && params.key !== null) {
903
+ if (/*#__PURE__*/React.isValidElement(node)) {
904
+ node = /*#__PURE__*/React.cloneElement(node, {
905
+ key: params.key
906
+ });
907
+ } else {
908
+ node = /*#__PURE__*/_jsx(React.Fragment, {
909
+ children: node
910
+ }, params.key);
911
+ }
912
+ }
813
913
  return node;
814
914
  }
815
915
  function buildActionPayload(props, json) {
@@ -920,6 +1020,100 @@ function normalizeDimension(value) {
920
1020
  }
921
1021
  return value;
922
1022
  }
1023
+ function parseRootCrossAlignment(value) {
1024
+ if (!value) return 'stretch';
1025
+ return parseCrossAlignment(value);
1026
+ }
1027
+ function parseLayoutDimension(value, zeroIsNull = false) {
1028
+ if (typeof value === 'string') {
1029
+ const trimmed = value.trim();
1030
+ const lowered = trimmed.toLowerCase();
1031
+ if (lowered === 'double.infinity' || lowered === 'infinity' || lowered === '+infinity') {
1032
+ return Number.POSITIVE_INFINITY;
1033
+ }
1034
+ if (trimmed.endsWith('%')) {
1035
+ const numeric = Number(trimmed.slice(0, -1));
1036
+ if (!Number.isNaN(numeric) && Number.isFinite(numeric)) {
1037
+ if (zeroIsNull && numeric === 0) return undefined;
1038
+ return `${numeric}%`;
1039
+ }
1040
+ }
1041
+ }
1042
+ return parseDimension(value, zeroIsNull);
1043
+ }
1044
+ function SduiImage({
1045
+ uri,
1046
+ fit,
1047
+ width,
1048
+ height
1049
+ }) {
1050
+ const hasExplicitWidth = width !== undefined;
1051
+ const hasExplicitHeight = height !== undefined;
1052
+ const [aspectRatio, setAspectRatio] = React.useState(undefined);
1053
+ const [hasError, setHasError] = React.useState(false);
1054
+ const opacity = React.useRef(new Animated.Value(0)).current;
1055
+ const updateAspectRatio = (w, h) => {
1056
+ if (!w || !h) return;
1057
+ if (!Number.isFinite(w) || !Number.isFinite(h) || h <= 0) return;
1058
+ const ratio = w / h;
1059
+ if (Number.isFinite(ratio) && ratio > 0) {
1060
+ setAspectRatio(ratio);
1061
+ }
1062
+ };
1063
+ React.useEffect(() => {
1064
+ if (hasExplicitWidth && hasExplicitHeight) return;
1065
+ Image.getSize(uri, (w, h) => updateAspectRatio(w, h), () => null);
1066
+ }, [uri, hasExplicitWidth, hasExplicitHeight]);
1067
+ React.useEffect(() => {
1068
+ opacity.setValue(0);
1069
+ }, [uri, opacity]);
1070
+ if (hasError) {
1071
+ return null;
1072
+ }
1073
+ const style = {
1074
+ width,
1075
+ height
1076
+ };
1077
+ if (!hasExplicitWidth && !hasExplicitHeight) {
1078
+ style.width = '100%';
1079
+ if (aspectRatio) {
1080
+ style.aspectRatio = aspectRatio;
1081
+ } else {
1082
+ style.minHeight = 160;
1083
+ }
1084
+ } else if (hasExplicitWidth && !hasExplicitHeight) {
1085
+ if (aspectRatio) {
1086
+ style.aspectRatio = aspectRatio;
1087
+ } else {
1088
+ style.minHeight = 100;
1089
+ }
1090
+ } else if (!hasExplicitWidth && hasExplicitHeight) {
1091
+ if (aspectRatio) {
1092
+ style.aspectRatio = aspectRatio;
1093
+ } else {
1094
+ style.width = '100%';
1095
+ }
1096
+ }
1097
+ return /*#__PURE__*/_jsx(Animated.Image, {
1098
+ source: {
1099
+ uri
1100
+ },
1101
+ style: [style, {
1102
+ opacity
1103
+ }],
1104
+ resizeMode: fit,
1105
+ onLoad: event => {
1106
+ const source = event?.nativeEvent?.source;
1107
+ updateAspectRatio(source?.width, source?.height);
1108
+ Animated.timing(opacity, {
1109
+ toValue: 1,
1110
+ duration: 160,
1111
+ useNativeDriver: true
1112
+ }).start();
1113
+ },
1114
+ onError: () => setHasError(true)
1115
+ });
1116
+ }
923
1117
  function GradientText({
924
1118
  text,
925
1119
  gradient,
@@ -1085,7 +1279,9 @@ function SelectionList({
1085
1279
  next = [value];
1086
1280
  onChanged(value);
1087
1281
  if (properties.autoGoNext === true) {
1088
- onAction('next');
1282
+ requestAnimationFrame(() => onAction('next', {
1283
+ selectedValue: value
1284
+ }));
1089
1285
  }
1090
1286
  }
1091
1287
  return next;
@@ -1157,6 +1353,274 @@ function SelectionList({
1157
1353
  }, `column-option-${index}`))
1158
1354
  });
1159
1355
  }
1356
+ function WheelPicker({
1357
+ properties,
1358
+ options,
1359
+ itemTemplate,
1360
+ formData,
1361
+ initialValue,
1362
+ onChanged,
1363
+ onAction,
1364
+ buildWidget
1365
+ }) {
1366
+ const multiSelect = properties.multiSelect === true;
1367
+ const [selectedValues, setSelectedValues] = React.useState(() => {
1368
+ if (initialValue === null || initialValue === undefined) return [];
1369
+ if (Array.isArray(initialValue)) return initialValue.map(String);
1370
+ if (typeof initialValue === 'string') {
1371
+ if (multiSelect) {
1372
+ try {
1373
+ return initialValue.replace('[', '').replace(']', '').split(',').map(val => val.trim()).filter(val => val.length > 0);
1374
+ } catch {
1375
+ return [initialValue];
1376
+ }
1377
+ }
1378
+ return [initialValue];
1379
+ }
1380
+ return [];
1381
+ });
1382
+ const resolvedOptions = React.useMemo(() => buildWheelPickerOptions({
1383
+ options,
1384
+ properties,
1385
+ formData,
1386
+ itemTemplate
1387
+ }), [formData, itemTemplate, options, properties]);
1388
+ const toggleSelection = value => {
1389
+ setSelectedValues(prev => {
1390
+ let next = [...prev];
1391
+ if (multiSelect) {
1392
+ if (next.includes(value)) {
1393
+ next = next.filter(item => item !== value);
1394
+ } else {
1395
+ next.push(value);
1396
+ }
1397
+ onChanged(next.join(','));
1398
+ } else {
1399
+ next = [value];
1400
+ onChanged(value);
1401
+ if (properties.autoGoNext === true) {
1402
+ requestAnimationFrame(() => onAction('next', {
1403
+ selectedValue: value
1404
+ }));
1405
+ }
1406
+ }
1407
+ return next;
1408
+ });
1409
+ };
1410
+ const layout = properties.layout ?? 'wheel';
1411
+ const spacing = Number(properties.spacing ?? 8);
1412
+ const renderItem = (option, index) => {
1413
+ const value = String(option.value ?? '');
1414
+ const isSelected = selectedValues.includes(value);
1415
+ const styleProps = isSelected ? option.selectedStyle ?? properties.selectedStyle ?? {
1416
+ backgroundColor: properties.wheelSelectedBackgroundColor,
1417
+ borderColor: properties.wheelSelectedBorderColor
1418
+ } : option.unselectedStyle ?? properties.unselectedStyle ?? {
1419
+ backgroundColor: properties.wheelUnselectedBackgroundColor,
1420
+ borderColor: properties.wheelUnselectedBorderColor
1421
+ };
1422
+ const backgroundColor = parseColor(styleProps.backgroundColor);
1423
+ const borderColor = parseColor(styleProps.borderColor);
1424
+ const borderRadius = Number(styleProps.borderRadius ?? 8);
1425
+ const borderWidth = Number(styleProps.borderWidth ?? 1);
1426
+ return /*#__PURE__*/_jsx(Pressable, {
1427
+ onPress: () => toggleSelection(value),
1428
+ style: {
1429
+ width: '100%',
1430
+ backgroundColor,
1431
+ borderColor,
1432
+ borderWidth,
1433
+ borderRadius,
1434
+ padding: 0
1435
+ },
1436
+ children: buildWidget(option.child)
1437
+ }, `wheel-option-${index}`);
1438
+ };
1439
+ if (layout === 'row') {
1440
+ return /*#__PURE__*/_jsx(View, {
1441
+ style: {
1442
+ flexDirection: 'row',
1443
+ flexWrap: 'wrap'
1444
+ },
1445
+ children: resolvedOptions.map((option, index) => /*#__PURE__*/_jsx(View, {
1446
+ style: {
1447
+ marginRight: spacing,
1448
+ marginBottom: spacing
1449
+ },
1450
+ children: renderItem(option, index)
1451
+ }, `wheel-row-option-${index}`))
1452
+ });
1453
+ }
1454
+ if (layout === 'grid') {
1455
+ const crossAxisCount = Number(properties.gridCrossAxisCount ?? 2);
1456
+ const aspectRatio = Number(properties.gridAspectRatio ?? 1);
1457
+ return /*#__PURE__*/_jsx(View, {
1458
+ style: {
1459
+ flexDirection: 'row',
1460
+ flexWrap: 'wrap'
1461
+ },
1462
+ children: resolvedOptions.map((option, index) => /*#__PURE__*/_jsx(View, {
1463
+ style: {
1464
+ width: `${100 / crossAxisCount}%`,
1465
+ paddingRight: (index + 1) % crossAxisCount === 0 ? 0 : spacing,
1466
+ paddingBottom: spacing,
1467
+ aspectRatio
1468
+ },
1469
+ children: renderItem(option, index)
1470
+ }, `wheel-grid-option-${index}`))
1471
+ });
1472
+ }
1473
+ if (layout === 'wheel') {
1474
+ const itemHeight = Math.max(1, Number(properties.wheelItemHeight ?? 48));
1475
+ const requestedVisible = Math.max(3, Math.floor(Number(properties.visibleItems ?? 5)));
1476
+ const visibleItems = requestedVisible % 2 === 0 ? requestedVisible + 1 : requestedVisible;
1477
+ const containerHeight = itemHeight * visibleItems;
1478
+ const centerPadding = Math.max(0, (containerHeight - itemHeight) / 2);
1479
+ return /*#__PURE__*/_jsx(View, {
1480
+ style: {
1481
+ height: containerHeight
1482
+ },
1483
+ children: /*#__PURE__*/_jsx(ScrollView, {
1484
+ showsVerticalScrollIndicator: false,
1485
+ contentContainerStyle: {
1486
+ paddingVertical: centerPadding
1487
+ },
1488
+ children: resolvedOptions.map((option, index) => /*#__PURE__*/_jsx(View, {
1489
+ style: {
1490
+ minHeight: itemHeight,
1491
+ justifyContent: 'center',
1492
+ marginBottom: spacing > 0 && index < resolvedOptions.length - 1 ? spacing : 0
1493
+ },
1494
+ children: renderItem(option, index)
1495
+ }, `wheel-column-option-${index}`))
1496
+ })
1497
+ });
1498
+ }
1499
+ return /*#__PURE__*/_jsx(View, {
1500
+ children: resolvedOptions.map((option, index) => /*#__PURE__*/_jsx(View, {
1501
+ style: {
1502
+ marginBottom: spacing
1503
+ },
1504
+ children: renderItem(option, index)
1505
+ }, `wheel-list-option-${index}`))
1506
+ });
1507
+ }
1508
+ function buildWheelPickerOptions({
1509
+ options,
1510
+ properties,
1511
+ formData,
1512
+ itemTemplate
1513
+ }) {
1514
+ const templateContext = buildWheelTemplateBaseContext(formData, properties);
1515
+ const itemCountRaw = properties.itemCount ?? properties.item_count;
1516
+ const itemCountResolved = resolveNumericValue(itemCountRaw, templateContext);
1517
+ const itemCount = normalizeItemCount(itemCountResolved);
1518
+ if (itemCount > 0) {
1519
+ const generatedTemplate = normalizeWheelOptionTemplate(itemTemplate ?? properties.itemTemplate ?? options[0]);
1520
+ if (!generatedTemplate) return [];
1521
+ const start = resolveNumericValue(properties.start ?? properties.itemStart ?? 0, templateContext) ?? 0;
1522
+ const step = resolveNumericValue(properties.step ?? properties.itemStep ?? 1, templateContext) ?? 1;
1523
+ return Array.from({
1524
+ length: itemCount
1525
+ }).map((_, index) => {
1526
+ const itemValue = start + step * index;
1527
+ const context = {
1528
+ ...templateContext,
1529
+ start,
1530
+ step,
1531
+ index,
1532
+ item: itemValue
1533
+ };
1534
+ const resolvedOption = resolveWheelTemplateValue(generatedTemplate, context);
1535
+ return ensureWheelOptionShape(resolvedOption, String(itemValue));
1536
+ });
1537
+ }
1538
+ return options.map((option, index) => {
1539
+ const itemValue = option?.value ?? index;
1540
+ const context = {
1541
+ ...templateContext,
1542
+ index,
1543
+ item: itemValue
1544
+ };
1545
+ const resolvedOption = resolveWheelTemplateValue(option, context);
1546
+ return ensureWheelOptionShape(resolvedOption, String(itemValue));
1547
+ });
1548
+ }
1549
+ function normalizeItemCount(value) {
1550
+ if (value === null || !Number.isFinite(value)) return 0;
1551
+ return Math.max(0, Math.floor(value));
1552
+ }
1553
+ function buildWheelTemplateBaseContext(formData, properties) {
1554
+ const context = {
1555
+ ...formData
1556
+ };
1557
+ const start = resolveNumericValue(properties.start ?? properties.itemStart, context);
1558
+ const step = resolveNumericValue(properties.step ?? properties.itemStep, context);
1559
+ if (start !== null) context.start = start;
1560
+ if (step !== null) context.step = step;
1561
+ return context;
1562
+ }
1563
+ function normalizeWheelOptionTemplate(template) {
1564
+ if (!template || typeof template !== 'object') return null;
1565
+ const asMap = template;
1566
+ if (Object.prototype.hasOwnProperty.call(asMap, 'value') || Object.prototype.hasOwnProperty.call(asMap, 'child')) {
1567
+ return {
1568
+ ...asMap
1569
+ };
1570
+ }
1571
+ if (typeof asMap.type === 'string') {
1572
+ return {
1573
+ value: '{{item}}',
1574
+ child: asMap
1575
+ };
1576
+ }
1577
+ return null;
1578
+ }
1579
+ function ensureWheelOptionShape(option, fallbackValue) {
1580
+ if (!option || typeof option !== 'object') {
1581
+ return {
1582
+ value: fallbackValue,
1583
+ child: {
1584
+ type: 'text',
1585
+ properties: {
1586
+ text: fallbackValue
1587
+ }
1588
+ }
1589
+ };
1590
+ }
1591
+ const normalized = {
1592
+ ...option
1593
+ };
1594
+ const rawValue = normalized.value ?? fallbackValue;
1595
+ const value = String(rawValue);
1596
+ const child = normalized.child && typeof normalized.child === 'object' ? normalized.child : {
1597
+ type: 'text',
1598
+ properties: {
1599
+ text: value
1600
+ }
1601
+ };
1602
+ return {
1603
+ ...normalized,
1604
+ value,
1605
+ child
1606
+ };
1607
+ }
1608
+ function resolveWheelTemplateValue(value, context) {
1609
+ if (typeof value === 'string') {
1610
+ return resolveText(value, context);
1611
+ }
1612
+ if (Array.isArray(value)) {
1613
+ return value.map(entry => resolveWheelTemplateValue(entry, context));
1614
+ }
1615
+ if (value && typeof value === 'object') {
1616
+ const result = {};
1617
+ Object.keys(value).forEach(key => {
1618
+ result[key] = resolveWheelTemplateValue(value[key], context);
1619
+ });
1620
+ return result;
1621
+ }
1622
+ return value;
1623
+ }
1160
1624
  function FakeProgressBar({
1161
1625
  progressColor,
1162
1626
  backgroundColor,
@@ -1278,27 +1742,32 @@ function SliderWidget({
1278
1742
  const registry = useSliderRegistry();
1279
1743
  const pagerRef = React.useRef(null);
1280
1744
  const pageLength = children.length;
1281
- const [currentPage, setCurrentPage] = React.useState(() => loop && pageLength > 0 ? pageLength * 1000 : 0);
1745
+ const pageCount = pageLength;
1746
+ const [currentPage, setCurrentPage] = React.useState(0);
1282
1747
  const timerRef = React.useRef(null);
1283
1748
  React.useEffect(() => {
1284
- if (autoPlay && !linkedTo) {
1749
+ setCurrentPage(prev => clampSliderPage(prev, pageCount));
1750
+ }, [pageCount]);
1751
+ React.useEffect(() => {
1752
+ if (autoPlay && !linkedTo && pageLength > 0) {
1285
1753
  timerRef.current = setInterval(() => {
1286
1754
  setCurrentPage(prev => {
1287
- if (loop) return prev + 1;
1288
- if (prev < children.length - 1) return prev + 1;
1289
- return 0;
1755
+ const safePrev = clampSliderPage(prev, pageCount);
1756
+ if (safePrev < pageLength - 1) return safePrev + 1;
1757
+ if (loop) return 0;
1758
+ return safePrev;
1290
1759
  });
1291
1760
  }, autoPlayDelay);
1292
1761
  }
1293
1762
  return () => {
1294
1763
  if (timerRef.current) clearInterval(timerRef.current);
1295
1764
  };
1296
- }, [autoPlay, autoPlayDelay, loop, linkedTo, children.length]);
1765
+ }, [autoPlay, autoPlayDelay, linkedTo, loop, pageCount, pageLength]);
1297
1766
  React.useEffect(() => {
1298
- if (pagerRef.current) {
1299
- pagerRef.current.setPage(currentPage);
1767
+ if (pagerRef.current && pageCount > 0) {
1768
+ pagerRef.current.setPage(clampSliderPage(currentPage, pageCount));
1300
1769
  }
1301
- }, [currentPage]);
1770
+ }, [currentPage, pageCount]);
1302
1771
  React.useEffect(() => {
1303
1772
  if (registry && id && children.length > 0) {
1304
1773
  registry.update(id, currentPage % children.length, children.length);
@@ -1307,26 +1776,31 @@ function SliderWidget({
1307
1776
  React.useEffect(() => {
1308
1777
  if (!linkedTo || !registry) return;
1309
1778
  return registry.getNotifier(linkedTo, value => {
1779
+ const normalized = normalizeLoopIndex(value, pageLength);
1780
+ const targetPage = clampSliderPage(normalized, pageCount);
1781
+ setCurrentPage(targetPage);
1310
1782
  if (pagerRef.current) {
1311
- pagerRef.current.setPage(value);
1783
+ pagerRef.current.setPage(targetPage);
1312
1784
  }
1313
1785
  });
1314
- }, [linkedTo, registry]);
1786
+ }, [linkedTo, pageCount, pageLength, registry]);
1315
1787
  if (pageLength === 0) return null;
1316
- const pageCount = loop ? pageLength * 1000 : pageLength;
1317
1788
  return /*#__PURE__*/_jsx(PagerView, {
1318
1789
  ref: pagerRef,
1319
- style: {
1320
- height: height ?? 200
1790
+ style: height === undefined ? {
1791
+ flex: 1
1792
+ } : {
1793
+ height
1321
1794
  },
1322
1795
  orientation: direction,
1323
1796
  scrollEnabled: physics !== 'never',
1324
- initialPage: currentPage,
1797
+ initialPage: clampSliderPage(currentPage, pageCount),
1325
1798
  onPageSelected: event => {
1326
1799
  const page = event.nativeEvent.position;
1327
- setCurrentPage(page);
1800
+ const safePage = clampSliderPage(page, pageCount);
1801
+ setCurrentPage(safePage);
1328
1802
  if (registry && id) {
1329
- registry.update(id, page % pageLength, pageLength);
1803
+ registry.update(id, safePage % pageLength, pageLength);
1330
1804
  }
1331
1805
  },
1332
1806
  children: Array.from({
@@ -1339,6 +1813,18 @@ function SliderWidget({
1339
1813
  }, `slider-page-${index}`))
1340
1814
  });
1341
1815
  }
1816
+ function clampSliderPage(page, pageCount) {
1817
+ if (pageCount <= 0) return 0;
1818
+ if (!Number.isFinite(page)) return 0;
1819
+ if (page < 0) return 0;
1820
+ if (page >= pageCount) return pageCount - 1;
1821
+ return Math.floor(page);
1822
+ }
1823
+ function normalizeLoopIndex(index, size) {
1824
+ if (size <= 0 || !Number.isFinite(index)) return 0;
1825
+ const normalized = Math.floor(index) % size;
1826
+ return normalized < 0 ? normalized + size : normalized;
1827
+ }
1342
1828
  function PageViewIndicator({
1343
1829
  linkedTo,
1344
1830
  activeColor,
@@ -1383,15 +1869,34 @@ function RadarChart({
1383
1869
  }) {
1384
1870
  const animate = properties.animate !== false;
1385
1871
  const animationDurationMs = Number(properties.animationDurationMs ?? 800);
1386
- const scale = useMemo(() => new Animated.Value(animate ? 0 : 1), [animate]);
1872
+ const reveal = useMemo(() => new Animated.Value(animate ? 0 : 1), [animate]);
1873
+ const [lineProgress, setLineProgress] = React.useState(animate ? 0 : 1);
1387
1874
  React.useEffect(() => {
1388
- if (!animate) return;
1389
- Animated.timing(scale, {
1875
+ if (!animate) {
1876
+ reveal.setValue(1);
1877
+ setLineProgress(1);
1878
+ return;
1879
+ }
1880
+ reveal.setValue(0);
1881
+ setLineProgress(0);
1882
+ const listenerId = reveal.addListener(({
1883
+ value
1884
+ }) => {
1885
+ setLineProgress(Math.max(0, Math.min(1, value)));
1886
+ });
1887
+ Animated.timing(reveal, {
1390
1888
  toValue: 1,
1391
1889
  duration: animationDurationMs,
1392
- useNativeDriver: true
1890
+ useNativeDriver: false
1393
1891
  }).start();
1394
- }, [animate, animationDurationMs, scale]);
1892
+ return () => {
1893
+ reveal.removeListener(listenerId);
1894
+ };
1895
+ }, [animate, animationDurationMs, reveal]);
1896
+ const parsedWidth = parseLayoutDimension(properties.width);
1897
+ const normalizedWidth = normalizeDimension(parsedWidth);
1898
+ const [measuredWidth, setMeasuredWidth] = React.useState(0);
1899
+ const width = resolveRadarWidth(parsedWidth, measuredWidth);
1395
1900
  const axes = parseRadarAxes(properties.axes);
1396
1901
  if (axes.length < 3) {
1397
1902
  return radarPlaceholder('Radar chart requires at least 3 axes.');
@@ -1402,9 +1907,8 @@ function RadarChart({
1402
1907
  }
1403
1908
  const normalizedAxes = normalizeAxisMaximums(axes, datasets);
1404
1909
  const backgroundColor = parseColor(properties.backgroundColor);
1405
- const width = parseDimension(properties.width) ?? 300;
1406
- const rawHeight = parseDimension(properties.height);
1407
- const height = rawHeight === undefined || rawHeight === Number.POSITIVE_INFINITY ? 300 : rawHeight;
1910
+ const rawHeight = parseLayoutDimension(properties.height);
1911
+ const height = typeof rawHeight === 'number' && Number.isFinite(rawHeight) && rawHeight > 0 ? rawHeight : 300;
1408
1912
  const padding = parseInsets(properties.padding);
1409
1913
  const gridLevels = Math.min(Math.max(Number(properties.gridLevels ?? 5), 1), 12);
1410
1914
  const gridColor = parseColor(properties.gridColor ?? '0xFFE0E0E0');
@@ -1418,7 +1922,7 @@ function RadarChart({
1418
1922
  const legendStyle = getTextStyle(properties.legendStyle ?? {}, 'color', '0xFF212121');
1419
1923
  const shape = (properties.shape ?? 'polygon').toLowerCase();
1420
1924
  const chartSize = Math.min(width, height);
1421
- const radius = chartSize / 2 - Math.max(padding.left, padding.right, padding.top, padding.bottom);
1925
+ const radius = Math.max(chartSize / 2 - Math.max(padding.left, padding.right, padding.top, padding.bottom), 8);
1422
1926
  const center = {
1423
1927
  x: width / 2,
1424
1928
  y: height / 2
@@ -1442,7 +1946,7 @@ function RadarChart({
1442
1946
  const points = dataset.values.map((value, index) => {
1443
1947
  const axis = normalizedAxes[index];
1444
1948
  const maxValue = axis?.maxValue ?? 0;
1445
- const ratio = maxValue > 0 ? value / maxValue : 0;
1949
+ const ratio = (maxValue > 0 ? value / maxValue : 0) * lineProgress;
1446
1950
  const angle = index * axisAngle - Math.PI / 2;
1447
1951
  const x = center.x + radius * ratio * Math.cos(angle);
1448
1952
  const y = center.y + radius * ratio * Math.sin(angle);
@@ -1481,15 +1985,19 @@ function RadarChart({
1481
1985
  });
1482
1986
  const chart = /*#__PURE__*/_jsx(Animated.View, {
1483
1987
  style: {
1484
- width,
1988
+ width: normalizedWidth ?? width,
1485
1989
  height,
1486
1990
  paddingLeft: padding.left,
1487
1991
  paddingRight: padding.right,
1488
1992
  paddingTop: padding.top,
1489
1993
  paddingBottom: padding.bottom,
1490
- transform: [{
1491
- scale
1492
- }]
1994
+ opacity: reveal
1995
+ },
1996
+ onLayout: event => {
1997
+ const nextWidth = Math.floor(event.nativeEvent.layout.width);
1998
+ if (nextWidth > 0 && nextWidth !== measuredWidth) {
1999
+ setMeasuredWidth(nextWidth);
2000
+ }
1493
2001
  },
1494
2002
  children: /*#__PURE__*/_jsx(Svg, {
1495
2003
  width: width,
@@ -1662,6 +2170,15 @@ export function normalizeAxisMaximums(axes, datasets) {
1662
2170
  };
1663
2171
  });
1664
2172
  }
2173
+ function resolveRadarWidth(parsedWidth, measuredWidth) {
2174
+ if (typeof parsedWidth === 'number' && Number.isFinite(parsedWidth)) {
2175
+ return parsedWidth;
2176
+ }
2177
+ if (measuredWidth > 0) {
2178
+ return measuredWidth;
2179
+ }
2180
+ return 300;
2181
+ }
1665
2182
  function radarPlaceholder(message) {
1666
2183
  return /*#__PURE__*/_jsx(View, {
1667
2184
  style: {