flowboard-react 0.6.8 → 0.6.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +16 -0
  2. package/lib/module/components/FlowboardFlow.js +24 -66
  3. package/lib/module/components/FlowboardFlow.js.map +1 -1
  4. package/lib/module/components/FlowboardRenderer.js +590 -196
  5. package/lib/module/components/FlowboardRenderer.js.map +1 -1
  6. package/lib/module/components/layout/stackOverlayModel.js +156 -0
  7. package/lib/module/components/layout/stackOverlayModel.js.map +1 -0
  8. package/lib/module/fonts/fontLoader.js +285 -0
  9. package/lib/module/fonts/fontLoader.js.map +1 -0
  10. package/lib/module/fonts/fontResolver.js +38 -0
  11. package/lib/module/fonts/fontResolver.js.map +1 -0
  12. package/lib/module/fonts/google-fonts-meta.json +1 -0
  13. package/lib/module/fonts/googleFontCatalog.js +56 -0
  14. package/lib/module/fonts/googleFontCatalog.js.map +1 -0
  15. package/lib/module/fonts/googleFontLoader.js +101 -0
  16. package/lib/module/fonts/googleFontLoader.js.map +1 -0
  17. package/lib/module/fonts/googleFontsLoader.js +68 -0
  18. package/lib/module/fonts/googleFontsLoader.js.map +1 -0
  19. package/lib/module/index.js +2 -0
  20. package/lib/module/index.js.map +1 -1
  21. package/lib/typescript/src/components/FlowboardFlow.d.ts.map +1 -1
  22. package/lib/typescript/src/components/FlowboardRenderer.d.ts +11 -4
  23. package/lib/typescript/src/components/FlowboardRenderer.d.ts.map +1 -1
  24. package/lib/typescript/src/components/layout/stackOverlayModel.d.ts +13 -0
  25. package/lib/typescript/src/components/layout/stackOverlayModel.d.ts.map +1 -0
  26. package/lib/typescript/src/fonts/fontLoader.d.ts +17 -0
  27. package/lib/typescript/src/fonts/fontLoader.d.ts.map +1 -0
  28. package/lib/typescript/src/fonts/fontResolver.d.ts +11 -0
  29. package/lib/typescript/src/fonts/fontResolver.d.ts.map +1 -0
  30. package/lib/typescript/src/fonts/googleFontCatalog.d.ts +4 -0
  31. package/lib/typescript/src/fonts/googleFontCatalog.d.ts.map +1 -0
  32. package/lib/typescript/src/fonts/googleFontLoader.d.ts +7 -0
  33. package/lib/typescript/src/fonts/googleFontLoader.d.ts.map +1 -0
  34. package/lib/typescript/src/fonts/googleFontsLoader.d.ts +18 -0
  35. package/lib/typescript/src/fonts/googleFontsLoader.d.ts.map +1 -0
  36. package/lib/typescript/src/index.d.ts +2 -0
  37. package/lib/typescript/src/index.d.ts.map +1 -1
  38. package/package.json +14 -2
  39. package/src/components/FlowboardFlow.tsx +33 -82
  40. package/src/components/FlowboardRenderer.tsx +852 -210
  41. package/src/components/layout/stackOverlayModel.ts +185 -0
  42. package/src/fonts/fontLoader.ts +426 -0
  43. package/src/fonts/fontResolver.ts +69 -0
  44. package/src/fonts/google-fonts-meta.json +1 -0
  45. package/src/fonts/googleFontCatalog.ts +77 -0
  46. package/src/fonts/googleFontLoader.ts +136 -0
  47. package/src/fonts/googleFontsLoader.ts +124 -0
  48. package/src/index.tsx +13 -0
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
 
3
- import React, { useMemo } from 'react';
3
+ import React, { useEffect, useMemo, useState } from 'react';
4
4
  import { Animated, KeyboardAvoidingView, Platform, Pressable, ScrollView, StyleSheet, Text, TextInput, View, Image, Vibration } from 'react-native';
5
5
  import MaskInput from 'react-native-mask-input';
6
6
  import LinearGradient from "../native/linearGradient.js";
@@ -12,6 +12,10 @@ import Svg, { Circle, G, Line, Polygon, SvgText } from "../native/svg.js";
12
12
  import { insetsToStyle, insetsToMarginStyle, parseAlignment, parseColor, parseCrossAlignment, parseDimension, parseFlexAlignment, parseFontWeight, parseInsets, parseResizeMode, parseTextAlign, parseTextDecoration, resolveNumericValue, resolveText } from "../utils/flowboardUtils.js";
13
13
  import { resolveFontAwesomeIcon, FontAwesome6 } from "../core/fontAwesome.js";
14
14
  import { useSliderRegistry } from "./widgets/sliderRegistry.js";
