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.
@@ -429,18 +429,33 @@ function isFillDimensionValue(value) {
429
429
  if (typeof value === 'number') return value === Number.POSITIVE_INFINITY;
430
430
  if (typeof value !== 'string') return false;
431
431
  const normalized = value.trim().toLowerCase();
432
- return normalized === 'infinity' || normalized === 'double.infinity' || normalized === '+infinity' || normalized === '100%';
432
+ return normalized === 'infinity' || normalized === 'infinite' || normalized === 'double.infinity' || normalized === '+infinity' || normalized === '100%';
433
433
  }
434
434
  function isFixedDimensionValue(value) {
435
435
  if (isFillDimensionValue(value)) return false;
436
436
  const parsed = Number(value);
437
437
  return Number.isFinite(parsed);
438
438
  }
439
+ function normalizeStackSizeMode(value) {
440
+ if (typeof value !== 'string') return undefined;
441
+ const normalized = value.trim().toLowerCase();
442
+ if (normalized === 'fit' || normalized === 'fixed') return normalized;
443
+ if (normalized === 'fill' || normalized === 'infinity' || normalized === 'infinite' || normalized === 'double.infinity' || normalized === '+infinity') {
444
+ return 'fill';
445
+ }
446
+ return undefined;
447
+ }
448
+ function wantsFillWidth(props) {
449
+ return normalizeStackSizeMode(props.size?.width) === 'fill' || isFillDimensionValue(props.width);
450
+ }
451
+ function wantsFillHeight(props) {
452
+ return normalizeStackSizeMode(props.size?.height) === 'fill' || isFillDimensionValue(props.height);
453
+ }
439
454
  function normalizeStackSize(props, axis) {
440
- const widthMode = String(props.size?.width ?? '').toLowerCase();
441
- const heightMode = String(props.size?.height ?? '').toLowerCase();
442
- const width = widthMode === 'fill' || widthMode === 'fit' || widthMode === 'fixed' ? widthMode : props.fit === 'expand' || props.width === 'infinity' ? 'fill' : isFixedDimensionValue(props.width) ? 'fixed' : axis === 'horizontal' ? 'fit' : 'fill';
443
- const height = heightMode === 'fill' || heightMode === 'fit' || heightMode === 'fixed' ? heightMode : props.fit === 'expand' || props.height === 'infinity' ? 'fill' : isFixedDimensionValue(props.height) ? 'fixed' : axis === 'vertical' ? 'fit' : 'fill';
455
+ const widthMode = normalizeStackSizeMode(props.size?.width);
456
+ const heightMode = normalizeStackSizeMode(props.size?.height);
457
+ const width = widthMode ? widthMode : props.fit === 'expand' || isFillDimensionValue(props.width) ? 'fill' : isFixedDimensionValue(props.width) ? 'fixed' : axis === 'horizontal' ? 'fit' : 'fill';
458
+ const height = heightMode ? heightMode : props.fit === 'expand' || isFillDimensionValue(props.height) ? 'fill' : isFixedDimensionValue(props.height) ? 'fixed' : axis === 'vertical' ? 'fit' : 'fill';
444
459
  return {
445
460
  width,
446
461
  height
@@ -524,8 +539,6 @@ function mergeStackProps(parentProps, childProps) {
524
539
  }
525
540
  const axisSource = axis === parentAxis && axis !== childAxis ? parentProps : childProps;
526
541
  const fallbackAxisSource = axisSource === childProps ? parentProps : childProps;
527
- const parentPadding = spacingToInsets(parentProps.layout?.padding);
528
- const childPadding = spacingToInsets(childProps.layout?.padding);
529
542
  const parentMargin = spacingToInsets(parentProps.layout?.margin);
530
543
  const childMargin = spacingToInsets(childProps.layout?.margin);
531
544
  const mergedProps = {
@@ -538,12 +551,6 @@ function mergeStackProps(parentProps, childProps) {
538
551
  height: childProps.size?.height ?? parentProps.size?.height ?? 'fit'
539
552
  },
540
553
  layout: {
541
- padding: insetsToSpacing({
542
- top: parentPadding.top + childPadding.top,
543
- right: parentPadding.right + childPadding.right,
544
- bottom: parentPadding.bottom + childPadding.bottom,
545
- left: parentPadding.left + childPadding.left
546
- }),
547
554
  margin: insetsToSpacing({
548
555
  top: parentMargin.top + childMargin.top,
549
556
  right: parentMargin.right + childMargin.right,
@@ -598,7 +605,6 @@ function normalizeStackLikeNode(json) {
598
605
  childSpacing: Number(props.childSpacing ?? props.gap ?? props.spacing ?? 0),
599
606
  size,
600
607
  layout: {
601
- padding: normalizeStackSpacingConfig(props.layout?.padding ?? props.padding),
602
608
  margin: normalizeStackSpacingConfig(props.layout?.margin ?? props.margin)
603
609
  },
604
610
  appearance: {
@@ -697,10 +703,11 @@ function parseOverlayGridAlignment(value) {
697
703
  function resolveStackDimension(props, axis, key, axisBounds) {
698
704
  const mode = props.size?.[key];
699
705
  const legacy = normalizeDimension(parseLayoutDimension(props[key]));
706
+ const fixedPx = parseLayoutDimension(props.size?.[`${key}Px`], true);
700
707
  const isBounded = key === 'width' ? axisBounds.widthBounded : axisBounds.heightBounded;
701
708
  if (mode === 'fill') return isBounded ? '100%' : legacy;
702
709
  if (mode === 'fit') return legacy;
703
- if (mode === 'fixed') return legacy;
710
+ if (mode === 'fixed') return legacy ?? fixedPx;
704
711
  if (legacy !== undefined) return legacy;
705
712
  if (key === 'width' && axis !== 'horizontal' && axisBounds.widthBounded) return '100%';
706
713
  if (key === 'height' && axis === 'horizontal' && axisBounds.heightBounded) return '100%';
@@ -1204,19 +1211,24 @@ function buildWidget(json, params) {
1204
1211
  }
1205
1212
  case 'slider':
1206
1213
  {
1214
+ const direction = props.direction === 'vertical' ? 'vertical' : 'horizontal';
1215
+ const interaction = props.interaction ?? {};
1216
+ const pageControl = props.pageControl ?? {};
1217
+ const sliderChildren = (childrenJson ?? []).filter(Boolean);
1207
1218
  node = /*#__PURE__*/_jsx(SliderWidget, {
1208
1219
  id: json._internalId,
1209
- linkedTo: props.linkedTo,
1210
- direction: props.direction === 'vertical' ? 'vertical' : 'horizontal',
1211
- autoPlay: props.autoPlay === true,
1212
- autoPlayDelay: Number(props.autoPlayDelay ?? 3000),
1213
- loop: props.loop === true,
1214
- height: parseLayoutDimension(props.height),
1215
- physics: props.physics,
1216
- children: (childrenJson ?? []).map((child, index) => /*#__PURE__*/_jsx(View, {
1217
- style: {
1218
- flex: 1
1219
- },
1220
+ sliderProps: props,
1221
+ slidePropsList: sliderChildren.map(child => child?.properties ?? {}),
1222
+ direction: direction,
1223
+ pageAlignment: props.pageAlignment === 'start' || props.pageAlignment === 'end' ? props.pageAlignment : 'center',
1224
+ pageSpacing: Number(props.pageSpacing ?? 16),
1225
+ pagePeek: Number(props.pagePeek ?? 16),
1226
+ loop: interaction.loop === true,
1227
+ autoAdvance: interaction.autoAdvance === true,
1228
+ autoAdvanceIntervalMs: Number(interaction.autoAdvanceIntervalMs ?? 4000),
1229
+ pageControlEnabled: pageControl.enabled !== false,
1230
+ pageControlPosition: pageControl.position === 'top' ? 'top' : 'bottom',
1231
+ children: sliderChildren.map((child, index) => /*#__PURE__*/_jsx(View, {
1220
1232
  children: buildWidget(child, {
1221
1233
  ...params
1222
1234
  })
@@ -1224,6 +1236,19 @@ function buildWidget(json, params) {
1224
1236
  });
1225
1237
  break;
1226
1238
  }
1239
+ case 'slide':
1240
+ {
1241
+ node = buildWidget({
1242
+ ...json,
1243
+ type: 'stack',
1244
+ properties: {
1245
+ ...(props || {})
1246
+ }
1247
+ }, {
1248
+ ...params
1249
+ });
1250
+ break;
1251
+ }
1227
1252
  case 'pageview_indicator':
1228
1253
  {
1229
1254
  node = /*#__PURE__*/_jsx(PageViewIndicator, {
@@ -1285,7 +1310,6 @@ function buildWidget(json, params) {
1285
1310
  const isOverlay = axis === 'overlay';
1286
1311
  const spacing = Number(props.childSpacing ?? 0);
1287
1312
  const applySpacing = !STACK_SPACE_DISTRIBUTIONS.has(String(props.distribution ?? 'start'));
1288
- const paddingInsets = stackSpacingToInsets(props.layout?.padding ?? props.padding);
1289
1313
  const width = resolveStackDimension(props, axis, 'width', pageAxisBounds);
1290
1314
  const height = resolveStackDimension(props, axis, 'height', pageAxisBounds);
1291
1315
  const mainAxisBounded = axis === 'horizontal' ? pageAxisBounds.widthBounded : pageAxisBounds.heightBounded;
@@ -1318,14 +1342,18 @@ function buildWidget(json, params) {
1318
1342
  overflow: borderRadius > 0 ? 'hidden' : undefined,
1319
1343
  borderWidth: borderColor ? borderWidth : undefined,
1320
1344
  borderColor: borderColor ?? undefined,
1321
- ...insetsToStyle(paddingInsets),
1322
1345
  ...shadowStyle
1323
1346
  };
1347
+ const stackContentSizeStyle = {
1348
+ width: width !== undefined ? '100%' : undefined,
1349
+ height: height !== undefined ? '100%' : undefined
1350
+ };
1324
1351
  const content = isOverlay ? /*#__PURE__*/_jsxs(View, {
1325
1352
  style: {
1326
1353
  position: 'relative',
1327
1354
  minHeight: 0,
1328
- minWidth: 0
1355
+ minWidth: 0,
1356
+ ...stackContentSizeStyle
1329
1357
  },
1330
1358
  children: [(childrenJson ?? []).map((child, index) => {
1331
1359
  const alignment = parseOverlayGridAlignment(props.overlayAlignment ?? props.alignment);
@@ -1352,7 +1380,8 @@ function buildWidget(json, params) {
1352
1380
  justifyContent: parseFlexAlignment(props.distribution),
1353
1381
  alignItems: parseCrossAlignment(props.alignment),
1354
1382
  minHeight: 0,
1355
- minWidth: 0
1383
+ minWidth: 0,
1384
+ ...stackContentSizeStyle
1356
1385
  },
1357
1386
  children: (childrenJson ?? []).map((child, index) => /*#__PURE__*/_jsxs(React.Fragment, {
1358
1387
  children: [buildWidget(child, {
@@ -1442,6 +1471,17 @@ function buildWidget(json, params) {
1442
1471
  children: node
1443
1472
  });
1444
1473
  }
1474
+ const shouldStretchCrossAxis = params.parentFlexAxis === 'vertical' && (type === 'selection_list' || type === 'wheel_picker' || wantsFillWidth(props)) || params.parentFlexAxis === 'horizontal' && wantsFillHeight(props);
1475
+ if (shouldStretchCrossAxis) {
1476
+ node = /*#__PURE__*/_jsx(View, {
1477
+ style: {
1478
+ alignSelf: 'stretch',
1479
+ minWidth: 0,
1480
+ minHeight: 0
1481
+ },
1482
+ children: node
1483
+ });
1484
+ }
1445
1485
  if (params.allowFlexExpansion) {
1446
1486
  let shouldExpand = false;
1447
1487
  const parentAxis = params.parentFlexAxis ?? 'vertical';
@@ -1750,7 +1790,7 @@ function parseLayoutDimension(value, zeroIsNull = false) {
1750
1790
  if (typeof value === 'string') {
1751
1791
  const trimmed = value.trim();
1752
1792
  const lowered = trimmed.toLowerCase();
1753
- if (lowered === 'double.infinity' || lowered === 'infinity' || lowered === '+infinity') {
1793
+ if (lowered === 'double.infinity' || lowered === 'infinite' || lowered === 'infinity' || lowered === '+infinity') {
1754
1794
  return Number.POSITIVE_INFINITY;
1755
1795
  }
1756
1796
  if (trimmed.endsWith('%')) {
@@ -2086,6 +2126,12 @@ function SelectionList({
2086
2126
  };
2087
2127
  const layout = properties.layout ?? 'column';
2088
2128
  const spacing = Number(properties.spacing ?? 8);
2129
+ const shouldOptionFillWidth = option => {
2130
+ if (layout === 'grid') return true;
2131
+ const child = option.child && typeof option.child === 'object' ? normalizeStackLikeNode(option.child) : option.child;
2132
+ const childProps = child && typeof child === 'object' ? child.properties ?? {} : {};
2133
+ return wantsFillWidth(childProps);
2134
+ };
2089
2135
  const renderItem = (option, index) => {
2090
2136
  const value = String(option.value ?? '');
2091
2137
  const isSelected = selectedValues.includes(value);
@@ -2094,10 +2140,12 @@ function SelectionList({
2094
2140
  const borderColor = parseColor(styleProps.borderColor);
2095
2141
  const borderRadius = Number(styleProps.borderRadius ?? 8);
2096
2142
  const borderWidth = Number(styleProps.borderWidth ?? 1);
2143
+ const fillWidth = shouldOptionFillWidth(option);
2097
2144
  return /*#__PURE__*/_jsx(Pressable, {
2098
2145
  onPress: () => toggleSelection(value),
2099
2146
  style: {
2100
- width: '100%',
2147
+ width: fillWidth ? '100%' : undefined,
2148
+ alignSelf: fillWidth ? 'stretch' : 'flex-start',
2101
2149
  backgroundColor,
2102
2150
  borderColor,
2103
2151
  borderWidth,
@@ -2149,7 +2197,8 @@ function SelectionList({
2149
2197
  },
2150
2198
  children: options.map((option, index) => /*#__PURE__*/_jsx(View, {
2151
2199
  style: {
2152
- width: '100%',
2200
+ width: shouldOptionFillWidth(option) ? '100%' : undefined,
2201
+ alignSelf: shouldOptionFillWidth(option) ? 'stretch' : 'flex-start',
2153
2202
  marginBottom: spacing
2154
2203
  },
2155
2204
  children: renderItem(option, index)
@@ -2751,15 +2800,167 @@ function FakeProgressBar({
2751
2800
  });
2752
2801
  }
2753
2802
  const AnimatedCircle = Animated.createAnimatedComponent(Circle);
2803
+ export function resolvePeekInsets(pageAlignment, pagePeek) {
2804
+ const leading = pageAlignment === 'start' ? 0 : pageAlignment === 'end' ? pagePeek * 2 : pagePeek;
2805
+ const trailing = pageAlignment === 'end' ? 0 : pageAlignment === 'start' ? pagePeek * 2 : pagePeek;
2806
+ return {
2807
+ leading,
2808
+ trailing
2809
+ };
2810
+ }
2811
+ function resolveSliderSizeMode(sliderProps, key, fallback) {
2812
+ const raw = String(sliderProps?.size?.[key] ?? '').toLowerCase();
2813
+ if (raw === 'fill' || raw === 'fit' || raw === 'fixed') return raw;
2814
+ return sliderProps?.[key] !== undefined ? 'fixed' : fallback;
2815
+ }
2816
+ function resolveSlideSizeMode(slideProps, key) {
2817
+ const raw = String(slideProps?.size?.[key] ?? '').toLowerCase();
2818
+ if (raw === 'fill' || raw === 'fit' || raw === 'fixed') return raw;
2819
+ return key === 'width' ? 'fill' : 'fit';
2820
+ }
2821
+ function toFinite(value) {
2822
+ if (typeof value === 'number' && Number.isFinite(value)) return value;
2823
+ if (typeof value === 'string') {
2824
+ const parsed = Number(value);
2825
+ if (Number.isFinite(parsed)) return parsed;
2826
+ }
2827
+ return null;
2828
+ }
2829
+ export function resolveSliderContainerSize(args) {
2830
+ const dotsInset = args.showDots ? 8 + 6 + 8 : 0;
2831
+ const fitPrimary = args.direction === 'horizontal' ? args.activeSlideWidth : args.activeSlideHeight;
2832
+ const fitViewportPrimary = Math.max(1, fitPrimary + args.leadingPeek + args.trailingPeek);
2833
+ const viewportWidth = args.direction === 'horizontal' ? fitViewportPrimary : Math.max(1, args.activeSlideWidth);
2834
+ const viewportHeight = args.direction === 'vertical' ? fitViewportPrimary : Math.max(1, args.activeSlideHeight);
2835
+ const fitOuterWidth = Math.min(Math.max(1, viewportWidth), Math.max(1, args.availableWidth || viewportWidth));
2836
+ const fitOuterHeight = Math.max(1, viewportHeight + dotsInset);
2837
+ const outerWidth = args.widthMode === 'fill' ? '100%' : args.widthMode === 'fixed' ? Math.max(1, args.fixedWidth ?? fitOuterWidth) : fitOuterWidth;
2838
+ const outerHeight = args.heightMode === 'fill' ? '100%' : args.heightMode === 'fixed' ? Math.max(1, args.fixedHeight ?? fitOuterHeight) : fitOuterHeight;
2839
+ return {
2840
+ outerWidth,
2841
+ outerHeight,
2842
+ viewportWidth: args.widthMode === 'fit' ? fitOuterWidth : Math.max(1, args.availableWidth),
2843
+ viewportHeight: args.heightMode === 'fit' ? Math.max(1, fitOuterHeight - dotsInset) : Math.max(1, args.availableHeight || fitOuterHeight - dotsInset)
2844
+ };
2845
+ }
2846
+ export function resolveSlidePageSize(args) {
2847
+ const widthMode = resolveSlideSizeMode(args.slideProps, 'width');
2848
+ const heightMode = resolveSlideSizeMode(args.slideProps, 'height');
2849
+ const fixedWidth = toFinite(parseLayoutDimension(args.slideProps.width ?? args.slideProps.size?.widthPx, true));
2850
+ const fixedHeight = toFinite(parseLayoutDimension(args.slideProps.height ?? args.slideProps.size?.heightPx, true));
2851
+ const width = widthMode === 'fixed' ? Math.max(1, fixedWidth ?? args.pagePrimary) : widthMode === 'fit' ? undefined : args.direction === 'horizontal' ? args.pagePrimary : args.crossSize;
2852
+ const height = heightMode === 'fixed' ? Math.max(1, fixedHeight ?? args.pagePrimary) : heightMode === 'fit' ? undefined : args.direction === 'vertical' ? args.pagePrimary : args.crossSize;
2853
+ return {
2854
+ width: width ?? 'auto',
2855
+ height: height ?? 'auto'
2856
+ };
2857
+ }
2858
+ function resolveSliderBackgroundColor(sliderProps) {
2859
+ const extractColorCandidate = input => {
2860
+ if (input === null || input === undefined) return undefined;
2861
+ if (typeof input === 'string' || typeof input === 'number') return input;
2862
+ if (Array.isArray(input)) {
2863
+ for (const value of input) {
2864
+ const nested = extractColorCandidate(value);
2865
+ if (nested !== undefined) return nested;
2866
+ }
2867
+ return undefined;
2868
+ }
2869
+ if (typeof input !== 'object') return undefined;
2870
+ const source = input;
2871
+ if (Number.isFinite(Number(source.r)) && Number.isFinite(Number(source.g)) && Number.isFinite(Number(source.b))) {
2872
+ const alphaRaw = Number.isFinite(Number(source.a)) ? Number(source.a) : 1;
2873
+ const alpha = Math.max(0, Math.min(1, alphaRaw));
2874
+ return `rgba(${Number(source.r)},${Number(source.g)},${Number(source.b)},${alpha})`;
2875
+ }
2876
+
2877
+ // If a gradient is provided for slider fill, use the first stop as fallback.
2878
+ if (String(source.type ?? '').toLowerCase() === 'gradient' && Array.isArray(source.colors)) {
2879
+ const first = extractColorCandidate(source.colors);
2880
+ if (first !== undefined) return first;
2881
+ }
2882
+ const candidateKeys = ['value', 'color', 'backgroundColor', 'hex', 'argb', 'rgba', 'raw'];
2883
+ for (const key of candidateKeys) {
2884
+ const nested = extractColorCandidate(source[key]);
2885
+ if (nested !== undefined) return nested;
2886
+ }
2887
+ return undefined;
2888
+ };
2889
+ const parseSliderFillColor = color => {
2890
+ const candidate = extractColorCandidate(color);
2891
+ if (candidate === undefined) return 'transparent';
2892
+ if (typeof candidate === 'string') {
2893
+ const trimmed = candidate.trim();
2894
+ if (/^#[\da-fA-F]{8}$/.test(trimmed)) {
2895
+ // Dashboard stores alpha-enabled web hex as #RRGGBBAA.
2896
+ const rrggbbaa = trimmed.slice(1);
2897
+ const aarrggbb = `${rrggbbaa.slice(6, 8)}${rrggbbaa.slice(0, 6)}`;
2898
+ return parseColor(`0x${aarrggbb}`);
2899
+ }
2900
+ return parseColor(trimmed);
2901
+ }
2902
+ if (typeof candidate === 'number') {
2903
+ return parseColor(candidate);
2904
+ }
2905
+ if (typeof color === 'string') {
2906
+ const trimmed = color.trim();
2907
+ if (/^#[\da-fA-F]{8}$/.test(trimmed)) {
2908
+ // Dashboard stores alpha-enabled web hex as #RRGGBBAA.
2909
+ const rrggbbaa = trimmed.slice(1);
2910
+ const aarrggbb = `${rrggbbaa.slice(6, 8)}${rrggbbaa.slice(0, 6)}`;
2911
+ return parseColor(`0x${aarrggbb}`);
2912
+ }
2913
+ }
2914
+ return parseColor(candidate);
2915
+ };
2916
+ const appearanceFill = sliderProps?.appearance?.fill;
2917
+ if (typeof appearanceFill === 'string' || typeof appearanceFill === 'number') {
2918
+ return parseSliderFillColor(appearanceFill);
2919
+ }
2920
+ if (appearanceFill && typeof appearanceFill === 'object') {
2921
+ const type = String(appearanceFill.type ?? '').toLowerCase();
2922
+ if (type === 'color' || type === 'solid' || type === '') {
2923
+ const color = appearanceFill.value ?? appearanceFill.color;
2924
+ if (color !== undefined) return parseSliderFillColor(color);
2925
+ }
2926
+ }
2927
+ const fill = sliderProps?.fill;
2928
+ if (typeof fill === 'string' || typeof fill === 'number') {
2929
+ return parseSliderFillColor(fill);
2930
+ }
2931
+ if (fill && typeof fill === 'object') {
2932
+ const type = String(fill.type ?? '').toLowerCase();
2933
+ if (type === 'color' || type === 'solid' || type === '') {
2934
+ const color = fill.value ?? fill.color;
2935
+ if (color !== undefined) return parseSliderFillColor(color);
2936
+ }
2937
+ }
2938
+ const background = sliderProps?.background;
2939
+ if (background && typeof background === 'object') {
2940
+ const type = String(background.type ?? '').toLowerCase();
2941
+ if (type === 'color') {
2942
+ const color = background.value ?? background.color;
2943
+ if (color !== undefined) return parseSliderFillColor(color);
2944
+ }
2945
+ }
2946
+ if (sliderProps?.backgroundColor !== undefined) {
2947
+ return parseSliderFillColor(sliderProps.backgroundColor);
2948
+ }
2949
+ return undefined;
2950
+ }
2754
2951
  function SliderWidget({
2755
2952
  id,
2756
- linkedTo,
2953
+ sliderProps,
2954
+ slidePropsList,
2757
2955
  direction,
2758
- autoPlay,
2759
- autoPlayDelay,
2956
+ pageAlignment,
2957
+ pageSpacing,
2958
+ pagePeek,
2760
2959
  loop,
2761
- height,
2762
- physics,
2960
+ autoAdvance,
2961
+ autoAdvanceIntervalMs,
2962
+ pageControlEnabled,
2963
+ pageControlPosition,
2763
2964
  children
2764
2965
  }) {
2765
2966
  const registry = useSliderRegistry();
@@ -2768,11 +2969,28 @@ function SliderWidget({
2768
2969
  const pageCount = pageLength;
2769
2970
  const [currentPage, setCurrentPage] = React.useState(0);
2770
2971
  const timerRef = React.useRef(null);
2972
+ const [isInteracting, setIsInteracting] = React.useState(false);
2973
+ const idleTimerRef = React.useRef(null);
2974
+ const [availableSize, setAvailableSize] = React.useState({
2975
+ width: 1,
2976
+ height: 1
2977
+ });
2978
+ const [activeSlideSize, setActiveSlideSize] = React.useState({
2979
+ width: 1,
2980
+ height: 1
2981
+ });
2982
+ const markInteracting = React.useCallback(() => {
2983
+ setIsInteracting(true);
2984
+ if (idleTimerRef.current) {
2985
+ clearTimeout(idleTimerRef.current);
2986
+ }
2987
+ idleTimerRef.current = setTimeout(() => setIsInteracting(false), 1400);
2988
+ }, []);
2771
2989
  React.useEffect(() => {
2772
2990
  setCurrentPage(prev => clampSliderPage(prev, pageCount));
2773
2991
  }, [pageCount]);
2774
2992
  React.useEffect(() => {
2775
- if (autoPlay && !linkedTo && pageLength > 0) {
2993
+ if (autoAdvance && !isInteracting && pageLength > 1) {
2776
2994
  timerRef.current = setInterval(() => {
2777
2995
  setCurrentPage(prev => {
2778
2996
  const safePrev = clampSliderPage(prev, pageCount);
@@ -2780,12 +2998,12 @@ function SliderWidget({
2780
2998
  if (loop) return 0;
2781
2999
  return safePrev;
2782
3000
  });
2783
- }, autoPlayDelay);
3001
+ }, autoAdvanceIntervalMs);
2784
3002
  }
2785
3003
  return () => {
2786
3004
  if (timerRef.current) clearInterval(timerRef.current);
2787
3005
  };
2788
- }, [autoPlay, autoPlayDelay, linkedTo, loop, pageCount, pageLength]);
3006
+ }, [autoAdvance, autoAdvanceIntervalMs, isInteracting, loop, pageCount, pageLength]);
2789
3007
  React.useEffect(() => {
2790
3008
  if (pagerRef.current && pageCount > 0) {
2791
3009
  pagerRef.current.setPage(clampSliderPage(currentPage, pageCount));
@@ -2796,44 +3014,165 @@ function SliderWidget({
2796
3014
  registry.update(id, currentPage % children.length, children.length);
2797
3015
  }
2798
3016
  }, [registry, id, currentPage, children.length]);
2799
- React.useEffect(() => {
2800
- if (!linkedTo || !registry) return;
2801
- return registry.getNotifier(linkedTo, value => {
2802
- const normalized = normalizeLoopIndex(value, pageLength);
2803
- const targetPage = clampSliderPage(normalized, pageCount);
2804
- setCurrentPage(targetPage);
2805
- if (pagerRef.current) {
2806
- pagerRef.current.setPage(targetPage);
2807
- }
2808
- });
2809
- }, [linkedTo, pageCount, pageLength, registry]);
2810
3017
  if (pageLength === 0) return null;
2811
- return /*#__PURE__*/_jsx(PagerView, {
2812
- ref: pagerRef,
2813
- style: height === undefined ? {
2814
- flex: 1
2815
- } : {
2816
- height
3018
+
3019
+ // Slider layout parity rules copied from Web Preview:
3020
+ // 1) leadingPeek = start?0 : end?pagePeek*2 : pagePeek
3021
+ // 2) trailingPeek = end?0 : start?pagePeek*2 : pagePeek
3022
+ // 3) pagePrimary = viewportPrimary - leadingPeek - trailingPeek (clamped >= 1)
3023
+ // 4) track offset = activeIndex * (pagePrimary + pageSpacing)
3024
+ // 5) fit parent mode follows active slide size but is clamped to container width
3025
+ // 6) viewport always clips track content (no root horizontal overflow)
3026
+ const {
3027
+ leading,
3028
+ trailing
3029
+ } = resolvePeekInsets(pageAlignment, pagePeek);
3030
+ const showDots = pageControlEnabled && pageLength > 1;
3031
+ const widthMode = resolveSliderSizeMode(sliderProps, 'width', 'fill');
3032
+ const heightMode = resolveSliderSizeMode(sliderProps, 'height', 'fit');
3033
+ const fixedWidth = toFinite(parseLayoutDimension(sliderProps.width, true));
3034
+ const fixedHeight = toFinite(parseLayoutDimension(sliderProps.height, true));
3035
+ const sliderBackgroundColor = resolveSliderBackgroundColor(sliderProps);
3036
+ const resolved = resolveSliderContainerSize({
3037
+ direction,
3038
+ widthMode,
3039
+ heightMode,
3040
+ availableWidth: availableSize.width,
3041
+ availableHeight: availableSize.height,
3042
+ activeSlideWidth: activeSlideSize.width,
3043
+ activeSlideHeight: activeSlideSize.height,
3044
+ fixedWidth,
3045
+ fixedHeight,
3046
+ leadingPeek: leading,
3047
+ trailingPeek: trailing,
3048
+ showDots
3049
+ });
3050
+ const viewportPrimary = direction === 'horizontal' ? resolved.viewportWidth : resolved.viewportHeight;
3051
+ const pagePrimary = Math.max(1, viewportPrimary - leading - trailing);
3052
+ return /*#__PURE__*/_jsxs(View, {
3053
+ onTouchStart: markInteracting,
3054
+ onTouchEnd: markInteracting,
3055
+ onTouchCancel: markInteracting,
3056
+ onPointerDown: markInteracting,
3057
+ onPointerUp: markInteracting,
3058
+ onLayout: event => {
3059
+ const nextWidth = Math.max(1, Math.round(event.nativeEvent.layout.width));
3060
+ const nextHeight = Math.max(1, Math.round(event.nativeEvent.layout.height));
3061
+ setAvailableSize(prev => prev.width === nextWidth && prev.height === nextHeight ? prev : {
3062
+ width: nextWidth,
3063
+ height: nextHeight
3064
+ });
2817
3065
  },
2818
- orientation: direction,
2819
- scrollEnabled: physics !== 'never',
2820
- initialPage: clampSliderPage(currentPage, pageCount),
2821
- onPageSelected: event => {
2822
- const page = event.nativeEvent.position;
2823
- const safePage = clampSliderPage(page, pageCount);
2824
- setCurrentPage(safePage);
2825
- if (registry && id) {
2826
- registry.update(id, safePage % pageLength, pageLength);
2827
- }
3066
+ style: {
3067
+ width: resolved.outerWidth,
3068
+ height: resolved.outerHeight,
3069
+ minWidth: 1,
3070
+ minHeight: 1,
3071
+ backgroundColor: sliderBackgroundColor,
3072
+ borderRadius: 0,
3073
+ overflow: 'visible'
2828
3074
  },
2829
- children: Array.from({
2830
- length: pageCount
2831
- }).map((_, index) => /*#__PURE__*/_jsx(View, {
3075
+ children: [showDots && pageControlPosition === 'top' ? /*#__PURE__*/_jsx(View, {
2832
3076
  style: {
2833
- flex: 1
3077
+ marginBottom: 8,
3078
+ flexDirection: 'row',
3079
+ justifyContent: 'center',
3080
+ alignItems: 'center'
2834
3081
  },
2835
- children: children[index % pageLength]
2836
- }, `slider-page-${index}`))
3082
+ children: Array.from({
3083
+ length: pageLength
3084
+ }).map((_, index) => {
3085
+ const isActive = index === currentPage;
3086
+ return /*#__PURE__*/_jsx(View, {
3087
+ style: {
3088
+ width: 6,
3089
+ height: 6,
3090
+ borderRadius: 3,
3091
+ marginHorizontal: 3,
3092
+ backgroundColor: isActive ? parseColor('0xFF111827') : parseColor('0xFFD1D5DB')
3093
+ }
3094
+ }, `slider-dot-top-${index}`);
3095
+ })
3096
+ }) : null, /*#__PURE__*/_jsx(View, {
3097
+ style: {
3098
+ overflow: 'hidden',
3099
+ ...(direction === 'horizontal' ? {
3100
+ width: resolved.viewportWidth,
3101
+ height: resolved.viewportHeight,
3102
+ paddingLeft: leading,
3103
+ paddingRight: trailing
3104
+ } : {
3105
+ width: resolved.viewportWidth,
3106
+ height: resolved.viewportHeight,
3107
+ paddingTop: leading,
3108
+ paddingBottom: trailing
3109
+ })
3110
+ },
3111
+ children: /*#__PURE__*/_jsx(PagerView, {
3112
+ ref: pagerRef,
3113
+ style: {
3114
+ width: '100%',
3115
+ height: '100%',
3116
+ minHeight: 1
3117
+ },
3118
+ orientation: direction,
3119
+ pageMargin: Math.max(0, pageSpacing),
3120
+ initialPage: clampSliderPage(currentPage, pageCount),
3121
+ onPageSelected: event => {
3122
+ const page = event.nativeEvent.position;
3123
+ const safePage = clampSliderPage(page, pageCount);
3124
+ setCurrentPage(safePage);
3125
+ markInteracting();
3126
+ if (registry && id) {
3127
+ registry.update(id, safePage % pageLength, pageLength);
3128
+ }
3129
+ },
3130
+ children: Array.from({
3131
+ length: pageCount
3132
+ }).map((_, index) => /*#__PURE__*/_jsx(View, {
3133
+ onLayout: index === currentPage ? event => {
3134
+ const nextWidth = Math.max(1, Math.round(event.nativeEvent.layout.width));
3135
+ const nextHeight = Math.max(1, Math.round(event.nativeEvent.layout.height));
3136
+ setActiveSlideSize(prev => prev.width === nextWidth && prev.height === nextHeight ? prev : {
3137
+ width: nextWidth,
3138
+ height: nextHeight
3139
+ });
3140
+ } : undefined,
3141
+ style: {
3142
+ alignSelf: 'flex-start',
3143
+ overflow: 'hidden',
3144
+ ...resolveSlidePageSize({
3145
+ slideProps: slidePropsList[index] ?? {},
3146
+ direction,
3147
+ pagePrimary,
3148
+ crossSize: '100%'
3149
+ })
3150
+ },
3151
+ children: children[index % pageLength]
3152
+ }, `slider-page-${index}`))
3153
+ })
3154
+ }), showDots && pageControlPosition === 'bottom' ? /*#__PURE__*/_jsx(View, {
3155
+ style: {
3156
+ marginTop: 8,
3157
+ flexDirection: 'row',
3158
+ justifyContent: 'center',
3159
+ alignItems: 'center'
3160
+ },
3161
+ children: Array.from({
3162
+ length: pageLength
3163
+ }).map((_, index) => {
3164
+ const isActive = index === currentPage;
3165
+ return /*#__PURE__*/_jsx(View, {
3166
+ style: {
3167
+ width: 6,
3168
+ height: 6,
3169
+ borderRadius: 3,
3170
+ marginHorizontal: 3,
3171
+ backgroundColor: isActive ? parseColor('0xFF111827') : parseColor('0xFFD1D5DB')
3172
+ }
3173
+ }, `slider-dot-bottom-${index}`);
3174
+ })
3175
+ }) : null]
2837
3176
  });
2838
3177
  }
2839
3178
  function clampSliderPage(page, pageCount) {
@@ -2843,11 +3182,6 @@ function clampSliderPage(page, pageCount) {
2843
3182
  if (page >= pageCount) return pageCount - 1;
2844
3183
  return Math.floor(page);
2845
3184
  }
2846
- function normalizeLoopIndex(index, size) {
2847
- if (size <= 0 || !Number.isFinite(index)) return 0;
2848
- const normalized = Math.floor(index) % size;
2849
- return normalized < 0 ? normalized + size : normalized;
2850
- }
2851
3185
  function PageViewIndicator({
2852
3186
  linkedTo,
2853
3187
  activeColor,