Skip to content

Commit baa0fde

Browse files
committed
feat(carousel-native): association
1 parent 9d2a729 commit baa0fde

File tree

4 files changed

+96
-8
lines changed

4 files changed

+96
-8
lines changed

packages/pluggableWidgets/carousel-native/src/Carousel.editorConfig.ts

+9
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { RowLayoutProps, StructurePreviewProps, topBar } from "@mendix/piw-utils-internal";
2+
import { hidePropertiesIn, Properties } from "@mendix/pluggable-widgets-tools";
23

34
import paginationSVG from "./assets/pagination.svg";
45

@@ -37,3 +38,11 @@ export function getPreview(values: CarouselPreviewProps, isDarkMode: boolean): S
3738

3839
return topBar("Carousel", content, isDarkMode);
3940
}
41+
42+
export function getProperties(values: CarouselPreviewProps, defaultProperties: Properties): Properties {
43+
if (!values.activeSelection) {
44+
hidePropertiesIn(defaultProperties, values, ["onChangeAction", "animateExpression"]);
45+
}
46+
47+
return defaultProperties;
48+
}

packages/pluggableWidgets/carousel-native/src/Carousel.tsx

+61-7
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,71 @@ export const Carousel = (props: CarouselProps<CarouselStyle>): ReactElement => {
2323

2424
const [activeSlide, setActiveSlide] = useState(0);
2525

26+
const [firstItem, setFirstItem] = useState(0);
27+
2628
const [loading, setLoading] = useState(true);
2729

2830
useEffect(() => {
29-
if (props.contentSource?.status === ValueStatus.Available) {
31+
if (props.contentSource?.status === ValueStatus.Available && loading) {
32+
// Set initial index of the first item to show the associated active selection.
33+
const index =
34+
(props.activeSelection?.value
35+
? props.contentSource?.items?.findIndex(i => i.id === props.activeSelection?.value?.id)
36+
: 0) ?? 0;
37+
setFirstItem(index);
38+
setActiveSlide(index);
3039
setLoading(false);
3140
}
32-
}, [props.contentSource]);
41+
}, [loading, props.activeSelection, props.contentSource]);
3342

34-
const onSnap = useCallback((index: number) => {
35-
setActiveSlide(index);
36-
}, []);
43+
useEffect(() => {
44+
if (
45+
carouselRef &&
46+
props.contentSource.status === "available" &&
47+
props.activeSelection?.status === "available"
48+
) {
49+
let index = props.contentSource.items?.findIndex(i => i.id === props.activeSelection?.value?.id) ?? 0;
50+
// Removed item that is active selection can not be found
51+
index = index >= 0 ? index : 0;
52+
// Should check carouselRef.currentIndex though this is not fast enough for update.
53+
if (index !== activeSlide) {
54+
// Update carousel when associated item is changed
55+
setActiveSlide(index);
56+
const animate = props.animateExpression?.value ?? true;
57+
// Async snap to index, use case add item is added before current selected
58+
setTimeout(() => {
59+
(carouselRef as NativeCarousel<ObjectItem>).snapToItem(index, animate);
60+
}, 1);
61+
}
62+
}
63+
}, [activeSlide, carouselRef, props.activeSelection, props.animateExpression, props.contentSource]);
64+
65+
useEffect(() => {
66+
if (props.contentSource.status === "available" && props.activeSelection?.status === "available") {
67+
// Check if selected item is still available, reset to index 0 or null
68+
let item = props.contentSource.items?.find(i => i.id === props.activeSelection?.value?.id);
69+
if (item == null) {
70+
item = props.contentSource.items?.[0];
71+
}
72+
if (props.activeSelection.value?.id !== item?.id) {
73+
// Set association when empty to first slide
74+
props.activeSelection.setValue(item);
75+
}
76+
}
77+
}, [props.activeSelection, props.contentSource]);
78+
79+
const onSnap = useCallback(
80+
(index: number) => {
81+
setActiveSlide(index);
82+
if (props.activeSelection) {
83+
const item = props.contentSource?.items?.[index];
84+
if (item?.id !== props.activeSelection.value?.id) {
85+
props.activeSelection.setValue(item);
86+
}
87+
}
88+
},
89+
[props.activeSelection, props.contentSource]
90+
);
3791