15
+ import { isOverlayStack, isPositionedChild, resolveAlignmentToCss, resolveClipBehavior, resolveFitBehavior, resolveOverlayAlignmentValue, resolvePositionedStyle } from "./layout/stackOverlayModel.js";
16
+ import { ensureGoogleFontLoaded as ensureWebGoogleFontLoaded, resolveFontFamily } from "../fonts/googleFontLoader.js";
17
+ import { ensureFontLoaded as ensureNativeFontLoaded, isFontLoaded, resolveAppliedFontFamily, subscribeFontLoad } from "../fonts/fontLoader.js";
18
+ import { resolveFontSpec } from "../fonts/fontResolver.js";
15
19
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
16
20
  const styles = StyleSheet.create({
17
21
  root: {
@@ -48,6 +52,126 @@ function getAxisBoundsForPageScroll(pageScroll) {
48
52
  heightBounded: true
49
53
  };
50
54
  }
55
+ const LEGACY_SLIDE_DROP_KEYS = new Set(['badge']);
56
+ function isDevelopmentMode() {
57
+ return process.env.NODE_ENV !== 'production';
58
+ }
59
+ function warnLegacySlideMigration(contextPath, componentId, droppedKeys) {
60
+ if (!isDevelopmentMode() || droppedKeys.length === 0) return;
61
+ const idSuffix = componentId ? ` (id: ${componentId})` : '';
62
+ console.warn(`[slider-migration] Migrated legacy slide to stack at "${contextPath}"${idSuffix}. Dropped unsupported keys: ${droppedKeys.join(', ')}`);
63
+ }
64
+ function warnLegacySliderChildWrap(contextPath, childType) {
65
+ if (!isDevelopmentMode()) return;
66
+ console.warn(`[slider-migration] Wrapped non-stack slider child at "${contextPath}" as a stack page${childType ? ` (type: ${childType})` : ''}.`);
67
+ }
68
+ function normalizeLegacySlidePropsToStackProps(input, contextPath, componentId) {
69
+ const props = input && typeof input === 'object' ? {
70
+ ...input
71
+ } : {};
72
+ const droppedKeys = [];
73
+ if (props.fill && typeof props.fill === 'object') {
74
+ const normalizedFill = {
75
+ ...props.fill
76
+ };
77
+ const fillType = String(normalizedFill.type ?? '').toLowerCase();
78
+ if (fillType === 'color') {
79
+ normalizedFill.type = 'solid';
80
+ }
81
+ props.fill = normalizedFill;
82
+ }
83
+ for (const key of LEGACY_SLIDE_DROP_KEYS) {
84
+ if (key in props) {
85
+ droppedKeys.push(key);
86
+ delete props[key];
87
+ }
88
+ }
89
+ warnLegacySlideMigration(contextPath, componentId, droppedKeys);
90
+ return props;
91
+ }
92
+ function createStackPageWrapper(child, pageIndex) {
93
+ return {
94
+ id: child?.id ? `${String(child.id)}-page` : `legacy-page-${pageIndex + 1}`,
95
+ type: 'stack',
96
+ properties: {
97
+ axis: 'vertical',
98
+ alignment: 'start',
99
+ distribution: 'start',
100
+ childSpacing: 0,
101
+ size: {
102
+ width: 'fill',
103
+ height: 'fit'
104
+ },
105
+ layout: {
106
+ margin: {
107
+ top: 0,
108
+ right: 0,
109
+ bottom: 0,
110
+ left: 0
111
+ }
112
+ },
113
+ appearance: {
114
+ shape: 'rectangle',
115
+ cornerRadius: 0
116
+ }
117
+ },
118
+ children: child ? [child] : []
119
+ };
120
+ }
121
+ function migrateLegacySlideNodes(input, contextPath = 'screen') {
122
+ if (Array.isArray(input)) {
123
+ return input.map((item, index) => migrateLegacySlideNodes(item, `${contextPath}[${index}]`));
124
+ }
125
+ if (!input || typeof input !== 'object') return input;
126
+ const node = {
127
+ ...input
128
+ };
129
+ const rawType = String(node.type ?? '');
130
+ if (Array.isArray(node.children)) {
131
+ node.children = node.children.map((child, index) => migrateLegacySlideNodes(child, `${contextPath}.children[${index}]`));
132
+ }
133
+ if (node.child && typeof node.child === 'object') {
134
+ node.child = migrateLegacySlideNodes(node.child, `${contextPath}.child`);
135
+ }
136
+ if (Array.isArray(node.options)) {
137
+ node.options = node.options.map((option, index) => {
138
+ if (!option || typeof option !== 'object') return option;
139
+ const optionNode = {
140
+ ...option
141
+ };
142
+ if (optionNode.child && typeof optionNode.child === 'object') {
143
+ optionNode.child = migrateLegacySlideNodes(optionNode.child, `${contextPath}.options[${index}].child`);
144
+ }
145
+ return optionNode;
146
+ });
147
+ }
148
+ if (rawType === 'slide') {
149
+ node.type = 'stack';
150
+ node.properties = normalizeLegacySlidePropsToStackProps(node.properties ?? {}, contextPath, node.id ? String(node.id) : undefined);
151
+ }
152
+ if (String(node.type ?? '') === 'slider') {
153
+ const children = Array.isArray(node.children) ? node.children : [];
154
+ const normalizedPages = children.map((child, index) => {
155
+ if (!child || typeof child !== 'object') {
156
+ warnLegacySliderChildWrap(`${contextPath}.children[${index}]`, undefined);
157
+ return createStackPageWrapper(null, index);
158
+ }
159
+ const pageNode = child;
160
+ const childType = String(pageNode.type ?? '');
161
+ if (childType === 'stack') return pageNode;
162
+ warnLegacySliderChildWrap(`${contextPath}.children[${index}]`, childType);
163
+ return createStackPageWrapper(pageNode, index);
164
+ });
165
+ node.children = normalizedPages.length > 0 ? normalizedPages : [createStackPageWrapper(null, 0)];
166
+ }
167
+ return node;
168
+ }
169
+ function useGoogleFontLoadSignal() {
170
+ const [, setVersion] = useState(0);
171
+ useEffect(() => subscribeFontLoad(() => {
172
+ setVersion(version => version + 1);
173
+ }), []);
174
+ }
51
175
  export default function FlowboardRenderer(props) {
52
176
  const {
53
177
  screenData,
@@ -58,28 +182,30 @@ export default function FlowboardRenderer(props) {
58
182
  currentIndex = 0,
59
183
  totalScreens = 1
60
184
  } = props;
61
- const childrenData = Array.isArray(screenData.children) ? screenData.children : [];
62
- const backgroundData = screenData.background;
63
- const bgColorCode = screenData.backgroundColor;
64
- const showProgress = screenData.showProgress === true;
65
- const progressColor = parseColor(screenData.progressColor ?? '0xFF000000');
66
- const progressThickness = Number(screenData.progressThickness ?? 4);
67
- const progressRadius = Number(screenData.progressRadius ?? 0);
68
- const progressStyle = screenData.progressStyle ?? 'linear';
69
- const pageScroll = resolvePageScrollAxis(screenData);
185
+ useGoogleFontLoadSignal();
186
+ const migratedScreenData = useMemo(() => migrateLegacySlideNodes(screenData, 'screen') ?? {}, [screenData]);
187
+ const childrenData = Array.isArray(migratedScreenData.children) ? migratedScreenData.children : [];
188
+ const backgroundData = migratedScreenData.background;
189
+ const bgColorCode = migratedScreenData.backgroundColor;
190
+ const showProgress = migratedScreenData.showProgress === true;
191
+ const progressColor = parseColor(migratedScreenData.progressColor ?? '0xFF000000');
192
+ const progressThickness = Number(migratedScreenData.progressThickness ?? 4);
193
+ const progressRadius = Number(migratedScreenData.progressRadius ?? 0);
194
+ const progressStyle = migratedScreenData.progressStyle ?? 'linear';
195
+ const pageScroll = resolvePageScrollAxis(migratedScreenData);
70
196
  const pageAxisBounds = getAxisBoundsForPageScroll(pageScroll);
71
197
  const scrollable = pageScroll !== 'none';
72
- const safeArea = screenData.safeArea !== false;
198
+ const safeArea = migratedScreenData.safeArea !== false;
73
199
  const safeAreaInsets = useSafeAreaInsets();
74
200
  const rootAxis = 'vertical';
75
- const padding = parseInsets(screenData.padding);
201
+ const padding = parseInsets(migratedScreenData.padding);
76
202
  const contentPaddingStyle = insetsToStyle(padding);
77
203
  const progressPaddingStyle = {
78
204
  paddingTop: padding.top,
79
205
  paddingRight: padding.right,
80
206
  paddingLeft: padding.left
81
207
  };
82
- const rootCrossAxisAlignment = screenData.crossAxisAlignment ?? screenData.crossAxis;
208
+ const rootCrossAxisAlignment = migratedScreenData.crossAxisAlignment ?? migratedScreenData.crossAxis;
83
209
  const safeAreaPaddingStyle = safeArea ? {
84
210
  paddingTop: safeAreaInsets.top,
85
211
  paddingRight: safeAreaInsets.right,
@@ -100,6 +226,7 @@ export default function FlowboardRenderer(props) {
100
226
  keyboardShouldPersistTaps: "handled",
101
227
  keyboardDismissMode: Platform.OS === 'ios' ? 'interactive' : 'on-drag',
102
228
  contentContainerStyle: {
229
+ flexGrow: 1,
103
230
  ...contentPaddingStyle,
104
231
  paddingTop: showProgress ? 0 : padding.top
105
232
  },
@@ -107,9 +234,9 @@ export default function FlowboardRenderer(props) {
107
234
  style: {
108
235
  flexGrow: 1,
109
236
  flexDirection: 'column',
110
- justifyContent: parseFlexAlignment(screenData.mainAxisAlignment),
237
+ justifyContent: parseFlexAlignment(migratedScreenData.mainAxisAlignment),
111
238
  alignItems: parseRootCrossAlignment(rootCrossAxisAlignment),
112
- minHeight: 0,
239
+ minHeight: '100%',
113
240
  minWidth: 0
114
241
  },
115
242
  children: childrenData.map((child, index) => buildWidget(child, {
@@ -120,7 +247,7 @@ export default function FlowboardRenderer(props) {
120
247
  parentFlexAxis: rootAxis,
121
248
  parentMainAxisBounded: pageAxisBounds.heightBounded,
122
249
  pageAxisBounds,
123
- screenData,
250
+ screenData: migratedScreenData,
124
251
  enableFontAwesomeIcons,
125
252
  key: `child-${index}`
126
253
  }))
@@ -130,7 +257,7 @@ export default function FlowboardRenderer(props) {
130
257
  flex: 1,
131
258
  ...contentPaddingStyle,
132
259
  paddingTop: showProgress ? 0 : padding.top,
133
- justifyContent: parseFlexAlignment(screenData.mainAxisAlignment),
260
+ justifyContent: parseFlexAlignment(migratedScreenData.mainAxisAlignment),
134
261
  alignItems: parseRootCrossAlignment(rootCrossAxisAlignment)
135
262
  },
136
263
  children: childrenData.map((child, index) => buildWidget(child, {
@@ -141,7 +268,7 @@ export default function FlowboardRenderer(props) {
141
268
  parentFlexAxis: rootAxis,
142
269
  parentMainAxisBounded: true,
143
270
  pageAxisBounds,
144
- screenData,
271
+ screenData: migratedScreenData,
145
272
  enableFontAwesomeIcons,
146
273
  key: `child-${index}`
147
274
  }))
@@ -374,7 +501,7 @@ function normalizeStackAxis(type, props) {
374
501
  if (type === 'layout') {
375
502
  return props.direction === 'horizontal' ? 'horizontal' : 'vertical';
376
503
  }
377
- if (type === 'stack' && (props.fit !== undefined || isLegacyOverlayAlignment(props.alignment))) {
504
+ if (type === 'stack' && isLegacyOverlayAlignment(props.alignment)) {
378
505
  return 'overlay';
379
506
  }
380
507
  return 'vertical';
@@ -422,7 +549,7 @@ function normalizeOverlayAlignment(value) {
422
549
  case 'bottomRight':
423
550
  return 'bottomEnd';
424
551
  default:
425
- return 'center';
552
+ return 'topStart';
426
553
  }
427
554
  }
428
555
  function isFillDimensionValue(value) {
@@ -445,6 +572,51 @@ function normalizeStackSizeMode(value) {
445
572
  }
446
573
  return undefined;
447
574
  }
575
+ export function resolveFillSizeStyle(size, options) {
576
+ const allowWidthFill = options?.allowWidthFill !== false;
577
+ const allowHeightFill = options?.allowHeightFill !== false;
578
+ const fillWidth = allowWidthFill && normalizeStackSizeMode(size?.width) === 'fill';
579
+ const fillHeight = allowHeightFill && normalizeStackSizeMode(size?.height) === 'fill';
580
+ const style = {};
581
+ if (fillWidth) {
582
+ style.width = '100%';
583
+ style.alignSelf = 'stretch';
584
+ style.minWidth = 0;
585
+ }
586
+ if (fillHeight) {
587
+ style.height = '100%';
588
+ style.flexGrow = 1;
589
+ style.flexShrink = 1;
590
+ style.flexBasis = 'auto';
591
+ style.minHeight = 0;
592
+ }
593
+ if (fillWidth && fillHeight) {
594
+ style.flexGrow = 1;
595
+ style.flexShrink = 1;
596
+ style.flexBasis = 'auto';
597
+ }
598
+ return style;
599
+ }
600
+ function resolveOverlayContainerFitStyle(props, axisBounds) {
601
+ if (resolveFitBehavior(props.fit) !== 'expand') return {};
602
+ const widthMode = normalizeStackSizeMode(props.size?.width);
603
+ const heightMode = normalizeStackSizeMode(props.size?.height);
604
+ const style = {
605
+ minWidth: 0,
606
+ minHeight: 0
607
+ };
608
+ if (axisBounds.widthBounded && widthMode !== 'fixed') {
609
+ style.width = '100%';
610
+ style.alignSelf = 'stretch';
611
+ }
612
+ if (axisBounds.heightBounded && heightMode !== 'fixed') {
613
+ style.height = '100%';
614
+ style.flexGrow = 1;
615
+ style.flexShrink = 1;
616
+ style.flexBasis = 'auto';
617
+ }
618
+ return style;
619
+ }
448
620
  function wantsFillWidth(props) {
449
621
  return normalizeStackSizeMode(props.size?.width) === 'fill' || isFillDimensionValue(props.width);
450
622
  }
@@ -467,13 +639,21 @@ function normalizeStackFill(props) {
467
639
  ...props.fill
468
640
  };
469
641
  if (!next.type && next.color) next.type = 'solid';
642
+ if (String(next.type ?? '').toLowerCase() === 'color') {
643
+ next.type = 'solid';
644
+ }
645
+ const fillType = String(next.type ?? '').toLowerCase();
646
+ if ((fillType === '' || fillType === 'solid') && (typeof next.color !== 'string' || next.color.trim().length === 0)) {
647
+ next.type = 'solid';
648
+ next.color = '0x00FFFFFF';
649
+ }
470
650
  return next;
471
651
  }
472
652
  if (props.background && typeof props.background === 'object') {
473
653
  if (props.background.type === 'color') {
474
654
  return {
475
655
  type: 'solid',
476
- color: props.background.color
656
+ color: typeof props.background.color === 'string' && props.background.color.trim().length > 0 ? props.background.color : '0x00FFFFFF'
477
657
  };
478
658
  }
479
659
  return {
@@ -486,7 +666,10 @@ function normalizeStackFill(props) {
486
666
  color: props.backgroundColor
487
667
  };
488
668
  }
489
- return undefined;
669
+ return {
670
+ type: 'solid',
671
+ color: '0x00FFFFFF'
672
+ };
490
673
  }
491
674
  function normalizeStackBorder(props) {
492
675
  if (props.border && typeof props.border === 'object') {
@@ -563,8 +746,17 @@ function mergeStackProps(parentProps, childProps) {
563
746
  cornerRadius: Number(childProps.appearance?.cornerRadius ?? parentProps.appearance?.cornerRadius ?? 0)
564
747
  }
565
748
  };
566
- if (axis === 'overlay') {
567
- mergedProps.overlayAlignment = childProps.overlayAlignment ?? parentProps.overlayAlignment ?? 'center';
749
+ if (axis === 'overlay' || childProps.overlayAlignment !== undefined || parentProps.overlayAlignment !== undefined || childProps.mode === 'overlay' || parentProps.mode === 'overlay') {
750
+ mergedProps.overlayAlignment = childProps.overlayAlignment ?? parentProps.overlayAlignment ?? 'topStart';
751
+ }
752
+ if (childProps.mode !== undefined || parentProps.mode !== undefined) {
753
+ mergedProps.mode = childProps.mode ?? parentProps.mode;
754
+ }
755
+ if (childProps.fit !== undefined || parentProps.fit !== undefined) {
756
+ mergedProps.fit = childProps.fit ?? parentProps.fit;
757
+ }
758
+ if (childProps.clipBehavior !== undefined || parentProps.clipBehavior !== undefined) {
759
+ mergedProps.clipBehavior = childProps.clipBehavior ?? parentProps.clipBehavior;
568
760
  }
569
761
  if (parentProps.fill || childProps.fill) {
570
762
  mergedProps.fill = childProps.fill ?? parentProps.fill;
@@ -612,9 +804,16 @@ function normalizeStackLikeNode(json) {
612
804
  cornerRadius: Number(props.appearance?.cornerRadius ?? props.borderRadius ?? 0)
613
805
  }
614
806
  };
615
- if (axis === 'overlay') {
616
- normalizedProps.overlayAlignment = normalizeOverlayAlignment(props.overlayAlignment ?? props.alignment);
807
+ if (props.mode === 'overlay' || props.mode === 'linear') {
808
+ normalizedProps.mode = props.mode;
617
809
  }
810
+ if (props.fit === 'expand' || props.fit === 'loose' || String(props.fit).toLowerCase() === 'expand' || String(props.fit).toLowerCase() === 'loose') {
811
+ normalizedProps.fit = String(props.fit).toLowerCase() === 'expand' ? 'expand' : 'loose';
812
+ }
813
+ if (String(props.clipBehavior ?? '').toLowerCase() === 'none' || String(props.clipBehavior ?? '').toLowerCase() === 'hardedge') {
814
+ normalizedProps.clipBehavior = String(props.clipBehavior).toLowerCase() === 'hardedge' ? 'hardEdge' : 'none';
815
+ }
816
+ normalizedProps.overlayAlignment = normalizeOverlayAlignment(props.overlayAlignment ?? props.alignment);
618
817
  const fill = normalizeStackFill(props);
619
818
  if (fill) normalizedProps.fill = fill;
620
819
  const border = normalizeStackBorder(props);
@@ -650,56 +849,6 @@ function normalizeStackLikeNode(json) {
650
849
  child: undefined
651
850
  };
652
851
  }
653
- function parseOverlayGridAlignment(value) {
654
- switch (normalizeOverlayAlignment(value)) {
655
- case 'topStart':
656
- return {
657
- justifyContent: 'flex-start',
658
- alignItems: 'flex-start'
659
- };
660
- case 'top':
661
- return {
662
- justifyContent: 'flex-start',
663
- alignItems: 'center'
664
- };
665
- case 'topEnd':
666
- return {
667
- justifyContent: 'flex-start',
668
- alignItems: 'flex-end'
669
- };
670
- case 'start':
671
- return {
672
- justifyContent: 'center',
673
- alignItems: 'flex-start'
674
- };
675
- case 'end':
676
- return {
677
- justifyContent: 'center',
678
- alignItems: 'flex-end'
679
- };
680
- case 'bottomStart':
681
- return {
682
- justifyContent: 'flex-end',
683
- alignItems: 'flex-start'
684
- };
685
- case 'bottom':
686
- return {
687
- justifyContent: 'flex-end',
688
- alignItems: 'center'
689
- };
690
- case 'bottomEnd':
691
- return {
692
- justifyContent: 'flex-end',
693
- alignItems: 'flex-end'
694
- };
695
- case 'center':
696
- default:
697
- return {
698
- justifyContent: 'center',
699
- alignItems: 'center'
700
- };
701
- }
702
- }
703
852
  function resolveStackDimension(props, axis, key, axisBounds) {
704
853
  const mode = props.size?.[key];
705
854
  const legacy = normalizeDimension(parseLayoutDimension(props[key]));
@@ -713,6 +862,21 @@ function resolveStackDimension(props, axis, key, axisBounds) {
713
862
  if (key === 'height' && axis === 'horizontal' && axisBounds.heightBounded) return '100%';
714
863
  return undefined;
715
864
  }
865
+ function resolveTextWidthMode(props) {
866
+ const raw = String(props.size?.width ?? '').trim().toLowerCase();
867
+ if (raw === 'fill' || raw === 'infinity' || raw === 'infinite' || raw === 'double.infinity' || raw === '+infinity') {
868
+ return 'fill';
869
+ }
870
+ if (raw === 'fit') return 'fit';
871
+ if (raw === 'fixed') return 'fixed';
872
+ if (isFillDimensionValue(props.width)) return 'fill';
873
+ return props.width !== undefined ? 'fixed' : 'fit';
874
+ }
875
+ function resolveTextFixedWidth(props) {
876
+ const explicit = normalizeDimension(parseLayoutDimension(props.width, true));
877
+ if (explicit !== undefined) return explicit;
878
+ return normalizeDimension(parseLayoutDimension(props.size?.widthPx, true));
879
+ }
716
880
  function buildWidget(json, params) {
717
881
  if (!json || typeof json !== 'object') return null;
718
882
  const normalizedJson = normalizeStackLikeNode(json);
@@ -831,6 +995,13 @@ function buildWidget(json, params) {
831
995
  const gradient = parseGradient(props.foreground);
832
996
  const textStyle = getTextStyle(props, 'color');
833
997
  const align = parseTextAlign(props.textAlign);
998
+ const widthMode = resolveTextWidthMode(props);
999
+ const fixedWidth = resolveTextFixedWidth(props);
1000
+ const widthBounded = params.pageAxisBounds?.widthBounded ?? true;
1001
+ // Shared width-mode contract across React/Flutter:
1002
+ // fit = intrinsic width (no flex, no 100% stretch)
1003
+ // fill = expand within bounded parent constraints
1004
+ // fixed = explicit width value
834
1005
  if (gradient) {
835
1006
  node = /*#__PURE__*/_jsx(GradientText, {
836
1007
  text: resolvedText,
@@ -847,6 +1018,58 @@ function buildWidget(json, params) {
847
1018
  children: resolvedText
848
1019
  });
849
1020
  }
1021
+ if (widthMode === 'fill') {
1022
+ if (params.parentFlexAxis === 'horizontal') {
1023
+ if (params.parentMainAxisBounded ?? true) {
1024
+ node = /*#__PURE__*/_jsx(View, {
1025
+ style: {
1026
+ flex: 1,
1027
+ minWidth: 0,
1028
+ alignSelf: 'stretch'
1029
+ },
1030
+ children: node
1031
+ });
1032
+ } else {
1033
+ node = /*#__PURE__*/_jsx(View, {
1034
+ style: {
1035
+ minWidth: 0
1036
+ },
1037
+ children: node
1038
+ });
1039
+ }
1040
+ } else if (widthBounded) {
1041
+ node = /*#__PURE__*/_jsx(View, {
1042
+ style: {
1043
+ width: '100%',
1044
+ minWidth: 0,
1045
+ alignSelf: 'stretch'
1046
+ },
1047
+ children: node
1048
+ });
1049
+ } else {
1050
+ node = /*#__PURE__*/_jsx(View, {
1051
+ style: {
1052
+ minWidth: 0
1053
+ },
1054
+ children: node
1055
+ });
1056
+ }
1057
+ } else if (widthMode === 'fixed') {
1058
+ node = /*#__PURE__*/_jsx(View, {
1059
+ style: {
1060
+ width: fixedWidth,
1061
+ minWidth: 0
1062
+ },
1063
+ children: node
1064
+ });
1065
+ } else {
1066
+ node = /*#__PURE__*/_jsx(View, {
1067
+ style: {
1068
+ minWidth: 0
1069
+ },
1070
+ children: node
1071
+ });
1072
+ }
850
1073
  break;
851
1074
  }
852
1075
  case 'rich_text':
@@ -1214,11 +1437,11 @@ function buildWidget(json, params) {
1214
1437
  const direction = props.direction === 'vertical' ? 'vertical' : 'horizontal';
1215
1438
  const interaction = props.interaction ?? {};
1216
1439
  const pageControl = props.pageControl ?? {};
1217
- const sliderChildren = (childrenJson ?? []).filter(Boolean);
1440
+ const sliderPages = (childrenJson ?? []).filter(child => child && String(child.type ?? '') === 'stack');
1218
1441
  node = /*#__PURE__*/_jsx(SliderWidget, {
1219
- id: json._internalId,
1442
+ id: json._internalId ?? json.id,
1220
1443
  sliderProps: props,
1221
- slidePropsList: sliderChildren.map(child => child?.properties ?? {}),
1444
+ pagePropsList: sliderPages.map(child => child?.properties ?? {}),
1222
1445
  direction: direction,
1223
1446
  pageAlignment: props.pageAlignment === 'start' || props.pageAlignment === 'end' ? props.pageAlignment : 'center',
1224
1447
  pageSpacing: Number(props.pageSpacing ?? 16),
@@ -1228,27 +1451,19 @@ function buildWidget(json, params) {
1228
1451
  autoAdvanceIntervalMs: Number(interaction.autoAdvanceIntervalMs ?? 4000),
1229
1452
  pageControlEnabled: pageControl.enabled !== false,
1230
1453
  pageControlPosition: pageControl.position === 'top' ? 'top' : 'bottom',
1231
- children: sliderChildren.map((child, index) => /*#__PURE__*/_jsx(View, {
1454
+ children: sliderPages.map((child, index) => /*#__PURE__*/_jsx(View, {
1232
1455
  children: buildWidget(child, {
1233
- ...params
1456
+ ...params,
1457
+ // Slider pages always render inside a bounded pager viewport.
1458
+ pageAxisBounds: {
1459
+ widthBounded: true,
1460
+ heightBounded: true
1461
+ }
1234
1462
  })
1235
1463
  }, `slider-${index}`))
1236
1464
  });
1237
1465
  break;
1238
1466
  }
1239
- case 'slide':
1240
- {
1241
- node = buildWidget({
1242
- ...json,
1243
- type: 'stack',
1244
- properties: {
1245
- ...(props || {})
1246
- }
1247
- }, {
1248
- ...params
1249
- });
1250
- break;
1251
- }
1252
1467
  case 'pageview_indicator':
1253
1468
  {
1254
1469
  node = /*#__PURE__*/_jsx(PageViewIndicator, {
@@ -1306,12 +1521,13 @@ function buildWidget(json, params) {
1306
1521
  }
1307
1522
  case 'stack':
1308
1523
  {
1309
- const axis = props.axis === 'horizontal' || props.axis === 'overlay' ? props.axis : 'vertical';
1310
- const isOverlay = axis === 'overlay';
1524
+ const axis = props.axis === 'horizontal' ? 'horizontal' : 'vertical';
1525
+ const isOverlay = isOverlayStack(childrenJson, props);
1311
1526
  const spacing = Number(props.childSpacing ?? 0);
1312
1527
  const applySpacing = !STACK_SPACE_DISTRIBUTIONS.has(String(props.distribution ?? 'start'));
1313
- const width = resolveStackDimension(props, axis, 'width', pageAxisBounds);
1314
- const height = resolveStackDimension(props, axis, 'height', pageAxisBounds);
1528
+ const resolvedAxis = isOverlay ? 'overlay' : axis;
1529
+ const width = resolveStackDimension(props, resolvedAxis, 'width', pageAxisBounds);
1530
+ const height = resolveStackDimension(props, resolvedAxis, 'height', pageAxisBounds);
1315
1531
  const mainAxisBounded = axis === 'horizontal' ? pageAxisBounds.widthBounded : pageAxisBounds.heightBounded;
1316
1532
  const borderRadius = Number(props.appearance?.cornerRadius ?? props.borderRadius ?? 0);
1317
1533
  const borderWidth = Number(props.border?.width ?? 0);
@@ -1334,12 +1550,17 @@ function buildWidget(json, params) {
1334
1550
  ...fill,
1335
1551
  type: 'gradient'
1336
1552
  }) : undefined;
1337
- const stackColor = fill?.type === 'solid' || !fill?.type && fill?.color ? parseColor(fill?.color ?? '#FFFFFFFF') : parseColor(props.backgroundColor ?? '#00000000');
1553
+ const stackColor = fill?.type === 'solid' || !fill?.type && fill?.color ? parseColor(fill?.color ?? '0x00FFFFFF') : parseColor(props.backgroundColor ?? '#00000000');
1338
1554
  const baseStackStyle = {
1339
1555
  width,
1340
1556
  height,
1557
+ ...resolveFillSizeStyle(props.size, {
1558
+ allowWidthFill: pageAxisBounds.widthBounded,
1559
+ allowHeightFill: pageAxisBounds.heightBounded
1560
+ }),
1561
+ ...(isOverlay ? resolveOverlayContainerFitStyle(props, pageAxisBounds) : {}),
1341
1562
  borderRadius: borderRadius > 0 ? borderRadius : undefined,
1342
- overflow: borderRadius > 0 ? 'hidden' : undefined,
1563
+ overflow: isOverlay ? resolveClipBehavior(props.clipBehavior) === 'hardEdge' ? 'hidden' : 'visible' : borderRadius > 0 ? 'hidden' : undefined,
1343
1564
  borderWidth: borderColor ? borderWidth : undefined,
1344
1565
  borderColor: borderColor ?? undefined,
1345
1566
  ...shadowStyle
@@ -1356,7 +1577,24 @@ function buildWidget(json, params) {
1356
1577
  ...stackContentSizeStyle
1357
1578
  },
1358
1579
  children: [(childrenJson ?? []).map((child, index) => {
1359
- const alignment = parseOverlayGridAlignment(props.overlayAlignment ?? props.alignment);
1580
+ if (isPositionedChild(child)) {
1581
+ if (String(child?.type ?? '') === 'positioned') {
1582
+ return /*#__PURE__*/_jsx(React.Fragment, {
1583
+ children: buildWidget(child, {
1584
+ ...params,
1585
+ pageAxisBounds
1586
+ })
1587
+ }, `stack-overlay-positioned-${index}`);
1588
+ }
1589
+ return /*#__PURE__*/_jsx(View, {
1590
+ style: resolvePositionedStyle(child?.properties ?? {}),
1591
+ children: buildWidget(child, {
1592
+ ...params,
1593
+ pageAxisBounds
1594
+ })
1595
+ }, `stack-overlay-positioned-${index}`);
1596
+ }
1597
+ const alignment = resolveAlignmentToCss(resolveOverlayAlignmentValue(props));
1360
1598
  return /*#__PURE__*/_jsx(View, {
1361
1599
  style: {
1362
1600
  position: 'absolute',
@@ -1372,7 +1610,7 @@ function buildWidget(json, params) {
1372
1610
  allowFlexExpansion: true,
1373
1611
  pageAxisBounds
1374
1612
  })
1375
- }, `stack-overlay-${index}`);
1613
+ }, `stack-overlay-non-positioned-${index}`);
1376
1614
  }), (childrenJson ?? []).length === 0 ? null : null]
1377
1615
  }) : /*#__PURE__*/_jsx(View, {
1378
1616
  style: {
@@ -1420,22 +1658,8 @@ function buildWidget(json, params) {
1420
1658
  }
1421
1659
  case 'positioned':
1422
1660
  {
1423
- const left = parseLayoutDimension(props.left);
1424
- const top = parseLayoutDimension(props.top);
1425
- const right = parseLayoutDimension(props.right);
1426
- const bottom = parseLayoutDimension(props.bottom);
1427
- const width = parseLayoutDimension(props.width);
1428
- const height = parseLayoutDimension(props.height);
1429
1661
  node = /*#__PURE__*/_jsx(View, {
1430
- style: {
1431
- position: 'absolute',
1432
- left,
1433
- top,
1434
- right,
1435
- bottom,
1436
- width,
1437
- height
1438
- },
1662
+ style: resolvePositionedStyle(props),
1439
1663
  children: childJson ? buildWidget(childJson, {
1440
1664
  ...params
1441
1665
  }) : null
@@ -1486,6 +1710,7 @@ function buildWidget(json, params) {
1486
1710
  let shouldExpand = false;
1487
1711
  const parentAxis = params.parentFlexAxis ?? 'vertical';
1488
1712
  const parentMainAxisBounded = params.parentMainAxisBounded ?? true;
1713
+ const sliderWantsMainAxisFill = type === 'slider' && (parentAxis === 'vertical' && wantsFillHeight(props) || parentAxis === 'horizontal' && wantsFillWidth(props));
1489
1714
  if (parentMainAxisBounded) {
1490
1715
  if (type === 'stack') {
1491
1716
  const legacyExpand = props.fit === 'expand';
@@ -1511,12 +1736,18 @@ function buildWidget(json, params) {
1511
1736
  }
1512
1737
  }
1513
1738
  }
1739
+ if (sliderWantsMainAxisFill) {
1740
+ shouldExpand = true;
1741
+ }
1514
1742
  if (shouldExpand) {
1743
+ // Keep cross-axis stretching on the outer expansion wrapper as well.
1744
+ // Without this, a centered parent cross-axis can collapse effective fill width/height.
1515
1745
  node = /*#__PURE__*/_jsx(View, {
1516
1746
  style: {
1517
1747
  flex: 1,
1518
1748
  minHeight: 0,
1519
- minWidth: 0
1749
+ minWidth: 0,
1750
+ alignSelf: shouldStretchCrossAxis ? 'stretch' : undefined
1520
1751
  },
1521
1752
  children: node
1522
1753
  });
@@ -1548,9 +1779,30 @@ function buildActionPayload(props, json) {
1548
1779
  return payload;
1549
1780
  }
1550
1781
  function getTextStyle(props, colorKey, defaultColor) {
1551
- const fontFamily = props.fontFamily;
1782
+ const requestedFontFamily = resolveFontFamily(props.fontFamily);
1552
1783
  const fontSize = Number(props.fontSize ?? 14);
1553
- const fontWeight = parseFontWeight(props.fontWeight);
1784
+ const parsedFontWeight = parseFontWeight(props.fontWeight);
1785
+ const requestedWeight = Number(parsedFontWeight);
1786
+ const numericRequestedWeight = Number.isFinite(requestedWeight) ? requestedWeight : undefined;
1787
+ let fontFamily = requestedFontFamily;
1788
+ let fontWeight = parsedFontWeight;
1789
+ if (Platform.OS === 'web' && requestedFontFamily) {
1790
+ const resolvedSpec = resolveFontSpec({
1791
+ family: requestedFontFamily,
1792
+ requestedWeight: numericRequestedWeight
1793
+ });
1794
+ fontFamily = resolvedSpec.resolvedFamily;
1795
+ fontWeight = resolvedSpec.resolvedWeight !== undefined ? String(resolvedSpec.resolvedWeight) : parsedFontWeight;
1796
+ ensureWebGoogleFontLoaded(resolvedSpec.resolvedFamily, [fontWeight]);
1797
+ } else if (requestedFontFamily) {
1798
+ const resolvedSpec = resolveFontSpec({
1799
+ family: requestedFontFamily,
1800
+ requestedWeight: numericRequestedWeight
1801
+ });
1802
+ void ensureNativeFontLoaded(resolvedSpec).catch(() => undefined);
1803
+ fontWeight = undefined;
1804
+ fontFamily = isFontLoaded(resolvedSpec) ? resolveAppliedFontFamily(resolvedSpec) : resolvedSpec.resolvedFamily;
1805
+ }
1554
1806
  const fontStyle = props.fontStyle === 'italic' ? 'italic' : 'normal';
1555
1807
  const color = parseColor(props[colorKey] ?? defaultColor ?? '0xFF000000');
1556
1808
  const letterSpacing = props.letterSpacing !== undefined ? Number(props.letterSpacing) : undefined;
@@ -1573,7 +1825,8 @@ function parseGradient(value) {
1573
1825
  if (colors.length === 0) return undefined;
1574
1826
  const start = alignmentToGradient(value.begin ?? 'topLeft');
1575
1827
  const end = alignmentToGradient(value.end ?? 'bottomRight');
1576
- const stops = Array.isArray(value.stops) ? value.stops.map(s => Number(s)) : undefined;
1828
+ const parsedStops = Array.isArray(value.stops) ? value.stops.map(s => Number(s)) : undefined;
1829
+ const stops = parsedStops && parsedStops.length === colors.length ? parsedStops : undefined;
1577
1830
  return {
1578
1831
  colors,
1579
1832
  start,
@@ -1910,9 +2163,16 @@ function GradientText({
1910
2163
  gradient,
1911
2164
  style
1912
2165
  }) {
2166
+ if (Platform.OS === 'web') {
2167
+ return /*#__PURE__*/_jsx(Text, {
2168
+ style: [style, getWebGradientTextStyle(gradient)],
2169
+ children: text
2170
+ });
2171
+ }
1913
2172
  return /*#__PURE__*/_jsx(MaskedView, {
1914
2173
  maskElement: /*#__PURE__*/_jsx(Text, {
1915
2174
  style: [style, {
2175
+ color: '#FFFFFF',
1916
2176
  backgroundColor: 'transparent'
1917
2177
  }],
1918
2178
  children: text
@@ -1924,6 +2184,7 @@ function GradientText({
1924
2184
  locations: gradient.stops,
1925
2185
  children: /*#__PURE__*/_jsx(Text, {
1926
2186
  style: [style, {
2187
+ color: '#FFFFFF',
1927
2188
  opacity: 0
1928
2189
  }],
1929
2190
  children: text
@@ -1931,6 +2192,27 @@ function GradientText({
1931
2192
  })
1932
2193
  });
1933
2194
  }
2195
+ function getWebGradientTextStyle(gradient) {
2196
+ const angle = gradientToCssAngle(gradient.start, gradient.end);
2197
+ const normalizedStops = gradient.stops && gradient.stops.length === gradient.colors.length ? gradient.stops.map(stop => `${clamp01(stop) * 100}%`) : undefined;
2198
+ const gradientStops = gradient.colors.map((color, index) => {
2199
+ if (!normalizedStops) return color;
2200
+ return `${color} ${normalizedStops[index]}`;
2201
+ });
2202
+ return {
2203
+ backgroundImage: `linear-gradient(${angle}deg, ${gradientStops.join(', ')})`,
2204
+ backgroundClip: 'text',
2205
+ WebkitBackgroundClip: 'text',
2206
+ color: 'transparent'
2207
+ };
2208
+ }
2209
+ function gradientToCssAngle(start, end) {
2210
+ const dx = end.x - start.x;
2211
+ const dy = end.y - start.y;
2212
+ const radians = Math.atan2(dy, dx);
2213
+ const degrees = radians * 180 / Math.PI;
2214
+ return (degrees + 90 + 360) % 360;
2215
+ }
1934
2216
  function resolveTextInputBackgroundColor(properties) {
1935
2217
  const canonical = properties.backgroundColor;
1936
2218
  if (typeof canonical === 'string' && canonical.trim().length > 0) {
@@ -2772,10 +3054,18 @@ function FakeProgressBar({
2772
3054
  });
2773
3055
  }
2774
3056
  return /*#__PURE__*/_jsxs(View, {
3057
+ style: {
3058
+ width: '100%',
3059
+ alignSelf: 'stretch',
3060
+ minWidth: 0,
3061
+ flexDirection: 'column'
3062
+ },
2775
3063
  children: [/*#__PURE__*/_jsx(View, {
2776
3064
  style: {
2777
3065
  height: thickness,
2778
3066
  width: '100%',
3067
+ alignSelf: 'stretch',
3068
+ minWidth: 0,
2779
3069
  borderRadius,
2780
3070
  backgroundColor,
2781
3071
  overflow: 'hidden'
@@ -2793,7 +3083,9 @@ function FakeProgressBar({
2793
3083
  })
2794
3084
  }), showProgressText ? /*#__PURE__*/_jsx(Text, {
2795
3085
  style: [progressTextStyle, {
2796
- marginTop: 8
3086
+ marginTop: 8,
3087
+ width: '100%',
3088
+ alignSelf: 'stretch'
2797
3089
  }],
2798
3090
  children: progressText
2799
3091
  }) : null]
@@ -2810,12 +3102,20 @@ export function resolvePeekInsets(pageAlignment, pagePeek) {
2810
3102
  }
2811
3103
  function resolveSliderSizeMode(sliderProps, key, fallback) {
2812
3104
  const raw = String(sliderProps?.size?.[key] ?? '').toLowerCase();
2813
- if (raw === 'fill' || raw === 'fit' || raw === 'fixed') return raw;
3105
+ if (raw === 'fill' || raw === 'infinity' || raw === 'infinite' || raw === 'double.infinity' || raw === '+infinity') {
3106
+ return 'fill';
3107
+ }
3108
+ if (raw === 'fit' || raw === 'fixed') return raw;
3109
+ if (isFillDimensionValue(sliderProps?.[key])) return 'fill';
2814
3110
  return sliderProps?.[key] !== undefined ? 'fixed' : fallback;
2815
3111
  }
2816
- function resolveSlideSizeMode(slideProps, key) {
2817
- const raw = String(slideProps?.size?.[key] ?? '').toLowerCase();
2818
- if (raw === 'fill' || raw === 'fit' || raw === 'fixed') return raw;
3112
+ function resolvePageSizeMode(pageProps, key) {
3113
+ const raw = String(pageProps?.size?.[key] ?? '').toLowerCase();
3114
+ if (raw === 'fill' || raw === 'infinity' || raw === 'infinite' || raw === 'double.infinity' || raw === '+infinity') {
3115
+ return 'fill';
3116
+ }
3117
+ if (raw === 'fit' || raw === 'fixed') return raw;
3118
+ if (isFillDimensionValue(pageProps?.[key])) return 'fill';
2819
3119
  return key === 'width' ? 'fill' : 'fit';
2820
3120
  }
2821
3121
  function toFinite(value) {
@@ -2827,11 +3127,13 @@ function toFinite(value) {
2827
3127
  return null;
2828
3128
  }
2829
3129
  export function resolveSliderContainerSize(args) {
2830
- const dotsInset = args.showDots ? 8 + 6 + 8 : 0;
2831
- const fitPrimary = args.direction === 'horizontal' ? args.activeSlideWidth : args.activeSlideHeight;
3130
+ const dotsTopInset = Math.max(0, args.dotsTopInset ?? 0);
3131
+ const dotsBottomInset = Math.max(0, args.dotsBottomInset ?? (args.showDots ? 18 : 0));
3132
+ const dotsInset = dotsTopInset + dotsBottomInset;
3133
+ const fitPrimary = args.direction === 'horizontal' ? args.activePageWidth : args.activePageHeight;
2832
3134
  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);
3135
+ const viewportWidth = args.direction === 'horizontal' ? fitViewportPrimary : Math.max(1, args.activePageWidth);
3136
+ const viewportHeight = args.direction === 'vertical' ? fitViewportPrimary : Math.max(1, args.activePageHeight);
2835
3137
  const fitOuterWidth = Math.min(Math.max(1, viewportWidth), Math.max(1, args.availableWidth || viewportWidth));
2836
3138
  const fitOuterHeight = Math.max(1, viewportHeight + dotsInset);
2837
3139
  const outerWidth = args.widthMode === 'fill' ? '100%' : args.widthMode === 'fixed' ? Math.max(1, args.fixedWidth ?? fitOuterWidth) : fitOuterWidth;
@@ -2843,11 +3145,21 @@ export function resolveSliderContainerSize(args) {
2843
3145
  viewportHeight: args.heightMode === 'fit' ? Math.max(1, fitOuterHeight - dotsInset) : Math.max(1, args.availableHeight || fitOuterHeight - dotsInset)
2844
3146
  };
2845
3147
  }
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));
3148
+ function resolvePageFixedSizeHint(pageProps) {
3149
+ const widthMode = resolvePageSizeMode(pageProps, 'width');
3150
+ const heightMode = resolvePageSizeMode(pageProps, 'height');
3151
+ const width = widthMode === 'fixed' ? toFinite(parseLayoutDimension(pageProps.width ?? pageProps.size?.widthPx, true)) : null;
3152
+ const height = heightMode === 'fixed' ? toFinite(parseLayoutDimension(pageProps.height ?? pageProps.size?.heightPx, true)) : null;
3153
+ return {
3154
+ width,
3155
+ height
3156
+ };
3157
+ }
3158
+ export function resolvePageSize(args) {
3159
+ const widthMode = resolvePageSizeMode(args.pageProps, 'width');
3160
+ const heightMode = resolvePageSizeMode(args.pageProps, 'height');
3161
+ const fixedWidth = toFinite(parseLayoutDimension(args.pageProps.width ?? args.pageProps.size?.widthPx, true));
3162
+ const fixedHeight = toFinite(parseLayoutDimension(args.pageProps.height ?? args.pageProps.size?.heightPx, true));
2851
3163
  const width = widthMode === 'fixed' ? Math.max(1, fixedWidth ?? args.pagePrimary) : widthMode === 'fit' ? undefined : args.direction === 'horizontal' ? args.pagePrimary : args.crossSize;
2852
3164
  const height = heightMode === 'fixed' ? Math.max(1, fixedHeight ?? args.pagePrimary) : heightMode === 'fit' ? undefined : args.direction === 'vertical' ? args.pagePrimary : args.crossSize;
2853
3165
  return {
@@ -2951,7 +3263,7 @@ function resolveSliderBackgroundColor(sliderProps) {
2951
3263
  function SliderWidget({
2952
3264
  id,
2953
3265
  sliderProps,
2954
- slidePropsList,
3266
+ pagePropsList,
2955
3267
  direction,
2956
3268
  pageAlignment,
2957
3269
  pageSpacing,
@@ -2975,7 +3287,8 @@ function SliderWidget({
2975
3287
  width: 1,
2976
3288
  height: 1
2977
3289
  });
2978
- const [activeSlideSize, setActiveSlideSize] = React.useState({
3290
+ const [measuredPageSizes, setMeasuredPageSizes] = React.useState({});
3291
+ const [activePageSize, setActivePageSize] = React.useState({
2979
3292
  width: 1,
2980
3293
  height: 1
2981
3294
  });
@@ -2989,6 +3302,19 @@ function SliderWidget({
2989
3302
  React.useEffect(() => {
2990
3303
  setCurrentPage(prev => clampSliderPage(prev, pageCount));
2991
3304
  }, [pageCount]);
3305
+ React.useEffect(() => {
3306
+ setMeasuredPageSizes(prev => {
3307
+ const next = {};
3308
+ Object.keys(prev).forEach(key => {
3309
+ const index = Number(key);
3310
+ if (Number.isFinite(index) && index < pageCount) {
3311
+ const cached = prev[index];
3312
+ if (cached) next[index] = cached;
3313
+ }
3314
+ });
3315
+ return Object.keys(next).length === Object.keys(prev).length ? prev : next;
3316
+ });
3317
+ }, [pageCount]);
2992
3318
  React.useEffect(() => {
2993
3319
  if (autoAdvance && !isInteracting && pageLength > 1) {
2994
3320
  timerRef.current = setInterval(() => {
@@ -3009,6 +3335,23 @@ function SliderWidget({
3009
3335
  pagerRef.current.setPage(clampSliderPage(currentPage, pageCount));
3010
3336
  }
3011
3337
  }, [currentPage, pageCount]);
3338
+ const syncActivePageSize = React.useCallback(page => {
3339
+ if (pageCount <= 0) return;
3340
+ const safePage = clampSliderPage(page, pageCount);
3341
+ const measured = measuredPageSizes[safePage];
3342
+ const hint = resolvePageFixedSizeHint(pagePropsList[safePage] ?? {});
3343
+ setActivePageSize(prev => {
3344
+ const next = {
3345
+ width: Math.max(1, hint.width ?? measured?.width ?? prev.width),
3346
+ height: Math.max(1, hint.height ?? measured?.height ?? prev.height)
3347
+ };
3348
+ return prev.width === next.width && prev.height === next.height ? prev : next;
3349
+ });
3350
+ }, [measuredPageSizes, pageCount, pagePropsList]);
3351
+ React.useEffect(() => {
3352
+ if (pageCount <= 0) return;
3353
+ syncActivePageSize(currentPage);
3354
+ }, [currentPage, pageCount, syncActivePageSize]);
3012
3355
  React.useEffect(() => {
3013
3356
  if (registry && id && children.length > 0) {
3014
3357
  registry.update(id, currentPage % children.length, children.length);
@@ -3021,13 +3364,15 @@ function SliderWidget({
3021
3364
  // 2) trailingPeek = end?0 : start?pagePeek*2 : pagePeek
3022
3365
  // 3) pagePrimary = viewportPrimary - leadingPeek - trailingPeek (clamped >= 1)
3023
3366
  // 4) track offset = activeIndex * (pagePrimary + pageSpacing)
3024
- // 5) fit parent mode follows active slide size but is clamped to container width
3367
+ // 5) fit parent mode follows active page size but is clamped to container width
3025
3368
  // 6) viewport always clips track content (no root horizontal overflow)
3026
3369
  const {
3027
3370
  leading,
3028
3371
  trailing
3029
3372
  } = resolvePeekInsets(pageAlignment, pagePeek);
3030
3373
  const showDots = pageControlEnabled && pageLength > 1;
3374
+ const dotsTopInset = showDots && pageControlPosition === 'top' ? 18 : 0;
3375
+ const dotsBottomInset = showDots && pageControlPosition !== 'top' ? 18 : 0;
3031
3376
  const widthMode = resolveSliderSizeMode(sliderProps, 'width', 'fill');
3032
3377
  const heightMode = resolveSliderSizeMode(sliderProps, 'height', 'fit');
3033
3378
  const fixedWidth = toFinite(parseLayoutDimension(sliderProps.width, true));
@@ -3039,14 +3384,17 @@ function SliderWidget({
3039
3384
  heightMode,
3040
3385
  availableWidth: availableSize.width,
3041
3386
  availableHeight: availableSize.height,
3042
- activeSlideWidth: activeSlideSize.width,
3043
- activeSlideHeight: activeSlideSize.height,
3387
+ activePageWidth: activePageSize.width,
3388
+ activePageHeight: activePageSize.height,
3044
3389
  fixedWidth,
3045
3390
  fixedHeight,
3046
3391
  leadingPeek: leading,
3047
3392
  trailingPeek: trailing,
3048
- showDots
3393
+ showDots,
3394
+ dotsTopInset,
3395
+ dotsBottomInset
3049
3396
  });
3397
+ const sliderFillSizeStyle = resolveFillSizeStyle(sliderProps.size);
3050
3398
  const viewportPrimary = direction === 'horizontal' ? resolved.viewportWidth : resolved.viewportHeight;
3051
3399
  const pagePrimary = Math.max(1, viewportPrimary - leading - trailing);
3052
3400
  return /*#__PURE__*/_jsxs(View, {
@@ -3066,46 +3414,35 @@ function SliderWidget({
3066
3414
  style: {
3067
3415
  width: resolved.outerWidth,
3068
3416
  height: resolved.outerHeight,
3069
- minWidth: 1,
3070
- minHeight: 1,
3417
+ minWidth: 0,
3418
+ minHeight: 0,
3419
+ display: 'flex',
3420
+ flexDirection: 'column',
3421
+ ...sliderFillSizeStyle,
3071
3422
  backgroundColor: sliderBackgroundColor,
3072
3423
  borderRadius: 0,
3073
- overflow: 'visible'
3424
+ overflow: 'hidden',
3425
+ position: 'relative'
3074
3426
  },
3075
- children: [showDots && pageControlPosition === 'top' ? /*#__PURE__*/_jsx(View, {
3076
- style: {
3077
- marginBottom: 8,
3078
- flexDirection: 'row',
3079
- justifyContent: 'center',
3080
- alignItems: 'center'
3081
- },
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, {
3427
+ children: [/*#__PURE__*/_jsx(View, {
3097
3428
  style: {
3098
3429
  overflow: 'hidden',
3430
+ width: widthMode === 'fit' ? resolved.viewportWidth : '100%',
3431
+ height: heightMode === 'fit' ? resolved.viewportHeight : '100%',
3432
+ minWidth: 0,
3433
+ minHeight: 0,
3434
+ display: 'flex',
3435
+ flexDirection: 'column',
3436
+ flexGrow: heightMode === 'fit' ? 0 : 1,
3437
+ flexShrink: 1,
3099
3438
  ...(direction === 'horizontal' ? {
3100
- width: resolved.viewportWidth,
3101
- height: resolved.viewportHeight,
3102
3439
  paddingLeft: leading,
3103
- paddingRight: trailing
3440
+ paddingRight: trailing,
3441
+ paddingTop: dotsTopInset,
3442
+ paddingBottom: dotsBottomInset
3104
3443
  } : {
3105
- width: resolved.viewportWidth,
3106
- height: resolved.viewportHeight,
3107
- paddingTop: leading,
3108
- paddingBottom: trailing
3444
+ paddingTop: leading + dotsTopInset,
3445
+ paddingBottom: trailing + dotsBottomInset
3109
3446
  })
3110
3447
  },
3111
3448
  children: /*#__PURE__*/_jsx(PagerView, {
@@ -3113,7 +3450,9 @@ function SliderWidget({
3113
3450
  style: {
3114
3451
  width: '100%',
3115
3452
  height: '100%',
3116
- minHeight: 1
3453
+ minWidth: 0,
3454
+ minHeight: 0,
3455
+ flex: 1
3117
3456
  },
3118
3457
  orientation: direction,
3119
3458
  pageMargin: Math.max(0, pageSpacing),
@@ -3122,6 +3461,7 @@ function SliderWidget({
3122
3461
  const page = event.nativeEvent.position;
3123
3462
  const safePage = clampSliderPage(page, pageCount);
3124
3463
  setCurrentPage(safePage);
3464
+ syncActivePageSize(safePage);
3125
3465
  markInteracting();
3126
3466
  if (registry && id) {
3127
3467
  registry.update(id, safePage % pageLength, pageLength);
@@ -3130,33 +3470,83 @@ function SliderWidget({
3130
3470
  children: Array.from({
3131
3471
  length: pageCount
3132
3472
  }).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
3473
  style: {
3142
- alignSelf: 'flex-start',
3474
+ width: '100%',
3475
+ height: '100%',
3143
3476
  overflow: 'hidden',
3144
- ...resolveSlidePageSize({
3145
- slideProps: slidePropsList[index] ?? {},
3146
- direction,
3147
- pagePrimary,
3148
- crossSize: '100%'
3149
- })
3477
+ minWidth: 0,
3478
+ minHeight: 0,
3479
+ display: 'flex',
3480
+ flexDirection: 'column'
3150
3481
  },
3151
- children: children[index % pageLength]
3482
+ children: /*#__PURE__*/_jsx(View, {
3483
+ onLayout: event => {
3484
+ const nextWidth = Math.max(1, Math.round(event.nativeEvent.layout.width));
3485
+ const nextHeight = Math.max(1, Math.round(event.nativeEvent.layout.height));
3486
+ setMeasuredPageSizes(prev => {
3487
+ const current = prev[index];
3488
+ if (current && current.width === nextWidth && current.height === nextHeight) {
3489
+ return prev;
3490
+ }
3491
+ return {
3492
+ ...prev,
3493
+ [index]: {
3494
+ width: nextWidth,
3495
+ height: nextHeight
3496
+ }
3497
+ };
3498
+ });
3499
+ if (index === currentPage) {
3500
+ setActivePageSize(prev => prev.width === nextWidth && prev.height === nextHeight ? prev : {
3501
+ width: nextWidth,
3502
+ height: nextHeight
3503
+ });
3504
+ }
3505
+ },
3506
+ style: {
3507
+ alignSelf: (direction === 'horizontal' ? resolvePageSizeMode(pagePropsList[index] ?? {}, 'height') === 'fill' : resolvePageSizeMode(pagePropsList[index] ?? {}, 'width') === 'fill') ? 'stretch' : 'flex-start',
3508
+ overflow: 'hidden',
3509
+ minWidth: 0,
3510
+ minHeight: 0,
3511
+ display: 'flex',
3512
+ flexDirection: 'column',
3513
+ ...resolvePageSize({
3514
+ pageProps: pagePropsList[index] ?? {},
3515
+ direction,
3516
+ pagePrimary,
3517
+ crossSize: '100%'
3518
+ })
3519
+ },
3520
+ children: /*#__PURE__*/_jsx(View, {
3521
+ style: {
3522
+ width: '100%',
3523
+ height: '100%',
3524
+ minWidth: 0,
3525
+ minHeight: 0,
3526
+ display: 'flex',
3527
+ flexDirection: 'column',
3528
+ flexGrow: 1
3529
+ },
3530
+ children: children[index % pageLength]
3531
+ })
3532
+ })
3152
3533
  }, `slider-page-${index}`))
3153
3534
  })
3154
- }), showDots && pageControlPosition === 'bottom' ? /*#__PURE__*/_jsx(View, {
3535
+ }), showDots ? /*#__PURE__*/_jsx(View, {
3155
3536
  style: {
3156
- marginTop: 8,
3537
+ position: 'absolute',
3538
+ left: 0,
3539
+ right: 0,
3540
+ ...(pageControlPosition === 'top' ? {
3541
+ top: 4
3542
+ } : {
3543
+ bottom: 4
3544
+ }),
3545
+ height: 12,
3157
3546
  flexDirection: 'row',
3158
3547
  justifyContent: 'center',
3159
- alignItems: 'center'
3548
+ alignItems: 'center',
3549
+ pointerEvents: 'none'
3160
3550
  },
3161
3551
  children: Array.from({
3162
3552
  length: pageLength
@@ -3170,7 +3560,7 @@ function SliderWidget({
3170
3560
  marginHorizontal: 3,
3171
3561
  backgroundColor: isActive ? parseColor('0xFF111827') : parseColor('0xFFD1D5DB')
3172
3562
  }
3173
- }, `slider-dot-bottom-${index}`);
3563
+ }, `slider-dot-${index}`);
3174
3564
  })
3175
3565
  }) : null]
3176
3566
  });
@@ -3192,11 +3582,15 @@ function PageViewIndicator({
3192
3582
  }) {
3193
3583
  const registry = useSliderRegistry();
3194
3584
  const [current, setCurrent] = React.useState(0);
3585
+ const [count, setCount] = React.useState(0);
3195
3586
  const indicatorStyle = style ?? 'dots';
3196
- const count = linkedTo && registry ? registry.getPageCount(linkedTo) : 0;
3197
3587
  React.useEffect(() => {
3198
3588
  if (!linkedTo || !registry) return;
3199
- return registry.getNotifier(linkedTo, setCurrent);
3589
+ setCount(registry.getPageCount(linkedTo));
3590
+ return registry.getNotifier(linkedTo, page => {
3591
+ setCurrent(page);
3592
+ setCount(registry.getPageCount(linkedTo));
3593
+ });
3200
3594
  }, [linkedTo, registry]);
3201
3595
  if (!linkedTo || !registry || count <= 1) return null;
3202
3596
  if (indicatorStyle !== 'dots') {