flowboard-react 0.4.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -7
- package/lib/module/components/FlowboardFlow.js +47 -30
- package/lib/module/components/FlowboardFlow.js.map +1 -1
- package/lib/module/components/FlowboardRenderer.js +1121 -149
- package/lib/module/components/FlowboardRenderer.js.map +1 -1
- package/lib/module/utils/flowboardUtils.js +3 -0
- package/lib/module/utils/flowboardUtils.js.map +1 -1
- package/lib/typescript/src/components/FlowboardFlow.d.ts.map +1 -1
- package/lib/typescript/src/components/FlowboardRenderer.d.ts +33 -1
- package/lib/typescript/src/components/FlowboardRenderer.d.ts.map +1 -1
- package/lib/typescript/src/types/flowboard.d.ts +1 -0
- package/lib/typescript/src/types/flowboard.d.ts.map +1 -1
- package/lib/typescript/src/utils/flowboardUtils.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/FlowboardFlow.tsx +69 -40
- package/src/components/FlowboardRenderer.tsx +1656 -196
- package/src/types/flowboard.ts +1 -0
- package/src/utils/flowboardUtils.ts +4 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import React, { useMemo } from 'react';
|
|
4
|
-
import { Animated, Pressable, ScrollView, StyleSheet, Text, TextInput, View, Image } from 'react-native';
|
|
5
|
-
import {
|
|
4
|
+
import { Animated, Platform, Pressable, ScrollView, StyleSheet, Text, TextInput, View, Image, Vibration } from 'react-native';
|
|
5
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
6
6
|
import MaskedView from '@react-native-masked-view/masked-view';
|
|
7
7
|
import LinearGradient from 'react-native-linear-gradient';
|
|
8
8
|
import LottieView from 'lottie-react-native';
|
|
@@ -24,13 +24,30 @@ const styles = StyleSheet.create({
|
|
|
24
24
|
whiteBg: {
|
|
25
25
|
backgroundColor: '#ffffff'
|
|
26
26
|
},
|
|
27
|
-
|
|
27
|
+
contentLayer: {
|
|
28
28
|
flex: 1
|
|
29
29
|
},
|
|
30
30
|
progressWrapper: {
|
|
31
31
|
width: '100%'
|
|
32
32
|
}
|
|
33
33
|
});
|
|
34
|
+
function resolvePageScrollAxis(screenData) {
|
|
35
|
+
const raw = String(screenData.pageScroll ?? screenData.scrollDirection ?? (screenData.scrollable === true ? 'vertical' : 'none')).toLowerCase();
|
|
36
|
+
if (raw === 'none') return 'none';
|
|
37
|
+
return 'vertical';
|
|
38
|
+
}
|
|
39
|
+
function getAxisBoundsForPageScroll(pageScroll) {
|
|
40
|
+
if (pageScroll === 'vertical') {
|
|
41
|
+
return {
|
|
42
|
+
widthBounded: true,
|
|
43
|
+
heightBounded: false
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
widthBounded: true,
|
|
48
|
+
heightBounded: true
|
|
49
|
+
};
|
|
50
|
+
}
|
|
34
51
|
export default function FlowboardRenderer(props) {
|
|
35
52
|
const {
|
|
36
53
|
screenData,
|
|
@@ -49,8 +66,12 @@ export default function FlowboardRenderer(props) {
|
|
|
49
66
|
const progressThickness = Number(screenData.progressThickness ?? 4);
|
|
50
67
|
const progressRadius = Number(screenData.progressRadius ?? 0);
|
|
51
68
|
const progressStyle = screenData.progressStyle ?? 'linear';
|
|
52
|
-
const
|
|
69
|
+
const pageScroll = resolvePageScrollAxis(screenData);
|
|
70
|
+
const pageAxisBounds = getAxisBoundsForPageScroll(pageScroll);
|
|
71
|
+
const scrollable = pageScroll !== 'none';
|
|
53
72
|
const safeArea = screenData.safeArea !== false;
|
|
73
|
+
const safeAreaInsets = useSafeAreaInsets();
|
|
74
|
+
const rootAxis = 'vertical';
|
|
54
75
|
const padding = parseInsets(screenData.padding);
|
|
55
76
|
const contentPaddingStyle = insetsToStyle(padding);
|
|
56
77
|
const progressPaddingStyle = {
|
|
@@ -59,6 +80,12 @@ export default function FlowboardRenderer(props) {
|
|
|
59
80
|
paddingLeft: padding.left
|
|
60
81
|
};
|
|
61
82
|
const rootCrossAxisAlignment = screenData.crossAxisAlignment ?? screenData.crossAxis;
|
|
83
|
+
const safeAreaPaddingStyle = safeArea ? {
|
|
84
|
+
paddingTop: safeAreaInsets.top,
|
|
85
|
+
paddingRight: safeAreaInsets.right,
|
|
86
|
+
paddingBottom: safeAreaInsets.bottom,
|
|
87
|
+
paddingLeft: safeAreaInsets.left
|
|
88
|
+
} : null;
|
|
62
89
|
const content = /*#__PURE__*/_jsxs(View, {
|
|
63
90
|
style: {
|
|
64
91
|
flex: 1
|
|
@@ -69,6 +96,7 @@ export default function FlowboardRenderer(props) {
|
|
|
69
96
|
}],
|
|
70
97
|
children: renderProgressBar(currentIndex, totalScreens, progressColor, progressThickness, progressRadius, progressStyle)
|
|
71
98
|
}), scrollable ? /*#__PURE__*/_jsx(ScrollView, {
|
|
99
|
+
horizontal: false,
|
|
72
100
|
contentContainerStyle: {
|
|
73
101
|
...contentPaddingStyle,
|
|
74
102
|
paddingTop: showProgress ? 0 : padding.top
|
|
@@ -76,14 +104,20 @@ export default function FlowboardRenderer(props) {
|
|
|
76
104
|
children: /*#__PURE__*/_jsx(View, {
|
|
77
105
|
style: {
|
|
78
106
|
flexGrow: 1,
|
|
107
|
+
flexDirection: 'column',
|
|
79
108
|
justifyContent: parseFlexAlignment(screenData.mainAxisAlignment),
|
|
80
|
-
alignItems: parseRootCrossAlignment(rootCrossAxisAlignment)
|
|
109
|
+
alignItems: parseRootCrossAlignment(rootCrossAxisAlignment),
|
|
110
|
+
minHeight: 0,
|
|
111
|
+
minWidth: 0
|
|
81
112
|
},
|
|
82
113
|
children: childrenData.map((child, index) => buildWidget(child, {
|
|
83
114
|
onAction,
|
|
84
115
|
formData,
|
|
85
116
|
onInputChange,
|
|
86
117
|
allowFlexExpansion: true,
|
|
118
|
+
parentFlexAxis: rootAxis,
|
|
119
|
+
parentMainAxisBounded: pageAxisBounds.heightBounded,
|
|
120
|
+
pageAxisBounds,
|
|
87
121
|
screenData,
|
|
88
122
|
enableFontAwesomeIcons,
|
|
89
123
|
key: `child-${index}`
|
|
@@ -102,6 +136,9 @@ export default function FlowboardRenderer(props) {
|
|
|
102
136
|
formData,
|
|
103
137
|
onInputChange,
|
|
104
138
|
allowFlexExpansion: true,
|
|
139
|
+
parentFlexAxis: rootAxis,
|
|
140
|
+
parentMainAxisBounded: true,
|
|
141
|
+
pageAxisBounds,
|
|
105
142
|
screenData,
|
|
106
143
|
enableFontAwesomeIcons,
|
|
107
144
|
key: `child-${index}`
|
|
@@ -110,12 +147,10 @@ export default function FlowboardRenderer(props) {
|
|
|
110
147
|
});
|
|
111
148
|
return /*#__PURE__*/_jsxs(View, {
|
|
112
149
|
style: styles.root,
|
|
113
|
-
children: [renderBackground(backgroundData, bgColorCode),
|
|
114
|
-
style: styles.
|
|
115
|
-
mode: "padding",
|
|
116
|
-
edges: ['top', 'right', 'bottom', 'left'],
|
|
150
|
+
children: [renderBackground(backgroundData, bgColorCode), /*#__PURE__*/_jsx(View, {
|
|
151
|
+
style: [styles.contentLayer, safeAreaPaddingStyle],
|
|
117
152
|
children: content
|
|
118
|
-
})
|
|
153
|
+
})]
|
|
119
154
|
});
|
|
120
155
|
}
|
|
121
156
|
function renderBackground(bgData, legacyColorCode) {
|
|
@@ -272,17 +307,411 @@ function renderProgressBar(currentIndex, totalScreens, color, thickness, radius,
|
|
|
272
307
|
})
|
|
273
308
|
});
|
|
274
309
|
}
|
|
310
|
+
const STACK_SPACE_DISTRIBUTIONS = new Set(['spaceBetween', 'spaceAround', 'spaceEvenly']);
|
|
311
|
+
function normalizeStackSpacingConfig(value) {
|
|
312
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
313
|
+
return {
|
|
314
|
+
x: value,
|
|
315
|
+
y: value
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
if (!value || typeof value !== 'object') {
|
|
319
|
+
return {
|
|
320
|
+
x: 0,
|
|
321
|
+
y: 0
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
const x = Number(value.x ?? value.horizontal);
|
|
325
|
+
const y = Number(value.y ?? value.vertical);
|
|
326
|
+
const top = Number(value.top);
|
|
327
|
+
const right = Number(value.right);
|
|
328
|
+
const bottom = Number(value.bottom);
|
|
329
|
+
const left = Number(value.left);
|
|
330
|
+
const normalized = {};
|
|
331
|
+
if (!Number.isNaN(x)) normalized.x = x;
|
|
332
|
+
if (!Number.isNaN(y)) normalized.y = y;
|
|
333
|
+
if (!Number.isNaN(top)) normalized.top = top;
|
|
334
|
+
if (!Number.isNaN(right)) normalized.right = right;
|
|
335
|
+
if (!Number.isNaN(bottom)) normalized.bottom = bottom;
|
|
336
|
+
if (!Number.isNaN(left)) normalized.left = left;
|
|
337
|
+
if (Object.keys(normalized).length === 0) {
|
|
338
|
+
return {
|
|
339
|
+
x: 0,
|
|
340
|
+
y: 0
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
return normalized;
|
|
344
|
+
}
|
|
345
|
+
function stackSpacingToInsets(value) {
|
|
346
|
+
const normalized = normalizeStackSpacingConfig(value);
|
|
347
|
+
return parseInsets({
|
|
348
|
+
horizontal: normalized.x,
|
|
349
|
+
vertical: normalized.y,
|
|
350
|
+
top: normalized.top,
|
|
351
|
+
right: normalized.right,
|
|
352
|
+
bottom: normalized.bottom,
|
|
353
|
+
left: normalized.left
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
function isLegacyOverlayAlignment(value) {
|
|
357
|
+
return ['topLeft', 'topCenter', 'topRight', 'centerLeft', 'center', 'centerRight', 'bottomLeft', 'bottomCenter', 'bottomRight'].includes(String(value ?? ''));
|
|
358
|
+
}
|
|
359
|
+
function normalizeStackAxis(type, props) {
|
|
360
|
+
const explicit = String(props.axis ?? '').toLowerCase();
|
|
361
|
+
if (explicit === 'vertical' || explicit === 'horizontal' || explicit === 'overlay') {
|
|
362
|
+
return explicit;
|
|
363
|
+
}
|
|
364
|
+
if (type === 'container') return 'overlay';
|
|
365
|
+
if (type === 'row') return 'horizontal';
|
|
366
|
+
if (type === 'column') return 'vertical';
|
|
367
|
+
if (type === 'layout') {
|
|
368
|
+
return props.direction === 'horizontal' ? 'horizontal' : 'vertical';
|
|
369
|
+
}
|
|
370
|
+
if (type === 'stack' && (props.fit !== undefined || isLegacyOverlayAlignment(props.alignment))) {
|
|
371
|
+
return 'overlay';
|
|
372
|
+
}
|
|
373
|
+
return 'vertical';
|
|
374
|
+
}
|
|
375
|
+
function normalizeStackDistribution(value) {
|
|
376
|
+
const normalized = String(value ?? '').trim();
|
|
377
|
+
if (normalized === 'center' || normalized === 'end' || normalized === 'spaceBetween' || normalized === 'spaceAround' || normalized === 'spaceEvenly') {
|
|
378
|
+
return normalized;
|
|
379
|
+
}
|
|
380
|
+
return 'start';
|
|
381
|
+
}
|
|
382
|
+
function normalizeStackCrossAlignment(value) {
|
|
383
|
+
const raw = String(value ?? '').toLowerCase();
|
|
384
|
+
if (raw === 'center' || raw.includes('center')) return 'center';
|
|
385
|
+
if (raw === 'end' || raw.endsWith('right') || raw.startsWith('bottom')) return 'end';
|
|
386
|
+
return 'start';
|
|
387
|
+
}
|
|
388
|
+
function normalizeOverlayAlignment(value) {
|
|
389
|
+
const raw = String(value ?? '').trim();
|
|
390
|
+
switch (raw) {
|
|
391
|
+
case 'topStart':
|
|
392
|
+
case 'top':
|
|
393
|
+
case 'topEnd':
|
|
394
|
+
case 'start':
|
|
395
|
+
case 'center':
|
|
396
|
+
case 'end':
|
|
397
|
+
case 'bottomStart':
|
|
398
|
+
case 'bottom':
|
|
399
|
+
case 'bottomEnd':
|
|
400
|
+
return raw;
|
|
401
|
+
case 'topLeft':
|
|
402
|
+
return 'topStart';
|
|
403
|
+
case 'topCenter':
|
|
404
|
+
return 'top';
|
|
405
|
+
case 'topRight':
|
|
406
|
+
return 'topEnd';
|
|
407
|
+
case 'centerLeft':
|
|
408
|
+
return 'start';
|
|
409
|
+
case 'centerRight':
|
|
410
|
+
return 'end';
|
|
411
|
+
case 'bottomLeft':
|
|
412
|
+
return 'bottomStart';
|
|
413
|
+
case 'bottomCenter':
|
|
414
|
+
return 'bottom';
|
|
415
|
+
case 'bottomRight':
|
|
416
|
+
return 'bottomEnd';
|
|
417
|
+
default:
|
|
418
|
+
return 'center';
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
function isFillDimensionValue(value) {
|
|
422
|
+
if (typeof value === 'number') return value === Number.POSITIVE_INFINITY;
|
|
423
|
+
if (typeof value !== 'string') return false;
|
|
424
|
+
const normalized = value.trim().toLowerCase();
|
|
425
|
+
return normalized === 'infinity' || normalized === 'double.infinity' || normalized === '+infinity' || normalized === '100%';
|
|
426
|
+
}
|
|
427
|
+
function isFixedDimensionValue(value) {
|
|
428
|
+
if (isFillDimensionValue(value)) return false;
|
|
429
|
+
const parsed = Number(value);
|
|
430
|
+
return Number.isFinite(parsed);
|
|
431
|
+
}
|
|
432
|
+
function normalizeStackSize(props, axis) {
|
|
433
|
+
const widthMode = String(props.size?.width ?? '').toLowerCase();
|
|
434
|
+
const heightMode = String(props.size?.height ?? '').toLowerCase();
|
|
435
|
+
const width = widthMode === 'fill' || widthMode === 'fit' || widthMode === 'fixed' ? widthMode : props.fit === 'expand' || props.width === 'infinity' ? 'fill' : isFixedDimensionValue(props.width) ? 'fixed' : axis === 'horizontal' ? 'fit' : 'fill';
|
|
436
|
+
const height = heightMode === 'fill' || heightMode === 'fit' || heightMode === 'fixed' ? heightMode : props.fit === 'expand' || props.height === 'infinity' ? 'fill' : isFixedDimensionValue(props.height) ? 'fixed' : axis === 'vertical' ? 'fit' : 'fill';
|
|
437
|
+
return {
|
|
438
|
+
width,
|
|
439
|
+
height
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
function normalizeStackFill(props) {
|
|
443
|
+
if (props.fill && typeof props.fill === 'object') {
|
|
444
|
+
const next = {
|
|
445
|
+
...props.fill
|
|
446
|
+
};
|
|
447
|
+
if (!next.type && next.color) next.type = 'solid';
|
|
448
|
+
return next;
|
|
449
|
+
}
|
|
450
|
+
if (props.background && typeof props.background === 'object') {
|
|
451
|
+
if (props.background.type === 'color') {
|
|
452
|
+
return {
|
|
453
|
+
type: 'solid',
|
|
454
|
+
color: props.background.color
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
return {
|
|
458
|
+
...props.background
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
if (props.backgroundColor) {
|
|
462
|
+
return {
|
|
463
|
+
type: 'solid',
|
|
464
|
+
color: props.backgroundColor
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
return undefined;
|
|
468
|
+
}
|
|
469
|
+
function normalizeStackBorder(props) {
|
|
470
|
+
if (props.border && typeof props.border === 'object') {
|
|
471
|
+
return {
|
|
472
|
+
...props.border
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
if (props.borderColor || props.borderWidth !== undefined) {
|
|
476
|
+
return {
|
|
477
|
+
width: Number(props.borderWidth ?? 1),
|
|
478
|
+
color: props.borderColor
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
return undefined;
|
|
482
|
+
}
|
|
483
|
+
function normalizeStackShadow(props) {
|
|
484
|
+
if (props.shadow && typeof props.shadow === 'object') {
|
|
485
|
+
return {
|
|
486
|
+
...props.shadow
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
return undefined;
|
|
490
|
+
}
|
|
491
|
+
function spacingToInsets(value) {
|
|
492
|
+
const normalized = normalizeStackSpacingConfig(value);
|
|
493
|
+
const x = normalized.x ?? 0;
|
|
494
|
+
const y = normalized.y ?? 0;
|
|
495
|
+
return {
|
|
496
|
+
top: normalized.top ?? y,
|
|
497
|
+
right: normalized.right ?? x,
|
|
498
|
+
bottom: normalized.bottom ?? y,
|
|
499
|
+
left: normalized.left ?? x
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
function insetsToSpacing(insets) {
|
|
503
|
+
if (insets.top === insets.bottom && insets.left === insets.right) {
|
|
504
|
+
return {
|
|
505
|
+
x: insets.left,
|
|
506
|
+
y: insets.top
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
return insets;
|
|
510
|
+
}
|
|
511
|
+
function mergeStackProps(parentProps, childProps) {
|
|
512
|
+
const parentAxis = String(parentProps.axis ?? 'vertical');
|
|
513
|
+
const childAxis = String(childProps.axis ?? 'vertical');
|
|
514
|
+
let axis = childAxis;
|
|
515
|
+
if (parentAxis !== childAxis) {
|
|
516
|
+
if (parentAxis === 'overlay' && childAxis !== 'overlay') axis = childAxis;else if (childAxis === 'overlay' && parentAxis !== 'overlay') axis = parentAxis;
|
|
517
|
+
}
|
|
518
|
+
const axisSource = axis === parentAxis && axis !== childAxis ? parentProps : childProps;
|
|
519
|
+
const fallbackAxisSource = axisSource === childProps ? parentProps : childProps;
|
|
520
|
+
const parentPadding = spacingToInsets(parentProps.layout?.padding);
|
|
521
|
+
const childPadding = spacingToInsets(childProps.layout?.padding);
|
|
522
|
+
const parentMargin = spacingToInsets(parentProps.layout?.margin);
|
|
523
|
+
const childMargin = spacingToInsets(childProps.layout?.margin);
|
|
524
|
+
const mergedProps = {
|
|
525
|
+
axis,
|
|
526
|
+
alignment: axisSource.alignment ?? fallbackAxisSource.alignment ?? 'start',
|
|
527
|
+
distribution: axisSource.distribution ?? fallbackAxisSource.distribution ?? 'start',
|
|
528
|
+
childSpacing: Number(axisSource.childSpacing ?? fallbackAxisSource.childSpacing ?? 0) || 0,
|
|
529
|
+
size: {
|
|
530
|
+
width: childProps.size?.width ?? parentProps.size?.width ?? 'fill',
|
|
531
|
+
height: childProps.size?.height ?? parentProps.size?.height ?? 'fit'
|
|
532
|
+
},
|
|
533
|
+
layout: {
|
|
534
|
+
padding: insetsToSpacing({
|
|
535
|
+
top: parentPadding.top + childPadding.top,
|
|
536
|
+
right: parentPadding.right + childPadding.right,
|
|
537
|
+
bottom: parentPadding.bottom + childPadding.bottom,
|
|
538
|
+
left: parentPadding.left + childPadding.left
|
|
539
|
+
}),
|
|
540
|
+
margin: insetsToSpacing({
|
|
541
|
+
top: parentMargin.top + childMargin.top,
|
|
542
|
+
right: parentMargin.right + childMargin.right,
|
|
543
|
+
bottom: parentMargin.bottom + childMargin.bottom,
|
|
544
|
+
left: parentMargin.left + childMargin.left
|
|
545
|
+
})
|
|
546
|
+
},
|
|
547
|
+
appearance: {
|
|
548
|
+
shape: 'rectangle',
|
|
549
|
+
cornerRadius: Number(childProps.appearance?.cornerRadius ?? parentProps.appearance?.cornerRadius ?? 0)
|
|
550
|
+
}
|
|
551
|
+
};
|
|
552
|
+
if (axis === 'overlay') {
|
|
553
|
+
mergedProps.overlayAlignment = childProps.overlayAlignment ?? parentProps.overlayAlignment ?? 'center';
|
|
554
|
+
}
|
|
555
|
+
if (parentProps.fill || childProps.fill) {
|
|
556
|
+
mergedProps.fill = childProps.fill ?? parentProps.fill;
|
|
557
|
+
}
|
|
558
|
+
if (parentProps.border || childProps.border) {
|
|
559
|
+
mergedProps.border = childProps.border ?? parentProps.border;
|
|
560
|
+
}
|
|
561
|
+
if (parentProps.shadow || childProps.shadow) {
|
|
562
|
+
mergedProps.shadow = childProps.shadow ?? parentProps.shadow;
|
|
563
|
+
}
|
|
564
|
+
if (childProps.width !== undefined || parentProps.width !== undefined) {
|
|
565
|
+
mergedProps.width = childProps.width ?? parentProps.width;
|
|
566
|
+
}
|
|
567
|
+
if (childProps.height !== undefined || parentProps.height !== undefined) {
|
|
568
|
+
mergedProps.height = childProps.height ?? parentProps.height;
|
|
569
|
+
}
|
|
570
|
+
return mergedProps;
|
|
571
|
+
}
|
|
572
|
+
function normalizeStackLikeNode(json) {
|
|
573
|
+
if (!json || typeof json !== 'object') return json;
|
|
574
|
+
const type = String(json.type ?? '');
|
|
575
|
+
if (!['stack', 'container', 'layout', 'row', 'column', 'grid'].includes(type)) {
|
|
576
|
+
return json;
|
|
577
|
+
}
|
|
578
|
+
const props = {
|
|
579
|
+
...(json.properties ?? {})
|
|
580
|
+
};
|
|
581
|
+
const axis = normalizeStackAxis(type, props);
|
|
582
|
+
const size = normalizeStackSize(props, axis);
|
|
583
|
+
const children = Array.isArray(json.children) ? [...json.children] : [];
|
|
584
|
+
if (json.child) {
|
|
585
|
+
children.push(json.child);
|
|
586
|
+
}
|
|
587
|
+
const normalizedProps = {
|
|
588
|
+
axis,
|
|
589
|
+
alignment: normalizeStackCrossAlignment(props.alignment ?? props.crossAxisAlignment),
|
|
590
|
+
distribution: normalizeStackDistribution(props.distribution ?? props.mainAxisAlignment),
|
|
591
|
+
childSpacing: Number(props.childSpacing ?? props.gap ?? props.spacing ?? 0),
|
|
592
|
+
size,
|
|
593
|
+
layout: {
|
|
594
|
+
padding: normalizeStackSpacingConfig(props.layout?.padding ?? props.padding),
|
|
595
|
+
margin: normalizeStackSpacingConfig(props.layout?.margin ?? props.margin)
|
|
596
|
+
},
|
|
597
|
+
appearance: {
|
|
598
|
+
shape: 'rectangle',
|
|
599
|
+
cornerRadius: Number(props.appearance?.cornerRadius ?? props.borderRadius ?? 0)
|
|
600
|
+
}
|
|
601
|
+
};
|
|
602
|
+
if (axis === 'overlay') {
|
|
603
|
+
normalizedProps.overlayAlignment = normalizeOverlayAlignment(props.overlayAlignment ?? props.alignment);
|
|
604
|
+
}
|
|
605
|
+
const fill = normalizeStackFill(props);
|
|
606
|
+
if (fill) normalizedProps.fill = fill;
|
|
607
|
+
const border = normalizeStackBorder(props);
|
|
608
|
+
if (border) normalizedProps.border = border;
|
|
609
|
+
const shadow = normalizeStackShadow(props);
|
|
610
|
+
if (shadow) normalizedProps.shadow = shadow;
|
|
611
|
+
if (props.width !== undefined) normalizedProps.width = props.width;
|
|
612
|
+
if (props.height !== undefined) normalizedProps.height = props.height;
|
|
613
|
+
if ((type === 'container' || type === 'layout') && children.length === 1) {
|
|
614
|
+
const childCandidate = children[0];
|
|
615
|
+
if (childCandidate && typeof childCandidate === 'object') {
|
|
616
|
+
const normalizedChild = normalizeStackLikeNode(childCandidate);
|
|
617
|
+
if (normalizedChild?.type === 'stack') {
|
|
618
|
+
const childProps = {
|
|
619
|
+
...(normalizedChild.properties ?? {})
|
|
620
|
+
};
|
|
621
|
+
const mergedProps = mergeStackProps(normalizedProps, childProps);
|
|
622
|
+
return {
|
|
623
|
+
...json,
|
|
624
|
+
type: 'stack',
|
|
625
|
+
properties: mergedProps,
|
|
626
|
+
children: Array.isArray(normalizedChild.children) ? normalizedChild.children : [],
|
|
627
|
+
child: undefined
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
return {
|
|
633
|
+
...json,
|
|
634
|
+
type: 'stack',
|
|
635
|
+
properties: normalizedProps,
|
|
636
|
+
children,
|
|
637
|
+
child: undefined
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
function parseOverlayGridAlignment(value) {
|
|
641
|
+
switch (normalizeOverlayAlignment(value)) {
|
|
642
|
+
case 'topStart':
|
|
643
|
+
return {
|
|
644
|
+
justifyContent: 'flex-start',
|
|
645
|
+
alignItems: 'flex-start'
|
|
646
|
+
};
|
|
647
|
+
case 'top':
|
|
648
|
+
return {
|
|
649
|
+
justifyContent: 'flex-start',
|
|
650
|
+
alignItems: 'center'
|
|
651
|
+
};
|
|
652
|
+
case 'topEnd':
|
|
653
|
+
return {
|
|
654
|
+
justifyContent: 'flex-start',
|
|
655
|
+
alignItems: 'flex-end'
|
|
656
|
+
};
|
|
657
|
+
case 'start':
|
|
658
|
+
return {
|
|
659
|
+
justifyContent: 'center',
|
|
660
|
+
alignItems: 'flex-start'
|
|
661
|
+
};
|
|
662
|
+
case 'end':
|
|
663
|
+
return {
|
|
664
|
+
justifyContent: 'center',
|
|
665
|
+
alignItems: 'flex-end'
|
|
666
|
+
};
|
|
667
|
+
case 'bottomStart':
|
|
668
|
+
return {
|
|
669
|
+
justifyContent: 'flex-end',
|
|
670
|
+
alignItems: 'flex-start'
|
|
671
|
+
};
|
|
672
|
+
case 'bottom':
|
|
673
|
+
return {
|
|
674
|
+
justifyContent: 'flex-end',
|
|
675
|
+
alignItems: 'center'
|
|
676
|
+
};
|
|
677
|
+
case 'bottomEnd':
|
|
678
|
+
return {
|
|
679
|
+
justifyContent: 'flex-end',
|
|
680
|
+
alignItems: 'flex-end'
|
|
681
|
+
};
|
|
682
|
+
case 'center':
|
|
683
|
+
default:
|
|
684
|
+
return {
|
|
685
|
+
justifyContent: 'center',
|
|
686
|
+
alignItems: 'center'
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
function resolveStackDimension(props, axis, key, axisBounds) {
|
|
691
|
+
const mode = props.size?.[key];
|
|
692
|
+
const legacy = normalizeDimension(parseLayoutDimension(props[key]));
|
|
693
|
+
const isBounded = key === 'width' ? axisBounds.widthBounded : axisBounds.heightBounded;
|
|
694
|
+
if (mode === 'fill') return isBounded ? '100%' : legacy;
|
|
695
|
+
if (mode === 'fit') return legacy;
|
|
696
|
+
if (mode === 'fixed') return legacy;
|
|
697
|
+
if (legacy !== undefined) return legacy;
|
|
698
|
+
if (key === 'width' && axis !== 'horizontal' && axisBounds.widthBounded) return '100%';
|
|
699
|
+
if (key === 'height' && axis === 'horizontal' && axisBounds.heightBounded) return '100%';
|
|
700
|
+
return undefined;
|
|
701
|
+
}
|
|
275
702
|
function buildWidget(json, params) {
|
|
276
703
|
if (!json || typeof json !== 'object') return null;
|
|
277
|
-
const
|
|
278
|
-
const
|
|
704
|
+
const normalizedJson = normalizeStackLikeNode(json);
|
|
705
|
+
const type = normalizedJson.type;
|
|
706
|
+
const id = normalizedJson.id;
|
|
279
707
|
const props = {
|
|
280
|
-
...(
|
|
708
|
+
...(normalizedJson.properties ?? {})
|
|
281
709
|
};
|
|
282
|
-
const
|
|
283
|
-
const
|
|
284
|
-
const
|
|
285
|
-
const
|
|
710
|
+
const pageAxisBounds = params.pageAxisBounds ?? getAxisBoundsForPageScroll(resolvePageScrollAxis(params.screenData));
|
|
711
|
+
const childrenJson = Array.isArray(normalizedJson.children) ? normalizedJson.children : undefined;
|
|
712
|
+
const childJson = normalizedJson.child;
|
|
713
|
+
const action = normalizedJson.action;
|
|
714
|
+
const actionPayload = action ? buildActionPayload(props, normalizedJson) : undefined;
|
|
286
715
|
let node = null;
|
|
287
716
|
switch (type) {
|
|
288
717
|
case 'column':
|
|
@@ -457,55 +886,83 @@ function buildWidget(json, params) {
|
|
|
457
886
|
const width = normalizeDimension(parseLayoutDimension(props.width)) ?? '100%';
|
|
458
887
|
const height = normalizeDimension(parseLayoutDimension(props.height)) ?? 50;
|
|
459
888
|
const label = props.label ?? 'Button';
|
|
889
|
+
const normalizedStroke = normalizeButtonStroke(props.stroke);
|
|
890
|
+
const normalizedEffects = normalizeButtonEffects(props.effects, props.shadow);
|
|
460
891
|
const textStyle = getTextStyle({
|
|
461
892
|
...props,
|
|
462
893
|
height: null
|
|
463
894
|
}, 'textColor', '0xFFFFFFFF');
|
|
464
|
-
const
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
895
|
+
const backgroundColor = parseColor(props.color ?? '0xFF2196F3');
|
|
896
|
+
const shadowBounds = getButtonShadowLayerBounds(normalizedStroke, borderRadius);
|
|
897
|
+
const webShadowStyle = buildButtonWebShadowStyle(normalizedEffects);
|
|
898
|
+
const firstNativeEffect = normalizedEffects.length > 0 ? normalizedEffects[0] : null;
|
|
899
|
+
const nativePrimaryShadowStyle = Platform.OS === 'web' || !firstNativeEffect ? null : buildButtonNativeShadowStyle(firstNativeEffect);
|
|
900
|
+
const centeredStrokeStyle = normalizedStroke && normalizedStroke.position === 'center' ? {
|
|
901
|
+
borderWidth: normalizedStroke.width,
|
|
902
|
+
borderColor: normalizedStroke.color
|
|
903
|
+
} : null;
|
|
904
|
+
const fillLayerStyle = {
|
|
905
|
+
position: 'absolute',
|
|
906
|
+
top: 0,
|
|
907
|
+
right: 0,
|
|
908
|
+
bottom: 0,
|
|
909
|
+
left: 0,
|
|
910
|
+
borderRadius,
|
|
911
|
+
overflow: 'hidden',
|
|
912
|
+
...(centeredStrokeStyle ?? {})
|
|
913
|
+
};
|
|
914
|
+
const nativeShadowEffectsInPaintOrder = normalizedEffects.slice().reverse();
|
|
915
|
+
const pressableStyle = {
|
|
916
|
+
width,
|
|
917
|
+
height,
|
|
918
|
+
borderRadius,
|
|
919
|
+
justifyContent: 'center',
|
|
920
|
+
alignItems: 'center',
|
|
921
|
+
overflow: 'visible',
|
|
922
|
+
...(nativePrimaryShadowStyle ?? {})
|
|
923
|
+
};
|
|
924
|
+
node = /*#__PURE__*/_jsxs(Pressable, {
|
|
925
|
+
collapsable: false,
|
|
926
|
+
onPress: action ? () => params.onAction(action, actionPayload ?? props) : undefined,
|
|
927
|
+
style: pressableStyle,
|
|
928
|
+
children: [Platform.OS === 'web' ? webShadowStyle ? /*#__PURE__*/_jsx(View, {
|
|
929
|
+
pointerEvents: "none",
|
|
478
930
|
style: {
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
locations: gradient.stops,
|
|
487
|
-
style: {
|
|
488
|
-
flex: 1,
|
|
489
|
-
borderRadius,
|
|
490
|
-
overflow: 'hidden'
|
|
491
|
-
},
|
|
492
|
-
children: content
|
|
493
|
-
})
|
|
494
|
-
});
|
|
495
|
-
} else {
|
|
496
|
-
node = /*#__PURE__*/_jsx(Pressable, {
|
|
497
|
-
onPress: action ? () => params.onAction(action, actionPayload ?? props) : undefined,
|
|
931
|
+
position: 'absolute',
|
|
932
|
+
...shadowBounds,
|
|
933
|
+
...webShadowStyle
|
|
934
|
+
}
|
|
935
|
+
}) : null : nativeShadowEffectsInPaintOrder.map((effect, index) => /*#__PURE__*/_jsx(View, {
|
|
936
|
+
collapsable: false,
|
|
937
|
+
pointerEvents: "none",
|
|
498
938
|
style: {
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
939
|
+
position: 'absolute',
|
|
940
|
+
...shadowBounds,
|
|
941
|
+
...buildButtonNativeShadowStyle(effect)
|
|
942
|
+
}
|
|
943
|
+
}, `shadow-${index}`)), gradient ? /*#__PURE__*/_jsx(LinearGradient, {
|
|
944
|
+
colors: gradient.colors,
|
|
945
|
+
start: gradient.start,
|
|
946
|
+
end: gradient.end,
|
|
947
|
+
locations: gradient.stops,
|
|
948
|
+
style: fillLayerStyle
|
|
949
|
+
}) : /*#__PURE__*/_jsx(View, {
|
|
950
|
+
style: {
|
|
951
|
+
...fillLayerStyle,
|
|
952
|
+
backgroundColor
|
|
953
|
+
}
|
|
954
|
+
}), renderButtonStrokeOverlay(normalizedStroke, borderRadius), /*#__PURE__*/_jsx(View, {
|
|
955
|
+
style: {
|
|
956
|
+
flex: 1,
|
|
503
957
|
justifyContent: 'center',
|
|
504
958
|
alignItems: 'center'
|
|
505
959
|
},
|
|
506
|
-
children:
|
|
507
|
-
|
|
508
|
-
|
|
960
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
961
|
+
style: textStyle,
|
|
962
|
+
children: label
|
|
963
|
+
})
|
|
964
|
+
})]
|
|
965
|
+
});
|
|
509
966
|
break;
|
|
510
967
|
}
|
|
511
968
|
case 'text_input':
|
|
@@ -819,22 +1276,112 @@ function buildWidget(json, params) {
|
|
|
819
1276
|
}
|
|
820
1277
|
case 'stack':
|
|
821
1278
|
{
|
|
822
|
-
const
|
|
823
|
-
const
|
|
824
|
-
|
|
1279
|
+
const axis = props.axis === 'horizontal' || props.axis === 'overlay' ? props.axis : 'vertical';
|
|
1280
|
+
const isOverlay = axis === 'overlay';
|
|
1281
|
+
const spacing = Number(props.childSpacing ?? 0);
|
|
1282
|
+
const applySpacing = !STACK_SPACE_DISTRIBUTIONS.has(String(props.distribution ?? 'start'));
|
|
1283
|
+
const paddingInsets = stackSpacingToInsets(props.layout?.padding ?? props.padding);
|
|
1284
|
+
const width = resolveStackDimension(props, axis, 'width', pageAxisBounds);
|
|
1285
|
+
const height = resolveStackDimension(props, axis, 'height', pageAxisBounds);
|
|
1286
|
+
const mainAxisBounded = axis === 'horizontal' ? pageAxisBounds.widthBounded : pageAxisBounds.heightBounded;
|
|
1287
|
+
const borderRadius = Number(props.appearance?.cornerRadius ?? props.borderRadius ?? 0);
|
|
1288
|
+
const borderWidth = Number(props.border?.width ?? 0);
|
|
1289
|
+
const borderColor = borderWidth > 0 ? parseColor(props.border?.color ?? '#E5E7EB') : null;
|
|
1290
|
+
const shadowColor = props.shadow?.color ? parseColor(props.shadow.color) : null;
|
|
1291
|
+
const shadowStyle = shadowColor && props.shadow ? Platform.OS === 'web' ? {
|
|
1292
|
+
boxShadow: `${Number(props.shadow.x ?? 0)}px ${Number(props.shadow.y ?? 8)}px ${Number(props.shadow.blur ?? 24)}px ${shadowColor}`
|
|
1293
|
+
} : {
|
|
1294
|
+
shadowColor,
|
|
1295
|
+
shadowOffset: {
|
|
1296
|
+
width: Number(props.shadow.x ?? 0),
|
|
1297
|
+
height: Number(props.shadow.y ?? 8)
|
|
1298
|
+
},
|
|
1299
|
+
shadowOpacity: 0.35,
|
|
1300
|
+
shadowRadius: Math.max(0, Number(props.shadow.blur ?? 24) / 2),
|
|
1301
|
+
elevation: Math.max(1, Math.round((Number(props.shadow.blur ?? 24) + Math.abs(Number(props.shadow.y ?? 8))) / 3))
|
|
1302
|
+
} : {};
|
|
1303
|
+
const fill = props.fill;
|
|
1304
|
+
const stackGradient = fill?.type === 'gradient' ? parseGradient({
|
|
1305
|
+
...fill,
|
|
1306
|
+
type: 'gradient'
|
|
1307
|
+
}) : undefined;
|
|
1308
|
+
const stackColor = fill?.type === 'solid' || !fill?.type && fill?.color ? parseColor(fill?.color ?? '#FFFFFFFF') : parseColor(props.backgroundColor ?? '#00000000');
|
|
1309
|
+
const baseStackStyle = {
|
|
1310
|
+
width,
|
|
1311
|
+
height,
|
|
1312
|
+
borderRadius: borderRadius > 0 ? borderRadius : undefined,
|
|
1313
|
+
overflow: borderRadius > 0 ? 'hidden' : undefined,
|
|
1314
|
+
borderWidth: borderColor ? borderWidth : undefined,
|
|
1315
|
+
borderColor: borderColor ?? undefined,
|
|
1316
|
+
...insetsToStyle(paddingInsets),
|
|
1317
|
+
...shadowStyle
|
|
1318
|
+
};
|
|
1319
|
+
const content = isOverlay ? /*#__PURE__*/_jsxs(View, {
|
|
825
1320
|
style: {
|
|
826
1321
|
position: 'relative',
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
justifyContent: alignment.justifyContent,
|
|
830
|
-
alignItems: alignment.alignItems
|
|
1322
|
+
minHeight: 0,
|
|
1323
|
+
minWidth: 0
|
|
831
1324
|
},
|
|
832
|
-
children: (childrenJson ?? []).map((child, index) =>
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
1325
|
+
children: [(childrenJson ?? []).map((child, index) => {
|
|
1326
|
+
const alignment = parseOverlayGridAlignment(props.overlayAlignment ?? props.alignment);
|
|
1327
|
+
return /*#__PURE__*/_jsx(View, {
|
|
1328
|
+
style: {
|
|
1329
|
+
position: 'absolute',
|
|
1330
|
+
top: 0,
|
|
1331
|
+
right: 0,
|
|
1332
|
+
bottom: 0,
|
|
1333
|
+
left: 0,
|
|
1334
|
+
justifyContent: alignment.justifyContent,
|
|
1335
|
+
alignItems: alignment.alignItems
|
|
1336
|
+
},
|
|
1337
|
+
children: buildWidget(child, {
|
|
1338
|
+
...params,
|
|
1339
|
+
allowFlexExpansion: true,
|
|
1340
|
+
pageAxisBounds
|
|
1341
|
+
})
|
|
1342
|
+
}, `stack-overlay-${index}`);
|
|
1343
|
+
}), (childrenJson ?? []).length === 0 ? null : null]
|
|
1344
|
+
}) : /*#__PURE__*/_jsx(View, {
|
|
1345
|
+
style: {
|
|
1346
|
+
flexDirection: axis === 'horizontal' ? 'row' : 'column',
|
|
1347
|
+
justifyContent: parseFlexAlignment(props.distribution),
|
|
1348
|
+
alignItems: parseCrossAlignment(props.alignment),
|
|
1349
|
+
minHeight: 0,
|
|
1350
|
+
minWidth: 0
|
|
1351
|
+
},
|
|
1352
|
+
children: (childrenJson ?? []).map((child, index) => /*#__PURE__*/_jsxs(React.Fragment, {
|
|
1353
|
+
children: [buildWidget(child, {
|
|
1354
|
+
...params,
|
|
1355
|
+
allowFlexExpansion: true,
|
|
1356
|
+
parentFlexAxis: axis === 'horizontal' ? 'horizontal' : 'vertical',
|
|
1357
|
+
parentMainAxisBounded: mainAxisBounded,
|
|
1358
|
+
pageAxisBounds
|
|
1359
|
+
}), applySpacing && spacing > 0 && index < (childrenJson?.length ?? 0) - 1 ? /*#__PURE__*/_jsx(View, {
|
|
1360
|
+
style: {
|
|
1361
|
+
width: axis === 'horizontal' ? spacing : undefined,
|
|
1362
|
+
height: axis === 'vertical' ? spacing : undefined
|
|
1363
|
+
}
|
|
1364
|
+
}) : null]
|
|
836
1365
|
}, `stack-${index}`))
|
|
837
1366
|
});
|
|
1367
|
+
if (stackGradient) {
|
|
1368
|
+
node = /*#__PURE__*/_jsx(LinearGradient, {
|
|
1369
|
+
colors: stackGradient.colors,
|
|
1370
|
+
start: stackGradient.start,
|
|
1371
|
+
end: stackGradient.end,
|
|
1372
|
+
locations: stackGradient.stops,
|
|
1373
|
+
style: baseStackStyle,
|
|
1374
|
+
children: content
|
|
1375
|
+
});
|
|
1376
|
+
} else {
|
|
1377
|
+
node = /*#__PURE__*/_jsx(View, {
|
|
1378
|
+
style: {
|
|
1379
|
+
...baseStackStyle,
|
|
1380
|
+
backgroundColor: stackColor
|
|
1381
|
+
},
|
|
1382
|
+
children: content
|
|
1383
|
+
});
|
|
1384
|
+
}
|
|
838
1385
|
break;
|
|
839
1386
|
}
|
|
840
1387
|
case 'positioned':
|
|
@@ -865,35 +1412,55 @@ function buildWidget(json, params) {
|
|
|
865
1412
|
node = null;
|
|
866
1413
|
}
|
|
867
1414
|
if (!node) return null;
|
|
868
|
-
const marginVal = props.margin;
|
|
1415
|
+
const marginVal = type === 'stack' ? props.layout?.margin ?? props.margin : props.margin;
|
|
869
1416
|
if (marginVal !== undefined && marginVal !== null) {
|
|
870
|
-
const marginInsets = parseInsets(marginVal);
|
|
1417
|
+
const marginInsets = type === 'stack' ? stackSpacingToInsets(marginVal) : parseInsets(marginVal);
|
|
871
1418
|
node = /*#__PURE__*/_jsx(View, {
|
|
872
1419
|
style: insetsToMarginStyle(marginInsets),
|
|
873
1420
|
children: node
|
|
874
1421
|
});
|
|
875
|
-
} else if (type !== 'container' && type !== 'padding' && props.padding) {
|
|
1422
|
+
} else if (type !== 'container' && type !== 'padding' && type !== 'stack' && props.padding) {
|
|
876
1423
|
const paddingInsets = parseInsets(props.padding);
|
|
877
1424
|
node = /*#__PURE__*/_jsx(View, {
|
|
878
1425
|
style: insetsToStyle(paddingInsets),
|
|
879
1426
|
children: node
|
|
880
1427
|
});
|
|
881
1428
|
}
|
|
882
|
-
if (params.allowFlexExpansion
|
|
1429
|
+
if (params.allowFlexExpansion) {
|
|
883
1430
|
let shouldExpand = false;
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
1431
|
+
const parentAxis = params.parentFlexAxis ?? 'vertical';
|
|
1432
|
+
const parentMainAxisBounded = params.parentMainAxisBounded ?? true;
|
|
1433
|
+
if (parentMainAxisBounded) {
|
|
1434
|
+
if (type === 'stack') {
|
|
1435
|
+
const legacyExpand = props.fit === 'expand';
|
|
1436
|
+
const widthMode = String(props.size?.width ?? '').toLowerCase();
|
|
1437
|
+
const heightMode = String(props.size?.height ?? '').toLowerCase();
|
|
1438
|
+
if (parentAxis === 'vertical' && (legacyExpand || heightMode === 'fill')) {
|
|
1439
|
+
shouldExpand = true;
|
|
1440
|
+
}
|
|
1441
|
+
if (parentAxis === 'horizontal' && (legacyExpand || widthMode === 'fill')) {
|
|
1442
|
+
shouldExpand = true;
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
if (parentAxis === 'vertical' && props.height !== undefined) {
|
|
1446
|
+
const heightVal = parseLayoutDimension(props.height);
|
|
1447
|
+
if (heightVal === Number.POSITIVE_INFINITY) {
|
|
1448
|
+
shouldExpand = true;
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
if (parentAxis === 'horizontal' && props.width !== undefined) {
|
|
1452
|
+
const widthVal = parseLayoutDimension(props.width);
|
|
1453
|
+
if (widthVal === Number.POSITIVE_INFINITY) {
|
|
1454
|
+
shouldExpand = true;
|
|
1455
|
+
}
|
|
891
1456
|
}
|
|
892
1457
|
}
|
|
893
1458
|
if (shouldExpand) {
|
|
894
1459
|
node = /*#__PURE__*/_jsx(View, {
|
|
895
1460
|
style: {
|
|
896
|
-
flex: 1
|
|
1461
|
+
flex: 1,
|
|
1462
|
+
minHeight: 0,
|
|
1463
|
+
minWidth: 0
|
|
897
1464
|
},
|
|
898
1465
|
children: node
|
|
899
1466
|
});
|
|
@@ -958,6 +1525,132 @@ function parseGradient(value) {
|
|
|
958
1525
|
stops
|
|
959
1526
|
};
|
|
960
1527
|
}
|
|
1528
|
+
function normalizeButtonStroke(input) {
|
|
1529
|
+
if (!input || typeof input !== 'object') return null;
|
|
1530
|
+
const legacyStrokeWithoutEnabled = input.enabled === undefined;
|
|
1531
|
+
const enabled = legacyStrokeWithoutEnabled ? true : input.enabled === true;
|
|
1532
|
+
if (!enabled) return null;
|
|
1533
|
+
const width = Number(input.width ?? 1);
|
|
1534
|
+
if (!Number.isFinite(width) || width <= 0) return null;
|
|
1535
|
+
const position = input.position === 'inside' || input.position === 'center' || input.position === 'outside' ? input.position : 'center';
|
|
1536
|
+
const opacity = clamp01(Number(input.opacity ?? 1));
|
|
1537
|
+
const color = normalizeColorWithOpacity(input.color, opacity);
|
|
1538
|
+
if (color === 'transparent') return null;
|
|
1539
|
+
return {
|
|
1540
|
+
color,
|
|
1541
|
+
width,
|
|
1542
|
+
position
|
|
1543
|
+
};
|
|
1544
|
+
}
|
|
1545
|
+
function normalizeButtonEffects(effectsInput, legacyShadowInput) {
|
|
1546
|
+
if (Array.isArray(effectsInput)) {
|
|
1547
|
+
return effectsInput.map(normalizeDropShadowEffect).filter(effect => effect !== null);
|
|
1548
|
+
}
|
|
1549
|
+
if (!legacyShadowInput || typeof legacyShadowInput !== 'object') {
|
|
1550
|
+
return [];
|
|
1551
|
+
}
|
|
1552
|
+
if (legacyShadowInput.enabled !== true) return [];
|
|
1553
|
+
const migrated = normalizeDropShadowEffect({
|
|
1554
|
+
type: 'dropShadow',
|
|
1555
|
+
enabled: true,
|
|
1556
|
+
x: legacyShadowInput.x,
|
|
1557
|
+
y: legacyShadowInput.y,
|
|
1558
|
+
blur: legacyShadowInput.blur,
|
|
1559
|
+
spread: 0,
|
|
1560
|
+
color: legacyShadowInput.color,
|
|
1561
|
+
opacity: 1
|
|
1562
|
+
});
|
|
1563
|
+
return migrated ? [migrated] : [];
|
|
1564
|
+
}
|
|
1565
|
+
function normalizeDropShadowEffect(effect) {
|
|
1566
|
+
if (!effect || typeof effect !== 'object') return null;
|
|
1567
|
+
if ((effect.type ?? 'dropShadow') !== 'dropShadow') return null;
|
|
1568
|
+
if (effect.enabled === false) return null;
|
|
1569
|
+
const opacity = clamp01(Number(effect.opacity ?? 1));
|
|
1570
|
+
const color = normalizeColorWithOpacity(effect.color, opacity);
|
|
1571
|
+
if (color === 'transparent') return null;
|
|
1572
|
+
return {
|
|
1573
|
+
x: Number(effect.x ?? 0),
|
|
1574
|
+
y: Number(effect.y ?? 0),
|
|
1575
|
+
blur: Math.max(0, Number(effect.blur ?? 0)),
|
|
1576
|
+
spread: Number(effect.spread ?? 0),
|
|
1577
|
+
color,
|
|
1578
|
+
opacity
|
|
1579
|
+
};
|
|
1580
|
+
}
|
|
1581
|
+
function normalizeColorWithOpacity(input, opacity) {
|
|
1582
|
+
const raw = typeof input === 'string' ? input.trim() : '';
|
|
1583
|
+
const normalizedOpacity = clamp01(opacity);
|
|
1584
|
+
if (raw.startsWith('rgba(') || raw.startsWith('rgb(')) {
|
|
1585
|
+
return withOpacity(raw, normalizedOpacity);
|
|
1586
|
+
}
|
|
1587
|
+
return withOpacity(parseColor(input ?? '0xFF000000'), normalizedOpacity);
|
|
1588
|
+
}
|
|
1589
|
+
function buildButtonWebShadowStyle(effects) {
|
|
1590
|
+
if (effects.length === 0) return null;
|
|
1591
|
+
const layers = effects.map(effect => `${effect.x}px ${effect.y}px ${effect.blur}px ${effect.spread}px ${effect.color}`);
|
|
1592
|
+
return {
|
|
1593
|
+
boxShadow: layers.join(', ')
|
|
1594
|
+
};
|
|
1595
|
+
}
|
|
1596
|
+
function buildButtonNativeShadowStyle(effect) {
|
|
1597
|
+
const rgba = parseRgbaColor(effect.color);
|
|
1598
|
+
return {
|
|
1599
|
+
// Android elevation requires a drawable host shape; keep it effectively invisible.
|
|
1600
|
+
backgroundColor: 'rgba(255,255,255,0.02)',
|
|
1601
|
+
shadowColor: rgba ? `rgba(${rgba.r},${rgba.g},${rgba.b},1)` : effect.color,
|
|
1602
|
+
shadowOffset: {
|
|
1603
|
+
width: effect.x,
|
|
1604
|
+
height: effect.y
|
|
1605
|
+
},
|
|
1606
|
+
shadowOpacity: rgba ? rgba.a : effect.opacity,
|
|
1607
|
+
shadowRadius: effect.blur / 2,
|
|
1608
|
+
elevation: Math.max(1, Math.round((effect.blur + Math.abs(effect.y)) / 3))
|
|
1609
|
+
};
|
|
1610
|
+
}
|
|
1611
|
+
function getButtonShadowLayerBounds(stroke, borderRadius) {
|
|
1612
|
+
const expandBy = !stroke || stroke.position === 'inside' ? 0 : stroke.position === 'center' ? stroke.width / 2 : stroke.width;
|
|
1613
|
+
return {
|
|
1614
|
+
top: -expandBy,
|
|
1615
|
+
right: -expandBy,
|
|
1616
|
+
bottom: -expandBy,
|
|
1617
|
+
left: -expandBy,
|
|
1618
|
+
borderRadius: borderRadius + expandBy
|
|
1619
|
+
};
|
|
1620
|
+
}
|
|
1621
|
+
function renderButtonStrokeOverlay(stroke, borderRadius) {
|
|
1622
|
+
if (!stroke || stroke.position === 'center') {
|
|
1623
|
+
return null;
|
|
1624
|
+
}
|
|
1625
|
+
if (stroke.position === 'inside') {
|
|
1626
|
+
return /*#__PURE__*/_jsx(View, {
|
|
1627
|
+
pointerEvents: "none",
|
|
1628
|
+
style: {
|
|
1629
|
+
position: 'absolute',
|
|
1630
|
+
top: stroke.width / 2,
|
|
1631
|
+
left: stroke.width / 2,
|
|
1632
|
+
right: stroke.width / 2,
|
|
1633
|
+
bottom: stroke.width / 2,
|
|
1634
|
+
borderRadius: Math.max(0, borderRadius - stroke.width / 2),
|
|
1635
|
+
borderWidth: stroke.width,
|
|
1636
|
+
borderColor: stroke.color
|
|
1637
|
+
}
|
|
1638
|
+
});
|
|
1639
|
+
}
|
|
1640
|
+
return /*#__PURE__*/_jsx(View, {
|
|
1641
|
+
pointerEvents: "none",
|
|
1642
|
+
style: {
|
|
1643
|
+
position: 'absolute',
|
|
1644
|
+
top: -stroke.width,
|
|
1645
|
+
left: -stroke.width,
|
|
1646
|
+
right: -stroke.width,
|
|
1647
|
+
bottom: -stroke.width,
|
|
1648
|
+
borderRadius: borderRadius + stroke.width,
|
|
1649
|
+
borderWidth: stroke.width,
|
|
1650
|
+
borderColor: stroke.color
|
|
1651
|
+
}
|
|
1652
|
+
});
|
|
1653
|
+
}
|
|
961
1654
|
function alignmentToGradient(value) {
|
|
962
1655
|
switch (value) {
|
|
963
1656
|
case 'topCenter':
|
|
@@ -1009,10 +1702,23 @@ function alignmentToGradient(value) {
|
|
|
1009
1702
|
}
|
|
1010
1703
|
}
|
|
1011
1704
|
function withOpacity(color, opacity) {
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
}
|
|
1015
|
-
|
|
1705
|
+
const rgba = parseRgbaColor(color);
|
|
1706
|
+
if (!rgba) return color;
|
|
1707
|
+
return `rgba(${rgba.r},${rgba.g},${rgba.b},${clamp01(rgba.a * opacity)})`;
|
|
1708
|
+
}
|
|
1709
|
+
function parseRgbaColor(color) {
|
|
1710
|
+
const match = color.match(/^rgba?\(\s*([0-9.]+)\s*,\s*([0-9.]+)\s*,\s*([0-9.]+)(?:\s*,\s*([0-9.]+))?\s*\)$/i);
|
|
1711
|
+
if (!match) return null;
|
|
1712
|
+
return {
|
|
1713
|
+
r: Math.round(Number(match[1])),
|
|
1714
|
+
g: Math.round(Number(match[2])),
|
|
1715
|
+
b: Math.round(Number(match[3])),
|
|
1716
|
+
a: match[4] === undefined ? 1 : clamp01(Number(match[4]))
|
|
1717
|
+
};
|
|
1718
|
+
}
|
|
1719
|
+
function clamp01(value) {
|
|
1720
|
+
if (!Number.isFinite(value)) return 1;
|
|
1721
|
+
return Math.max(0, Math.min(1, value));
|
|
1016
1722
|
}
|
|
1017
1723
|
function normalizeDimension(value) {
|
|
1018
1724
|
if (value === Number.POSITIVE_INFINITY) {
|
|
@@ -1140,6 +1846,44 @@ function GradientText({
|
|
|
1140
1846
|
})
|
|
1141
1847
|
});
|
|
1142
1848
|
}
|
|
1849
|
+
function resolveTextInputBackgroundColor(properties) {
|
|
1850
|
+
const canonical = properties.backgroundColor;
|
|
1851
|
+
if (typeof canonical === 'string' && canonical.trim().length > 0) {
|
|
1852
|
+
return parseColor(canonical);
|
|
1853
|
+
}
|
|
1854
|
+
const legacyBackground = properties.background;
|
|
1855
|
+
if (legacyBackground && typeof legacyBackground === 'object' && !Array.isArray(legacyBackground)) {
|
|
1856
|
+
const type = String(legacyBackground.type ?? '').toLowerCase();
|
|
1857
|
+
if (type === 'color' && typeof legacyBackground.color === 'string') {
|
|
1858
|
+
return parseColor(legacyBackground.color);
|
|
1859
|
+
}
|
|
1860
|
+
if (type === 'gradient' && Array.isArray(legacyBackground.colors)) {
|
|
1861
|
+
const firstGradientColor = legacyBackground.colors.find(value => typeof value === 'string' && value.trim().length > 0);
|
|
1862
|
+
if (typeof firstGradientColor === 'string') {
|
|
1863
|
+
return parseColor(firstGradientColor);
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
} else if (typeof legacyBackground === 'string' && legacyBackground.trim().length > 0) {
|
|
1867
|
+
return parseColor(legacyBackground);
|
|
1868
|
+
}
|
|
1869
|
+
return parseColor('0xFFF0F0F0');
|
|
1870
|
+
}
|
|
1871
|
+
function normalizeTextInputStroke(properties) {
|
|
1872
|
+
const parsedWidth = Number(properties.strokeWidth ?? 0);
|
|
1873
|
+
const width = Number.isFinite(parsedWidth) && parsedWidth > 0 ? parsedWidth : 0;
|
|
1874
|
+
const enabled = typeof properties.strokeEnabled === 'boolean' ? properties.strokeEnabled : width > 0;
|
|
1875
|
+
const normalizedWidth = enabled ? width : 0;
|
|
1876
|
+
const color = normalizedWidth > 0 ? parseColor(properties.strokeColor ?? '#000000') : 'transparent';
|
|
1877
|
+
const parsedRadius = Number(properties.strokeRadius ?? properties.borderRadius ?? 8);
|
|
1878
|
+
const radius = Number.isFinite(parsedRadius) ? Math.max(0, parsedRadius) : 8;
|
|
1879
|
+
const style = properties.strokeStyle === 'dashed' || properties.strokeStyle === 'dotted' ? properties.strokeStyle : 'solid';
|
|
1880
|
+
return {
|
|
1881
|
+
width: normalizedWidth,
|
|
1882
|
+
color,
|
|
1883
|
+
radius,
|
|
1884
|
+
style
|
|
1885
|
+
};
|
|
1886
|
+
}
|
|
1143
1887
|
function DynamicInput({
|
|
1144
1888
|
initialValue,
|
|
1145
1889
|
properties,
|
|
@@ -1163,8 +1907,10 @@ function DynamicInput({
|
|
|
1163
1907
|
...properties,
|
|
1164
1908
|
height: null
|
|
1165
1909
|
}, 'textColor');
|
|
1166
|
-
const backgroundColor =
|
|
1167
|
-
const
|
|
1910
|
+
const backgroundColor = resolveTextInputBackgroundColor(properties);
|
|
1911
|
+
const stroke = normalizeTextInputStroke(properties);
|
|
1912
|
+
const parsedPadding = Number(properties.padding ?? 12);
|
|
1913
|
+
const padding = Number.isFinite(parsedPadding) ? Math.max(0, parsedPadding) : 12;
|
|
1168
1914
|
return /*#__PURE__*/_jsxs(View, {
|
|
1169
1915
|
children: [label ? /*#__PURE__*/_jsx(Text, {
|
|
1170
1916
|
style: labelStyle,
|
|
@@ -1172,9 +1918,11 @@ function DynamicInput({
|
|
|
1172
1918
|
}) : null, /*#__PURE__*/_jsx(View, {
|
|
1173
1919
|
style: {
|
|
1174
1920
|
backgroundColor,
|
|
1175
|
-
borderRadius,
|
|
1176
|
-
|
|
1177
|
-
|
|
1921
|
+
borderRadius: stroke.radius,
|
|
1922
|
+
borderStyle: stroke.width > 0 ? stroke.style : undefined,
|
|
1923
|
+
borderWidth: stroke.width,
|
|
1924
|
+
borderColor: stroke.color,
|
|
1925
|
+
padding
|
|
1178
1926
|
},
|
|
1179
1927
|
children: mask ? /*#__PURE__*/_jsx(MaskInput, {
|
|
1180
1928
|
value: value,
|
|
@@ -1313,6 +2061,7 @@ function SelectionList({
|
|
|
1313
2061
|
if (layout === 'row') {
|
|
1314
2062
|
return /*#__PURE__*/_jsx(View, {
|
|
1315
2063
|
style: {
|
|
2064
|
+
width: '100%',
|
|
1316
2065
|
flexDirection: 'row',
|
|
1317
2066
|
flexWrap: 'wrap'
|
|
1318
2067
|
},
|
|
@@ -1330,6 +2079,7 @@ function SelectionList({
|
|
|
1330
2079
|
const aspectRatio = Number(properties.gridAspectRatio ?? 1);
|
|
1331
2080
|
return /*#__PURE__*/_jsx(View, {
|
|
1332
2081
|
style: {
|
|
2082
|
+
width: '100%',
|
|
1333
2083
|
flexDirection: 'row',
|
|
1334
2084
|
flexWrap: 'wrap'
|
|
1335
2085
|
},
|
|
@@ -1345,8 +2095,12 @@ function SelectionList({
|
|
|
1345
2095
|
});
|
|
1346
2096
|
}
|
|
1347
2097
|
return /*#__PURE__*/_jsx(View, {
|
|
2098
|
+
style: {
|
|
2099
|
+
width: '100%'
|
|
2100
|
+
},
|
|
1348
2101
|
children: options.map((option, index) => /*#__PURE__*/_jsx(View, {
|
|
1349
2102
|
style: {
|
|
2103
|
+
width: '100%',
|
|
1350
2104
|
marginBottom: spacing
|
|
1351
2105
|
},
|
|
1352
2106
|
children: renderItem(option, index)
|
|
@@ -1363,6 +2117,9 @@ function WheelPicker({
|
|
|
1363
2117
|
onAction,
|
|
1364
2118
|
buildWidget: _buildWidget
|
|
1365
2119
|
}) {
|
|
2120
|
+
const triggerLightHaptic = React.useCallback(() => {
|
|
2121
|
+
Vibration.vibrate(8);
|
|
2122
|
+
}, []);
|
|
1366
2123
|
const multiSelect = properties.multiSelect === true;
|
|
1367
2124
|
const [selectedValues, setSelectedValues] = React.useState(() => {
|
|
1368
2125
|
if (initialValue === null || initialValue === undefined) return [];
|
|
@@ -1417,6 +2174,18 @@ function WheelPicker({
|
|
|
1417
2174
|
const visibleItems = requestedVisible % 2 === 0 ? requestedVisible + 1 : requestedVisible;
|
|
1418
2175
|
const wheelRowStride = wheelItemHeight + Math.max(0, spacing);
|
|
1419
2176
|
const wheelContainerHeight = wheelItemHeight * visibleItems;
|
|
2177
|
+
const hasProvidedInitialValue = React.useMemo(() => {
|
|
2178
|
+
if (initialValue === null || initialValue === undefined) return false;
|
|
2179
|
+
if (Array.isArray(initialValue)) return initialValue.length > 0;
|
|
2180
|
+
if (typeof initialValue === 'string') return initialValue.trim().length > 0;
|
|
2181
|
+
return true;
|
|
2182
|
+
}, [initialValue]);
|
|
2183
|
+
const resolveStartItemIndex = React.useCallback(() => {
|
|
2184
|
+
const raw = properties.startItemIndex ?? properties.start_index ?? properties.startIndex ?? 1;
|
|
2185
|
+
const parsed = Number(raw);
|
|
2186
|
+
if (!Number.isFinite(parsed)) return 1;
|
|
2187
|
+
return Math.max(1, Math.floor(parsed));
|
|
2188
|
+
}, [properties.startIndex, properties.startItemIndex, properties.start_index]);
|
|
1420
2189
|
const clampWheelIndex = React.useCallback(index => Math.max(0, Math.min(index, Math.max(0, resolvedOptions.length - 1))), [resolvedOptions.length]);
|
|
1421
2190
|
const getWheelSelectedIndex = React.useCallback(() => {
|
|
1422
2191
|
if (resolvedOptions.length === 0) return 0;
|
|
@@ -1439,6 +2208,7 @@ function WheelPicker({
|
|
|
1439
2208
|
});
|
|
1440
2209
|
if (lastCommittedWheelValue.current !== value) {
|
|
1441
2210
|
lastCommittedWheelValue.current = value;
|
|
2211
|
+
triggerLightHaptic();
|
|
1442
2212
|
onChanged(value);
|
|
1443
2213
|
if (properties.autoGoNext === true) {
|
|
1444
2214
|
requestAnimationFrame(() => onAction('next', {
|
|
@@ -1446,7 +2216,7 @@ function WheelPicker({
|
|
|
1446
2216
|
}));
|
|
1447
2217
|
}
|
|
1448
2218
|
}
|
|
1449
|
-
}, [clampWheelIndex, onAction, onChanged, properties.autoGoNext, resolvedOptions]);
|
|
2219
|
+
}, [clampWheelIndex, onAction, onChanged, properties.autoGoNext, resolvedOptions, triggerLightHaptic]);
|
|
1450
2220
|
React.useEffect(() => {
|
|
1451
2221
|
if (layout !== 'wheel' || resolvedOptions.length === 0) return;
|
|
1452
2222
|
const selectedIndex = getWheelSelectedIndex();
|
|
@@ -1459,6 +2229,18 @@ function WheelPicker({
|
|
|
1459
2229
|
wheelScrollY.setValue(offsetY);
|
|
1460
2230
|
});
|
|
1461
2231
|
}, [getWheelSelectedIndex, layout, resolvedOptions.length, wheelRowStride, wheelScrollY]);
|
|
2232
|
+
React.useEffect(() => {
|
|
2233
|
+
if (layout !== 'wheel') return;
|
|
2234
|
+
if (hasProvidedInitialValue) return;
|
|
2235
|
+
if (selectedValues.length > 0) return;
|
|
2236
|
+
if (resolvedOptions.length === 0) return;
|
|
2237
|
+
const oneBasedIndex = resolveStartItemIndex();
|
|
2238
|
+
const targetIndex = Math.max(0, Math.min(resolvedOptions.length - 1, oneBasedIndex - 1));
|
|
2239
|
+
const value = String(resolvedOptions[targetIndex]?.value ?? '');
|
|
2240
|
+
if (!value) return;
|
|
2241
|
+
setSelectedValues([value]);
|
|
2242
|
+
onChanged(value);
|
|
2243
|
+
}, [hasProvidedInitialValue, layout, onChanged, resolveStartItemIndex, resolvedOptions, selectedValues.length]);
|
|
1462
2244
|
const renderItem = (option, index) => {
|
|
1463
2245
|
const value = String(option.value ?? '');
|
|
1464
2246
|
const label = String(option.text ?? value);
|
|
@@ -1538,6 +2320,8 @@ function WheelPicker({
|
|
|
1538
2320
|
const baseUnselectedStyle = properties.unselectedStyle ?? properties.selectedStyle ?? {};
|
|
1539
2321
|
const overlayBorderColor = parseColor(properties.wheelCenterBorderColor ?? properties.wheelSelectedBorderColor ?? baseUnselectedStyle.borderColor ?? '#D6D6DC');
|
|
1540
2322
|
const overlayBackgroundColor = parseColor(properties.wheelCenterBackgroundColor ?? '#24FFFFFF');
|
|
2323
|
+
const wheelEdgeFadeOpacity = Math.max(0, Math.min(1, Number(properties.wheelEdgeFadeOpacity ?? 0)));
|
|
2324
|
+
const wheelEdgeFadeColor = parseColor(properties.wheelEdgeFadeColor ?? '#FFFFFF');
|
|
1541
2325
|
const handleWheelMomentumEnd = event => {
|
|
1542
2326
|
const offsetY = Number(event?.nativeEvent?.contentOffset?.y ?? 0);
|
|
1543
2327
|
const settledIndex = clampWheelIndex(Math.round(offsetY / wheelRowStride));
|
|
@@ -1636,9 +2420,9 @@ function WheelPicker({
|
|
|
1636
2420
|
borderColor: overlayBorderColor,
|
|
1637
2421
|
backgroundColor: overlayBackgroundColor
|
|
1638
2422
|
}
|
|
1639
|
-
}), /*#__PURE__*/_jsx(LinearGradient, {
|
|
2423
|
+
}), wheelEdgeFadeOpacity > 0 && centerPadding > 0 ? /*#__PURE__*/_jsx(LinearGradient, {
|
|
1640
2424
|
pointerEvents: "none",
|
|
1641
|
-
colors: [
|
|
2425
|
+
colors: [withOpacity(wheelEdgeFadeColor, wheelEdgeFadeOpacity), withOpacity(wheelEdgeFadeColor, 0)],
|
|
1642
2426
|
style: {
|
|
1643
2427
|
position: 'absolute',
|
|
1644
2428
|
top: 0,
|
|
@@ -1646,9 +2430,9 @@ function WheelPicker({
|
|
|
1646
2430
|
right: 0,
|
|
1647
2431
|
height: centerPadding
|
|
1648
2432
|
}
|
|
1649
|
-
}), /*#__PURE__*/_jsx(LinearGradient, {
|
|
2433
|
+
}) : null, wheelEdgeFadeOpacity > 0 && centerPadding > 0 ? /*#__PURE__*/_jsx(LinearGradient, {
|
|
1650
2434
|
pointerEvents: "none",
|
|
1651
|
-
colors: [
|
|
2435
|
+
colors: [withOpacity(wheelEdgeFadeColor, 0), withOpacity(wheelEdgeFadeColor, wheelEdgeFadeOpacity)],
|
|
1652
2436
|
style: {
|
|
1653
2437
|
position: 'absolute',
|
|
1654
2438
|
bottom: 0,
|
|
@@ -1656,7 +2440,7 @@ function WheelPicker({
|
|
|
1656
2440
|
right: 0,
|
|
1657
2441
|
height: centerPadding
|
|
1658
2442
|
}
|
|
1659
|
-
})]
|
|
2443
|
+
}) : null]
|
|
1660
2444
|
});
|
|
1661
2445
|
}
|
|
1662
2446
|
return /*#__PURE__*/_jsx(View, {
|
|
@@ -2085,16 +2869,33 @@ function RadarChart({
|
|
|
2085
2869
|
const axisColor = parseColor(properties.axisColor ?? '0xFF9E9E9E');
|
|
2086
2870
|
const axisStrokeWidth = Number(properties.axisStrokeWidth ?? 1);
|
|
2087
2871
|
const axisLabelStyle = getTextStyle(properties.axisLabelStyle ?? {}, 'color', '0xFF424242');
|
|
2872
|
+
const resolvedAxisLabelFontSize = resolveRadarAxisLabelFontSize(axisLabelStyle.fontSize);
|
|
2873
|
+
const autoFitLabels = properties.autoFitLabels !== false;
|
|
2874
|
+
const resolvedLabelOffset = resolveRadarLabelOffset(properties.labelOffset, resolvedAxisLabelFontSize);
|
|
2088
2875
|
const showLegend = properties.showLegend !== false;
|
|
2089
2876
|
const legendPosition = (properties.legendPosition ?? 'bottom').toLowerCase();
|
|
2090
2877
|
const legendSpacing = Number(properties.legendSpacing ?? 12);
|
|
2091
2878
|
const legendStyle = getTextStyle(properties.legendStyle ?? {}, 'color', '0xFF212121');
|
|
2092
2879
|
const shape = (properties.shape ?? 'polygon').toLowerCase();
|
|
2093
|
-
const
|
|
2094
|
-
const
|
|
2880
|
+
const innerWidth = Math.max(1, width - padding.left - padding.right);
|
|
2881
|
+
const innerHeight = Math.max(1, height - padding.top - padding.bottom);
|
|
2882
|
+
const radius = calculateRadarChartRadius({
|
|
2883
|
+
width: innerWidth,
|
|
2884
|
+
height: innerHeight,
|
|
2885
|
+
padding: {
|
|
2886
|
+
left: 0,
|
|
2887
|
+
right: 0,
|
|
2888
|
+
top: 0,
|
|
2889
|
+
bottom: 0
|
|
2890
|
+
},
|
|
2891
|
+
axisLabels: axes.map(axis => axis.label),
|
|
2892
|
+
fontSize: resolvedAxisLabelFontSize,
|
|
2893
|
+
autoFitLabels,
|
|
2894
|
+
labelOffset: resolvedLabelOffset
|
|
2895
|
+
});
|
|
2095
2896
|
const center = {
|
|
2096
|
-
x:
|
|
2097
|
-
y:
|
|
2897
|
+
x: padding.left + innerWidth / 2,
|
|
2898
|
+
y: padding.top + innerHeight / 2
|
|
2098
2899
|
};
|
|
2099
2900
|
const axisAngle = Math.PI * 2 / axes.length;
|
|
2100
2901
|
const gridPolygons = Array.from({
|
|
@@ -2115,7 +2916,8 @@ function RadarChart({
|
|
|
2115
2916
|
const points = dataset.values.map((value, index) => {
|
|
2116
2917
|
const axis = normalizedAxes[index];
|
|
2117
2918
|
const maxValue = axis?.maxValue ?? 0;
|
|
2118
|
-
const
|
|
2919
|
+
const normalizedRatio = maxValue > 0 ? value / maxValue : 0;
|
|
2920
|
+
const ratio = Math.max(0, Math.min(1, normalizedRatio)) * Math.max(0, Math.min(1, lineProgress));
|
|
2119
2921
|
const angle = index * axisAngle - Math.PI / 2;
|
|
2120
2922
|
const x = center.x + radius * ratio * Math.cos(angle);
|
|
2121
2923
|
const y = center.y + radius * ratio * Math.sin(angle);
|
|
@@ -2126,27 +2928,32 @@ function RadarChart({
|
|
|
2126
2928
|
points
|
|
2127
2929
|
};
|
|
2128
2930
|
});
|
|
2931
|
+
const isVerticalLegend = legendPosition === 'left' || legendPosition === 'right';
|
|
2129
2932
|
const legend = /*#__PURE__*/_jsx(View, {
|
|
2130
2933
|
style: {
|
|
2131
|
-
flexDirection: 'row',
|
|
2132
|
-
flexWrap: 'wrap'
|
|
2934
|
+
flexDirection: isVerticalLegend ? 'column' : 'row',
|
|
2935
|
+
flexWrap: isVerticalLegend ? 'nowrap' : 'wrap',
|
|
2936
|
+
alignItems: isVerticalLegend ? 'flex-start' : 'center'
|
|
2133
2937
|
},
|
|
2134
2938
|
children: datasets.map((dataset, index) => /*#__PURE__*/_jsxs(View, {
|
|
2135
2939
|
style: {
|
|
2136
2940
|
flexDirection: 'row',
|
|
2137
2941
|
alignItems: 'center',
|
|
2138
|
-
marginRight: legendSpacing,
|
|
2139
|
-
marginBottom: 6
|
|
2942
|
+
marginRight: isVerticalLegend ? 0 : legendSpacing,
|
|
2943
|
+
marginBottom: isVerticalLegend ? legendSpacing : 6
|
|
2140
2944
|
},
|
|
2141
2945
|
children: [/*#__PURE__*/_jsx(View, {
|
|
2142
2946
|
style: {
|
|
2143
2947
|
width: 12,
|
|
2144
2948
|
height: 12,
|
|
2145
2949
|
borderRadius: 6,
|
|
2146
|
-
backgroundColor: dataset.borderColor,
|
|
2950
|
+
backgroundColor: dataset.fillColor ?? dataset.borderColor,
|
|
2951
|
+
borderWidth: 1,
|
|
2952
|
+
borderColor: dataset.borderColor,
|
|
2147
2953
|
marginRight: 6
|
|
2148
2954
|
}
|
|
2149
2955
|
}), /*#__PURE__*/_jsx(Text, {
|
|
2956
|
+
numberOfLines: 1,
|
|
2150
2957
|
style: legendStyle,
|
|
2151
2958
|
children: dataset.label
|
|
2152
2959
|
})]
|
|
@@ -2154,12 +2961,10 @@ function RadarChart({
|
|
|
2154
2961
|
});
|
|
2155
2962
|
const chart = /*#__PURE__*/_jsx(Animated.View, {
|
|
2156
2963
|
style: {
|
|
2157
|
-
width: normalizedWidth ??
|
|
2964
|
+
width: isVerticalLegend ? width : normalizedWidth ?? '100%',
|
|
2965
|
+
maxWidth: '100%',
|
|
2966
|
+
flexShrink: 1,
|
|
2158
2967
|
height,
|
|
2159
|
-
paddingLeft: padding.left,
|
|
2160
|
-
paddingRight: padding.right,
|
|
2161
|
-
paddingTop: padding.top,
|
|
2162
|
-
paddingBottom: padding.bottom,
|
|
2163
2968
|
opacity: reveal
|
|
2164
2969
|
},
|
|
2165
2970
|
onLayout: event => {
|
|
@@ -2198,24 +3003,28 @@ function RadarChart({
|
|
|
2198
3003
|
}, `axis-${index}`);
|
|
2199
3004
|
}), axes.map((axis, index) => {
|
|
2200
3005
|
const angle = index * axisAngle - Math.PI / 2;
|
|
2201
|
-
const labelX = center.x + (radius +
|
|
2202
|
-
const labelY = center.y + (radius +
|
|
3006
|
+
const labelX = center.x + (radius + resolvedLabelOffset) * Math.cos(angle);
|
|
3007
|
+
const labelY = center.y + (radius + resolvedLabelOffset) * Math.sin(angle);
|
|
2203
3008
|
return /*#__PURE__*/_jsx(SvgText, {
|
|
2204
3009
|
x: labelX,
|
|
2205
3010
|
y: labelY,
|
|
2206
|
-
fontSize:
|
|
3011
|
+
fontSize: resolvedAxisLabelFontSize,
|
|
2207
3012
|
fontWeight: axisLabelStyle.fontWeight,
|
|
2208
3013
|
fill: axisLabelStyle.color,
|
|
2209
3014
|
textAnchor: "middle",
|
|
2210
3015
|
children: axis.label
|
|
2211
3016
|
}, `label-${index}`);
|
|
2212
|
-
}), datasetPolygons.map((entry, index) =>
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
3017
|
+
}), datasetPolygons.map((entry, index) => {
|
|
3018
|
+
const strokePattern = resolveRadarStrokePattern(entry.dataset);
|
|
3019
|
+
return /*#__PURE__*/_jsx(Polygon, {
|
|
3020
|
+
points: entry.points.join(' '),
|
|
3021
|
+
stroke: entry.dataset.borderColor,
|
|
3022
|
+
strokeWidth: entry.dataset.borderWidth,
|
|
3023
|
+
fill: entry.dataset.fillColor ?? 'transparent',
|
|
3024
|
+
strokeDasharray: strokePattern.strokeDasharray,
|
|
3025
|
+
strokeLinecap: strokePattern.strokeLinecap
|
|
3026
|
+
}, `dataset-${index}`);
|
|
3027
|
+
}), datasetPolygons.map((entry, datasetIndex) => entry.dataset.showPoints ? entry.points.map((point, pointIndex) => {
|
|
2219
3028
|
const [x, y] = point.split(',').map(val => Number(val));
|
|
2220
3029
|
return /*#__PURE__*/_jsx(Circle, {
|
|
2221
3030
|
cx: x,
|
|
@@ -2227,23 +3036,23 @@ function RadarChart({
|
|
|
2227
3036
|
})
|
|
2228
3037
|
})
|
|
2229
3038
|
});
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
backgroundColor
|
|
2233
|
-
},
|
|
2234
|
-
children: [chart, showLegend && legendPosition === 'bottom' ? /*#__PURE__*/_jsx(View, {
|
|
3039
|
+
if (!showLegend) {
|
|
3040
|
+
return /*#__PURE__*/_jsx(View, {
|
|
2235
3041
|
style: {
|
|
2236
|
-
|
|
3042
|
+
backgroundColor
|
|
2237
3043
|
},
|
|
2238
|
-
children:
|
|
2239
|
-
})
|
|
2240
|
-
}
|
|
2241
|
-
if (!showLegend || legendPosition === 'bottom') return composed;
|
|
3044
|
+
children: chart
|
|
3045
|
+
});
|
|
3046
|
+
}
|
|
2242
3047
|
if (legendPosition === 'top') {
|
|
2243
3048
|
return /*#__PURE__*/_jsxs(View, {
|
|
3049
|
+
style: {
|
|
3050
|
+
backgroundColor,
|
|
3051
|
+
alignItems: 'center'
|
|
3052
|
+
},
|
|
2244
3053
|
children: [legend, /*#__PURE__*/_jsx(View, {
|
|
2245
3054
|
style: {
|
|
2246
|
-
height:
|
|
3055
|
+
height: legendSpacing
|
|
2247
3056
|
}
|
|
2248
3057
|
}), chart]
|
|
2249
3058
|
});
|
|
@@ -2251,11 +3060,13 @@ function RadarChart({
|
|
|
2251
3060
|
if (legendPosition === 'left') {
|
|
2252
3061
|
return /*#__PURE__*/_jsxs(View, {
|
|
2253
3062
|
style: {
|
|
2254
|
-
|
|
3063
|
+
backgroundColor,
|
|
3064
|
+
flexDirection: 'row',
|
|
3065
|
+
alignItems: 'center'
|
|
2255
3066
|
},
|
|
2256
3067
|
children: [legend, /*#__PURE__*/_jsx(View, {
|
|
2257
3068
|
style: {
|
|
2258
|
-
width:
|
|
3069
|
+
width: legendSpacing
|
|
2259
3070
|
}
|
|
2260
3071
|
}), chart]
|
|
2261
3072
|
});
|
|
@@ -2263,16 +3074,167 @@ function RadarChart({
|
|
|
2263
3074
|
if (legendPosition === 'right') {
|
|
2264
3075
|
return /*#__PURE__*/_jsxs(View, {
|
|
2265
3076
|
style: {
|
|
2266
|
-
|
|
3077
|
+
backgroundColor,
|
|
3078
|
+
flexDirection: 'row',
|
|
3079
|
+
alignItems: 'center'
|
|
2267
3080
|
},
|
|
2268
3081
|
children: [chart, /*#__PURE__*/_jsx(View, {
|
|
2269
3082
|
style: {
|
|
2270
|
-
width:
|
|
3083
|
+
width: legendSpacing
|
|
2271
3084
|
}
|
|
2272
3085
|
}), legend]
|
|
2273
3086
|
});
|
|
2274
3087
|
}
|
|
2275
|
-
return
|
|
3088
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
3089
|
+
style: {
|
|
3090
|
+
backgroundColor,
|
|
3091
|
+
alignItems: 'center'
|
|
3092
|
+
},
|
|
3093
|
+
children: [chart, /*#__PURE__*/_jsx(View, {
|
|
3094
|
+
style: {
|
|
3095
|
+
height: legendSpacing
|
|
3096
|
+
}
|
|
3097
|
+
}), legend]
|
|
3098
|
+
});
|
|
3099
|
+
}
|
|
3100
|
+
const RADAR_DASH_DEFAULT_LENGTH = 8;
|
|
3101
|
+
const RADAR_DASH_GAP_FACTOR = 0.6;
|
|
3102
|
+
const RADAR_DOT_DASH_LENGTH = 0.001;
|
|
3103
|
+
const RADAR_DOT_GAP_FACTOR = 2.4;
|
|
3104
|
+
const RADAR_DOT_MIN_GAP = 3;
|
|
3105
|
+
const radarDashConflictWarnings = new Set();
|
|
3106
|
+
function isDevelopmentEnvironment() {
|
|
3107
|
+
return process.env.NODE_ENV !== 'production';
|
|
3108
|
+
}
|
|
3109
|
+
function warnRadarDashConflictOnce(label) {
|
|
3110
|
+
if (!isDevelopmentEnvironment()) return;
|
|
3111
|
+
if (radarDashConflictWarnings.has(label)) return;
|
|
3112
|
+
radarDashConflictWarnings.add(label);
|
|
3113
|
+
// Keep API backward-compatible but deterministic when both flags are passed.
|
|
3114
|
+
console.warn(`[RadarChart] Dataset "${label}" received both dotted=true and dashed=true. dashed takes precedence.`);
|
|
3115
|
+
}
|
|
3116
|
+
function resolveRadarBorderStyle(dataset, fallbackLabel) {
|
|
3117
|
+
const dotted = dataset.dotted === true;
|
|
3118
|
+
const dashed = dataset.dashed === true;
|
|
3119
|
+
if (dotted && dashed) {
|
|
3120
|
+
warnRadarDashConflictOnce(fallbackLabel);
|
|
3121
|
+
return 'dashed';
|
|
3122
|
+
}
|
|
3123
|
+
if (dashed) return 'dashed';
|
|
3124
|
+
if (dotted) return 'dotted';
|
|
3125
|
+
const borderStyle = (dataset.borderStyle ?? 'solid').toString().toLowerCase();
|
|
3126
|
+
if (borderStyle === 'dotted' || borderStyle === 'dashed') {
|
|
3127
|
+
return borderStyle;
|
|
3128
|
+
}
|
|
3129
|
+
return 'solid';
|
|
3130
|
+
}
|
|
3131
|
+
function resolveRadarStrokePattern(dataset) {
|
|
3132
|
+
if (dataset.borderStyle === 'dotted') {
|
|
3133
|
+
// Tiny dash + round caps renders circular dots more reliably than short dashes.
|
|
3134
|
+
const gap = Math.max(RADAR_DOT_MIN_GAP, dataset.borderWidth * RADAR_DOT_GAP_FACTOR);
|
|
3135
|
+
return {
|
|
3136
|
+
strokeDasharray: `${RADAR_DOT_DASH_LENGTH} ${gap}`,
|
|
3137
|
+
strokeLinecap: 'round'
|
|
3138
|
+
};
|
|
3139
|
+
}
|
|
3140
|
+
if (dataset.borderStyle === 'dashed') {
|
|
3141
|
+
if (Array.isArray(dataset.dashArray) && dataset.dashArray.length > 0) {
|
|
3142
|
+
const dash = Math.max(1, dataset.dashArray[0] ?? RADAR_DASH_DEFAULT_LENGTH);
|
|
3143
|
+
const gap = Math.max(1, dataset.dashArray[1] ?? dash * RADAR_DASH_GAP_FACTOR);
|
|
3144
|
+
return {
|
|
3145
|
+
strokeDasharray: `${dash} ${gap}`,
|
|
3146
|
+
strokeLinecap: 'butt'
|
|
3147
|
+
};
|
|
3148
|
+
}
|
|
3149
|
+
const dashLength = typeof dataset.dashLength === 'number' && Number.isFinite(dataset.dashLength) && dataset.dashLength > 0 ? dataset.dashLength : RADAR_DASH_DEFAULT_LENGTH;
|
|
3150
|
+
const gap = Math.max(2, dashLength * RADAR_DASH_GAP_FACTOR);
|
|
3151
|
+
return {
|
|
3152
|
+
strokeDasharray: `${dashLength} ${gap}`,
|
|
3153
|
+
strokeLinecap: 'butt'
|
|
3154
|
+
};
|
|
3155
|
+
}
|
|
3156
|
+
return {};
|
|
3157
|
+
}
|
|
3158
|
+
export const RADAR_LABEL_ESTIMATION = {
|
|
3159
|
+
minRadius: 10,
|
|
3160
|
+
safeEdge: 4,
|
|
3161
|
+
minLabelWidthFactor: 1.2,
|
|
3162
|
+
avgCharWidthFactor: 0.58,
|
|
3163
|
+
lineHeightFactor: 1.2,
|
|
3164
|
+
defaultFontSize: 14,
|
|
3165
|
+
minLabelOffset: 20,
|
|
3166
|
+
labelOffsetFactor: 1.5
|
|
3167
|
+
};
|
|
3168
|
+
export function resolveRadarAxisLabelFontSize(value) {
|
|
3169
|
+
const parsed = Number(value);
|
|
3170
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
3171
|
+
return RADAR_LABEL_ESTIMATION.defaultFontSize;
|
|
3172
|
+
}
|
|
3173
|
+
return parsed;
|
|
3174
|
+
}
|
|
3175
|
+
export function resolveRadarLabelOffset(value, fontSize) {
|
|
3176
|
+
const parsed = Number(value);
|
|
3177
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
3178
|
+
return parsed;
|
|
3179
|
+
}
|
|
3180
|
+
return Math.max(RADAR_LABEL_ESTIMATION.minLabelOffset, fontSize * RADAR_LABEL_ESTIMATION.labelOffsetFactor);
|
|
3181
|
+
}
|
|
3182
|
+
export function estimateRadarLabelSize(label, fontSize) {
|
|
3183
|
+
const safeLabel = `${label ?? ''}`;
|
|
3184
|
+
const width = Math.max(safeLabel.length * fontSize * RADAR_LABEL_ESTIMATION.avgCharWidthFactor, fontSize * RADAR_LABEL_ESTIMATION.minLabelWidthFactor);
|
|
3185
|
+
const height = fontSize * RADAR_LABEL_ESTIMATION.lineHeightFactor;
|
|
3186
|
+
return {
|
|
3187
|
+
width,
|
|
3188
|
+
height
|
|
3189
|
+
};
|
|
3190
|
+
}
|
|
3191
|
+
export function calculateRadarChartRadius({
|
|
3192
|
+
width,
|
|
3193
|
+
height,
|
|
3194
|
+
padding,
|
|
3195
|
+
axisLabels,
|
|
3196
|
+
fontSize,
|
|
3197
|
+
autoFitLabels = true,
|
|
3198
|
+
labelOffset
|
|
3199
|
+
}) {
|
|
3200
|
+
const center = {
|
|
3201
|
+
x: width / 2,
|
|
3202
|
+
y: height / 2
|
|
3203
|
+
};
|
|
3204
|
+
const baseRadius = Math.max(Math.min(width, height) / 2 - Math.max(padding.left, padding.right, padding.top, padding.bottom), RADAR_LABEL_ESTIMATION.minRadius);
|
|
3205
|
+
if (!autoFitLabels || axisLabels.length === 0) {
|
|
3206
|
+
return baseRadius;
|
|
3207
|
+
}
|
|
3208
|
+
const angleStep = Math.PI * 2 / axisLabels.length;
|
|
3209
|
+
let maxAllowedRadius = baseRadius;
|
|
3210
|
+
for (let index = 0; index < axisLabels.length; index += 1) {
|
|
3211
|
+
const {
|
|
3212
|
+
width: labelWidth,
|
|
3213
|
+
height: labelHeight
|
|
3214
|
+
} = estimateRadarLabelSize(axisLabels[index] ?? '', fontSize);
|
|
3215
|
+
const halfWidth = labelWidth / 2;
|
|
3216
|
+
const halfHeight = labelHeight / 2;
|
|
3217
|
+
const angle = index * angleStep - Math.PI / 2;
|
|
3218
|
+
const dx = Math.cos(angle);
|
|
3219
|
+
const dy = Math.sin(angle);
|
|
3220
|
+
|
|
3221
|
+
// Solve per-axis line constraints so estimated label bounds stay inside.
|
|
3222
|
+
if (dx > 0.0001) {
|
|
3223
|
+
maxAllowedRadius = Math.min(maxAllowedRadius, (width - RADAR_LABEL_ESTIMATION.safeEdge - halfWidth - center.x) / dx - labelOffset);
|
|
3224
|
+
} else if (dx < -0.0001) {
|
|
3225
|
+
maxAllowedRadius = Math.min(maxAllowedRadius, (RADAR_LABEL_ESTIMATION.safeEdge + halfWidth - center.x) / dx - labelOffset);
|
|
3226
|
+
}
|
|
3227
|
+
if (dy > 0.0001) {
|
|
3228
|
+
maxAllowedRadius = Math.min(maxAllowedRadius, (height - RADAR_LABEL_ESTIMATION.safeEdge - halfHeight - center.y) / dy - labelOffset);
|
|
3229
|
+
} else if (dy < -0.0001) {
|
|
3230
|
+
maxAllowedRadius = Math.min(maxAllowedRadius, (RADAR_LABEL_ESTIMATION.safeEdge + halfHeight - center.y) / dy - labelOffset);
|
|
3231
|
+
}
|
|
3232
|
+
}
|
|
3233
|
+
const boundedRadius = Math.min(maxAllowedRadius, baseRadius);
|
|
3234
|
+
if (!Number.isFinite(boundedRadius)) {
|
|
3235
|
+
return baseRadius;
|
|
3236
|
+
}
|
|
3237
|
+
return Math.max(boundedRadius, RADAR_LABEL_ESTIMATION.minRadius);
|
|
2276
3238
|
}
|
|
2277
3239
|
export function parseRadarAxes(rawAxes) {
|
|
2278
3240
|
if (!Array.isArray(rawAxes)) return [];
|
|
@@ -2282,9 +3244,10 @@ export function parseRadarAxes(rawAxes) {
|
|
|
2282
3244
|
const label = axis.label ?? '';
|
|
2283
3245
|
if (!label) return;
|
|
2284
3246
|
const maxValue = resolveNumericValue(axis.maxValue, {}) ?? 100;
|
|
3247
|
+
const normalizedMaxValue = Number.isFinite(maxValue) && maxValue > 0 ? maxValue : 100;
|
|
2285
3248
|
axes.push({
|
|
2286
3249
|
label,
|
|
2287
|
-
maxValue:
|
|
3250
|
+
maxValue: normalizedMaxValue
|
|
2288
3251
|
});
|
|
2289
3252
|
});
|
|
2290
3253
|
return axes;
|
|
@@ -2292,24 +3255,27 @@ export function parseRadarAxes(rawAxes) {
|
|
|
2292
3255
|
export function parseRadarDatasets(raw, axisLength, formData) {
|
|
2293
3256
|
if (!Array.isArray(raw) || axisLength === 0) return [];
|
|
2294
3257
|
const datasets = [];
|
|
2295
|
-
raw.forEach(dataset => {
|
|
3258
|
+
raw.forEach((dataset, datasetIndex) => {
|
|
2296
3259
|
if (!dataset || typeof dataset !== 'object') return;
|
|
2297
3260
|
const valuesRaw = Array.isArray(dataset.data) ? dataset.data : [];
|
|
2298
3261
|
const values = [];
|
|
2299
3262
|
for (let i = 0; i < axisLength; i += 1) {
|
|
2300
3263
|
const rawValue = i < valuesRaw.length ? valuesRaw[i] : null;
|
|
2301
|
-
const resolved = resolveNumericValue(rawValue, formData)
|
|
2302
|
-
values.push(resolved);
|
|
3264
|
+
const resolved = resolveNumericValue(rawValue, formData);
|
|
3265
|
+
values.push(typeof resolved === 'number' && Number.isFinite(resolved) ? resolved : 0);
|
|
2303
3266
|
}
|
|
2304
3267
|
const label = dataset.label ?? `Dataset ${datasets.length + 1}`;
|
|
2305
3268
|
const borderColor = parseColor(dataset.borderColor ?? '0xFF2196F3');
|
|
2306
3269
|
const fillColor = dataset.fillColor ? parseColor(dataset.fillColor) : undefined;
|
|
2307
3270
|
const pointColor = parseColor(dataset.pointColor ?? dataset.borderColor ?? '0xFF2196F3');
|
|
2308
|
-
const
|
|
2309
|
-
const
|
|
2310
|
-
const
|
|
2311
|
-
const
|
|
2312
|
-
const
|
|
3271
|
+
const rawBorderWidth = Number(dataset.borderWidth ?? 2);
|
|
3272
|
+
const borderWidth = Number.isFinite(rawBorderWidth) && rawBorderWidth > 0 ? rawBorderWidth : 2;
|
|
3273
|
+
const rawPointRadius = Number(dataset.pointRadius ?? 4);
|
|
3274
|
+
const pointRadius = Number.isFinite(rawPointRadius) && rawPointRadius >= 0 ? rawPointRadius : 4;
|
|
3275
|
+
const normalizedStyle = resolveRadarBorderStyle(dataset, label ?? `Dataset ${datasetIndex + 1}`);
|
|
3276
|
+
const dashArray = Array.isArray(dataset.dashArray) ? dataset.dashArray.map(val => Number(val)).filter(val => Number.isFinite(val) && val > 0) : undefined;
|
|
3277
|
+
const rawDashLength = Number(dataset.dashLength);
|
|
3278
|
+
const dashLength = Number.isFinite(rawDashLength) && rawDashLength > 0 ? rawDashLength : undefined;
|
|
2313
3279
|
const showPoints = dataset.showPoints !== false;
|
|
2314
3280
|
datasets.push({
|
|
2315
3281
|
label,
|
|
@@ -2318,6 +3284,7 @@ export function parseRadarDatasets(raw, axisLength, formData) {
|
|
|
2318
3284
|
borderWidth,
|
|
2319
3285
|
borderStyle: normalizedStyle,
|
|
2320
3286
|
dashArray,
|
|
3287
|
+
dashLength,
|
|
2321
3288
|
showPoints,
|
|
2322
3289
|
pointRadius,
|
|
2323
3290
|
pointColor,
|
|
@@ -2331,7 +3298,9 @@ export function normalizeAxisMaximums(axes, datasets) {
|
|
|
2331
3298
|
let maxValue = axis.maxValue;
|
|
2332
3299
|
datasets.forEach(dataset => {
|
|
2333
3300
|
const value = dataset.values[index];
|
|
2334
|
-
if (value !== undefined && value > maxValue)
|
|
3301
|
+
if (value !== undefined && Number.isFinite(value) && value > maxValue) {
|
|
3302
|
+
maxValue = value;
|
|
3303
|
+
}
|
|
2335
3304
|
});
|
|
2336
3305
|
return {
|
|
2337
3306
|
...axis,
|
|
@@ -2340,12 +3309,15 @@ export function normalizeAxisMaximums(axes, datasets) {
|
|
|
2340
3309
|
});
|
|
2341
3310
|
}
|
|
2342
3311
|
function resolveRadarWidth(parsedWidth, measuredWidth) {
|
|
3312
|
+
if (measuredWidth > 0) {
|
|
3313
|
+
if (typeof parsedWidth === 'number' && Number.isFinite(parsedWidth)) {
|
|
3314
|
+
return Math.max(1, Math.min(parsedWidth, measuredWidth));
|
|
3315
|
+
}
|
|
3316
|
+
return Math.max(1, measuredWidth);
|
|
3317
|
+
}
|
|
2343
3318
|
if (typeof parsedWidth === 'number' && Number.isFinite(parsedWidth)) {
|
|
2344
3319
|
return parsedWidth;
|
|
2345
3320
|
}
|
|
2346
|
-
if (measuredWidth > 0) {
|
|
2347
|
-
return measuredWidth;
|
|
2348
|
-
}
|
|
2349
3321
|
return 300;
|
|
2350
3322
|
}
|
|
2351
3323
|
function radarPlaceholder(message) {
|