3892
const renderItem = useCallback(({ item, index }: { item: ObjectItem; index: number }) => {
3993
const viewStyle = layoutSpecificStyle.slideItem;
@@ -97,7 +151,7 @@ export const Carousel = (props: CarouselProps<CarouselStyle>): ReactElement => {
97151
);
98152
}, [activeSlide, carouselRef, props.contentSource, props.showPagination]);
99153

100-
const onLayout = (event: LayoutChangeEvent) => {
154+
const onLayout = (event: LayoutChangeEvent): void => {
101155
let viewHeight = event.nativeEvent.layout.height;
102156
const viewWidth = event.nativeEvent.layout.width;
103157

@@ -149,7 +203,7 @@ export const Carousel = (props: CarouselProps<CarouselStyle>): ReactElement => {
149203
testID={`${props.name}$carousel`}
150204
activeSlideAlignment={props.activeSlideAlignment}
151205
layout="default"
152-
firstItem={0}
206+
firstItem={firstItem}
153207
useScrollView
154208
enableSnap
155209
data={props.contentSource.items}

packages/pluggableWidgets/carousel-native/src/Carousel.xml

+20
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,20 @@
1818
<caption>Content</caption>
1919
<description/>
2020
</property>
21+
<property key="activeSelection" type="association" selectableObjects="contentSource" onChange="onChangeAction" required="false">
22+
<caption>Active selection</caption>
23+
<description/>
24+
<associationTypes>
25+
<associationType name="Reference"/>
26+
</associationTypes>
27+
</property>
28+
</propertyGroup>
29+
<propertyGroup caption="Effects">
30+
<property key="animateExpression" type="expression" required="false">
31+
<caption>Animate changed</caption>
32+
<description>Animate when 'Active selection' association is changed, animation on user swiping will always be on.</description>
33+
<returnType type="Boolean" />
34+
</property>
2135
</propertyGroup>
2236
<propertyGroup caption="Display">
2337
<property key="layout" type="enumeration" defaultValue="card">
@@ -41,6 +55,12 @@
4155
</enumerationValues>
4256
</property>
4357
</propertyGroup>
58+
<propertyGroup caption="Events">
59+
<property key="onChangeAction" type="action" required="false">
60+
<caption>On change</caption>
61+
<description>When active selection association is changed.</description>
62+
</property>
63+
</propertyGroup>
4464
<!-- Library has a bug with loops-->
4565
<!-- https://github.com/archriss/react-native-snap-carousel/issues/653 - -->
4666
<!-- https://github.com/archriss/react-native-snap-carousel/issues/608-->

packages/pluggableWidgets/carousel-native/typings/CarouselProps.d.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* @author Mendix Widgets Framework Team
55
*/
66
import { ComponentType, CSSProperties, ReactNode } from "react";
7-
import { ListValue, ListWidgetValue } from "mendix";
7+
import { DynamicValue, ListValue, ListWidgetValue, ReferenceValue } from "mendix";
88

99
export type LayoutEnum = "card" | "fullWidth";
1010

@@ -15,6 +15,8 @@ export interface CarouselProps<Style> {
1515
style: Style[];
1616
contentSource: ListValue;
1717
content: ListWidgetValue;
18+
activeSelection?: ReferenceValue;
19+
animateExpression?: DynamicValue<boolean>;
1820
layout: LayoutEnum;
1921
showPagination: boolean;
2022
activeSlideAlignment: ActiveSlideAlignmentEnum;
@@ -31,7 +33,10 @@ export interface CarouselPreviewProps {
3133
readOnly: boolean;
3234
contentSource: {} | { caption: string } | { type: string } | null;
3335
content: { widgetCount: number; renderer: ComponentType<{ children: ReactNode; caption?: string }> };
36+
activeSelection: string;
37+
animateExpression: string;
3438
layout: LayoutEnum;
3539
showPagination: boolean;
3640
activeSlideAlignment: ActiveSlideAlignmentEnum;
41+
onChangeAction: {} | null;
3742
}

0 commit comments

Comments
 (0)