@applicaster/zapp-react-native-ui-components 16.0.0-alpha.9235607812 → 16.0.0-alpha.9338540010
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/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +12 -3
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/ActionButton.tsx +1 -1
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/PressableView.test.tsx +4 -2
- package/Components/MasterCell/SharedUI/CollapsibleTextContainer/__tests__/factory.test.ts +2 -2
- package/Components/MasterCell/SharedUI/CollapsibleTextContainer/index.ts +2 -1
- package/Components/MasterCell/__tests__/masterCell.test.js +60 -0
- package/Components/MasterCell/index.tsx +28 -4
- package/Components/MasterCell/utils/__tests__/withoutFocusedState.test.js +20 -0
- package/Components/MasterCell/utils/index.ts +18 -0
- package/Components/ModalComponent/AudioPlayer/Components/Item.tsx +22 -1
- package/Components/ModalComponent/AudioPlayer/Components/__tests__/Item.test.tsx +52 -1
- package/Components/ModalComponent/AudioPlayer/utils/__tests__/mergeStyles.test.ts +77 -0
- package/Components/ModalComponent/AudioPlayer/utils/mergeStyles.ts +58 -16
- package/Components/ModalComponent/BottomSheetModalContent.tsx +9 -74
- package/Components/ModalComponent/__tests__/showTextInput.test.ts +25 -0
- package/Components/ModalComponent/showTextInput.ts +2 -0
- package/Components/River/ComponentsMap/ComponentsMap.tsx +4 -1
- package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +2 -0
- package/Components/Transitioner/AnimationManager.js +1 -1
- package/Components/Transitioner/Scene.tsx +4 -3
- package/Components/Transitioner/Transitioner.tsx +69 -30
- package/Components/Transitioner/__tests__/transitioner.test.js +308 -20
- package/Components/VideoModal/PlayerWrapper.tsx +4 -1
- package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +1 -0
- package/Components/VideoModal/__tests__/playerWrapperStyle.test.ts +58 -0
- package/Components/VideoModal/__tests__/playerWrapperUtils.test.ts +16 -0
- package/Components/VideoModal/playerWrapperStyle.ts +21 -3
- package/Components/VideoModal/playerWrapperUtils.ts +7 -1
- package/Decorators/ZappPipesDataConnector/__tests__/screenToolbox.test.ts +347 -0
- package/Decorators/ZappPipesDataConnector/__tests__/screenToolboxUnavailable.test.ts +53 -0
- package/Decorators/ZappPipesDataConnector/index.tsx +19 -4
- package/Decorators/ZappPipesDataConnector/screenToolbox.ts +384 -0
- package/package.json +5 -5
|
@@ -245,10 +245,19 @@ const LiveImageComponent = (props: LiveImageProps) => {
|
|
|
245
245
|
|
|
246
246
|
const isCurrentlyFocused = useTrackCurrentAutoScrollingElement(componentId);
|
|
247
247
|
|
|
248
|
-
|
|
249
|
-
|
|
248
|
+
// New object identities here restart PlayerLiveImage's register effect,
|
|
249
|
+
// which throws away the in-flight player and runs preload hooks again.
|
|
250
|
+
const playerConfig = React.useMemo(
|
|
251
|
+
() => getPlayerConfig(player_screen_id, actionIdentifier),
|
|
252
|
+
[player_screen_id, actionIdentifier]
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
const { playerPluginId, screenConfig, preloadHooks } = playerConfig ?? {};
|
|
250
256
|
|
|
251
|
-
const playableEntry =
|
|
257
|
+
const playableEntry = React.useMemo(
|
|
258
|
+
() => (playerPluginId ? getPlayableEntry(item) : null),
|
|
259
|
+
[playerPluginId, item?.id]
|
|
260
|
+
);
|
|
252
261
|
|
|
253
262
|
const playerPlugin = getPluginByIdentifier(playerPluginId);
|
|
254
263
|
|
|
@@ -166,7 +166,7 @@ export const ActionButton = ({
|
|
|
166
166
|
<PressableView
|
|
167
167
|
testOnly_pressed={props.testOnly_pressed}
|
|
168
168
|
testID={props.testID || `${entry?.id}`}
|
|
169
|
-
style={style}
|
|
169
|
+
style={[style, props.alignment]}
|
|
170
170
|
pressedStyle={focusedStyles}
|
|
171
171
|
onPress={renderProps.onPress}
|
|
172
172
|
accessibilityLabel={props.accessibilityLabel || `${entry?.id}`}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { Pressable, Text as RNText, View } from "react-native";
|
|
2
|
+
import { Pressable, StyleSheet, Text as RNText, View } from "react-native";
|
|
3
3
|
import { render, fireEvent } from "@testing-library/react-native";
|
|
4
4
|
import { useActions } from "@applicaster/zapp-react-native-utils/reactHooks/actions";
|
|
5
5
|
|
|
@@ -282,7 +282,9 @@ describe("MobileActionButton", () => {
|
|
|
282
282
|
expect(pressable.props.testID).toBe("mobile_action_button_1");
|
|
283
283
|
expect(pressable.props.accessibilityLabel).toBe("entry-1");
|
|
284
284
|
|
|
285
|
-
expect(
|
|
285
|
+
expect(
|
|
286
|
+
StyleSheet.flatten(pressable.props.style({ pressed: false }))
|
|
287
|
+
).toMatchObject({
|
|
286
288
|
backgroundColor: "rgba(1,1,1,1)",
|
|
287
289
|
borderColor: "rgba(2,2,2,1)",
|
|
288
290
|
borderWidth: 1,
|
|
@@ -20,13 +20,13 @@ const getValue = (key: string) => {
|
|
|
20
20
|
};
|
|
21
21
|
|
|
22
22
|
describe("CollapsibleTextContainer view tree", () => {
|
|
23
|
-
it("
|
|
23
|
+
it("aligns the label to the text alignment instead of stretching the cell", () => {
|
|
24
24
|
const result = CollapsibleTextContainer({
|
|
25
25
|
getValue,
|
|
26
26
|
elements: [],
|
|
27
27
|
additionalStyles: { backgroundColor: "transparent" },
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
-
expect(result.style.alignSelf).toBe("
|
|
30
|
+
expect(result.style.alignSelf).toBe("flex-start");
|
|
31
31
|
});
|
|
32
32
|
});
|
|
@@ -3,6 +3,7 @@ import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/he
|
|
|
3
3
|
import {
|
|
4
4
|
getColorFromData,
|
|
5
5
|
getKeyForLabel,
|
|
6
|
+
mapSelfAlignment,
|
|
6
7
|
} from "@applicaster/zapp-react-native-utils/cellUtils";
|
|
7
8
|
import { toNumberWithDefaultZero } from "@applicaster/zapp-react-native-utils/numberUtils";
|
|
8
9
|
|
|
@@ -22,7 +23,7 @@ export const CollapsibleTextContainer = ({
|
|
|
22
23
|
return {
|
|
23
24
|
type: "CollapsibleTextContainer", // Collapsible Text Container,
|
|
24
25
|
style: {
|
|
25
|
-
alignSelf: "
|
|
26
|
+
alignSelf: mapSelfAlignment(getValue("text_alignment")),
|
|
26
27
|
marginTop: toNumberWithDefaultZero(getValue("margin_top")),
|
|
27
28
|
marginRight: toNumberWithDefaultZero(getValue("margin_right")),
|
|
28
29
|
marginBottom: toNumberWithDefaultZero(getValue("margin_bottom")),
|
|
@@ -76,6 +76,66 @@ describe("masterCellBuilder", () => {
|
|
|
76
76
|
});
|
|
77
77
|
});
|
|
78
78
|
|
|
79
|
+
describe("cellOptions.interactive", () => {
|
|
80
|
+
// renders a single View tagged with the state the cell was built with
|
|
81
|
+
const dataAdapter =
|
|
82
|
+
() =>
|
|
83
|
+
({ state }) => [
|
|
84
|
+
{ type: "View", style: {}, props: { testID: `state-${state}` } },
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
// Only the root View gets this testID: cell elements pass their own testID, which overrides it
|
|
88
|
+
const ROOT_TEST_ID = "master-cell-root";
|
|
89
|
+
|
|
90
|
+
// forwardRef: MasterCell attaches a ref to its root View
|
|
91
|
+
const TestView = React.forwardRef((props, ref) => (
|
|
92
|
+
<View testID={ROOT_TEST_ID} {...props} ref={ref} />
|
|
93
|
+
));
|
|
94
|
+
|
|
95
|
+
const renderCell = (cellOptions, state) => {
|
|
96
|
+
const MasterCell = masterCellBuilder({
|
|
97
|
+
cellConfiguration: singleView,
|
|
98
|
+
dataAdapter,
|
|
99
|
+
components: { View: TestView },
|
|
100
|
+
cellOptions,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
return renderWithProviders(<MasterCell item={entry} state={state} />);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const getRoot = (wrapper) => wrapper.getByTestId(ROOT_TEST_ID);
|
|
107
|
+
|
|
108
|
+
it("keeps focused state and default root props when omitted", () => {
|
|
109
|
+
const wrapper = renderCell(undefined, "focused");
|
|
110
|
+
const root = getRoot(wrapper);
|
|
111
|
+
|
|
112
|
+
expect(wrapper.getByTestId("state-focused")).toBeDefined();
|
|
113
|
+
expect(root.props.onStartShouldSetResponder).toBeUndefined();
|
|
114
|
+
expect(root.props.collapsable).toBeUndefined();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("claims body touches on a non-collapsable root when false", () => {
|
|
118
|
+
const wrapper = renderCell({ interactive: false }, "default");
|
|
119
|
+
const root = getRoot(wrapper);
|
|
120
|
+
|
|
121
|
+
expect(root.props.onStartShouldSetResponder()).toBe(true);
|
|
122
|
+
expect(root.props.collapsable).toBe(false);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("renders focused as default when false", () => {
|
|
126
|
+
const wrapper = renderCell({ interactive: false }, "focused");
|
|
127
|
+
|
|
128
|
+
expect(wrapper.getByTestId("state-default")).toBeDefined();
|
|
129
|
+
expect(wrapper.queryByTestId("state-focused")).toBeNull();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("renders focused_selected as selected when false", () => {
|
|
133
|
+
const wrapper = renderCell({ interactive: false }, "focused_selected");
|
|
134
|
+
|
|
135
|
+
expect(wrapper.getByTestId("state-selected")).toBeDefined();
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
79
139
|
describe.skip("when using custom default components", () => {
|
|
80
140
|
// the collapsable prop is added here to easily check from the snapshot that the mock
|
|
81
141
|
// component is being used instead of the default one
|
|
@@ -10,7 +10,11 @@ import { useScreenState } from "@applicaster/zapp-react-native-utils/screenState
|
|
|
10
10
|
import { defaultComponents } from "./DefaultComponents";
|
|
11
11
|
import { defaultDataAdapter } from "./dataAdapter";
|
|
12
12
|
import { elementMapper, mapElementWithKey } from "./elementMapper";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
getEntryState,
|
|
15
|
+
hasFocusableInsideBuilder,
|
|
16
|
+
withoutFocusedState,
|
|
17
|
+
} from "./utils";
|
|
14
18
|
|
|
15
19
|
type Props = {
|
|
16
20
|
item: ZappEntry;
|
|
@@ -19,6 +23,16 @@ type Props = {
|
|
|
19
23
|
|
|
20
24
|
const defaultContainerStyle = { flex: 1 };
|
|
21
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Root props of a non-interactive cell: the root view claims touches on the cell body,
|
|
28
|
+
* so the host list's touchable never fires (buttons, being deeper, still receive theirs).
|
|
29
|
+
* `collapsable: false` keeps Android / Fabric from flattening the view away.
|
|
30
|
+
*/
|
|
31
|
+
const nonInteractiveRootProps = {
|
|
32
|
+
onStartShouldSetResponder: () => true,
|
|
33
|
+
collapsable: false,
|
|
34
|
+
};
|
|
35
|
+
|
|
22
36
|
/**
|
|
23
37
|
* Constructs a MasterCell with the provided options
|
|
24
38
|
* @param {Object} options
|
|
@@ -27,6 +41,8 @@ const defaultContainerStyle = { flex: 1 };
|
|
|
27
41
|
* @param {Function} options.dataAdapter optional dataAdapter to use instead of the default one.
|
|
28
42
|
* See dataAdapter.js for more info
|
|
29
43
|
* @param {containerStyles} options.containerStyle style to apply at the top level of the master cell
|
|
44
|
+
* @param {Object} options.cellOptions optional cell options. `interactive: false` makes the cell body
|
|
45
|
+
* ignore taps and render without focused / pressed states (inner buttons keep working). Defaults to `true`.
|
|
30
46
|
* @returns {Function} Functional React Component to render the masterCell with the provided options
|
|
31
47
|
*/
|
|
32
48
|
|
|
@@ -39,6 +55,7 @@ type MasterCellBuilderOptions = {
|
|
|
39
55
|
isRTL?: boolean;
|
|
40
56
|
skipButtons?: boolean;
|
|
41
57
|
component?: ZappComponent;
|
|
58
|
+
interactive?: boolean;
|
|
42
59
|
};
|
|
43
60
|
};
|
|
44
61
|
|
|
@@ -49,7 +66,7 @@ export function masterCellBuilder({
|
|
|
49
66
|
containerStyle = defaultContainerStyle,
|
|
50
67
|
cellOptions = {},
|
|
51
68
|
}: MasterCellBuilderOptions) {
|
|
52
|
-
const { skipButtons = false, component } = cellOptions;
|
|
69
|
+
const { skipButtons = false, component, interactive = true } = cellOptions;
|
|
53
70
|
|
|
54
71
|
const componentsMap = R.merge(defaultComponents, components);
|
|
55
72
|
const { View } = componentsMap;
|
|
@@ -88,7 +105,10 @@ export function masterCellBuilder({
|
|
|
88
105
|
() =>
|
|
89
106
|
elementsBuilder({
|
|
90
107
|
entry: item,
|
|
91
|
-
state: getEntryState(
|
|
108
|
+
state: getEntryState(
|
|
109
|
+
interactive ? state : withoutFocusedState(state),
|
|
110
|
+
entryIsSelected
|
|
111
|
+
),
|
|
92
112
|
allowDynamicColorsOutsideExtensions:
|
|
93
113
|
theme?.allow_dynamic_colors_outside_extensions,
|
|
94
114
|
}),
|
|
@@ -134,7 +154,11 @@ export function masterCellBuilder({
|
|
|
134
154
|
);
|
|
135
155
|
|
|
136
156
|
return (
|
|
137
|
-
<View
|
|
157
|
+
<View
|
|
158
|
+
style={containerStyle}
|
|
159
|
+
ref={wrapperRef}
|
|
160
|
+
{...(interactive ? undefined : nonInteractiveRootProps)}
|
|
161
|
+
>
|
|
138
162
|
{elements.map(remapElements)}
|
|
139
163
|
</View>
|
|
140
164
|
);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { withoutFocusedState } from "..";
|
|
2
|
+
|
|
3
|
+
describe("withoutFocusedState", () => {
|
|
4
|
+
it("maps focused to default", () => {
|
|
5
|
+
expect(withoutFocusedState("focused")).toBe("default");
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
it("maps focused_selected to selected", () => {
|
|
9
|
+
expect(withoutFocusedState("focused_selected")).toBe("selected");
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("keeps default and selected states", () => {
|
|
13
|
+
expect(withoutFocusedState("default")).toBe("default");
|
|
14
|
+
expect(withoutFocusedState("selected")).toBe("selected");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("keeps a missing state", () => {
|
|
18
|
+
expect(withoutFocusedState(undefined)).toBeUndefined();
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -386,3 +386,21 @@ export function getEntryState(state, selected) {
|
|
|
386
386
|
|
|
387
387
|
return selected ? "selected" : "default";
|
|
388
388
|
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Drops focused (pressed) variants of a cell state, so a non-interactive cell
|
|
392
|
+
* always renders its idle look. `selected` is kept – it is not touch feedback.
|
|
393
|
+
*/
|
|
394
|
+
export function withoutFocusedState(
|
|
395
|
+
state?: CellState | null
|
|
396
|
+
): CellState | null | undefined {
|
|
397
|
+
if (state === "focused_selected") {
|
|
398
|
+
return "selected";
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (state === "focused") {
|
|
402
|
+
return "default";
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return state;
|
|
406
|
+
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Green (configurable): background, image, nowPlayingAsset, nowPlayingLabel,
|
|
7
7
|
* textLabel1, textLabel2
|
|
8
8
|
* White (hardcoded): container padding/gutters, nowPlaying container layout,
|
|
9
|
-
* button sizes/padding/assets
|
|
9
|
+
* button sizes/padding. Play/pause icons use plugin assets when set.
|
|
10
10
|
*
|
|
11
11
|
* Variants (VARIANT_*) are preset configuration bundles — not passed as a field.
|
|
12
12
|
*/
|
|
@@ -94,6 +94,10 @@ export type ActionButtonAssetsConfiguration = {
|
|
|
94
94
|
removeAsset?: string;
|
|
95
95
|
/** Trailing drag handle on Edit Order / queue rows. */
|
|
96
96
|
dragAsset?: string;
|
|
97
|
+
/** Shown on the Now Playing row while the track is paused. */
|
|
98
|
+
playAsset?: string;
|
|
99
|
+
/** Shown on the Now Playing row while the track is playing. */
|
|
100
|
+
pauseAsset?: string;
|
|
97
101
|
};
|
|
98
102
|
|
|
99
103
|
export type ItemConfiguration = {
|
|
@@ -504,6 +508,23 @@ function ItemButton({
|
|
|
504
508
|
const renderIcon = () => {
|
|
505
509
|
switch (action) {
|
|
506
510
|
case "playPause": {
|
|
511
|
+
const playbackAsset = isSelected
|
|
512
|
+
? actionButtonAssets?.pauseAsset
|
|
513
|
+
: actionButtonAssets?.playAsset;
|
|
514
|
+
|
|
515
|
+
if (playbackAsset) {
|
|
516
|
+
return (
|
|
517
|
+
<Image
|
|
518
|
+
source={{ uri: playbackAsset }}
|
|
519
|
+
style={{
|
|
520
|
+
width: layout.assetWidth,
|
|
521
|
+
height: layout.assetHeight,
|
|
522
|
+
}}
|
|
523
|
+
resizeMode="contain"
|
|
524
|
+
/>
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
|
|
507
528
|
const iconSize = layout.assetWidth;
|
|
508
529
|
const iconRadius = iconSize / 2;
|
|
509
530
|
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { Pressable } from "react-native";
|
|
2
|
+
import { Image, Pressable } from "react-native";
|
|
3
3
|
import { fireEvent, render, screen } from "@testing-library/react-native";
|
|
4
|
+
import { ModalBlocksStyleContext } from "../../../ModalBlocksStyleContext";
|
|
4
5
|
import {
|
|
5
6
|
AudioPlayerTrackItem,
|
|
7
|
+
VARIANT_NOW_PLAYING,
|
|
6
8
|
VARIANT_PLAYLIST_ITEM,
|
|
7
9
|
mapMenuItemToNowPlayingData,
|
|
8
10
|
} from "../Item";
|
|
@@ -101,6 +103,55 @@ describe("mapMenuItemToNowPlayingData", () => {
|
|
|
101
103
|
expect(data.textLabel2).toBe("Artist Name");
|
|
102
104
|
});
|
|
103
105
|
|
|
106
|
+
it("uses the plugin play asset while paused and the pause asset while playing", () => {
|
|
107
|
+
const menuItem = {
|
|
108
|
+
id: "item_1",
|
|
109
|
+
title: "Track Title",
|
|
110
|
+
summary: "Artist Name",
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const style = {
|
|
114
|
+
nowPlayingItem: {
|
|
115
|
+
actionButtons: {
|
|
116
|
+
default: {
|
|
117
|
+
playAsset: "https://example.com/play.png",
|
|
118
|
+
pauseAsset: "https://example.com/pause.png",
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const { UNSAFE_getAllByType, rerender } = render(
|
|
125
|
+
<ModalBlocksStyleContext.Provider value={style}>
|
|
126
|
+
<AudioPlayerTrackItem
|
|
127
|
+
configuration={VARIANT_NOW_PLAYING}
|
|
128
|
+
data={mapMenuItemToNowPlayingData(menuItem as any, true)}
|
|
129
|
+
/>
|
|
130
|
+
</ModalBlocksStyleContext.Provider>
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
expect(
|
|
134
|
+
UNSAFE_getAllByType(Image).some(
|
|
135
|
+
(node) => node.props.source?.uri === "https://example.com/play.png"
|
|
136
|
+
)
|
|
137
|
+
).toBe(true);
|
|
138
|
+
|
|
139
|
+
rerender(
|
|
140
|
+
<ModalBlocksStyleContext.Provider value={style}>
|
|
141
|
+
<AudioPlayerTrackItem
|
|
142
|
+
configuration={VARIANT_NOW_PLAYING}
|
|
143
|
+
data={mapMenuItemToNowPlayingData(menuItem as any, false)}
|
|
144
|
+
/>
|
|
145
|
+
</ModalBlocksStyleContext.Provider>
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
expect(
|
|
149
|
+
UNSAFE_getAllByType(Image).some(
|
|
150
|
+
(node) => node.props.source?.uri === "https://example.com/pause.png"
|
|
151
|
+
)
|
|
152
|
+
).toBe(true);
|
|
153
|
+
});
|
|
154
|
+
|
|
104
155
|
it("uses custom nowPlayingLabel when provided", () => {
|
|
105
156
|
const data = mapMenuItemToNowPlayingData(
|
|
106
157
|
item as any,
|
|
@@ -131,6 +131,43 @@ describe("mergeItemConfiguration background", () => {
|
|
|
131
131
|
"https://placehold.co/24x24/FF3B30/FFFFFF/png?text=RH"
|
|
132
132
|
);
|
|
133
133
|
});
|
|
134
|
+
|
|
135
|
+
it("applies a text label font family and ignores a non-string font family", () => {
|
|
136
|
+
const withFont = mergeItemConfiguration(base as any, {
|
|
137
|
+
textLabel1: { default: { fontFamily: "Roboto-Bold" } },
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
expect(withFont.textLabel1.iosFontFamily).toBe("Roboto-Bold");
|
|
141
|
+
expect(withFont.textLabel1.androidFontFamily).toBe("Roboto-Bold");
|
|
142
|
+
|
|
143
|
+
const withEmpty = mergeItemConfiguration(base as any, {
|
|
144
|
+
textLabel1: {
|
|
145
|
+
default: { fontFamily: {}, iosFontFamily: "SFProText-Semibold" },
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
expect(withEmpty.textLabel1.iosFontFamily).toBe("SFProText-Semibold");
|
|
150
|
+
expect(withEmpty.textLabel1.androidFontFamily).toBeUndefined();
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("applies play and pause assets onto actionButtonAssets", () => {
|
|
154
|
+
const merged = mergeItemConfiguration(base as any, {
|
|
155
|
+
actionButtons: {
|
|
156
|
+
default: {
|
|
157
|
+
playAsset: "https://example.com/play.png",
|
|
158
|
+
pauseAsset: "https://example.com/pause.png",
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
expect(merged.actionButtonAssets?.playAsset).toBe(
|
|
164
|
+
"https://example.com/play.png"
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
expect(merged.actionButtonAssets?.pauseAsset).toBe(
|
|
168
|
+
"https://example.com/pause.png"
|
|
169
|
+
);
|
|
170
|
+
});
|
|
134
171
|
});
|
|
135
172
|
|
|
136
173
|
describe("mergeButtonConfiguration background", () => {
|
|
@@ -250,6 +287,46 @@ describe("mergeButtonConfiguration background", () => {
|
|
|
250
287
|
expect(merged.title.androidLetterSpacing).toBe(-0.5);
|
|
251
288
|
expect(merged.title.textTransform).toBe("uppercase");
|
|
252
289
|
});
|
|
290
|
+
|
|
291
|
+
it("uses the family string inside a font-selector object and ignores {}", () => {
|
|
292
|
+
const fromObject = mergeButtonConfiguration(buttonBase as any, {
|
|
293
|
+
title: {
|
|
294
|
+
default: { fontFamily: { fontFamily: "GreycliffCF-Bold" } },
|
|
295
|
+
},
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
expect(fromObject.title.iosFontFamily).toBe("GreycliffCF-Bold");
|
|
299
|
+
expect(fromObject.title.androidFontFamily).toBe("GreycliffCF-Bold");
|
|
300
|
+
|
|
301
|
+
const fromEmpty = mergeButtonConfiguration(buttonBase as any, {
|
|
302
|
+
title: {
|
|
303
|
+
default: { fontFamily: {} },
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
expect(fromEmpty.title.iosFontFamily).toBe(buttonBase.title.iosFontFamily);
|
|
308
|
+
|
|
309
|
+
expect(fromEmpty.title.androidFontFamily).toBe(
|
|
310
|
+
buttonBase.title.androidFontFamily
|
|
311
|
+
);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it("maps inactive color, gutter, and focused and inactive font colors", () => {
|
|
315
|
+
const merged = mergeButtonConfiguration(buttonBase as any, {
|
|
316
|
+
background: {
|
|
317
|
+
default: { inactiveColor: "#999999", horizontalGutter: 6 },
|
|
318
|
+
},
|
|
319
|
+
title: {
|
|
320
|
+
default: { inactiveFontColor: "#111111" },
|
|
321
|
+
focused: { fontColor: "#222222" },
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
expect(merged.background.inactiveColor).toBe("#999999");
|
|
326
|
+
expect(merged.background.horizontalGutter).toBe(6);
|
|
327
|
+
expect(merged.title.inactiveFontColor).toBe("#111111");
|
|
328
|
+
expect(merged.title.focusedFontColor).toBe("#222222");
|
|
329
|
+
});
|
|
253
330
|
});
|
|
254
331
|
|
|
255
332
|
describe("mergeHeaderConfiguration section header (Variant 3)", () => {
|
|
@@ -82,7 +82,7 @@ function mergeItemLabel(
|
|
|
82
82
|
...(override.lineHeight !== undefined
|
|
83
83
|
? { lineHeight: override.lineHeight }
|
|
84
84
|
: {}),
|
|
85
|
-
...(override.fontFamily
|
|
85
|
+
...(typeof override.fontFamily === "string" && override.fontFamily
|
|
86
86
|
? {
|
|
87
87
|
iosFontFamily: override.fontFamily,
|
|
88
88
|
androidFontFamily: override.fontFamily,
|
|
@@ -147,6 +147,8 @@ function mergeItemActionButtonAssets(
|
|
|
147
147
|
...base,
|
|
148
148
|
...(override.removeAsset ? { removeAsset: override.removeAsset } : {}),
|
|
149
149
|
...(override.dragAsset ? { dragAsset: override.dragAsset } : {}),
|
|
150
|
+
...(override.playAsset ? { playAsset: override.playAsset } : {}),
|
|
151
|
+
...(override.pauseAsset ? { pauseAsset: override.pauseAsset } : {}),
|
|
150
152
|
};
|
|
151
153
|
}
|
|
152
154
|
|
|
@@ -379,6 +381,22 @@ export function mergeHeaderConfiguration(
|
|
|
379
381
|
// Button Merging Helpers
|
|
380
382
|
// ---------------------------------------------------------------------------
|
|
381
383
|
|
|
384
|
+
function fontFamilyName(value: unknown): string | undefined {
|
|
385
|
+
if (typeof value === "string" && value.trim()) {
|
|
386
|
+
return value;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (value && typeof value === "object") {
|
|
390
|
+
const family = (value as { fontFamily?: unknown }).fontFamily;
|
|
391
|
+
|
|
392
|
+
if (typeof family === "string" && family.trim()) {
|
|
393
|
+
return family;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return undefined;
|
|
398
|
+
}
|
|
399
|
+
|
|
382
400
|
function mergeButtonBackground(
|
|
383
401
|
base: ButtonBackgroundConfiguration,
|
|
384
402
|
defaultOverride?: Record<string, any>,
|
|
@@ -397,6 +415,9 @@ function mergeButtonBackground(
|
|
|
397
415
|
...(defaultOverride?.backgroundColor
|
|
398
416
|
? { defaultColor: defaultOverride.backgroundColor }
|
|
399
417
|
: {}),
|
|
418
|
+
...(defaultOverride?.inactiveColor
|
|
419
|
+
? { inactiveColor: defaultOverride.inactiveColor }
|
|
420
|
+
: {}),
|
|
400
421
|
...(focusedColor != null && focusedColor !== "" ? { focusedColor } : {}),
|
|
401
422
|
...(defaultOverride?.borderRadius !== undefined
|
|
402
423
|
? { cornerRadius: defaultOverride.borderRadius }
|
|
@@ -413,47 +434,62 @@ function mergeButtonBackground(
|
|
|
413
434
|
...(defaultOverride?.paddingLeft !== undefined
|
|
414
435
|
? { paddingLeft: defaultOverride.paddingLeft }
|
|
415
436
|
: {}),
|
|
437
|
+
...(defaultOverride?.horizontalGutter !== undefined
|
|
438
|
+
? { horizontalGutter: defaultOverride.horizontalGutter }
|
|
439
|
+
: {}),
|
|
416
440
|
};
|
|
417
441
|
}
|
|
418
442
|
|
|
419
443
|
function mergeButtonTitle(
|
|
420
444
|
base: ButtonTitleConfiguration,
|
|
421
|
-
override?: Record<string, any
|
|
445
|
+
override?: Record<string, any>,
|
|
446
|
+
focusedOverride?: Record<string, any>
|
|
422
447
|
): ButtonTitleConfiguration {
|
|
423
|
-
if (!override) return base;
|
|
448
|
+
if (!override && !focusedOverride) return base;
|
|
449
|
+
|
|
450
|
+
const fontFamily = fontFamilyName(override?.fontFamily);
|
|
451
|
+
|
|
452
|
+
const focusedFontColor =
|
|
453
|
+
focusedOverride?.fontColor || override?.focusedFontColor;
|
|
454
|
+
|
|
455
|
+
const inactiveFontColor = override?.inactiveFontColor;
|
|
424
456
|
|
|
425
457
|
return {
|
|
426
458
|
...base,
|
|
427
|
-
...(override
|
|
428
|
-
...(
|
|
429
|
-
...(
|
|
459
|
+
...(override?.fontColor ? { fontColor: override.fontColor } : {}),
|
|
460
|
+
...(focusedFontColor ? { focusedFontColor } : {}),
|
|
461
|
+
...(inactiveFontColor ? { inactiveFontColor } : {}),
|
|
462
|
+
...(override?.fontSize !== undefined
|
|
463
|
+
? { fontSize: override.fontSize }
|
|
464
|
+
: {}),
|
|
465
|
+
...(override?.lineHeight !== undefined
|
|
430
466
|
? { lineHeight: override.lineHeight }
|
|
431
467
|
: {}),
|
|
432
|
-
...(
|
|
468
|
+
...(fontFamily
|
|
433
469
|
? {
|
|
434
|
-
iosFontFamily:
|
|
435
|
-
androidFontFamily:
|
|
470
|
+
iosFontFamily: fontFamily,
|
|
471
|
+
androidFontFamily: fontFamily,
|
|
436
472
|
}
|
|
437
473
|
: {}),
|
|
438
|
-
...(override
|
|
474
|
+
...(override?.iosFontFamily
|
|
439
475
|
? { iosFontFamily: override.iosFontFamily }
|
|
440
476
|
: {}),
|
|
441
|
-
...(override
|
|
477
|
+
...(override?.androidFontFamily
|
|
442
478
|
? { androidFontFamily: override.androidFontFamily }
|
|
443
479
|
: {}),
|
|
444
|
-
...(override
|
|
480
|
+
...(override?.letterSpacing !== undefined
|
|
445
481
|
? {
|
|
446
482
|
iosLetterSpacing: override.letterSpacing,
|
|
447
483
|
androidLetterSpacing: override.letterSpacing,
|
|
448
484
|
}
|
|
449
485
|
: {}),
|
|
450
|
-
...(override
|
|
486
|
+
...(override?.iosLetterSpacing !== undefined
|
|
451
487
|
? { iosLetterSpacing: override.iosLetterSpacing }
|
|
452
488
|
: {}),
|
|
453
|
-
...(override
|
|
489
|
+
...(override?.androidLetterSpacing !== undefined
|
|
454
490
|
? { androidLetterSpacing: override.androidLetterSpacing }
|
|
455
491
|
: {}),
|
|
456
|
-
...(override
|
|
492
|
+
...(override?.textTransform
|
|
457
493
|
? { textTransform: override.textTransform }
|
|
458
494
|
: {}),
|
|
459
495
|
};
|
|
@@ -509,13 +545,19 @@ export function mergeButtonConfiguration(
|
|
|
509
545
|
const titleOverride =
|
|
510
546
|
buttonBlock?.title?.default || actionBlock?.title?.default;
|
|
511
547
|
|
|
548
|
+
const titleFocused =
|
|
549
|
+
buttonBlock?.title?.focused ||
|
|
550
|
+
buttonBlock?.title?.focus ||
|
|
551
|
+
actionBlock?.title?.focused ||
|
|
552
|
+
actionBlock?.title?.focus;
|
|
553
|
+
|
|
512
554
|
const iconsOverride = buttonBlock?.icons || actionBlock?.icons;
|
|
513
555
|
const leadingOverride = buttonBlock?.leadingIcon || actionBlock?.leadingIcon;
|
|
514
556
|
|
|
515
557
|
return {
|
|
516
558
|
...base,
|
|
517
559
|
background: mergeButtonBackground(base.background, bgDefault, bgFocused),
|
|
518
|
-
title: mergeButtonTitle(base.title, titleOverride),
|
|
560
|
+
title: mergeButtonTitle(base.title, titleOverride, titleFocused),
|
|
519
561
|
icons: mergeButtonIcons(base.icons, iconsOverride, leadingOverride),
|
|
520
562
|
};
|
|
521
563
|
}
|