flowboard-react 0.6.1 → 0.6.2
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 +5 -5
- package/lib/module/Flowboard.js +1 -2
- package/lib/module/Flowboard.js.map +1 -1
- package/lib/module/components/FlowboardFlow.js +25 -3
- package/lib/module/components/FlowboardFlow.js.map +1 -1
- package/lib/module/components/FlowboardRenderer.js +413 -79
- package/lib/module/components/FlowboardRenderer.js.map +1 -1
- package/lib/module/utils/flowboardUtils.js +4 -1
- package/lib/module/utils/flowboardUtils.js.map +1 -1
- package/lib/typescript/src/Flowboard.d.ts.map +1 -1
- package/lib/typescript/src/components/FlowboardFlow.d.ts.map +1 -1
- package/lib/typescript/src/components/FlowboardRenderer.d.ts +33 -0
- package/lib/typescript/src/components/FlowboardRenderer.d.ts.map +1 -1
- package/lib/typescript/src/utils/flowboardUtils.d.ts.map +1 -1
- package/package.json +18 -19
- package/src/Flowboard.ts +1 -2
- package/src/components/FlowboardFlow.tsx +37 -2
- package/src/components/FlowboardRenderer.tsx +681 -100
- package/src/utils/flowboardUtils.ts +9 -1
|
@@ -563,6 +563,7 @@ function isFillDimensionValue(value: unknown) {
|
|
|
563
563
|
const normalized = value.trim().toLowerCase();
|
|
564
564
|
return (
|
|
565
565
|
normalized === 'infinity' ||
|
|
566
|
+
normalized === 'infinite' ||
|
|
566
567
|
normalized === 'double.infinity' ||
|
|
567
568
|
normalized === '+infinity' ||
|
|
568
569
|
normalized === '100%'
|
|
@@ -575,33 +576,63 @@ function isFixedDimensionValue(value: unknown) {
|
|
|
575
576
|
return Number.isFinite(parsed);
|
|
576
577
|
}
|
|
577
578
|
|
|
579
|
+
function normalizeStackSizeMode(
|
|
580
|
+
value: unknown
|
|
581
|
+
): 'fill' | 'fit' | 'fixed' | undefined {
|
|
582
|
+
if (typeof value !== 'string') return undefined;
|
|
583
|
+
const normalized = value.trim().toLowerCase();
|
|
584
|
+
if (normalized === 'fit' || normalized === 'fixed') return normalized;
|
|
585
|
+
if (
|
|
586
|
+
normalized === 'fill' ||
|
|
587
|
+
normalized === 'infinity' ||
|
|
588
|
+
normalized === 'infinite' ||
|
|
589
|
+
normalized === 'double.infinity' ||
|
|
590
|
+
normalized === '+infinity'
|
|
591
|
+
) {
|
|
592
|
+
return 'fill';
|
|
593
|
+
}
|
|
594
|
+
return undefined;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
function wantsFillWidth(props: Record<string, any>) {
|
|
598
|
+
return (
|
|
599
|
+
normalizeStackSizeMode(props.size?.width) === 'fill' ||
|
|
600
|
+
isFillDimensionValue(props.width)
|
|
601
|
+
);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function wantsFillHeight(props: Record<string, any>) {
|
|
605
|
+
return (
|
|
606
|
+
normalizeStackSizeMode(props.size?.height) === 'fill' ||
|
|
607
|
+
isFillDimensionValue(props.height)
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
|
|
578
611
|
function normalizeStackSize(
|
|
579
612
|
props: Record<string, any>,
|
|
580
613
|
axis: 'vertical' | 'horizontal' | 'overlay'
|
|
581
614
|
) {
|
|
582
|
-
const widthMode =
|
|
583
|
-
const heightMode =
|
|
584
|
-
|
|
585
|
-
const width =
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
? 'fit'
|
|
604
|
-
: 'fill';
|
|
615
|
+
const widthMode = normalizeStackSizeMode(props.size?.width);
|
|
616
|
+
const heightMode = normalizeStackSizeMode(props.size?.height);
|
|
617
|
+
|
|
618
|
+
const width = widthMode
|
|
619
|
+
? widthMode
|
|
620
|
+
: props.fit === 'expand' || isFillDimensionValue(props.width)
|
|
621
|
+
? 'fill'
|
|
622
|
+
: isFixedDimensionValue(props.width)
|
|
623
|
+
? 'fixed'
|
|
624
|
+
: axis === 'horizontal'
|
|
625
|
+
? 'fit'
|
|
626
|
+
: 'fill';
|
|
627
|
+
const height = heightMode
|
|
628
|
+
? heightMode
|
|
629
|
+
: props.fit === 'expand' || isFillDimensionValue(props.height)
|
|
630
|
+
? 'fill'
|
|
631
|
+
: isFixedDimensionValue(props.height)
|
|
632
|
+
? 'fixed'
|
|
633
|
+
: axis === 'vertical'
|
|
634
|
+
? 'fit'
|
|
635
|
+
: 'fill';
|
|
605
636
|
|
|
606
637
|
return { width, height };
|
|
607
638
|
}
|
|
@@ -687,8 +718,6 @@ function mergeStackProps(
|
|
|
687
718
|
const fallbackAxisSource =
|
|
688
719
|
axisSource === childProps ? parentProps : childProps;
|
|
689
720
|
|
|
690
|
-
const parentPadding = spacingToInsets(parentProps.layout?.padding);
|
|
691
|
-
const childPadding = spacingToInsets(childProps.layout?.padding);
|
|
692
721
|
const parentMargin = spacingToInsets(parentProps.layout?.margin);
|
|
693
722
|
const childMargin = spacingToInsets(childProps.layout?.margin);
|
|
694
723
|
|
|
@@ -705,12 +734,6 @@ function mergeStackProps(
|
|
|
705
734
|
height: childProps.size?.height ?? parentProps.size?.height ?? 'fit',
|
|
706
735
|
},
|
|
707
736
|
layout: {
|
|
708
|
-
padding: insetsToSpacing({
|
|
709
|
-
top: parentPadding.top + childPadding.top,
|
|
710
|
-
right: parentPadding.right + childPadding.right,
|
|
711
|
-
bottom: parentPadding.bottom + childPadding.bottom,
|
|
712
|
-
left: parentPadding.left + childPadding.left,
|
|
713
|
-
}),
|
|
714
737
|
margin: insetsToSpacing({
|
|
715
738
|
top: parentMargin.top + childMargin.top,
|
|
716
739
|
right: parentMargin.right + childMargin.right,
|
|
@@ -783,9 +806,6 @@ function normalizeStackLikeNode(
|
|
|
783
806
|
childSpacing: Number(props.childSpacing ?? props.gap ?? props.spacing ?? 0),
|
|
784
807
|
size,
|
|
785
808
|
layout: {
|
|
786
|
-
padding: normalizeStackSpacingConfig(
|
|
787
|
-
props.layout?.padding ?? props.padding
|
|
788
|
-
),
|
|
789
809
|
margin: normalizeStackSpacingConfig(props.layout?.margin ?? props.margin),
|
|
790
810
|
},
|
|
791
811
|
appearance: {
|
|
@@ -881,11 +901,12 @@ function resolveStackDimension(
|
|
|
881
901
|
) {
|
|
882
902
|
const mode = props.size?.[key];
|
|
883
903
|
const legacy = normalizeDimension(parseLayoutDimension(props[key]));
|
|
904
|
+
const fixedPx = parseLayoutDimension(props.size?.[`${key}Px`], true);
|
|
884
905
|
const isBounded =
|
|
885
906
|
key === 'width' ? axisBounds.widthBounded : axisBounds.heightBounded;
|
|
886
907
|
if (mode === 'fill') return isBounded ? '100%' : legacy;
|
|
887
908
|
if (mode === 'fit') return legacy;
|
|
888
|
-
if (mode === 'fixed') return legacy;
|
|
909
|
+
if (mode === 'fixed') return legacy ?? fixedPx;
|
|
889
910
|
if (legacy !== undefined) return legacy;
|
|
890
911
|
if (key === 'width' && axis !== 'horizontal' && axisBounds.widthBounded)
|
|
891
912
|
return '100%';
|
|
@@ -1485,19 +1506,38 @@ function buildWidget(
|
|
|
1485
1506
|
break;
|
|
1486
1507
|
}
|
|
1487
1508
|
case 'slider': {
|
|
1509
|
+
const direction =
|
|
1510
|
+
props.direction === 'vertical' ? 'vertical' : 'horizontal';
|
|
1511
|
+
const interaction = props.interaction ?? {};
|
|
1512
|
+
const pageControl = props.pageControl ?? {};
|
|
1513
|
+
const sliderChildren = (childrenJson ?? []).filter(Boolean);
|
|
1488
1514
|
node = (
|
|
1489
1515
|
<SliderWidget
|
|
1490
1516
|
id={json._internalId}
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1517
|
+
sliderProps={props}
|
|
1518
|
+
slidePropsList={sliderChildren.map(
|
|
1519
|
+
(child) => child?.properties ?? {}
|
|
1520
|
+
)}
|
|
1521
|
+
direction={direction}
|
|
1522
|
+
pageAlignment={
|
|
1523
|
+
props.pageAlignment === 'start' || props.pageAlignment === 'end'
|
|
1524
|
+
? props.pageAlignment
|
|
1525
|
+
: 'center'
|
|
1526
|
+
}
|
|
1527
|
+
pageSpacing={Number(props.pageSpacing ?? 16)}
|
|
1528
|
+
pagePeek={Number(props.pagePeek ?? 16)}
|
|
1529
|
+
loop={interaction.loop === true}
|
|
1530
|
+
autoAdvance={interaction.autoAdvance === true}
|
|
1531
|
+
autoAdvanceIntervalMs={Number(
|
|
1532
|
+
interaction.autoAdvanceIntervalMs ?? 4000
|
|
1533
|
+
)}
|
|
1534
|
+
pageControlEnabled={pageControl.enabled !== false}
|
|
1535
|
+
pageControlPosition={
|
|
1536
|
+
pageControl.position === 'top' ? 'top' : 'bottom'
|
|
1537
|
+
}
|
|
1498
1538
|
>
|
|
1499
|
-
{
|
|
1500
|
-
<View key={`slider-${index}`}
|
|
1539
|
+
{sliderChildren.map((child, index) => (
|
|
1540
|
+
<View key={`slider-${index}`}>
|
|
1501
1541
|
{buildWidget(child, { ...params })}
|
|
1502
1542
|
</View>
|
|
1503
1543
|
))}
|
|
@@ -1505,6 +1545,19 @@ function buildWidget(
|
|
|
1505
1545
|
);
|
|
1506
1546
|
break;
|
|
1507
1547
|
}
|
|
1548
|
+
case 'slide': {
|
|
1549
|
+
node = buildWidget(
|
|
1550
|
+
{
|
|
1551
|
+
...json,
|
|
1552
|
+
type: 'stack',
|
|
1553
|
+
properties: {
|
|
1554
|
+
...(props || {}),
|
|
1555
|
+
},
|
|
1556
|
+
},
|
|
1557
|
+
{ ...params }
|
|
1558
|
+
);
|
|
1559
|
+
break;
|
|
1560
|
+
}
|
|
1508
1561
|
case 'pageview_indicator': {
|
|
1509
1562
|
node = (
|
|
1510
1563
|
<PageViewIndicator
|
|
@@ -1558,9 +1611,6 @@ function buildWidget(
|
|
|
1558
1611
|
const applySpacing = !STACK_SPACE_DISTRIBUTIONS.has(
|
|
1559
1612
|
String(props.distribution ?? 'start')
|
|
1560
1613
|
);
|
|
1561
|
-
const paddingInsets = stackSpacingToInsets(
|
|
1562
|
-
props.layout?.padding ?? props.padding
|
|
1563
|
-
);
|
|
1564
1614
|
const width = resolveStackDimension(props, axis, 'width', pageAxisBounds);
|
|
1565
1615
|
const height = resolveStackDimension(
|
|
1566
1616
|
props,
|
|
@@ -1626,12 +1676,22 @@ function buildWidget(
|
|
|
1626
1676
|
overflow: borderRadius > 0 ? 'hidden' : undefined,
|
|
1627
1677
|
borderWidth: borderColor ? borderWidth : undefined,
|
|
1628
1678
|
borderColor: borderColor ?? undefined,
|
|
1629
|
-
...insetsToStyle(paddingInsets),
|
|
1630
1679
|
...shadowStyle,
|
|
1631
1680
|
};
|
|
1681
|
+
const stackContentSizeStyle = {
|
|
1682
|
+
width: width !== undefined ? '100%' : undefined,
|
|
1683
|
+
height: height !== undefined ? '100%' : undefined,
|
|
1684
|
+
};
|
|
1632
1685
|
|
|
1633
1686
|
const content = isOverlay ? (
|
|
1634
|
-
<View
|
|
1687
|
+
<View
|
|
1688
|
+
style={{
|
|
1689
|
+
position: 'relative',
|
|
1690
|
+
minHeight: 0,
|
|
1691
|
+
minWidth: 0,
|
|
1692
|
+
...stackContentSizeStyle,
|
|
1693
|
+
}}
|
|
1694
|
+
>
|
|
1635
1695
|
{(childrenJson ?? []).map((child, index) => {
|
|
1636
1696
|
const alignment = parseOverlayGridAlignment(
|
|
1637
1697
|
props.overlayAlignment ?? props.alignment
|
|
@@ -1667,6 +1727,7 @@ function buildWidget(
|
|
|
1667
1727
|
alignItems: parseCrossAlignment(props.alignment),
|
|
1668
1728
|
minHeight: 0,
|
|
1669
1729
|
minWidth: 0,
|
|
1730
|
+
...stackContentSizeStyle,
|
|
1670
1731
|
}}
|
|
1671
1732
|
>
|
|
1672
1733
|
{(childrenJson ?? []).map((child, index) => (
|
|
@@ -1775,6 +1836,27 @@ function buildWidget(
|
|
|
1775
1836
|
);
|
|
1776
1837
|
}
|
|
1777
1838
|
|
|
1839
|
+
const shouldStretchCrossAxis =
|
|
1840
|
+
(params.parentFlexAxis === 'vertical' &&
|
|
1841
|
+
(type === 'selection_list' ||
|
|
1842
|
+
type === 'wheel_picker' ||
|
|
1843
|
+
wantsFillWidth(props))) ||
|
|
1844
|
+
(params.parentFlexAxis === 'horizontal' && wantsFillHeight(props));
|
|
1845
|
+
|
|
1846
|
+
if (shouldStretchCrossAxis) {
|
|
1847
|
+
node = (
|
|
1848
|
+
<View
|
|
1849
|
+
style={{
|
|
1850
|
+
alignSelf: 'stretch',
|
|
1851
|
+
minWidth: 0,
|
|
1852
|
+
minHeight: 0,
|
|
1853
|
+
}}
|
|
1854
|
+
>
|
|
1855
|
+
{node}
|
|
1856
|
+
</View>
|
|
1857
|
+
);
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1778
1860
|
if (params.allowFlexExpansion) {
|
|
1779
1861
|
let shouldExpand = false;
|
|
1780
1862
|
const parentAxis = params.parentFlexAxis ?? 'vertical';
|
|
@@ -2188,6 +2270,7 @@ function parseLayoutDimension(
|
|
|
2188
2270
|
|
|
2189
2271
|
if (
|
|
2190
2272
|
lowered === 'double.infinity' ||
|
|
2273
|
+
lowered === 'infinite' ||
|
|
2191
2274
|
lowered === 'infinity' ||
|
|
2192
2275
|
lowered === '+infinity'
|
|
2193
2276
|
) {
|
|
@@ -2626,6 +2709,16 @@ function SelectionList({
|
|
|
2626
2709
|
|
|
2627
2710
|
const layout = properties.layout ?? 'column';
|
|
2628
2711
|
const spacing = Number(properties.spacing ?? 8);
|
|
2712
|
+
const shouldOptionFillWidth = (option: Record<string, any>) => {
|
|
2713
|
+
if (layout === 'grid') return true;
|
|
2714
|
+
const child =
|
|
2715
|
+
option.child && typeof option.child === 'object'
|
|
2716
|
+
? normalizeStackLikeNode(option.child)
|
|
2717
|
+
: option.child;
|
|
2718
|
+
const childProps =
|
|
2719
|
+
child && typeof child === 'object' ? child.properties ?? {} : {};
|
|
2720
|
+
return wantsFillWidth(childProps);
|
|
2721
|
+
};
|
|
2629
2722
|
|
|
2630
2723
|
const renderItem = (option: Record<string, any>, index: number) => {
|
|
2631
2724
|
const value = String(option.value ?? '');
|
|
@@ -2638,13 +2731,15 @@ function SelectionList({
|
|
|
2638
2731
|
const borderColor = parseColor(styleProps.borderColor);
|
|
2639
2732
|
const borderRadius = Number(styleProps.borderRadius ?? 8);
|
|
2640
2733
|
const borderWidth = Number(styleProps.borderWidth ?? 1);
|
|
2734
|
+
const fillWidth = shouldOptionFillWidth(option);
|
|
2641
2735
|
|
|
2642
2736
|
return (
|
|
2643
2737
|
<Pressable
|
|
2644
2738
|
key={`option-${index}`}
|
|
2645
2739
|
onPress={() => toggleSelection(value)}
|
|
2646
2740
|
style={{
|
|
2647
|
-
width: '100%',
|
|
2741
|
+
width: fillWidth ? '100%' : undefined,
|
|
2742
|
+
alignSelf: fillWidth ? 'stretch' : 'flex-start',
|
|
2648
2743
|
backgroundColor,
|
|
2649
2744
|
borderColor,
|
|
2650
2745
|
borderWidth,
|
|
@@ -2699,7 +2794,11 @@ function SelectionList({
|
|
|
2699
2794
|
{options.map((option, index) => (
|
|
2700
2795
|
<View
|
|
2701
2796
|
key={`column-option-${index}`}
|
|
2702
|
-
style={{
|
|
2797
|
+
style={{
|
|
2798
|
+
width: shouldOptionFillWidth(option) ? '100%' : undefined,
|
|
2799
|
+
alignSelf: shouldOptionFillWidth(option) ? 'stretch' : 'flex-start',
|
|
2800
|
+
marginBottom: spacing,
|
|
2801
|
+
}}
|
|
2703
2802
|
>
|
|
2704
2803
|
{renderItem(option, index)}
|
|
2705
2804
|
</View>
|
|
@@ -3529,25 +3628,325 @@ function FakeProgressBar({
|
|
|
3529
3628
|
|
|
3530
3629
|
const AnimatedCircle = Animated.createAnimatedComponent(Circle);
|
|
3531
3630
|
|
|
3631
|
+
type SliderSizeMode = 'fill' | 'fit' | 'fixed';
|
|
3632
|
+
|
|
3633
|
+
export function resolvePeekInsets(
|
|
3634
|
+
pageAlignment: 'start' | 'center' | 'end',
|
|
3635
|
+
pagePeek: number
|
|
3636
|
+
): { leading: number; trailing: number } {
|
|
3637
|
+
const leading =
|
|
3638
|
+
pageAlignment === 'start'
|
|
3639
|
+
? 0
|
|
3640
|
+
: pageAlignment === 'end'
|
|
3641
|
+
? pagePeek * 2
|
|
3642
|
+
: pagePeek;
|
|
3643
|
+
const trailing =
|
|
3644
|
+
pageAlignment === 'end'
|
|
3645
|
+
? 0
|
|
3646
|
+
: pageAlignment === 'start'
|
|
3647
|
+
? pagePeek * 2
|
|
3648
|
+
: pagePeek;
|
|
3649
|
+
return { leading, trailing };
|
|
3650
|
+
}
|
|
3651
|
+
|
|
3652
|
+
function resolveSliderSizeMode(
|
|
3653
|
+
sliderProps: Record<string, any>,
|
|
3654
|
+
key: 'width' | 'height',
|
|
3655
|
+
fallback: SliderSizeMode
|
|
3656
|
+
): SliderSizeMode {
|
|
3657
|
+
const raw = String(sliderProps?.size?.[key] ?? '').toLowerCase();
|
|
3658
|
+
if (raw === 'fill' || raw === 'fit' || raw === 'fixed') return raw;
|
|
3659
|
+
return sliderProps?.[key] !== undefined ? 'fixed' : fallback;
|
|
3660
|
+
}
|
|
3661
|
+
|
|
3662
|
+
function resolveSlideSizeMode(
|
|
3663
|
+
slideProps: Record<string, any>,
|
|
3664
|
+
key: 'width' | 'height'
|
|
3665
|
+
): SliderSizeMode {
|
|
3666
|
+
const raw = String(slideProps?.size?.[key] ?? '').toLowerCase();
|
|
3667
|
+
if (raw === 'fill' || raw === 'fit' || raw === 'fixed') return raw;
|
|
3668
|
+
return key === 'width' ? 'fill' : 'fit';
|
|
3669
|
+
}
|
|
3670
|
+
|
|
3671
|
+
function toFinite(value: unknown): number | null {
|
|
3672
|
+
if (typeof value === 'number' && Number.isFinite(value)) return value;
|
|
3673
|
+
if (typeof value === 'string') {
|
|
3674
|
+
const parsed = Number(value);
|
|
3675
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
3676
|
+
}
|
|
3677
|
+
return null;
|
|
3678
|
+
}
|
|
3679
|
+
|
|
3680
|
+
export function resolveSliderContainerSize(args: {
|
|
3681
|
+
direction: 'horizontal' | 'vertical';
|
|
3682
|
+
widthMode: SliderSizeMode;
|
|
3683
|
+
heightMode: SliderSizeMode;
|
|
3684
|
+
availableWidth: number;
|
|
3685
|
+
availableHeight: number;
|
|
3686
|
+
activeSlideWidth: number;
|
|
3687
|
+
activeSlideHeight: number;
|
|
3688
|
+
fixedWidth: number | null;
|
|
3689
|
+
fixedHeight: number | null;
|
|
3690
|
+
leadingPeek: number;
|
|
3691
|
+
trailingPeek: number;
|
|
3692
|
+
showDots: boolean;
|
|
3693
|
+
}): {
|
|
3694
|
+
outerWidth: number | string;
|
|
3695
|
+
outerHeight: number | string;
|
|
3696
|
+
viewportWidth: number;
|
|
3697
|
+
viewportHeight: number;
|
|
3698
|
+
} {
|
|
3699
|
+
const dotsInset = args.showDots ? 8 + 6 + 8 : 0;
|
|
3700
|
+
const fitPrimary =
|
|
3701
|
+
args.direction === 'horizontal'
|
|
3702
|
+
? args.activeSlideWidth
|
|
3703
|
+
: args.activeSlideHeight;
|
|
3704
|
+
const fitViewportPrimary = Math.max(
|
|
3705
|
+
1,
|
|
3706
|
+
fitPrimary + args.leadingPeek + args.trailingPeek
|
|
3707
|
+
);
|
|
3708
|
+
|
|
3709
|
+
const viewportWidth =
|
|
3710
|
+
args.direction === 'horizontal'
|
|
3711
|
+
? fitViewportPrimary
|
|
3712
|
+
: Math.max(1, args.activeSlideWidth);
|
|
3713
|
+
const viewportHeight =
|
|
3714
|
+
args.direction === 'vertical'
|
|
3715
|
+
? fitViewportPrimary
|
|
3716
|
+
: Math.max(1, args.activeSlideHeight);
|
|
3717
|
+
|
|
3718
|
+
const fitOuterWidth = Math.min(
|
|
3719
|
+
Math.max(1, viewportWidth),
|
|
3720
|
+
Math.max(1, args.availableWidth || viewportWidth)
|
|
3721
|
+
);
|
|
3722
|
+
const fitOuterHeight = Math.max(1, viewportHeight + dotsInset);
|
|
3723
|
+
|
|
3724
|
+
const outerWidth =
|
|
3725
|
+
args.widthMode === 'fill'
|
|
3726
|
+
? '100%'
|
|
3727
|
+
: args.widthMode === 'fixed'
|
|
3728
|
+
? Math.max(1, args.fixedWidth ?? fitOuterWidth)
|
|
3729
|
+
: fitOuterWidth;
|
|
3730
|
+
const outerHeight =
|
|
3731
|
+
args.heightMode === 'fill'
|
|
3732
|
+
? '100%'
|
|
3733
|
+
: args.heightMode === 'fixed'
|
|
3734
|
+
? Math.max(1, args.fixedHeight ?? fitOuterHeight)
|
|
3735
|
+
: fitOuterHeight;
|
|
3736
|
+
|
|
3737
|
+
return {
|
|
3738
|
+
outerWidth,
|
|
3739
|
+
outerHeight,
|
|
3740
|
+
viewportWidth:
|
|
3741
|
+
args.widthMode === 'fit'
|
|
3742
|
+
? fitOuterWidth
|
|
3743
|
+
: Math.max(1, args.availableWidth),
|
|
3744
|
+
viewportHeight:
|
|
3745
|
+
args.heightMode === 'fit'
|
|
3746
|
+
? Math.max(1, fitOuterHeight - dotsInset)
|
|
3747
|
+
: Math.max(1, args.availableHeight || fitOuterHeight - dotsInset),
|
|
3748
|
+
};
|
|
3749
|
+
}
|
|
3750
|
+
|
|
3751
|
+
export function resolveSlidePageSize(args: {
|
|
3752
|
+
slideProps: Record<string, any>;
|
|
3753
|
+
direction: 'horizontal' | 'vertical';
|
|
3754
|
+
pagePrimary: number;
|
|
3755
|
+
crossSize: number | string;
|
|
3756
|
+
}): { width: number | string; height: number | string } {
|
|
3757
|
+
const widthMode = resolveSlideSizeMode(args.slideProps, 'width');
|
|
3758
|
+
const heightMode = resolveSlideSizeMode(args.slideProps, 'height');
|
|
3759
|
+
const fixedWidth = toFinite(
|
|
3760
|
+
parseLayoutDimension(
|
|
3761
|
+
args.slideProps.width ?? args.slideProps.size?.widthPx,
|
|
3762
|
+
true
|
|
3763
|
+
)
|
|
3764
|
+
);
|
|
3765
|
+
const fixedHeight = toFinite(
|
|
3766
|
+
parseLayoutDimension(
|
|
3767
|
+
args.slideProps.height ?? args.slideProps.size?.heightPx,
|
|
3768
|
+
true
|
|
3769
|
+
)
|
|
3770
|
+
);
|
|
3771
|
+
const width =
|
|
3772
|
+
widthMode === 'fixed'
|
|
3773
|
+
? Math.max(1, fixedWidth ?? args.pagePrimary)
|
|
3774
|
+
: widthMode === 'fit'
|
|
3775
|
+
? undefined
|
|
3776
|
+
: args.direction === 'horizontal'
|
|
3777
|
+
? args.pagePrimary
|
|
3778
|
+
: args.crossSize;
|
|
3779
|
+
const height =
|
|
3780
|
+
heightMode === 'fixed'
|
|
3781
|
+
? Math.max(1, fixedHeight ?? args.pagePrimary)
|
|
3782
|
+
: heightMode === 'fit'
|
|
3783
|
+
? undefined
|
|
3784
|
+
: args.direction === 'vertical'
|
|
3785
|
+
? args.pagePrimary
|
|
3786
|
+
: args.crossSize;
|
|
3787
|
+
return {
|
|
3788
|
+
width: width ?? 'auto',
|
|
3789
|
+
height: height ?? 'auto',
|
|
3790
|
+
};
|
|
3791
|
+
}
|
|
3792
|
+
|
|
3793
|
+
function resolveSliderBackgroundColor(
|
|
3794
|
+
sliderProps: Record<string, any>
|
|
3795
|
+
): string | undefined {
|
|
3796
|
+
const extractColorCandidate = (input: unknown): unknown => {
|
|
3797
|
+
if (input === null || input === undefined) return undefined;
|
|
3798
|
+
if (typeof input === 'string' || typeof input === 'number') return input;
|
|
3799
|
+
|
|
3800
|
+
if (Array.isArray(input)) {
|
|
3801
|
+
for (const value of input) {
|
|
3802
|
+
const nested = extractColorCandidate(value);
|
|
3803
|
+
if (nested !== undefined) return nested;
|
|
3804
|
+
}
|
|
3805
|
+
return undefined;
|
|
3806
|
+
}
|
|
3807
|
+
|
|
3808
|
+
if (typeof input !== 'object') return undefined;
|
|
3809
|
+
|
|
3810
|
+
const source = input as Record<string, any>;
|
|
3811
|
+
if (
|
|
3812
|
+
Number.isFinite(Number(source.r)) &&
|
|
3813
|
+
Number.isFinite(Number(source.g)) &&
|
|
3814
|
+
Number.isFinite(Number(source.b))
|
|
3815
|
+
) {
|
|
3816
|
+
const alphaRaw = Number.isFinite(Number(source.a)) ? Number(source.a) : 1;
|
|
3817
|
+
const alpha = Math.max(0, Math.min(1, alphaRaw));
|
|
3818
|
+
return `rgba(${Number(source.r)},${Number(source.g)},${Number(
|
|
3819
|
+
source.b
|
|
3820
|
+
)},${alpha})`;
|
|
3821
|
+
}
|
|
3822
|
+
|
|
3823
|
+
// If a gradient is provided for slider fill, use the first stop as fallback.
|
|
3824
|
+
if (
|
|
3825
|
+
String(source.type ?? '').toLowerCase() === 'gradient' &&
|
|
3826
|
+
Array.isArray(source.colors)
|
|
3827
|
+
) {
|
|
3828
|
+
const first = extractColorCandidate(source.colors);
|
|
3829
|
+
if (first !== undefined) return first;
|
|
3830
|
+
}
|
|
3831
|
+
|
|
3832
|
+
const candidateKeys = [
|
|
3833
|
+
'value',
|
|
3834
|
+
'color',
|
|
3835
|
+
'backgroundColor',
|
|
3836
|
+
'hex',
|
|
3837
|
+
'argb',
|
|
3838
|
+
'rgba',
|
|
3839
|
+
'raw',
|
|
3840
|
+
];
|
|
3841
|
+
for (const key of candidateKeys) {
|
|
3842
|
+
const nested = extractColorCandidate(source[key]);
|
|
3843
|
+
if (nested !== undefined) return nested;
|
|
3844
|
+
}
|
|
3845
|
+
|
|
3846
|
+
return undefined;
|
|
3847
|
+
};
|
|
3848
|
+
|
|
3849
|
+
const parseSliderFillColor = (color: unknown): string => {
|
|
3850
|
+
const candidate = extractColorCandidate(color);
|
|
3851
|
+
if (candidate === undefined) return 'transparent';
|
|
3852
|
+
|
|
3853
|
+
if (typeof candidate === 'string') {
|
|
3854
|
+
const trimmed = candidate.trim();
|
|
3855
|
+
if (/^#[\da-fA-F]{8}$/.test(trimmed)) {
|
|
3856
|
+
// Dashboard stores alpha-enabled web hex as #RRGGBBAA.
|
|
3857
|
+
const rrggbbaa = trimmed.slice(1);
|
|
3858
|
+
const aarrggbb = `${rrggbbaa.slice(6, 8)}${rrggbbaa.slice(0, 6)}`;
|
|
3859
|
+
return parseColor(`0x${aarrggbb}`);
|
|
3860
|
+
}
|
|
3861
|
+
return parseColor(trimmed);
|
|
3862
|
+
}
|
|
3863
|
+
|
|
3864
|
+
if (typeof candidate === 'number') {
|
|
3865
|
+
return parseColor(candidate);
|
|
3866
|
+
}
|
|
3867
|
+
|
|
3868
|
+
if (typeof color === 'string') {
|
|
3869
|
+
const trimmed = color.trim();
|
|
3870
|
+
if (/^#[\da-fA-F]{8}$/.test(trimmed)) {
|
|
3871
|
+
// Dashboard stores alpha-enabled web hex as #RRGGBBAA.
|
|
3872
|
+
const rrggbbaa = trimmed.slice(1);
|
|
3873
|
+
const aarrggbb = `${rrggbbaa.slice(6, 8)}${rrggbbaa.slice(0, 6)}`;
|
|
3874
|
+
return parseColor(`0x${aarrggbb}`);
|
|
3875
|
+
}
|
|
3876
|
+
}
|
|
3877
|
+
return parseColor(candidate as string | number | null | undefined);
|
|
3878
|
+
};
|
|
3879
|
+
|
|
3880
|
+
const appearanceFill = sliderProps?.appearance?.fill;
|
|
3881
|
+
if (
|
|
3882
|
+
typeof appearanceFill === 'string' ||
|
|
3883
|
+
typeof appearanceFill === 'number'
|
|
3884
|
+
) {
|
|
3885
|
+
return parseSliderFillColor(appearanceFill);
|
|
3886
|
+
}
|
|
3887
|
+
if (appearanceFill && typeof appearanceFill === 'object') {
|
|
3888
|
+
const type = String(appearanceFill.type ?? '').toLowerCase();
|
|
3889
|
+
if (type === 'color' || type === 'solid' || type === '') {
|
|
3890
|
+
const color = appearanceFill.value ?? appearanceFill.color;
|
|
3891
|
+
if (color !== undefined) return parseSliderFillColor(color);
|
|
3892
|
+
}
|
|
3893
|
+
}
|
|
3894
|
+
|
|
3895
|
+
const fill = sliderProps?.fill;
|
|
3896
|
+
if (typeof fill === 'string' || typeof fill === 'number') {
|
|
3897
|
+
return parseSliderFillColor(fill);
|
|
3898
|
+
}
|
|
3899
|
+
if (fill && typeof fill === 'object') {
|
|
3900
|
+
const type = String(fill.type ?? '').toLowerCase();
|
|
3901
|
+
if (type === 'color' || type === 'solid' || type === '') {
|
|
3902
|
+
const color = fill.value ?? fill.color;
|
|
3903
|
+
if (color !== undefined) return parseSliderFillColor(color);
|
|
3904
|
+
}
|
|
3905
|
+
}
|
|
3906
|
+
|
|
3907
|
+
const background = sliderProps?.background;
|
|
3908
|
+
if (background && typeof background === 'object') {
|
|
3909
|
+
const type = String(background.type ?? '').toLowerCase();
|
|
3910
|
+
if (type === 'color') {
|
|
3911
|
+
const color = background.value ?? background.color;
|
|
3912
|
+
if (color !== undefined) return parseSliderFillColor(color);
|
|
3913
|
+
}
|
|
3914
|
+
}
|
|
3915
|
+
|
|
3916
|
+
if (sliderProps?.backgroundColor !== undefined) {
|
|
3917
|
+
return parseSliderFillColor(sliderProps.backgroundColor);
|
|
3918
|
+
}
|
|
3919
|
+
|
|
3920
|
+
return undefined;
|
|
3921
|
+
}
|
|
3922
|
+
|
|
3532
3923
|
function SliderWidget({
|
|
3533
3924
|
id,
|
|
3534
|
-
|
|
3925
|
+
sliderProps,
|
|
3926
|
+
slidePropsList,
|
|
3535
3927
|
direction,
|
|
3536
|
-
|
|
3537
|
-
|
|
3928
|
+
pageAlignment,
|
|
3929
|
+
pageSpacing,
|
|
3930
|
+
pagePeek,
|
|
3538
3931
|
loop,
|
|
3539
|
-
|
|
3540
|
-
|
|
3932
|
+
autoAdvance,
|
|
3933
|
+
autoAdvanceIntervalMs,
|
|
3934
|
+
pageControlEnabled,
|
|
3935
|
+
pageControlPosition,
|
|
3541
3936
|
children,
|
|
3542
3937
|
}: {
|
|
3543
3938
|
id?: string;
|
|
3544
|
-
|
|
3939
|
+
sliderProps: Record<string, any>;
|
|
3940
|
+
slidePropsList: Record<string, any>[];
|
|
3545
3941
|
direction: 'horizontal' | 'vertical';
|
|
3546
|
-
|
|
3547
|
-
|
|
3942
|
+
pageAlignment: 'start' | 'center' | 'end';
|
|
3943
|
+
pageSpacing: number;
|
|
3944
|
+
pagePeek: number;
|
|
3548
3945
|
loop: boolean;
|
|
3549
|
-
|
|
3550
|
-
|
|
3946
|
+
autoAdvance: boolean;
|
|
3947
|
+
autoAdvanceIntervalMs: number;
|
|
3948
|
+
pageControlEnabled: boolean;
|
|
3949
|
+
pageControlPosition: 'top' | 'bottom';
|
|
3551
3950
|
children: React.ReactNode[];
|
|
3552
3951
|
}) {
|
|
3553
3952
|
const registry = useSliderRegistry();
|
|
@@ -3556,13 +3955,31 @@ function SliderWidget({
|
|
|
3556
3955
|
const pageCount = pageLength;
|
|
3557
3956
|
const [currentPage, setCurrentPage] = React.useState(0);
|
|
3558
3957
|
const timerRef = React.useRef<NodeJS.Timeout | null>(null);
|
|
3958
|
+
const [isInteracting, setIsInteracting] = React.useState(false);
|
|
3959
|
+
const idleTimerRef = React.useRef<NodeJS.Timeout | null>(null);
|
|
3960
|
+
const [availableSize, setAvailableSize] = React.useState({
|
|
3961
|
+
width: 1,
|
|
3962
|
+
height: 1,
|
|
3963
|
+
});
|
|
3964
|
+
const [activeSlideSize, setActiveSlideSize] = React.useState({
|
|
3965
|
+
width: 1,
|
|
3966
|
+
height: 1,
|
|
3967
|
+
});
|
|
3968
|
+
|
|
3969
|
+
const markInteracting = React.useCallback(() => {
|
|
3970
|
+
setIsInteracting(true);
|
|
3971
|
+
if (idleTimerRef.current) {
|
|
3972
|
+
clearTimeout(idleTimerRef.current);
|
|
3973
|
+
}
|
|
3974
|
+
idleTimerRef.current = setTimeout(() => setIsInteracting(false), 1400);
|
|
3975
|
+
}, []);
|
|
3559
3976
|
|
|
3560
3977
|
React.useEffect(() => {
|
|
3561
3978
|
setCurrentPage((prev) => clampSliderPage(prev, pageCount));
|
|
3562
3979
|
}, [pageCount]);
|
|
3563
3980
|
|
|
3564
3981
|
React.useEffect(() => {
|
|
3565
|
-
if (
|
|
3982
|
+
if (autoAdvance && !isInteracting && pageLength > 1) {
|
|
3566
3983
|
timerRef.current = setInterval(() => {
|
|
3567
3984
|
setCurrentPage((prev) => {
|
|
3568
3985
|
const safePrev = clampSliderPage(prev, pageCount);
|
|
@@ -3570,12 +3987,19 @@ function SliderWidget({
|
|
|
3570
3987
|
if (loop) return 0;
|
|
3571
3988
|
return safePrev;
|
|
3572
3989
|
});
|
|
3573
|
-
},
|
|
3990
|
+
}, autoAdvanceIntervalMs);
|
|
3574
3991
|
}
|
|
3575
3992
|
return () => {
|
|
3576
3993
|
if (timerRef.current) clearInterval(timerRef.current);
|
|
3577
3994
|
};
|
|
3578
|
-
}, [
|
|
3995
|
+
}, [
|
|
3996
|
+
autoAdvance,
|
|
3997
|
+
autoAdvanceIntervalMs,
|
|
3998
|
+
isInteracting,
|
|
3999
|
+
loop,
|
|
4000
|
+
pageCount,
|
|
4001
|
+
pageLength,
|
|
4002
|
+
]);
|
|
3579
4003
|
|
|
3580
4004
|
React.useEffect(() => {
|
|
3581
4005
|
if (pagerRef.current && pageCount > 0) {
|
|
@@ -3589,42 +4013,205 @@ function SliderWidget({
|
|
|
3589
4013
|
}
|
|
3590
4014
|
}, [registry, id, currentPage, children.length]);
|
|
3591
4015
|
|
|
3592
|
-
React.useEffect(() => {
|
|
3593
|
-
if (!linkedTo || !registry) return;
|
|
3594
|
-
return registry.getNotifier(linkedTo, (value) => {
|
|
3595
|
-
const normalized = normalizeLoopIndex(value, pageLength);
|
|
3596
|
-
const targetPage = clampSliderPage(normalized, pageCount);
|
|
3597
|
-
setCurrentPage(targetPage);
|
|
3598
|
-
if (pagerRef.current) {
|
|
3599
|
-
pagerRef.current.setPage(targetPage);
|
|
3600
|
-
}
|
|
3601
|
-
});
|
|
3602
|
-
}, [linkedTo, pageCount, pageLength, registry]);
|
|
3603
|
-
|
|
3604
4016
|
if (pageLength === 0) return null;
|
|
3605
4017
|
|
|
4018
|
+
// Slider layout parity rules copied from Web Preview:
|
|
4019
|
+
// 1) leadingPeek = start?0 : end?pagePeek*2 : pagePeek
|
|
4020
|
+
// 2) trailingPeek = end?0 : start?pagePeek*2 : pagePeek
|
|
4021
|
+
// 3) pagePrimary = viewportPrimary - leadingPeek - trailingPeek (clamped >= 1)
|
|
4022
|
+
// 4) track offset = activeIndex * (pagePrimary + pageSpacing)
|
|
4023
|
+
// 5) fit parent mode follows active slide size but is clamped to container width
|
|
4024
|
+
// 6) viewport always clips track content (no root horizontal overflow)
|
|
4025
|
+
const { leading, trailing } = resolvePeekInsets(pageAlignment, pagePeek);
|
|
4026
|
+
const showDots = pageControlEnabled && pageLength > 1;
|
|
4027
|
+
const widthMode = resolveSliderSizeMode(sliderProps, 'width', 'fill');
|
|
4028
|
+
const heightMode = resolveSliderSizeMode(sliderProps, 'height', 'fit');
|
|
4029
|
+
const fixedWidth = toFinite(parseLayoutDimension(sliderProps.width, true));
|
|
4030
|
+
const fixedHeight = toFinite(parseLayoutDimension(sliderProps.height, true));
|
|
4031
|
+
const sliderBackgroundColor = resolveSliderBackgroundColor(sliderProps);
|
|
4032
|
+
const resolved = resolveSliderContainerSize({
|
|
4033
|
+
direction,
|
|
4034
|
+
widthMode,
|
|
4035
|
+
heightMode,
|
|
4036
|
+
availableWidth: availableSize.width,
|
|
4037
|
+
availableHeight: availableSize.height,
|
|
4038
|
+
activeSlideWidth: activeSlideSize.width,
|
|
4039
|
+
activeSlideHeight: activeSlideSize.height,
|
|
4040
|
+
fixedWidth,
|
|
4041
|
+
fixedHeight,
|
|
4042
|
+
leadingPeek: leading,
|
|
4043
|
+
trailingPeek: trailing,
|
|
4044
|
+
showDots,
|
|
4045
|
+
});
|
|
4046
|
+
const viewportPrimary =
|
|
4047
|
+
direction === 'horizontal'
|
|
4048
|
+
? resolved.viewportWidth
|
|
4049
|
+
: resolved.viewportHeight;
|
|
4050
|
+
const pagePrimary = Math.max(1, viewportPrimary - leading - trailing);
|
|
4051
|
+
|
|
3606
4052
|
return (
|
|
3607
|
-
<
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
const
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
4053
|
+
<View
|
|
4054
|
+
onTouchStart={markInteracting}
|
|
4055
|
+
onTouchEnd={markInteracting}
|
|
4056
|
+
onTouchCancel={markInteracting}
|
|
4057
|
+
onPointerDown={markInteracting as any}
|
|
4058
|
+
onPointerUp={markInteracting as any}
|
|
4059
|
+
onLayout={(event) => {
|
|
4060
|
+
const nextWidth = Math.max(
|
|
4061
|
+
1,
|
|
4062
|
+
Math.round(event.nativeEvent.layout.width)
|
|
4063
|
+
);
|
|
4064
|
+
const nextHeight = Math.max(
|
|
4065
|
+
1,
|
|
4066
|
+
Math.round(event.nativeEvent.layout.height)
|
|
4067
|
+
);
|
|
4068
|
+
setAvailableSize((prev) =>
|
|
4069
|
+
prev.width === nextWidth && prev.height === nextHeight
|
|
4070
|
+
? prev
|
|
4071
|
+
: { width: nextWidth, height: nextHeight }
|
|
4072
|
+
);
|
|
4073
|
+
}}
|
|
4074
|
+
style={{
|
|
4075
|
+
width: resolved.outerWidth,
|
|
4076
|
+
height: resolved.outerHeight,
|
|
4077
|
+
minWidth: 1,
|
|
4078
|
+
minHeight: 1,
|
|
4079
|
+
backgroundColor: sliderBackgroundColor,
|
|
4080
|
+
borderRadius: 0,
|
|
4081
|
+
overflow: 'visible',
|
|
3620
4082
|
}}
|
|
3621
4083
|
>
|
|
3622
|
-
{
|
|
3623
|
-
<View
|
|
3624
|
-
{
|
|
4084
|
+
{showDots && pageControlPosition === 'top' ? (
|
|
4085
|
+
<View
|
|
4086
|
+
style={{
|
|
4087
|
+
marginBottom: 8,
|
|
4088
|
+
flexDirection: 'row',
|
|
4089
|
+
justifyContent: 'center',
|
|
4090
|
+
alignItems: 'center',
|
|
4091
|
+
}}
|
|
4092
|
+
>
|
|
4093
|
+
{Array.from({ length: pageLength }).map((_, index) => {
|
|
4094
|
+
const isActive = index === currentPage;
|
|
4095
|
+
return (
|
|
4096
|
+
<View
|
|
4097
|
+
key={`slider-dot-top-${index}`}
|
|
4098
|
+
style={{
|
|
4099
|
+
width: 6,
|
|
4100
|
+
height: 6,
|
|
4101
|
+
borderRadius: 3,
|
|
4102
|
+
marginHorizontal: 3,
|
|
4103
|
+
backgroundColor: isActive
|
|
4104
|
+
? parseColor('0xFF111827')
|
|
4105
|
+
: parseColor('0xFFD1D5DB'),
|
|
4106
|
+
}}
|
|
4107
|
+
/>
|
|
4108
|
+
);
|
|
4109
|
+
})}
|
|
3625
4110
|
</View>
|
|
3626
|
-
)
|
|
3627
|
-
|
|
4111
|
+
) : null}
|
|
4112
|
+
|
|
4113
|
+
<View
|
|
4114
|
+
style={{
|
|
4115
|
+
overflow: 'hidden',
|
|
4116
|
+
...(direction === 'horizontal'
|
|
4117
|
+
? {
|
|
4118
|
+
width: resolved.viewportWidth,
|
|
4119
|
+
height: resolved.viewportHeight,
|
|
4120
|
+
paddingLeft: leading,
|
|
4121
|
+
paddingRight: trailing,
|
|
4122
|
+
}
|
|
4123
|
+
: {
|
|
4124
|
+
width: resolved.viewportWidth,
|
|
4125
|
+
height: resolved.viewportHeight,
|
|
4126
|
+
paddingTop: leading,
|
|
4127
|
+
paddingBottom: trailing,
|
|
4128
|
+
}),
|
|
4129
|
+
}}
|
|
4130
|
+
>
|
|
4131
|
+
<PagerView
|
|
4132
|
+
ref={pagerRef}
|
|
4133
|
+
style={{ width: '100%', height: '100%', minHeight: 1 }}
|
|
4134
|
+
orientation={direction}
|
|
4135
|
+
pageMargin={Math.max(0, pageSpacing)}
|
|
4136
|
+
initialPage={clampSliderPage(currentPage, pageCount)}
|
|
4137
|
+
onPageSelected={(event: PagerViewOnPageSelectedEvent) => {
|
|
4138
|
+
const page = event.nativeEvent.position;
|
|
4139
|
+
const safePage = clampSliderPage(page, pageCount);
|
|
4140
|
+
setCurrentPage(safePage);
|
|
4141
|
+
markInteracting();
|
|
4142
|
+
if (registry && id) {
|
|
4143
|
+
registry.update(id, safePage % pageLength, pageLength);
|
|
4144
|
+
}
|
|
4145
|
+
}}
|
|
4146
|
+
>
|
|
4147
|
+
{Array.from({ length: pageCount }).map((_, index) => (
|
|
4148
|
+
<View
|
|
4149
|
+
key={`slider-page-${index}`}
|
|
4150
|
+
onLayout={
|
|
4151
|
+
index === currentPage
|
|
4152
|
+
? (event) => {
|
|
4153
|
+
const nextWidth = Math.max(
|
|
4154
|
+
1,
|
|
4155
|
+
Math.round(event.nativeEvent.layout.width)
|
|
4156
|
+
);
|
|
4157
|
+
const nextHeight = Math.max(
|
|
4158
|
+
1,
|
|
4159
|
+
Math.round(event.nativeEvent.layout.height)
|
|
4160
|
+
);
|
|
4161
|
+
setActiveSlideSize((prev) =>
|
|
4162
|
+
prev.width === nextWidth && prev.height === nextHeight
|
|
4163
|
+
? prev
|
|
4164
|
+
: { width: nextWidth, height: nextHeight }
|
|
4165
|
+
);
|
|
4166
|
+
}
|
|
4167
|
+
: undefined
|
|
4168
|
+
}
|
|
4169
|
+
style={{
|
|
4170
|
+
alignSelf: 'flex-start',
|
|
4171
|
+
overflow: 'hidden',
|
|
4172
|
+
...(resolveSlidePageSize({
|
|
4173
|
+
slideProps: slidePropsList[index] ?? {},
|
|
4174
|
+
direction,
|
|
4175
|
+
pagePrimary,
|
|
4176
|
+
crossSize: '100%',
|
|
4177
|
+
}) as ViewStyle),
|
|
4178
|
+
}}
|
|
4179
|
+
>
|
|
4180
|
+
{children[index % pageLength]}
|
|
4181
|
+
</View>
|
|
4182
|
+
))}
|
|
4183
|
+
</PagerView>
|
|
4184
|
+
</View>
|
|
4185
|
+
|
|
4186
|
+
{showDots && pageControlPosition === 'bottom' ? (
|
|
4187
|
+
<View
|
|
4188
|
+
style={{
|
|
4189
|
+
marginTop: 8,
|
|
4190
|
+
flexDirection: 'row',
|
|
4191
|
+
justifyContent: 'center',
|
|
4192
|
+
alignItems: 'center',
|
|
4193
|
+
}}
|
|
4194
|
+
>
|
|
4195
|
+
{Array.from({ length: pageLength }).map((_, index) => {
|
|
4196
|
+
const isActive = index === currentPage;
|
|
4197
|
+
return (
|
|
4198
|
+
<View
|
|
4199
|
+
key={`slider-dot-bottom-${index}`}
|
|
4200
|
+
style={{
|
|
4201
|
+
width: 6,
|
|
4202
|
+
height: 6,
|
|
4203
|
+
borderRadius: 3,
|
|
4204
|
+
marginHorizontal: 3,
|
|
4205
|
+
backgroundColor: isActive
|
|
4206
|
+
? parseColor('0xFF111827')
|
|
4207
|
+
: parseColor('0xFFD1D5DB'),
|
|
4208
|
+
}}
|
|
4209
|
+
/>
|
|
4210
|
+
);
|
|
4211
|
+
})}
|
|
4212
|
+
</View>
|
|
4213
|
+
) : null}
|
|
4214
|
+
</View>
|
|
3628
4215
|
);
|
|
3629
4216
|
}
|
|
3630
4217
|
|
|
@@ -3636,12 +4223,6 @@ function clampSliderPage(page: number, pageCount: number): number {
|
|
|
3636
4223
|
return Math.floor(page);
|
|
3637
4224
|
}
|
|
3638
4225
|
|
|
3639
|
-
function normalizeLoopIndex(index: number, size: number): number {
|
|
3640
|
-
if (size <= 0 || !Number.isFinite(index)) return 0;
|
|
3641
|
-
const normalized = Math.floor(index) % size;
|
|
3642
|
-
return normalized < 0 ? normalized + size : normalized;
|
|
3643
|
-
}
|
|
3644
|
-
|
|
3645
4226
|
function PageViewIndicator({
|
|
3646
4227
|
linkedTo,
|
|
3647
4228
|
activeColor,
|