Skip to content

Commit d60c596

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

File tree

4 files changed

+92
-8
lines changed

4 files changed

+92
-8
lines changed

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

Lines changed: 9 additions & 0 deletions
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

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

3888
const renderItem = useCallback(({ item, index }: { item: ObjectItem; index: number }) => {
3989
const viewStyle = layoutSpecificStyle.slideItem;
@@ -97,7 +147,7 @@ export const Carousel = (props: CarouselProps<CarouselStyle>): ReactElement => {
97147
);
98148
}, [activeSlide, carouselRef, props.contentSource, props.showPagination]);
99149

100-
const onLayout = (event: LayoutChangeEvent) => {
150+
const onLayout = (event: LayoutChangeEvent): void => {
101151
let viewHeight = event.nativeEvent.layout.height;
102152
const viewWidth = event.nativeEvent.layout.width;
103153

@@ -149,7 +199,7 @@ export const Carousel = (props: CarouselProps<CarouselStyle>): ReactElement => {
149199
testID={`${props.name}$carousel`}
150200
activeSlideAlignment={props.activeSlideAlignment}
151201
layout="default"
152-
firstItem={0}
202+
firstItem={firstItem}
153203
useScrollView
154204
enableSnap
155205
data={props.contentSource.items}

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

Lines changed: 20 additions & 0 deletions
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

Lines changed: 6 additions & 1 deletion
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)