flowboard-react 0.6.8 → 0.6.12
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 +16 -0
- package/lib/module/components/FlowboardFlow.js +24 -66
- package/lib/module/components/FlowboardFlow.js.map +1 -1
- package/lib/module/components/FlowboardRenderer.js +590 -196
- package/lib/module/components/FlowboardRenderer.js.map +1 -1
- package/lib/module/components/layout/stackOverlayModel.js +156 -0
- package/lib/module/components/layout/stackOverlayModel.js.map +1 -0
- package/lib/module/fonts/fontLoader.js +285 -0
- package/lib/module/fonts/fontLoader.js.map +1 -0
- package/lib/module/fonts/fontResolver.js +38 -0
- package/lib/module/fonts/fontResolver.js.map +1 -0
- package/lib/module/fonts/google-fonts-meta.json +1 -0
- package/lib/module/fonts/googleFontCatalog.js +56 -0
- package/lib/module/fonts/googleFontCatalog.js.map +1 -0
- package/lib/module/fonts/googleFontLoader.js +101 -0
- package/lib/module/fonts/googleFontLoader.js.map +1 -0
- package/lib/module/fonts/googleFontsLoader.js +68 -0
- package/lib/module/fonts/googleFontsLoader.js.map +1 -0
- package/lib/module/index.js +2 -0
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/components/FlowboardFlow.d.ts.map +1 -1
- package/lib/typescript/src/components/FlowboardRenderer.d.ts +11 -4
- package/lib/typescript/src/components/FlowboardRenderer.d.ts.map +1 -1
- package/lib/typescript/src/components/layout/stackOverlayModel.d.ts +13 -0
- package/lib/typescript/src/components/layout/stackOverlayModel.d.ts.map +1 -0
- package/lib/typescript/src/fonts/fontLoader.d.ts +17 -0
- package/lib/typescript/src/fonts/fontLoader.d.ts.map +1 -0
- package/lib/typescript/src/fonts/fontResolver.d.ts +11 -0
- package/lib/typescript/src/fonts/fontResolver.d.ts.map +1 -0
- package/lib/typescript/src/fonts/googleFontCatalog.d.ts +4 -0
- package/lib/typescript/src/fonts/googleFontCatalog.d.ts.map +1 -0
- package/lib/typescript/src/fonts/googleFontLoader.d.ts +7 -0
- package/lib/typescript/src/fonts/googleFontLoader.d.ts.map +1 -0
- package/lib/typescript/src/fonts/googleFontsLoader.d.ts +18 -0
- package/lib/typescript/src/fonts/googleFontsLoader.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +2 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +25 -13
- package/src/components/FlowboardFlow.tsx +33 -82
- package/src/components/FlowboardRenderer.tsx +852 -210
- package/src/components/layout/stackOverlayModel.ts +185 -0
- package/src/fonts/fontLoader.ts +426 -0
- package/src/fonts/fontResolver.ts +69 -0
- package/src/fonts/google-fonts-meta.json +1 -0
- package/src/fonts/googleFontCatalog.ts +77 -0
- package/src/fonts/googleFontLoader.ts +136 -0
- package/src/fonts/googleFontsLoader.ts +124 -0
- package/src/index.tsx +13 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useMemo } from 'react';
|
|
1
|
+
import React, { useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
Animated,
|
|
4
4
|
KeyboardAvoidingView,
|
|
@@ -42,6 +42,26 @@ import {
|
|
|
42
42
|
} from '../utils/flowboardUtils';
|
|
43
43
|
import { resolveFontAwesomeIcon, FontAwesome6 } from '../core/fontAwesome';
|
|
44
44
|
import { useSliderRegistry } from './widgets/sliderRegistry';
|
|
45
|
+
import {
|
|
46
|
+
isOverlayStack,
|
|
47
|
+
isPositionedChild,
|
|
48
|
+
resolveAlignmentToCss,
|
|
49
|
+
resolveClipBehavior,
|
|
50
|
+
resolveFitBehavior,
|
|
51
|
+
resolveOverlayAlignmentValue,
|
|
52
|
+
resolvePositionedStyle,
|
|
53
|
+
} from './layout/stackOverlayModel';
|
|
54
|
+
import {
|
|
55
|
+
ensureGoogleFontLoaded as ensureWebGoogleFontLoaded,
|
|
56
|
+
resolveFontFamily,
|
|
57
|
+
} from '../fonts/googleFontLoader';
|
|
58
|
+
import {
|
|
59
|
+
ensureFontLoaded as ensureNativeFontLoaded,
|
|
60
|
+
isFontLoaded,
|
|
61
|
+
resolveAppliedFontFamily,
|
|
62
|
+
subscribeFontLoad,
|
|
63
|
+
} from '../fonts/fontLoader';
|
|
64
|
+
import { resolveFontSpec } from '../fonts/fontResolver';
|
|
45
65
|
|
|
46
66
|
const styles = StyleSheet.create({
|
|
47
67
|
root: { flex: 1, backgroundColor: '#ffffff' },
|
|
@@ -91,6 +111,175 @@ function getAxisBoundsForPageScroll(pageScroll: PageScrollAxis): AxisBounds {
|
|
|
91
111
|
return { widthBounded: true, heightBounded: true };
|
|
92
112
|
}
|
|
93
113
|
|
|
114
|
+
const LEGACY_SLIDE_DROP_KEYS = new Set(['badge']);
|
|
115
|
+
|
|
116
|
+
function isDevelopmentMode(): boolean {
|
|
117
|
+
return process.env.NODE_ENV !== 'production';
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function warnLegacySlideMigration(
|
|
121
|
+
contextPath: string,
|
|
122
|
+
componentId: string | undefined,
|
|
123
|
+
droppedKeys: string[]
|
|
124
|
+
): void {
|
|
125
|
+
if (!isDevelopmentMode() || droppedKeys.length === 0) return;
|
|
126
|
+
const idSuffix = componentId ? ` (id: ${componentId})` : '';
|
|
127
|
+
console.warn(
|
|
128
|
+
`[slider-migration] Migrated legacy slide to stack at "${contextPath}"${idSuffix}. Dropped unsupported keys: ${droppedKeys.join(
|
|
129
|
+
', '
|
|
130
|
+
)}`
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function warnLegacySliderChildWrap(
|
|
135
|
+
contextPath: string,
|
|
136
|
+
childType: string | undefined
|
|
137
|
+
): void {
|
|
138
|
+
if (!isDevelopmentMode()) return;
|
|
139
|
+
console.warn(
|
|
140
|
+
`[slider-migration] Wrapped non-stack slider child at "${contextPath}" as a stack page${
|
|
141
|
+
childType ? ` (type: ${childType})` : ''
|
|
142
|
+
}.`
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function normalizeLegacySlidePropsToStackProps(
|
|
147
|
+
input: Record<string, any> | undefined,
|
|
148
|
+
contextPath: string,
|
|
149
|
+
componentId?: string
|
|
150
|
+
): Record<string, any> {
|
|
151
|
+
const props =
|
|
152
|
+
input && typeof input === 'object'
|
|
153
|
+
? { ...input }
|
|
154
|
+
: ({} as Record<string, any>);
|
|
155
|
+
const droppedKeys: string[] = [];
|
|
156
|
+
|
|
157
|
+
if (props.fill && typeof props.fill === 'object') {
|
|
158
|
+
const normalizedFill = { ...(props.fill as Record<string, any>) };
|
|
159
|
+
const fillType = String(normalizedFill.type ?? '').toLowerCase();
|
|
160
|
+
if (fillType === 'color') {
|
|
161
|
+
normalizedFill.type = 'solid';
|
|
162
|
+
}
|
|
163
|
+
props.fill = normalizedFill;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
for (const key of LEGACY_SLIDE_DROP_KEYS) {
|
|
167
|
+
if (key in props) {
|
|
168
|
+
droppedKeys.push(key);
|
|
169
|
+
delete props[key];
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
warnLegacySlideMigration(contextPath, componentId, droppedKeys);
|
|
174
|
+
return props;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function createStackPageWrapper(
|
|
178
|
+
child: Record<string, any> | null,
|
|
179
|
+
pageIndex: number
|
|
180
|
+
): Record<string, any> {
|
|
181
|
+
return {
|
|
182
|
+
id: child?.id ? `${String(child.id)}-page` : `legacy-page-${pageIndex + 1}`,
|
|
183
|
+
type: 'stack',
|
|
184
|
+
properties: {
|
|
185
|
+
axis: 'vertical',
|
|
186
|
+
alignment: 'start',
|
|
187
|
+
distribution: 'start',
|
|
188
|
+
childSpacing: 0,
|
|
189
|
+
size: { width: 'fill', height: 'fit' },
|
|
190
|
+
layout: {
|
|
191
|
+
margin: { top: 0, right: 0, bottom: 0, left: 0 },
|
|
192
|
+
},
|
|
193
|
+
appearance: {
|
|
194
|
+
shape: 'rectangle',
|
|
195
|
+
cornerRadius: 0,
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
children: child ? [child] : [],
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function migrateLegacySlideNodes(
|
|
203
|
+
input: unknown,
|
|
204
|
+
contextPath = 'screen'
|
|
205
|
+
): unknown {
|
|
206
|
+
if (Array.isArray(input)) {
|
|
207
|
+
return input.map((item, index) =>
|
|
208
|
+
migrateLegacySlideNodes(item, `${contextPath}[${index}]`)
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
if (!input || typeof input !== 'object') return input;
|
|
212
|
+
|
|
213
|
+
const node = { ...(input as Record<string, any>) };
|
|
214
|
+
const rawType = String(node.type ?? '');
|
|
215
|
+
|
|
216
|
+
if (Array.isArray(node.children)) {
|
|
217
|
+
node.children = node.children.map((child, index) =>
|
|
218
|
+
migrateLegacySlideNodes(child, `${contextPath}.children[${index}]`)
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
if (node.child && typeof node.child === 'object') {
|
|
222
|
+
node.child = migrateLegacySlideNodes(node.child, `${contextPath}.child`);
|
|
223
|
+
}
|
|
224
|
+
if (Array.isArray(node.options)) {
|
|
225
|
+
node.options = node.options.map((option, index) => {
|
|
226
|
+
if (!option || typeof option !== 'object') return option;
|
|
227
|
+
const optionNode = { ...(option as Record<string, any>) };
|
|
228
|
+
if (optionNode.child && typeof optionNode.child === 'object') {
|
|
229
|
+
optionNode.child = migrateLegacySlideNodes(
|
|
230
|
+
optionNode.child,
|
|
231
|
+
`${contextPath}.options[${index}].child`
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
return optionNode;
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (rawType === 'slide') {
|
|
239
|
+
node.type = 'stack';
|
|
240
|
+
node.properties = normalizeLegacySlidePropsToStackProps(
|
|
241
|
+
(node.properties ?? {}) as Record<string, any>,
|
|
242
|
+
contextPath,
|
|
243
|
+
node.id ? String(node.id) : undefined
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (String(node.type ?? '') === 'slider') {
|
|
248
|
+
const children = Array.isArray(node.children) ? node.children : [];
|
|
249
|
+
const normalizedPages = children.map((child, index) => {
|
|
250
|
+
if (!child || typeof child !== 'object') {
|
|
251
|
+
warnLegacySliderChildWrap(
|
|
252
|
+
`${contextPath}.children[${index}]`,
|
|
253
|
+
undefined
|
|
254
|
+
);
|
|
255
|
+
return createStackPageWrapper(null, index);
|
|
256
|
+
}
|
|
257
|
+
const pageNode = child as Record<string, any>;
|
|
258
|
+
const childType = String(pageNode.type ?? '');
|
|
259
|
+
if (childType === 'stack') return pageNode;
|
|
260
|
+
warnLegacySliderChildWrap(`${contextPath}.children[${index}]`, childType);
|
|
261
|
+
return createStackPageWrapper(pageNode, index);
|
|
262
|
+
});
|
|
263
|
+
node.children =
|
|
264
|
+
normalizedPages.length > 0
|
|
265
|
+
? normalizedPages
|
|
266
|
+
: [createStackPageWrapper(null, 0)];
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return node;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function useGoogleFontLoadSignal(): void {
|
|
273
|
+
const [, setVersion] = useState(0);
|
|
274
|
+
useEffect(
|
|
275
|
+
() =>
|
|
276
|
+
subscribeFontLoad(() => {
|
|
277
|
+
setVersion((version) => version + 1);
|
|
278
|
+
}),
|
|
279
|
+
[]
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
94
283
|
export default function FlowboardRenderer(props: FlowboardRendererProps) {
|
|
95
284
|
const {
|
|
96
285
|
screenData,
|
|
@@ -101,27 +290,37 @@ export default function FlowboardRenderer(props: FlowboardRendererProps) {
|
|
|
101
290
|
currentIndex = 0,
|
|
102
291
|
totalScreens = 1,
|
|
103
292
|
} = props;
|
|
293
|
+
useGoogleFontLoadSignal();
|
|
104
294
|
|
|
105
|
-
const
|
|
106
|
-
|
|
295
|
+
const migratedScreenData = useMemo(
|
|
296
|
+
() =>
|
|
297
|
+
(migrateLegacySlideNodes(screenData, 'screen') as Record<string, any>) ??
|
|
298
|
+
{},
|
|
299
|
+
[screenData]
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
const childrenData: any[] = Array.isArray(migratedScreenData.children)
|
|
303
|
+
? migratedScreenData.children
|
|
107
304
|
: [];
|
|
108
305
|
|
|
109
|
-
const backgroundData =
|
|
110
|
-
const bgColorCode =
|
|
306
|
+
const backgroundData = migratedScreenData.background;
|
|
307
|
+
const bgColorCode = migratedScreenData.backgroundColor;
|
|
111
308
|
|
|
112
|
-
const showProgress =
|
|
113
|
-
const progressColor = parseColor(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const
|
|
117
|
-
const
|
|
309
|
+
const showProgress = migratedScreenData.showProgress === true;
|
|
310
|
+
const progressColor = parseColor(
|
|
311
|
+
migratedScreenData.progressColor ?? '0xFF000000'
|
|
312
|
+
);
|
|
313
|
+
const progressThickness = Number(migratedScreenData.progressThickness ?? 4);
|
|
314
|
+
const progressRadius = Number(migratedScreenData.progressRadius ?? 0);
|
|
315
|
+
const progressStyle = migratedScreenData.progressStyle ?? 'linear';
|
|
316
|
+
const pageScroll = resolvePageScrollAxis(migratedScreenData);
|
|
118
317
|
const pageAxisBounds = getAxisBoundsForPageScroll(pageScroll);
|
|
119
318
|
const scrollable = pageScroll !== 'none';
|
|
120
|
-
const safeArea =
|
|
319
|
+
const safeArea = migratedScreenData.safeArea !== false;
|
|
121
320
|
const safeAreaInsets = useSafeAreaInsets();
|
|
122
321
|
const rootAxis: 'vertical' = 'vertical';
|
|
123
322
|
|
|
124
|
-
const padding = parseInsets(
|
|
323
|
+
const padding = parseInsets(migratedScreenData.padding);
|
|
125
324
|
const contentPaddingStyle = insetsToStyle(padding);
|
|
126
325
|
const progressPaddingStyle = {
|
|
127
326
|
paddingTop: padding.top,
|
|
@@ -129,7 +328,7 @@ export default function FlowboardRenderer(props: FlowboardRendererProps) {
|
|
|
129
328
|
paddingLeft: padding.left,
|
|
130
329
|
};
|
|
131
330
|
const rootCrossAxisAlignment =
|
|
132
|
-
|
|
331
|
+
migratedScreenData.crossAxisAlignment ?? migratedScreenData.crossAxis;
|
|
133
332
|
const safeAreaPaddingStyle = safeArea
|
|
134
333
|
? {
|
|
135
334
|
paddingTop: safeAreaInsets.top,
|
|
@@ -162,6 +361,7 @@ export default function FlowboardRenderer(props: FlowboardRendererProps) {
|
|
|
162
361
|
Platform.OS === 'ios' ? 'interactive' : 'on-drag'
|
|
163
362
|
}
|
|
164
363
|
contentContainerStyle={{
|
|
364
|
+
flexGrow: 1,
|
|
165
365
|
...contentPaddingStyle,
|
|
166
366
|
paddingTop: showProgress ? 0 : padding.top,
|
|
167
367
|
}}
|
|
@@ -170,9 +370,11 @@ export default function FlowboardRenderer(props: FlowboardRendererProps) {
|
|
|
170
370
|
style={{
|
|
171
371
|
flexGrow: 1,
|
|
172
372
|
flexDirection: 'column',
|
|
173
|
-
justifyContent: parseFlexAlignment(
|
|
373
|
+
justifyContent: parseFlexAlignment(
|
|
374
|
+
migratedScreenData.mainAxisAlignment
|
|
375
|
+
),
|
|
174
376
|
alignItems: parseRootCrossAlignment(rootCrossAxisAlignment),
|
|
175
|
-
minHeight:
|
|
377
|
+
minHeight: '100%',
|
|
176
378
|
minWidth: 0,
|
|
177
379
|
}}
|
|
178
380
|
>
|
|
@@ -185,7 +387,7 @@ export default function FlowboardRenderer(props: FlowboardRendererProps) {
|
|
|
185
387
|
parentFlexAxis: rootAxis,
|
|
186
388
|
parentMainAxisBounded: pageAxisBounds.heightBounded,
|
|
187
389
|
pageAxisBounds,
|
|
188
|
-
screenData,
|
|
390
|
+
screenData: migratedScreenData,
|
|
189
391
|
enableFontAwesomeIcons,
|
|
190
392
|
key: `child-${index}`,
|
|
191
393
|
})
|
|
@@ -198,7 +400,9 @@ export default function FlowboardRenderer(props: FlowboardRendererProps) {
|
|
|
198
400
|
flex: 1,
|
|
199
401
|
...contentPaddingStyle,
|
|
200
402
|
paddingTop: showProgress ? 0 : padding.top,
|
|
201
|
-
justifyContent: parseFlexAlignment(
|
|
403
|
+
justifyContent: parseFlexAlignment(
|
|
404
|
+
migratedScreenData.mainAxisAlignment
|
|
405
|
+
),
|
|
202
406
|
alignItems: parseRootCrossAlignment(rootCrossAxisAlignment),
|
|
203
407
|
}}
|
|
204
408
|
>
|
|
@@ -211,7 +415,7 @@ export default function FlowboardRenderer(props: FlowboardRendererProps) {
|
|
|
211
415
|
parentFlexAxis: rootAxis,
|
|
212
416
|
parentMainAxisBounded: true,
|
|
213
417
|
pageAxisBounds,
|
|
214
|
-
screenData,
|
|
418
|
+
screenData: migratedScreenData,
|
|
215
419
|
enableFontAwesomeIcons,
|
|
216
420
|
key: `child-${index}`,
|
|
217
421
|
})
|
|
@@ -487,10 +691,7 @@ function normalizeStackAxis(type: string, props: Record<string, any>) {
|
|
|
487
691
|
if (type === 'layout') {
|
|
488
692
|
return props.direction === 'horizontal' ? 'horizontal' : 'vertical';
|
|
489
693
|
}
|
|
490
|
-
if (
|
|
491
|
-
type === 'stack' &&
|
|
492
|
-
(props.fit !== undefined || isLegacyOverlayAlignment(props.alignment))
|
|
493
|
-
) {
|
|
694
|
+
if (type === 'stack' && isLegacyOverlayAlignment(props.alignment)) {
|
|
494
695
|
return 'overlay';
|
|
495
696
|
}
|
|
496
697
|
return 'vertical';
|
|
@@ -548,7 +749,7 @@ function normalizeOverlayAlignment(value: unknown) {
|
|
|
548
749
|
case 'bottomRight':
|
|
549
750
|
return 'bottomEnd';
|
|
550
751
|
default:
|
|
551
|
-
return '
|
|
752
|
+
return 'topStart';
|
|
552
753
|
}
|
|
553
754
|
}
|
|
554
755
|
|
|
@@ -589,6 +790,67 @@ function normalizeStackSizeMode(
|
|
|
589
790
|
return undefined;
|
|
590
791
|
}
|
|
591
792
|
|
|
793
|
+
export function resolveFillSizeStyle(
|
|
794
|
+
size: Record<string, any> | null | undefined,
|
|
795
|
+
options?: { allowWidthFill?: boolean; allowHeightFill?: boolean }
|
|
796
|
+
): ViewStyle {
|
|
797
|
+
const allowWidthFill = options?.allowWidthFill !== false;
|
|
798
|
+
const allowHeightFill = options?.allowHeightFill !== false;
|
|
799
|
+
const fillWidth =
|
|
800
|
+
allowWidthFill && normalizeStackSizeMode(size?.width) === 'fill';
|
|
801
|
+
const fillHeight =
|
|
802
|
+
allowHeightFill && normalizeStackSizeMode(size?.height) === 'fill';
|
|
803
|
+
const style: Record<string, any> = {};
|
|
804
|
+
|
|
805
|
+
if (fillWidth) {
|
|
806
|
+
style.width = '100%';
|
|
807
|
+
style.alignSelf = 'stretch';
|
|
808
|
+
style.minWidth = 0;
|
|
809
|
+
}
|
|
810
|
+
if (fillHeight) {
|
|
811
|
+
style.height = '100%';
|
|
812
|
+
style.flexGrow = 1;
|
|
813
|
+
style.flexShrink = 1;
|
|
814
|
+
style.flexBasis = 'auto';
|
|
815
|
+
style.minHeight = 0;
|
|
816
|
+
}
|
|
817
|
+
if (fillWidth && fillHeight) {
|
|
818
|
+
style.flexGrow = 1;
|
|
819
|
+
style.flexShrink = 1;
|
|
820
|
+
style.flexBasis = 'auto';
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
return style;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
function resolveOverlayContainerFitStyle(
|
|
827
|
+
props: Record<string, any>,
|
|
828
|
+
axisBounds: AxisBounds
|
|
829
|
+
): ViewStyle {
|
|
830
|
+
if (resolveFitBehavior(props.fit) !== 'expand') return {};
|
|
831
|
+
|
|
832
|
+
const widthMode = normalizeStackSizeMode(props.size?.width);
|
|
833
|
+
const heightMode = normalizeStackSizeMode(props.size?.height);
|
|
834
|
+
const style: Record<string, any> = {
|
|
835
|
+
minWidth: 0,
|
|
836
|
+
minHeight: 0,
|
|
837
|
+
};
|
|
838
|
+
|
|
839
|
+
if (axisBounds.widthBounded && widthMode !== 'fixed') {
|
|
840
|
+
style.width = '100%';
|
|
841
|
+
style.alignSelf = 'stretch';
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
if (axisBounds.heightBounded && heightMode !== 'fixed') {
|
|
845
|
+
style.height = '100%';
|
|
846
|
+
style.flexGrow = 1;
|
|
847
|
+
style.flexShrink = 1;
|
|
848
|
+
style.flexBasis = 'auto';
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
return style;
|
|
852
|
+
}
|
|
853
|
+
|
|
592
854
|
function wantsFillWidth(props: Record<string, any>) {
|
|
593
855
|
return (
|
|
594
856
|
normalizeStackSizeMode(props.size?.width) === 'fill' ||
|
|
@@ -636,18 +898,36 @@ function normalizeStackFill(props: Record<string, any>) {
|
|
|
636
898
|
if (props.fill && typeof props.fill === 'object') {
|
|
637
899
|
const next = { ...props.fill };
|
|
638
900
|
if (!next.type && next.color) next.type = 'solid';
|
|
901
|
+
if (String(next.type ?? '').toLowerCase() === 'color') {
|
|
902
|
+
next.type = 'solid';
|
|
903
|
+
}
|
|
904
|
+
const fillType = String(next.type ?? '').toLowerCase();
|
|
905
|
+
if (
|
|
906
|
+
(fillType === '' || fillType === 'solid') &&
|
|
907
|
+
(typeof next.color !== 'string' || next.color.trim().length === 0)
|
|
908
|
+
) {
|
|
909
|
+
next.type = 'solid';
|
|
910
|
+
next.color = '0x00FFFFFF';
|
|
911
|
+
}
|
|
639
912
|
return next;
|
|
640
913
|
}
|
|
641
914
|
if (props.background && typeof props.background === 'object') {
|
|
642
915
|
if (props.background.type === 'color') {
|
|
643
|
-
return {
|
|
916
|
+
return {
|
|
917
|
+
type: 'solid',
|
|
918
|
+
color:
|
|
919
|
+
typeof props.background.color === 'string' &&
|
|
920
|
+
props.background.color.trim().length > 0
|
|
921
|
+
? props.background.color
|
|
922
|
+
: '0x00FFFFFF',
|
|
923
|
+
};
|
|
644
924
|
}
|
|
645
925
|
return { ...props.background };
|
|
646
926
|
}
|
|
647
927
|
if (props.backgroundColor) {
|
|
648
928
|
return { type: 'solid', color: props.backgroundColor };
|
|
649
929
|
}
|
|
650
|
-
return
|
|
930
|
+
return { type: 'solid', color: '0x00FFFFFF' };
|
|
651
931
|
}
|
|
652
932
|
|
|
653
933
|
function normalizeStackBorder(props: Record<string, any>) {
|
|
@@ -746,9 +1026,28 @@ function mergeStackProps(
|
|
|
746
1026
|
},
|
|
747
1027
|
};
|
|
748
1028
|
|
|
749
|
-
if (
|
|
1029
|
+
if (
|
|
1030
|
+
axis === 'overlay' ||
|
|
1031
|
+
childProps.overlayAlignment !== undefined ||
|
|
1032
|
+
parentProps.overlayAlignment !== undefined ||
|
|
1033
|
+
childProps.mode === 'overlay' ||
|
|
1034
|
+
parentProps.mode === 'overlay'
|
|
1035
|
+
) {
|
|
750
1036
|
mergedProps.overlayAlignment =
|
|
751
|
-
childProps.overlayAlignment ?? parentProps.overlayAlignment ?? '
|
|
1037
|
+
childProps.overlayAlignment ?? parentProps.overlayAlignment ?? 'topStart';
|
|
1038
|
+
}
|
|
1039
|
+
if (childProps.mode !== undefined || parentProps.mode !== undefined) {
|
|
1040
|
+
mergedProps.mode = childProps.mode ?? parentProps.mode;
|
|
1041
|
+
}
|
|
1042
|
+
if (childProps.fit !== undefined || parentProps.fit !== undefined) {
|
|
1043
|
+
mergedProps.fit = childProps.fit ?? parentProps.fit;
|
|
1044
|
+
}
|
|
1045
|
+
if (
|
|
1046
|
+
childProps.clipBehavior !== undefined ||
|
|
1047
|
+
parentProps.clipBehavior !== undefined
|
|
1048
|
+
) {
|
|
1049
|
+
mergedProps.clipBehavior =
|
|
1050
|
+
childProps.clipBehavior ?? parentProps.clipBehavior;
|
|
752
1051
|
}
|
|
753
1052
|
|
|
754
1053
|
if (parentProps.fill || childProps.fill) {
|
|
@@ -811,12 +1110,34 @@ function normalizeStackLikeNode(
|
|
|
811
1110
|
},
|
|
812
1111
|
};
|
|
813
1112
|
|
|
814
|
-
if (
|
|
815
|
-
normalizedProps.
|
|
816
|
-
|
|
817
|
-
|
|
1113
|
+
if (props.mode === 'overlay' || props.mode === 'linear') {
|
|
1114
|
+
normalizedProps.mode = props.mode;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
if (
|
|
1118
|
+
props.fit === 'expand' ||
|
|
1119
|
+
props.fit === 'loose' ||
|
|
1120
|
+
String(props.fit).toLowerCase() === 'expand' ||
|
|
1121
|
+
String(props.fit).toLowerCase() === 'loose'
|
|
1122
|
+
) {
|
|
1123
|
+
normalizedProps.fit =
|
|
1124
|
+
String(props.fit).toLowerCase() === 'expand' ? 'expand' : 'loose';
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
if (
|
|
1128
|
+
String(props.clipBehavior ?? '').toLowerCase() === 'none' ||
|
|
1129
|
+
String(props.clipBehavior ?? '').toLowerCase() === 'hardedge'
|
|
1130
|
+
) {
|
|
1131
|
+
normalizedProps.clipBehavior =
|
|
1132
|
+
String(props.clipBehavior).toLowerCase() === 'hardedge'
|
|
1133
|
+
? 'hardEdge'
|
|
1134
|
+
: 'none';
|
|
818
1135
|
}
|
|
819
1136
|
|
|
1137
|
+
normalizedProps.overlayAlignment = normalizeOverlayAlignment(
|
|
1138
|
+
props.overlayAlignment ?? props.alignment
|
|
1139
|
+
);
|
|
1140
|
+
|
|
820
1141
|
const fill = normalizeStackFill(props);
|
|
821
1142
|
if (fill) normalizedProps.fill = fill;
|
|
822
1143
|
|
|
@@ -862,32 +1183,6 @@ function normalizeStackLikeNode(
|
|
|
862
1183
|
};
|
|
863
1184
|
}
|
|
864
1185
|
|
|
865
|
-
function parseOverlayGridAlignment(
|
|
866
|
-
value?: string
|
|
867
|
-
): Pick<ViewStyle, 'justifyContent' | 'alignItems'> {
|
|
868
|
-
switch (normalizeOverlayAlignment(value)) {
|
|
869
|
-
case 'topStart':
|
|
870
|
-
return { justifyContent: 'flex-start', alignItems: 'flex-start' };
|
|
871
|
-
case 'top':
|
|
872
|
-
return { justifyContent: 'flex-start', alignItems: 'center' };
|
|
873
|
-
case 'topEnd':
|
|
874
|
-
return { justifyContent: 'flex-start', alignItems: 'flex-end' };
|
|
875
|
-
case 'start':
|
|
876
|
-
return { justifyContent: 'center', alignItems: 'flex-start' };
|
|
877
|
-
case 'end':
|
|
878
|
-
return { justifyContent: 'center', alignItems: 'flex-end' };
|
|
879
|
-
case 'bottomStart':
|
|
880
|
-
return { justifyContent: 'flex-end', alignItems: 'flex-start' };
|
|
881
|
-
case 'bottom':
|
|
882
|
-
return { justifyContent: 'flex-end', alignItems: 'center' };
|
|
883
|
-
case 'bottomEnd':
|
|
884
|
-
return { justifyContent: 'flex-end', alignItems: 'flex-end' };
|
|
885
|
-
case 'center':
|
|
886
|
-
default:
|
|
887
|
-
return { justifyContent: 'center', alignItems: 'center' };
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
|
|
891
1186
|
function resolveStackDimension(
|
|
892
1187
|
props: Record<string, any>,
|
|
893
1188
|
axis: 'vertical' | 'horizontal' | 'overlay',
|
|
@@ -910,6 +1205,35 @@ function resolveStackDimension(
|
|
|
910
1205
|
return undefined;
|
|
911
1206
|
}
|
|
912
1207
|
|
|
1208
|
+
type TextWidthMode = 'fit' | 'fill' | 'fixed';
|
|
1209
|
+
|
|
1210
|
+
function resolveTextWidthMode(props: Record<string, any>): TextWidthMode {
|
|
1211
|
+
const raw = String(props.size?.width ?? '')
|
|
1212
|
+
.trim()
|
|
1213
|
+
.toLowerCase();
|
|
1214
|
+
if (
|
|
1215
|
+
raw === 'fill' ||
|
|
1216
|
+
raw === 'infinity' ||
|
|
1217
|
+
raw === 'infinite' ||
|
|
1218
|
+
raw === 'double.infinity' ||
|
|
1219
|
+
raw === '+infinity'
|
|
1220
|
+
) {
|
|
1221
|
+
return 'fill';
|
|
1222
|
+
}
|
|
1223
|
+
if (raw === 'fit') return 'fit';
|
|
1224
|
+
if (raw === 'fixed') return 'fixed';
|
|
1225
|
+
if (isFillDimensionValue(props.width)) return 'fill';
|
|
1226
|
+
return props.width !== undefined ? 'fixed' : 'fit';
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
function resolveTextFixedWidth(
|
|
1230
|
+
props: Record<string, any>
|
|
1231
|
+
): number | string | undefined {
|
|
1232
|
+
const explicit = normalizeDimension(parseLayoutDimension(props.width, true));
|
|
1233
|
+
if (explicit !== undefined) return explicit;
|
|
1234
|
+
return normalizeDimension(parseLayoutDimension(props.size?.widthPx, true));
|
|
1235
|
+
}
|
|
1236
|
+
|
|
913
1237
|
function buildWidget(
|
|
914
1238
|
json: Record<string, any>,
|
|
915
1239
|
params: {
|
|
@@ -1059,6 +1383,13 @@ function buildWidget(
|
|
|
1059
1383
|
const gradient = parseGradient(props.foreground);
|
|
1060
1384
|
const textStyle = getTextStyle(props, 'color');
|
|
1061
1385
|
const align = parseTextAlign(props.textAlign);
|
|
1386
|
+
const widthMode = resolveTextWidthMode(props);
|
|
1387
|
+
const fixedWidth = resolveTextFixedWidth(props);
|
|
1388
|
+
const widthBounded = params.pageAxisBounds?.widthBounded ?? true;
|
|
1389
|
+
// Shared width-mode contract across React/Flutter:
|
|
1390
|
+
// fit = intrinsic width (no flex, no 100% stretch)
|
|
1391
|
+
// fill = expand within bounded parent constraints
|
|
1392
|
+
// fixed = explicit width value
|
|
1062
1393
|
if (gradient) {
|
|
1063
1394
|
node = (
|
|
1064
1395
|
<GradientText
|
|
@@ -1072,6 +1403,31 @@ function buildWidget(
|
|
|
1072
1403
|
<Text style={[textStyle, { textAlign: align }]}>{resolvedText}</Text>
|
|
1073
1404
|
);
|
|
1074
1405
|
}
|
|
1406
|
+
if (widthMode === 'fill') {
|
|
1407
|
+
if (params.parentFlexAxis === 'horizontal') {
|
|
1408
|
+
if (params.parentMainAxisBounded ?? true) {
|
|
1409
|
+
node = (
|
|
1410
|
+
<View style={{ flex: 1, minWidth: 0, alignSelf: 'stretch' }}>
|
|
1411
|
+
{node}
|
|
1412
|
+
</View>
|
|
1413
|
+
);
|
|
1414
|
+
} else {
|
|
1415
|
+
node = <View style={{ minWidth: 0 }}>{node}</View>;
|
|
1416
|
+
}
|
|
1417
|
+
} else if (widthBounded) {
|
|
1418
|
+
node = (
|
|
1419
|
+
<View style={{ width: '100%', minWidth: 0, alignSelf: 'stretch' }}>
|
|
1420
|
+
{node}
|
|
1421
|
+
</View>
|
|
1422
|
+
);
|
|
1423
|
+
} else {
|
|
1424
|
+
node = <View style={{ minWidth: 0 }}>{node}</View>;
|
|
1425
|
+
}
|
|
1426
|
+
} else if (widthMode === 'fixed') {
|
|
1427
|
+
node = <View style={{ width: fixedWidth, minWidth: 0 }}>{node}</View>;
|
|
1428
|
+
} else {
|
|
1429
|
+
node = <View style={{ minWidth: 0 }}>{node}</View>;
|
|
1430
|
+
}
|
|
1075
1431
|
break;
|
|
1076
1432
|
}
|
|
1077
1433
|
case 'rich_text': {
|
|
@@ -1505,14 +1861,14 @@ function buildWidget(
|
|
|
1505
1861
|
props.direction === 'vertical' ? 'vertical' : 'horizontal';
|
|
1506
1862
|
const interaction = props.interaction ?? {};
|
|
1507
1863
|
const pageControl = props.pageControl ?? {};
|
|
1508
|
-
const
|
|
1864
|
+
const sliderPages = (childrenJson ?? []).filter(
|
|
1865
|
+
(child) => child && String(child.type ?? '') === 'stack'
|
|
1866
|
+
);
|
|
1509
1867
|
node = (
|
|
1510
1868
|
<SliderWidget
|
|
1511
|
-
id={json._internalId}
|
|
1869
|
+
id={json._internalId ?? json.id}
|
|
1512
1870
|
sliderProps={props}
|
|
1513
|
-
|
|
1514
|
-
(child) => child?.properties ?? {}
|
|
1515
|
-
)}
|
|
1871
|
+
pagePropsList={sliderPages.map((child) => child?.properties ?? {})}
|
|
1516
1872
|
direction={direction}
|
|
1517
1873
|
pageAlignment={
|
|
1518
1874
|
props.pageAlignment === 'start' || props.pageAlignment === 'end'
|
|
@@ -1531,28 +1887,19 @@ function buildWidget(
|
|
|
1531
1887
|
pageControl.position === 'top' ? 'top' : 'bottom'
|
|
1532
1888
|
}
|
|
1533
1889
|
>
|
|
1534
|
-
{
|
|
1890
|
+
{sliderPages.map((child, index) => (
|
|
1535
1891
|
<View key={`slider-${index}`}>
|
|
1536
|
-
{buildWidget(child, {
|
|
1892
|
+
{buildWidget(child, {
|
|
1893
|
+
...params,
|
|
1894
|
+
// Slider pages always render inside a bounded pager viewport.
|
|
1895
|
+
pageAxisBounds: { widthBounded: true, heightBounded: true },
|
|
1896
|
+
})}
|
|
1537
1897
|
</View>
|
|
1538
1898
|
))}
|
|
1539
1899
|
</SliderWidget>
|
|
1540
1900
|
);
|
|
1541
1901
|
break;
|
|
1542
1902
|
}
|
|
1543
|
-
case 'slide': {
|
|
1544
|
-
node = buildWidget(
|
|
1545
|
-
{
|
|
1546
|
-
...json,
|
|
1547
|
-
type: 'stack',
|
|
1548
|
-
properties: {
|
|
1549
|
-
...(props || {}),
|
|
1550
|
-
},
|
|
1551
|
-
},
|
|
1552
|
-
{ ...params }
|
|
1553
|
-
);
|
|
1554
|
-
break;
|
|
1555
|
-
}
|
|
1556
1903
|
case 'pageview_indicator': {
|
|
1557
1904
|
node = (
|
|
1558
1905
|
<PageViewIndicator
|
|
@@ -1597,19 +1944,22 @@ function buildWidget(
|
|
|
1597
1944
|
break;
|
|
1598
1945
|
}
|
|
1599
1946
|
case 'stack': {
|
|
1600
|
-
const axis =
|
|
1601
|
-
|
|
1602
|
-
? props.axis
|
|
1603
|
-
: 'vertical';
|
|
1604
|
-
const isOverlay = axis === 'overlay';
|
|
1947
|
+
const axis = props.axis === 'horizontal' ? 'horizontal' : 'vertical';
|
|
1948
|
+
const isOverlay = isOverlayStack(childrenJson, props);
|
|
1605
1949
|
const spacing = Number(props.childSpacing ?? 0);
|
|
1606
1950
|
const applySpacing = !STACK_SPACE_DISTRIBUTIONS.has(
|
|
1607
1951
|
String(props.distribution ?? 'start')
|
|
1608
1952
|
);
|
|
1609
|
-
const
|
|
1953
|
+
const resolvedAxis = isOverlay ? 'overlay' : axis;
|
|
1954
|
+
const width = resolveStackDimension(
|
|
1955
|
+
props,
|
|
1956
|
+
resolvedAxis,
|
|
1957
|
+
'width',
|
|
1958
|
+
pageAxisBounds
|
|
1959
|
+
);
|
|
1610
1960
|
const height = resolveStackDimension(
|
|
1611
1961
|
props,
|
|
1612
|
-
|
|
1962
|
+
resolvedAxis,
|
|
1613
1963
|
'height',
|
|
1614
1964
|
pageAxisBounds
|
|
1615
1965
|
);
|
|
@@ -1661,14 +2011,27 @@ function buildWidget(
|
|
|
1661
2011
|
: undefined;
|
|
1662
2012
|
const stackColor =
|
|
1663
2013
|
fill?.type === 'solid' || (!fill?.type && fill?.color)
|
|
1664
|
-
? parseColor(fill?.color ?? '
|
|
2014
|
+
? parseColor(fill?.color ?? '0x00FFFFFF')
|
|
1665
2015
|
: parseColor(props.backgroundColor ?? '#00000000');
|
|
1666
2016
|
|
|
1667
2017
|
const baseStackStyle: any = {
|
|
1668
2018
|
width,
|
|
1669
2019
|
height,
|
|
2020
|
+
...resolveFillSizeStyle(props.size, {
|
|
2021
|
+
allowWidthFill: pageAxisBounds.widthBounded,
|
|
2022
|
+
allowHeightFill: pageAxisBounds.heightBounded,
|
|
2023
|
+
}),
|
|
2024
|
+
...(isOverlay
|
|
2025
|
+
? resolveOverlayContainerFitStyle(props, pageAxisBounds)
|
|
2026
|
+
: {}),
|
|
1670
2027
|
borderRadius: borderRadius > 0 ? borderRadius : undefined,
|
|
1671
|
-
overflow:
|
|
2028
|
+
overflow: isOverlay
|
|
2029
|
+
? resolveClipBehavior(props.clipBehavior) === 'hardEdge'
|
|
2030
|
+
? 'hidden'
|
|
2031
|
+
: 'visible'
|
|
2032
|
+
: borderRadius > 0
|
|
2033
|
+
? 'hidden'
|
|
2034
|
+
: undefined,
|
|
1672
2035
|
borderWidth: borderColor ? borderWidth : undefined,
|
|
1673
2036
|
borderColor: borderColor ?? undefined,
|
|
1674
2037
|
...shadowStyle,
|
|
@@ -1688,12 +2051,38 @@ function buildWidget(
|
|
|
1688
2051
|
}}
|
|
1689
2052
|
>
|
|
1690
2053
|
{(childrenJson ?? []).map((child, index) => {
|
|
1691
|
-
|
|
1692
|
-
|
|
2054
|
+
if (isPositionedChild(child)) {
|
|
2055
|
+
if (String(child?.type ?? '') === 'positioned') {
|
|
2056
|
+
return (
|
|
2057
|
+
<React.Fragment key={`stack-overlay-positioned-${index}`}>
|
|
2058
|
+
{buildWidget(child, {
|
|
2059
|
+
...params,
|
|
2060
|
+
pageAxisBounds,
|
|
2061
|
+
})}
|
|
2062
|
+
</React.Fragment>
|
|
2063
|
+
);
|
|
2064
|
+
}
|
|
2065
|
+
return (
|
|
2066
|
+
<View
|
|
2067
|
+
key={`stack-overlay-positioned-${index}`}
|
|
2068
|
+
style={resolvePositionedStyle(
|
|
2069
|
+
(child?.properties ?? {}) as Record<string, any>
|
|
2070
|
+
)}
|
|
2071
|
+
>
|
|
2072
|
+
{buildWidget(child, {
|
|
2073
|
+
...params,
|
|
2074
|
+
pageAxisBounds,
|
|
2075
|
+
})}
|
|
2076
|
+
</View>
|
|
2077
|
+
);
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
const alignment = resolveAlignmentToCss(
|
|
2081
|
+
resolveOverlayAlignmentValue(props)
|
|
1693
2082
|
);
|
|
1694
2083
|
return (
|
|
1695
2084
|
<View
|
|
1696
|
-
key={`stack-overlay-${index}`}
|
|
2085
|
+
key={`stack-overlay-non-positioned-${index}`}
|
|
1697
2086
|
style={{
|
|
1698
2087
|
position: 'absolute',
|
|
1699
2088
|
top: 0,
|
|
@@ -1772,24 +2161,8 @@ function buildWidget(
|
|
|
1772
2161
|
break;
|
|
1773
2162
|
}
|
|
1774
2163
|
case 'positioned': {
|
|
1775
|
-
const left = parseLayoutDimension(props.left);
|
|
1776
|
-
const top = parseLayoutDimension(props.top);
|
|
1777
|
-
const right = parseLayoutDimension(props.right);
|
|
1778
|
-
const bottom = parseLayoutDimension(props.bottom);
|
|
1779
|
-
const width = parseLayoutDimension(props.width);
|
|
1780
|
-
const height = parseLayoutDimension(props.height);
|
|
1781
2164
|
node = (
|
|
1782
|
-
<View
|
|
1783
|
-
style={{
|
|
1784
|
-
position: 'absolute',
|
|
1785
|
-
left,
|
|
1786
|
-
top,
|
|
1787
|
-
right,
|
|
1788
|
-
bottom,
|
|
1789
|
-
width,
|
|
1790
|
-
height,
|
|
1791
|
-
}}
|
|
1792
|
-
>
|
|
2165
|
+
<View style={resolvePositionedStyle(props)}>
|
|
1793
2166
|
{childJson ? buildWidget(childJson, { ...params }) : null}
|
|
1794
2167
|
</View>
|
|
1795
2168
|
);
|
|
@@ -1856,6 +2229,10 @@ function buildWidget(
|
|
|
1856
2229
|
let shouldExpand = false;
|
|
1857
2230
|
const parentAxis = params.parentFlexAxis ?? 'vertical';
|
|
1858
2231
|
const parentMainAxisBounded = params.parentMainAxisBounded ?? true;
|
|
2232
|
+
const sliderWantsMainAxisFill =
|
|
2233
|
+
type === 'slider' &&
|
|
2234
|
+
((parentAxis === 'vertical' && wantsFillHeight(props)) ||
|
|
2235
|
+
(parentAxis === 'horizontal' && wantsFillWidth(props)));
|
|
1859
2236
|
|
|
1860
2237
|
if (parentMainAxisBounded) {
|
|
1861
2238
|
if (type === 'stack') {
|
|
@@ -1889,9 +2266,25 @@ function buildWidget(
|
|
|
1889
2266
|
}
|
|
1890
2267
|
}
|
|
1891
2268
|
}
|
|
2269
|
+
if (sliderWantsMainAxisFill) {
|
|
2270
|
+
shouldExpand = true;
|
|
2271
|
+
}
|
|
1892
2272
|
|
|
1893
2273
|
if (shouldExpand) {
|
|
1894
|
-
|
|
2274
|
+
// Keep cross-axis stretching on the outer expansion wrapper as well.
|
|
2275
|
+
// Without this, a centered parent cross-axis can collapse effective fill width/height.
|
|
2276
|
+
node = (
|
|
2277
|
+
<View
|
|
2278
|
+
style={{
|
|
2279
|
+
flex: 1,
|
|
2280
|
+
minHeight: 0,
|
|
2281
|
+
minWidth: 0,
|
|
2282
|
+
alignSelf: shouldStretchCrossAxis ? 'stretch' : undefined,
|
|
2283
|
+
}}
|
|
2284
|
+
>
|
|
2285
|
+
{node}
|
|
2286
|
+
</View>
|
|
2287
|
+
);
|
|
1895
2288
|
}
|
|
1896
2289
|
}
|
|
1897
2290
|
|
|
@@ -1926,9 +2319,38 @@ function getTextStyle(
|
|
|
1926
2319
|
colorKey: string,
|
|
1927
2320
|
defaultColor?: string
|
|
1928
2321
|
): TextStyle {
|
|
1929
|
-
const
|
|
2322
|
+
const requestedFontFamily = resolveFontFamily(props.fontFamily);
|
|
1930
2323
|
const fontSize = Number(props.fontSize ?? 14);
|
|
1931
|
-
const
|
|
2324
|
+
const parsedFontWeight = parseFontWeight(props.fontWeight);
|
|
2325
|
+
const requestedWeight = Number(parsedFontWeight);
|
|
2326
|
+
const numericRequestedWeight = Number.isFinite(requestedWeight)
|
|
2327
|
+
? requestedWeight
|
|
2328
|
+
: undefined;
|
|
2329
|
+
let fontFamily = requestedFontFamily;
|
|
2330
|
+
let fontWeight = parsedFontWeight;
|
|
2331
|
+
|
|
2332
|
+
if (Platform.OS === 'web' && requestedFontFamily) {
|
|
2333
|
+
const resolvedSpec = resolveFontSpec({
|
|
2334
|
+
family: requestedFontFamily,
|
|
2335
|
+
requestedWeight: numericRequestedWeight,
|
|
2336
|
+
});
|
|
2337
|
+
fontFamily = resolvedSpec.resolvedFamily;
|
|
2338
|
+
fontWeight =
|
|
2339
|
+
resolvedSpec.resolvedWeight !== undefined
|
|
2340
|
+
? String(resolvedSpec.resolvedWeight)
|
|
2341
|
+
: parsedFontWeight;
|
|
2342
|
+
ensureWebGoogleFontLoaded(resolvedSpec.resolvedFamily, [fontWeight]);
|
|
2343
|
+
} else if (requestedFontFamily) {
|
|
2344
|
+
const resolvedSpec = resolveFontSpec({
|
|
2345
|
+
family: requestedFontFamily,
|
|
2346
|
+
requestedWeight: numericRequestedWeight,
|
|
2347
|
+
});
|
|
2348
|
+
void ensureNativeFontLoaded(resolvedSpec).catch(() => undefined);
|
|
2349
|
+
fontWeight = undefined;
|
|
2350
|
+
fontFamily = isFontLoaded(resolvedSpec)
|
|
2351
|
+
? resolveAppliedFontFamily(resolvedSpec)
|
|
2352
|
+
: resolvedSpec.resolvedFamily;
|
|
2353
|
+
}
|
|
1932
2354
|
const fontStyle = props.fontStyle === 'italic' ? 'italic' : 'normal';
|
|
1933
2355
|
const color = parseColor(props[colorKey] ?? defaultColor ?? '0xFF000000');
|
|
1934
2356
|
const letterSpacing =
|
|
@@ -1964,9 +2386,13 @@ function parseGradient(value: any):
|
|
|
1964
2386
|
if (colors.length === 0) return undefined;
|
|
1965
2387
|
const start = alignmentToGradient(value.begin ?? 'topLeft');
|
|
1966
2388
|
const end = alignmentToGradient(value.end ?? 'bottomRight');
|
|
1967
|
-
const
|
|
2389
|
+
const parsedStops = Array.isArray(value.stops)
|
|
1968
2390
|
? value.stops.map((s: number) => Number(s))
|
|
1969
2391
|
: undefined;
|
|
2392
|
+
const stops =
|
|
2393
|
+
parsedStops && parsedStops.length === colors.length
|
|
2394
|
+
? parsedStops
|
|
2395
|
+
: undefined;
|
|
1970
2396
|
return { colors, start, end, stops };
|
|
1971
2397
|
}
|
|
1972
2398
|
|
|
@@ -2419,10 +2845,26 @@ function GradientText({
|
|
|
2419
2845
|
gradient: { colors: string[]; start: any; end: any; stops?: number[] };
|
|
2420
2846
|
style?: any;
|
|
2421
2847
|
}) {
|
|
2848
|
+
if (Platform.OS === 'web') {
|
|
2849
|
+
return (
|
|
2850
|
+
<Text style={[style, getWebGradientTextStyle(gradient)]}>{text}</Text>
|
|
2851
|
+
);
|
|
2852
|
+
}
|
|
2853
|
+
|
|
2422
2854
|
return (
|
|
2423
2855
|
<MaskedView
|
|
2424
2856
|
maskElement={
|
|
2425
|
-
<Text
|
|
2857
|
+
<Text
|
|
2858
|
+
style={[
|
|
2859
|
+
style,
|
|
2860
|
+
{
|
|
2861
|
+
color: '#FFFFFF',
|
|
2862
|
+
backgroundColor: 'transparent',
|
|
2863
|
+
},
|
|
2864
|
+
]}
|
|
2865
|
+
>
|
|
2866
|
+
{text}
|
|
2867
|
+
</Text>
|
|
2426
2868
|
}
|
|
2427
2869
|
>
|
|
2428
2870
|
<LinearGradient
|
|
@@ -2431,12 +2873,48 @@ function GradientText({
|
|
|
2431
2873
|
end={gradient.end}
|
|
2432
2874
|
locations={gradient.stops}
|
|
2433
2875
|
>
|
|
2434
|
-
<Text style={[style, { opacity: 0 }]}>{text}</Text>
|
|
2876
|
+
<Text style={[style, { color: '#FFFFFF', opacity: 0 }]}>{text}</Text>
|
|
2435
2877
|
</LinearGradient>
|
|
2436
2878
|
</MaskedView>
|
|
2437
2879
|
);
|
|
2438
2880
|
}
|
|
2439
2881
|
|
|
2882
|
+
function getWebGradientTextStyle(gradient: {
|
|
2883
|
+
colors: string[];
|
|
2884
|
+
start: { x: number; y: number };
|
|
2885
|
+
end: { x: number; y: number };
|
|
2886
|
+
stops?: number[];
|
|
2887
|
+
}) {
|
|
2888
|
+
const angle = gradientToCssAngle(gradient.start, gradient.end);
|
|
2889
|
+
const normalizedStops =
|
|
2890
|
+
gradient.stops && gradient.stops.length === gradient.colors.length
|
|
2891
|
+
? gradient.stops.map((stop) => `${clamp01(stop) * 100}%`)
|
|
2892
|
+
: undefined;
|
|
2893
|
+
const gradientStops = gradient.colors.map((color, index) => {
|
|
2894
|
+
if (!normalizedStops) return color;
|
|
2895
|
+
return `${color} ${normalizedStops[index]}`;
|
|
2896
|
+
});
|
|
2897
|
+
return {
|
|
2898
|
+
backgroundImage: `linear-gradient(${angle}deg, ${gradientStops.join(
|
|
2899
|
+
', '
|
|
2900
|
+
)})`,
|
|
2901
|
+
backgroundClip: 'text',
|
|
2902
|
+
WebkitBackgroundClip: 'text',
|
|
2903
|
+
color: 'transparent',
|
|
2904
|
+
};
|
|
2905
|
+
}
|
|
2906
|
+
|
|
2907
|
+
function gradientToCssAngle(
|
|
2908
|
+
start: { x: number; y: number },
|
|
2909
|
+
end: { x: number; y: number }
|
|
2910
|
+
) {
|
|
2911
|
+
const dx = end.x - start.x;
|
|
2912
|
+
const dy = end.y - start.y;
|
|
2913
|
+
const radians = Math.atan2(dy, dx);
|
|
2914
|
+
const degrees = (radians * 180) / Math.PI;
|
|
2915
|
+
return (degrees + 90 + 360) % 360;
|
|
2916
|
+
}
|
|
2917
|
+
|
|
2440
2918
|
type TextInputStrokeStyle = 'solid' | 'dashed' | 'dotted';
|
|
2441
2919
|
|
|
2442
2920
|
type NormalizedTextInputStroke = {
|
|
@@ -3590,11 +4068,20 @@ function FakeProgressBar({
|
|
|
3590
4068
|
}
|
|
3591
4069
|
|
|
3592
4070
|
return (
|
|
3593
|
-
<View
|
|
4071
|
+
<View
|
|
4072
|
+
style={{
|
|
4073
|
+
width: '100%',
|
|
4074
|
+
alignSelf: 'stretch',
|
|
4075
|
+
minWidth: 0,
|
|
4076
|
+
flexDirection: 'column',
|
|
4077
|
+
}}
|
|
4078
|
+
>
|
|
3594
4079
|
<View
|
|
3595
4080
|
style={{
|
|
3596
4081
|
height: thickness,
|
|
3597
4082
|
width: '100%',
|
|
4083
|
+
alignSelf: 'stretch',
|
|
4084
|
+
minWidth: 0,
|
|
3598
4085
|
borderRadius,
|
|
3599
4086
|
backgroundColor,
|
|
3600
4087
|
overflow: 'hidden',
|
|
@@ -3613,7 +4100,12 @@ function FakeProgressBar({
|
|
|
3613
4100
|
/>
|
|
3614
4101
|
</View>
|
|
3615
4102
|
{showProgressText ? (
|
|
3616
|
-
<Text
|
|
4103
|
+
<Text
|
|
4104
|
+
style={[
|
|
4105
|
+
progressTextStyle,
|
|
4106
|
+
{ marginTop: 8, width: '100%', alignSelf: 'stretch' },
|
|
4107
|
+
]}
|
|
4108
|
+
>
|
|
3617
4109
|
{progressText}
|
|
3618
4110
|
</Text>
|
|
3619
4111
|
) : null}
|
|
@@ -3650,16 +4142,36 @@ function resolveSliderSizeMode(
|
|
|
3650
4142
|
fallback: SliderSizeMode
|
|
3651
4143
|
): SliderSizeMode {
|
|
3652
4144
|
const raw = String(sliderProps?.size?.[key] ?? '').toLowerCase();
|
|
3653
|
-
if (
|
|
4145
|
+
if (
|
|
4146
|
+
raw === 'fill' ||
|
|
4147
|
+
raw === 'infinity' ||
|
|
4148
|
+
raw === 'infinite' ||
|
|
4149
|
+
raw === 'double.infinity' ||
|
|
4150
|
+
raw === '+infinity'
|
|
4151
|
+
) {
|
|
4152
|
+
return 'fill';
|
|
4153
|
+
}
|
|
4154
|
+
if (raw === 'fit' || raw === 'fixed') return raw;
|
|
4155
|
+
if (isFillDimensionValue(sliderProps?.[key])) return 'fill';
|
|
3654
4156
|
return sliderProps?.[key] !== undefined ? 'fixed' : fallback;
|
|
3655
4157
|
}
|
|
3656
4158
|
|
|
3657
|
-
function
|
|
3658
|
-
|
|
4159
|
+
function resolvePageSizeMode(
|
|
4160
|
+
pageProps: Record<string, any>,
|
|
3659
4161
|
key: 'width' | 'height'
|
|
3660
4162
|
): SliderSizeMode {
|
|
3661
|
-
const raw = String(
|
|
3662
|
-
if (
|
|
4163
|
+
const raw = String(pageProps?.size?.[key] ?? '').toLowerCase();
|
|
4164
|
+
if (
|
|
4165
|
+
raw === 'fill' ||
|
|
4166
|
+
raw === 'infinity' ||
|
|
4167
|
+
raw === 'infinite' ||
|
|
4168
|
+
raw === 'double.infinity' ||
|
|
4169
|
+
raw === '+infinity'
|
|
4170
|
+
) {
|
|
4171
|
+
return 'fill';
|
|
4172
|
+
}
|
|
4173
|
+
if (raw === 'fit' || raw === 'fixed') return raw;
|
|
4174
|
+
if (isFillDimensionValue(pageProps?.[key])) return 'fill';
|
|
3663
4175
|
return key === 'width' ? 'fill' : 'fit';
|
|
3664
4176
|
}
|
|
3665
4177
|
|
|
@@ -3678,24 +4190,31 @@ export function resolveSliderContainerSize(args: {
|
|
|
3678
4190
|
heightMode: SliderSizeMode;
|
|
3679
4191
|
availableWidth: number;
|
|
3680
4192
|
availableHeight: number;
|
|
3681
|
-
|
|
3682
|
-
|
|
4193
|
+
activePageWidth: number;
|
|
4194
|
+
activePageHeight: number;
|
|
3683
4195
|
fixedWidth: number | null;
|
|
3684
4196
|
fixedHeight: number | null;
|
|
3685
4197
|
leadingPeek: number;
|
|
3686
4198
|
trailingPeek: number;
|
|
3687
4199
|
showDots: boolean;
|
|
4200
|
+
dotsTopInset?: number;
|
|
4201
|
+
dotsBottomInset?: number;
|
|
3688
4202
|
}): {
|
|
3689
4203
|
outerWidth: number | string;
|
|
3690
4204
|
outerHeight: number | string;
|
|
3691
4205
|
viewportWidth: number;
|
|
3692
4206
|
viewportHeight: number;
|
|
3693
4207
|
} {
|
|
3694
|
-
const
|
|
4208
|
+
const dotsTopInset = Math.max(0, args.dotsTopInset ?? 0);
|
|
4209
|
+
const dotsBottomInset = Math.max(
|
|
4210
|
+
0,
|
|
4211
|
+
args.dotsBottomInset ?? (args.showDots ? 18 : 0)
|
|
4212
|
+
);
|
|
4213
|
+
const dotsInset = dotsTopInset + dotsBottomInset;
|
|
3695
4214
|
const fitPrimary =
|
|
3696
4215
|
args.direction === 'horizontal'
|
|
3697
|
-
? args.
|
|
3698
|
-
: args.
|
|
4216
|
+
? args.activePageWidth
|
|
4217
|
+
: args.activePageHeight;
|
|
3699
4218
|
const fitViewportPrimary = Math.max(
|
|
3700
4219
|
1,
|
|
3701
4220
|
fitPrimary + args.leadingPeek + args.trailingPeek
|
|
@@ -3704,11 +4223,11 @@ export function resolveSliderContainerSize(args: {
|
|
|
3704
4223
|
const viewportWidth =
|
|
3705
4224
|
args.direction === 'horizontal'
|
|
3706
4225
|
? fitViewportPrimary
|
|
3707
|
-
: Math.max(1, args.
|
|
4226
|
+
: Math.max(1, args.activePageWidth);
|
|
3708
4227
|
const viewportHeight =
|
|
3709
4228
|
args.direction === 'vertical'
|
|
3710
4229
|
? fitViewportPrimary
|
|
3711
|
-
: Math.max(1, args.
|
|
4230
|
+
: Math.max(1, args.activePageHeight);
|
|
3712
4231
|
|
|
3713
4232
|
const fitOuterWidth = Math.min(
|
|
3714
4233
|
Math.max(1, viewportWidth),
|
|
@@ -3743,23 +4262,47 @@ export function resolveSliderContainerSize(args: {
|
|
|
3743
4262
|
};
|
|
3744
4263
|
}
|
|
3745
4264
|
|
|
3746
|
-
|
|
3747
|
-
|
|
4265
|
+
function resolvePageFixedSizeHint(pageProps: Record<string, any>): {
|
|
4266
|
+
width: number | null;
|
|
4267
|
+
height: number | null;
|
|
4268
|
+
} {
|
|
4269
|
+
const widthMode = resolvePageSizeMode(pageProps, 'width');
|
|
4270
|
+
const heightMode = resolvePageSizeMode(pageProps, 'height');
|
|
4271
|
+
const width =
|
|
4272
|
+
widthMode === 'fixed'
|
|
4273
|
+
? toFinite(
|
|
4274
|
+
parseLayoutDimension(pageProps.width ?? pageProps.size?.widthPx, true)
|
|
4275
|
+
)
|
|
4276
|
+
: null;
|
|
4277
|
+
const height =
|
|
4278
|
+
heightMode === 'fixed'
|
|
4279
|
+
? toFinite(
|
|
4280
|
+
parseLayoutDimension(
|
|
4281
|
+
pageProps.height ?? pageProps.size?.heightPx,
|
|
4282
|
+
true
|
|
4283
|
+
)
|
|
4284
|
+
)
|
|
4285
|
+
: null;
|
|
4286
|
+
return { width, height };
|
|
4287
|
+
}
|
|
4288
|
+
|
|
4289
|
+
export function resolvePageSize(args: {
|
|
4290
|
+
pageProps: Record<string, any>;
|
|
3748
4291
|
direction: 'horizontal' | 'vertical';
|
|
3749
4292
|
pagePrimary: number;
|
|
3750
4293
|
crossSize: number | string;
|
|
3751
4294
|
}): { width: number | string; height: number | string } {
|
|
3752
|
-
const widthMode =
|
|
3753
|
-
const heightMode =
|
|
4295
|
+
const widthMode = resolvePageSizeMode(args.pageProps, 'width');
|
|
4296
|
+
const heightMode = resolvePageSizeMode(args.pageProps, 'height');
|
|
3754
4297
|
const fixedWidth = toFinite(
|
|
3755
4298
|
parseLayoutDimension(
|
|
3756
|
-
args.
|
|
4299
|
+
args.pageProps.width ?? args.pageProps.size?.widthPx,
|
|
3757
4300
|
true
|
|
3758
4301
|
)
|
|
3759
4302
|
);
|
|
3760
4303
|
const fixedHeight = toFinite(
|
|
3761
4304
|
parseLayoutDimension(
|
|
3762
|
-
args.
|
|
4305
|
+
args.pageProps.height ?? args.pageProps.size?.heightPx,
|
|
3763
4306
|
true
|
|
3764
4307
|
)
|
|
3765
4308
|
);
|
|
@@ -3918,7 +4461,7 @@ function resolveSliderBackgroundColor(
|
|
|
3918
4461
|
function SliderWidget({
|
|
3919
4462
|
id,
|
|
3920
4463
|
sliderProps,
|
|
3921
|
-
|
|
4464
|
+
pagePropsList,
|
|
3922
4465
|
direction,
|
|
3923
4466
|
pageAlignment,
|
|
3924
4467
|
pageSpacing,
|
|
@@ -3932,7 +4475,7 @@ function SliderWidget({
|
|
|
3932
4475
|
}: {
|
|
3933
4476
|
id?: string;
|
|
3934
4477
|
sliderProps: Record<string, any>;
|
|
3935
|
-
|
|
4478
|
+
pagePropsList: Record<string, any>[];
|
|
3936
4479
|
direction: 'horizontal' | 'vertical';
|
|
3937
4480
|
pageAlignment: 'start' | 'center' | 'end';
|
|
3938
4481
|
pageSpacing: number;
|
|
@@ -3956,7 +4499,10 @@ function SliderWidget({
|
|
|
3956
4499
|
width: 1,
|
|
3957
4500
|
height: 1,
|
|
3958
4501
|
});
|
|
3959
|
-
const [
|
|
4502
|
+
const [measuredPageSizes, setMeasuredPageSizes] = React.useState<
|
|
4503
|
+
Record<number, { width: number; height: number }>
|
|
4504
|
+
>({});
|
|
4505
|
+
const [activePageSize, setActivePageSize] = React.useState({
|
|
3960
4506
|
width: 1,
|
|
3961
4507
|
height: 1,
|
|
3962
4508
|
});
|
|
@@ -3973,6 +4519,22 @@ function SliderWidget({
|
|
|
3973
4519
|
setCurrentPage((prev) => clampSliderPage(prev, pageCount));
|
|
3974
4520
|
}, [pageCount]);
|
|
3975
4521
|
|
|
4522
|
+
React.useEffect(() => {
|
|
4523
|
+
setMeasuredPageSizes((prev) => {
|
|
4524
|
+
const next: Record<number, { width: number; height: number }> = {};
|
|
4525
|
+
Object.keys(prev).forEach((key) => {
|
|
4526
|
+
const index = Number(key);
|
|
4527
|
+
if (Number.isFinite(index) && index < pageCount) {
|
|
4528
|
+
const cached = prev[index];
|
|
4529
|
+
if (cached) next[index] = cached;
|
|
4530
|
+
}
|
|
4531
|
+
});
|
|
4532
|
+
return Object.keys(next).length === Object.keys(prev).length
|
|
4533
|
+
? prev
|
|
4534
|
+
: next;
|
|
4535
|
+
});
|
|
4536
|
+
}, [pageCount]);
|
|
4537
|
+
|
|
3976
4538
|
React.useEffect(() => {
|
|
3977
4539
|
if (autoAdvance && !isInteracting && pageLength > 1) {
|
|
3978
4540
|
timerRef.current = setInterval(() => {
|
|
@@ -4002,6 +4564,30 @@ function SliderWidget({
|
|
|
4002
4564
|
}
|
|
4003
4565
|
}, [currentPage, pageCount]);
|
|
4004
4566
|
|
|
4567
|
+
const syncActivePageSize = React.useCallback(
|
|
4568
|
+
(page: number) => {
|
|
4569
|
+
if (pageCount <= 0) return;
|
|
4570
|
+
const safePage = clampSliderPage(page, pageCount);
|
|
4571
|
+
const measured = measuredPageSizes[safePage];
|
|
4572
|
+
const hint = resolvePageFixedSizeHint(pagePropsList[safePage] ?? {});
|
|
4573
|
+
setActivePageSize((prev) => {
|
|
4574
|
+
const next = {
|
|
4575
|
+
width: Math.max(1, hint.width ?? measured?.width ?? prev.width),
|
|
4576
|
+
height: Math.max(1, hint.height ?? measured?.height ?? prev.height),
|
|
4577
|
+
};
|
|
4578
|
+
return prev.width === next.width && prev.height === next.height
|
|
4579
|
+
? prev
|
|
4580
|
+
: next;
|
|
4581
|
+
});
|
|
4582
|
+
},
|
|
4583
|
+
[measuredPageSizes, pageCount, pagePropsList]
|
|
4584
|
+
);
|
|
4585
|
+
|
|
4586
|
+
React.useEffect(() => {
|
|
4587
|
+
if (pageCount <= 0) return;
|
|
4588
|
+
syncActivePageSize(currentPage);
|
|
4589
|
+
}, [currentPage, pageCount, syncActivePageSize]);
|
|
4590
|
+
|
|
4005
4591
|
React.useEffect(() => {
|
|
4006
4592
|
if (registry && id && children.length > 0) {
|
|
4007
4593
|
registry.update(id, currentPage % children.length, children.length);
|
|
@@ -4015,10 +4601,12 @@ function SliderWidget({
|
|
|
4015
4601
|
// 2) trailingPeek = end?0 : start?pagePeek*2 : pagePeek
|
|
4016
4602
|
// 3) pagePrimary = viewportPrimary - leadingPeek - trailingPeek (clamped >= 1)
|
|
4017
4603
|
// 4) track offset = activeIndex * (pagePrimary + pageSpacing)
|
|
4018
|
-
// 5) fit parent mode follows active
|
|
4604
|
+
// 5) fit parent mode follows active page size but is clamped to container width
|
|
4019
4605
|
// 6) viewport always clips track content (no root horizontal overflow)
|
|
4020
4606
|
const { leading, trailing } = resolvePeekInsets(pageAlignment, pagePeek);
|
|
4021
4607
|
const showDots = pageControlEnabled && pageLength > 1;
|
|
4608
|
+
const dotsTopInset = showDots && pageControlPosition === 'top' ? 18 : 0;
|
|
4609
|
+
const dotsBottomInset = showDots && pageControlPosition !== 'top' ? 18 : 0;
|
|
4022
4610
|
const widthMode = resolveSliderSizeMode(sliderProps, 'width', 'fill');
|
|
4023
4611
|
const heightMode = resolveSliderSizeMode(sliderProps, 'height', 'fit');
|
|
4024
4612
|
const fixedWidth = toFinite(parseLayoutDimension(sliderProps.width, true));
|
|
@@ -4030,14 +4618,17 @@ function SliderWidget({
|
|
|
4030
4618
|
heightMode,
|
|
4031
4619
|
availableWidth: availableSize.width,
|
|
4032
4620
|
availableHeight: availableSize.height,
|
|
4033
|
-
|
|
4034
|
-
|
|
4621
|
+
activePageWidth: activePageSize.width,
|
|
4622
|
+
activePageHeight: activePageSize.height,
|
|
4035
4623
|
fixedWidth,
|
|
4036
4624
|
fixedHeight,
|
|
4037
4625
|
leadingPeek: leading,
|
|
4038
4626
|
trailingPeek: trailing,
|
|
4039
4627
|
showDots,
|
|
4628
|
+
dotsTopInset,
|
|
4629
|
+
dotsBottomInset,
|
|
4040
4630
|
});
|
|
4631
|
+
const sliderFillSizeStyle = resolveFillSizeStyle(sliderProps.size);
|
|
4041
4632
|
const viewportPrimary =
|
|
4042
4633
|
direction === 'horizontal'
|
|
4043
4634
|
? resolved.viewportWidth
|
|
@@ -4069,63 +4660,50 @@ function SliderWidget({
|
|
|
4069
4660
|
style={{
|
|
4070
4661
|
width: resolved.outerWidth,
|
|
4071
4662
|
height: resolved.outerHeight,
|
|
4072
|
-
minWidth:
|
|
4073
|
-
minHeight:
|
|
4663
|
+
minWidth: 0,
|
|
4664
|
+
minHeight: 0,
|
|
4665
|
+
display: 'flex',
|
|
4666
|
+
flexDirection: 'column',
|
|
4667
|
+
...sliderFillSizeStyle,
|
|
4074
4668
|
backgroundColor: sliderBackgroundColor,
|
|
4075
4669
|
borderRadius: 0,
|
|
4076
|
-
overflow: '
|
|
4670
|
+
overflow: 'hidden',
|
|
4671
|
+
position: 'relative',
|
|
4077
4672
|
}}
|
|
4078
4673
|
>
|
|
4079
|
-
{showDots && pageControlPosition === 'top' ? (
|
|
4080
|
-
<View
|
|
4081
|
-
style={{
|
|
4082
|
-
marginBottom: 8,
|
|
4083
|
-
flexDirection: 'row',
|
|
4084
|
-
justifyContent: 'center',
|
|
4085
|
-
alignItems: 'center',
|
|
4086
|
-
}}
|
|
4087
|
-
>
|
|
4088
|
-
{Array.from({ length: pageLength }).map((_, index) => {
|
|
4089
|
-
const isActive = index === currentPage;
|
|
4090
|
-
return (
|
|
4091
|
-
<View
|
|
4092
|
-
key={`slider-dot-top-${index}`}
|
|
4093
|
-
style={{
|
|
4094
|
-
width: 6,
|
|
4095
|
-
height: 6,
|
|
4096
|
-
borderRadius: 3,
|
|
4097
|
-
marginHorizontal: 3,
|
|
4098
|
-
backgroundColor: isActive
|
|
4099
|
-
? parseColor('0xFF111827')
|
|
4100
|
-
: parseColor('0xFFD1D5DB'),
|
|
4101
|
-
}}
|
|
4102
|
-
/>
|
|
4103
|
-
);
|
|
4104
|
-
})}
|
|
4105
|
-
</View>
|
|
4106
|
-
) : null}
|
|
4107
|
-
|
|
4108
4674
|
<View
|
|
4109
4675
|
style={{
|
|
4110
4676
|
overflow: 'hidden',
|
|
4677
|
+
width: widthMode === 'fit' ? resolved.viewportWidth : '100%',
|
|
4678
|
+
height: heightMode === 'fit' ? resolved.viewportHeight : '100%',
|
|
4679
|
+
minWidth: 0,
|
|
4680
|
+
minHeight: 0,
|
|
4681
|
+
display: 'flex',
|
|
4682
|
+
flexDirection: 'column',
|
|
4683
|
+
flexGrow: heightMode === 'fit' ? 0 : 1,
|
|
4684
|
+
flexShrink: 1,
|
|
4111
4685
|
...(direction === 'horizontal'
|
|
4112
4686
|
? {
|
|
4113
|
-
width: resolved.viewportWidth,
|
|
4114
|
-
height: resolved.viewportHeight,
|
|
4115
4687
|
paddingLeft: leading,
|
|
4116
4688
|
paddingRight: trailing,
|
|
4689
|
+
paddingTop: dotsTopInset,
|
|
4690
|
+
paddingBottom: dotsBottomInset,
|
|
4117
4691
|
}
|
|
4118
4692
|
: {
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
paddingTop: leading,
|
|
4122
|
-
paddingBottom: trailing,
|
|
4693
|
+
paddingTop: leading + dotsTopInset,
|
|
4694
|
+
paddingBottom: trailing + dotsBottomInset,
|
|
4123
4695
|
}),
|
|
4124
4696
|
}}
|
|
4125
4697
|
>
|
|
4126
4698
|
<PagerView
|
|
4127
4699
|
ref={pagerRef}
|
|
4128
|
-
style={{
|
|
4700
|
+
style={{
|
|
4701
|
+
width: '100%',
|
|
4702
|
+
height: '100%',
|
|
4703
|
+
minWidth: 0,
|
|
4704
|
+
minHeight: 0,
|
|
4705
|
+
flex: 1,
|
|
4706
|
+
}}
|
|
4129
4707
|
orientation={direction}
|
|
4130
4708
|
pageMargin={Math.max(0, pageSpacing)}
|
|
4131
4709
|
initialPage={clampSliderPage(currentPage, pageCount)}
|
|
@@ -4133,6 +4711,7 @@ function SliderWidget({
|
|
|
4133
4711
|
const page = event.nativeEvent.position;
|
|
4134
4712
|
const safePage = clampSliderPage(page, pageCount);
|
|
4135
4713
|
setCurrentPage(safePage);
|
|
4714
|
+
syncActivePageSize(safePage);
|
|
4136
4715
|
markInteracting();
|
|
4137
4716
|
if (registry && id) {
|
|
4138
4717
|
registry.update(id, safePage % pageLength, pageLength);
|
|
@@ -4142,56 +4721,115 @@ function SliderWidget({
|
|
|
4142
4721
|
{Array.from({ length: pageCount }).map((_, index) => (
|
|
4143
4722
|
<View
|
|
4144
4723
|
key={`slider-page-${index}`}
|
|
4145
|
-
onLayout={
|
|
4146
|
-
index === currentPage
|
|
4147
|
-
? (event) => {
|
|
4148
|
-
const nextWidth = Math.max(
|
|
4149
|
-
1,
|
|
4150
|
-
Math.round(event.nativeEvent.layout.width)
|
|
4151
|
-
);
|
|
4152
|
-
const nextHeight = Math.max(
|
|
4153
|
-
1,
|
|
4154
|
-
Math.round(event.nativeEvent.layout.height)
|
|
4155
|
-
);
|
|
4156
|
-
setActiveSlideSize((prev) =>
|
|
4157
|
-
prev.width === nextWidth && prev.height === nextHeight
|
|
4158
|
-
? prev
|
|
4159
|
-
: { width: nextWidth, height: nextHeight }
|
|
4160
|
-
);
|
|
4161
|
-
}
|
|
4162
|
-
: undefined
|
|
4163
|
-
}
|
|
4164
4724
|
style={{
|
|
4165
|
-
|
|
4725
|
+
width: '100%',
|
|
4726
|
+
height: '100%',
|
|
4166
4727
|
overflow: 'hidden',
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
crossSize: '100%',
|
|
4172
|
-
}) as ViewStyle),
|
|
4728
|
+
minWidth: 0,
|
|
4729
|
+
minHeight: 0,
|
|
4730
|
+
display: 'flex',
|
|
4731
|
+
flexDirection: 'column',
|
|
4173
4732
|
}}
|
|
4174
4733
|
>
|
|
4175
|
-
|
|
4734
|
+
<View
|
|
4735
|
+
onLayout={(event) => {
|
|
4736
|
+
const nextWidth = Math.max(
|
|
4737
|
+
1,
|
|
4738
|
+
Math.round(event.nativeEvent.layout.width)
|
|
4739
|
+
);
|
|
4740
|
+
const nextHeight = Math.max(
|
|
4741
|
+
1,
|
|
4742
|
+
Math.round(event.nativeEvent.layout.height)
|
|
4743
|
+
);
|
|
4744
|
+
|
|
4745
|
+
setMeasuredPageSizes((prev) => {
|
|
4746
|
+
const current = prev[index];
|
|
4747
|
+
if (
|
|
4748
|
+
current &&
|
|
4749
|
+
current.width === nextWidth &&
|
|
4750
|
+
current.height === nextHeight
|
|
4751
|
+
) {
|
|
4752
|
+
return prev;
|
|
4753
|
+
}
|
|
4754
|
+
return {
|
|
4755
|
+
...prev,
|
|
4756
|
+
[index]: { width: nextWidth, height: nextHeight },
|
|
4757
|
+
};
|
|
4758
|
+
});
|
|
4759
|
+
|
|
4760
|
+
if (index === currentPage) {
|
|
4761
|
+
setActivePageSize((prev) =>
|
|
4762
|
+
prev.width === nextWidth && prev.height === nextHeight
|
|
4763
|
+
? prev
|
|
4764
|
+
: { width: nextWidth, height: nextHeight }
|
|
4765
|
+
);
|
|
4766
|
+
}
|
|
4767
|
+
}}
|
|
4768
|
+
style={{
|
|
4769
|
+
alignSelf: (
|
|
4770
|
+
direction === 'horizontal'
|
|
4771
|
+
? resolvePageSizeMode(
|
|
4772
|
+
pagePropsList[index] ?? {},
|
|
4773
|
+
'height'
|
|
4774
|
+
) === 'fill'
|
|
4775
|
+
: resolvePageSizeMode(
|
|
4776
|
+
pagePropsList[index] ?? {},
|
|
4777
|
+
'width'
|
|
4778
|
+
) === 'fill'
|
|
4779
|
+
)
|
|
4780
|
+
? 'stretch'
|
|
4781
|
+
: 'flex-start',
|
|
4782
|
+
overflow: 'hidden',
|
|
4783
|
+
minWidth: 0,
|
|
4784
|
+
minHeight: 0,
|
|
4785
|
+
display: 'flex',
|
|
4786
|
+
flexDirection: 'column',
|
|
4787
|
+
...(resolvePageSize({
|
|
4788
|
+
pageProps: pagePropsList[index] ?? {},
|
|
4789
|
+
direction,
|
|
4790
|
+
pagePrimary,
|
|
4791
|
+
crossSize: '100%',
|
|
4792
|
+
}) as ViewStyle),
|
|
4793
|
+
}}
|
|
4794
|
+
>
|
|
4795
|
+
<View
|
|
4796
|
+
style={{
|
|
4797
|
+
width: '100%',
|
|
4798
|
+
height: '100%',
|
|
4799
|
+
minWidth: 0,
|
|
4800
|
+
minHeight: 0,
|
|
4801
|
+
display: 'flex',
|
|
4802
|
+
flexDirection: 'column',
|
|
4803
|
+
flexGrow: 1,
|
|
4804
|
+
}}
|
|
4805
|
+
>
|
|
4806
|
+
{children[index % pageLength]}
|
|
4807
|
+
</View>
|
|
4808
|
+
</View>
|
|
4176
4809
|
</View>
|
|
4177
4810
|
))}
|
|
4178
4811
|
</PagerView>
|
|
4179
4812
|
</View>
|
|
4180
4813
|
|
|
4181
|
-
{showDots
|
|
4814
|
+
{showDots ? (
|
|
4182
4815
|
<View
|
|
4183
4816
|
style={{
|
|
4184
|
-
|
|
4817
|
+
position: 'absolute',
|
|
4818
|
+
left: 0,
|
|
4819
|
+
right: 0,
|
|
4820
|
+
...(pageControlPosition === 'top' ? { top: 4 } : { bottom: 4 }),
|
|
4821
|
+
height: 12,
|
|
4185
4822
|
flexDirection: 'row',
|
|
4186
4823
|
justifyContent: 'center',
|
|
4187
4824
|
alignItems: 'center',
|
|
4825
|
+
pointerEvents: 'none',
|
|
4188
4826
|
}}
|
|
4189
4827
|
>
|
|
4190
4828
|
{Array.from({ length: pageLength }).map((_, index) => {
|
|
4191
4829
|
const isActive = index === currentPage;
|
|
4192
4830
|
return (
|
|
4193
4831
|
<View
|
|
4194
|
-
key={`slider-dot
|
|
4832
|
+
key={`slider-dot-${index}`}
|
|
4195
4833
|
style={{
|
|
4196
4834
|
width: 6,
|
|
4197
4835
|
height: 6,
|
|
@@ -4235,12 +4873,16 @@ function PageViewIndicator({
|
|
|
4235
4873
|
}) {
|
|
4236
4874
|
const registry = useSliderRegistry();
|
|
4237
4875
|
const [current, setCurrent] = React.useState(0);
|
|
4876
|
+
const [count, setCount] = React.useState(0);
|
|
4238
4877
|
const indicatorStyle = style ?? 'dots';
|
|
4239
|
-
const count = linkedTo && registry ? registry.getPageCount(linkedTo) : 0;
|
|
4240
4878
|
|
|
4241
4879
|
React.useEffect(() => {
|
|
4242
4880
|
if (!linkedTo || !registry) return;
|
|
4243
|
-
|
|
4881
|
+
setCount(registry.getPageCount(linkedTo));
|
|
4882
|
+
return registry.getNotifier(linkedTo, (page) => {
|
|
4883
|
+
setCurrent(page);
|
|
4884
|
+
setCount(registry.getPageCount(linkedTo));
|
|
4885
|
+
});
|
|
4244
4886
|
}, [linkedTo, registry]);
|
|
4245
4887
|
|
|
4246
4888
|
if (!linkedTo || !registry || count <= 1) return null;
|