Skip to content

M3Carousel

Bases: LayoutControl

A Flet control that wraps the Flutter m3_carousel package.

The carousel layout and animation are delegated to the upstream package.

Source code in src/flet_m3_carousel/flet_m3_carousel.py
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
@control("M3Carousel")  # type: ignore[arg-type]
class M3Carousel(LayoutControl):
    """A Flet control that wraps the Flutter ``m3_carousel`` package.

    The carousel layout and animation are delegated to the upstream
    package.
    """

    items: list[M3CarouselItem] = field(default_factory=list)
    layout: str = "hero"
    axis: str = "horizontal"
    hero_alignment: str = "center"
    is_extended: bool | None = None
    free_scroll: bool | None = None
    uncontained_item_extent: float | None = None
    uncontained_shrink_extent: float | None = None
    child_element_border_radius: float | None = None
    large_max_width: float | None = None
    item_padding: float | None = None
    scroll_animation_duration: int | None = None
    fixed_pulse_delta: float = 4.0
    single_swipe_gesture_sensitivity_range: int | None = None
    haptic: str = "none"
    header: str | None = None
    show_all: bool = False
    on_tap: EventHandler[M3CarouselTapEvent] | None = None
    on_change: EventHandler[M3CarouselChangeEvent] | None = None

    def __post_init__(self, ref: Any | None = None) -> None:
        # Normalize and validate scalar fields.
        self.layout = _normalize_layout(self.layout)
        self.axis = _normalize_axis(self.axis)
        self.hero_alignment = _normalize_alignment(self.hero_alignment)
        self.haptic = _normalize_haptic(self.haptic)
        self.haptic = _normalize_haptic(self.haptic)

        if self.items is None:
            self.items = []
        if not isinstance(self.items, (list, tuple)):
            raise TypeError(f"items must be a list or tuple, got {type(self.items).__name__}")
        normalized_items: list[M3CarouselItem] = []
        seen_keys: set[str] = set()
        for item in self.items:
            if not isinstance(item, M3CarouselItem):
                raise TypeError(
                    f"items must contain M3CarouselItem instances, got {type(item).__name__}"
                )
            if item.key is not None:
                if item.key in seen_keys:
                    raise ValueError(f"duplicate key: {item.key!r}")
                seen_keys.add(item.key)
            normalized_items.append(item)
        self.items = normalized_items

        super().__post_init__(ref)

    def serialize_items(self) -> list[dict[str, Any]]:
        return serialize_items(self.items)

    async def scroll_to(self, index: int = 0) -> None:
        await self._invoke_method("scroll_to", {"index": index})

    async def jump_to_item(self, index: int = 0) -> None:
        await self._invoke_method("jump_to_item", {"index": index})

    async def animate_to_item(self, index: int = 0) -> None:
        await self._invoke_method("animate_to_item", {"index": index})

    async def next(self) -> None:
        await self._invoke_method("next", {})

    async def previous(self) -> None:
        await self._invoke_method("previous", {})

    async def show_all_items(self) -> None:
        await self._invoke_method("show_all", {})

    def to_dict(self) -> dict[str, Any]:
        result: dict[str, Any] = {
            "items": serialize_items(self.items),
            "layout": self.layout,
            "axis": self.axis,
            "hero_alignment": self.hero_alignment,
        }
        if self.is_extended is not None:
            result["is_extended"] = self.is_extended
        if self.free_scroll is not None:
            result["free_scroll"] = self.free_scroll
        if self.uncontained_item_extent is not None:
            result["uncontained_item_extent"] = self.uncontained_item_extent
        if self.uncontained_shrink_extent is not None:
            result["uncontained_shrink_extent"] = self.uncontained_shrink_extent
        if self.child_element_border_radius is not None:
            result["child_element_border_radius"] = self.child_element_border_radius
        if self.large_max_width is not None:
            result["large_max_width"] = self.large_max_width
        if self.item_padding is not None:
            result["item_padding"] = self.item_padding
        if self.scroll_animation_duration is not None:
            result["scroll_animation_duration"] = self.scroll_animation_duration
        if self.fixed_pulse_delta != 4.0:
            result["fixed_pulse_delta"] = self.fixed_pulse_delta
        if self.single_swipe_gesture_sensitivity_range is not None:
            result["single_swipe_gesture_sensitivity_range"] = (
                self.single_swipe_gesture_sensitivity_range
            )
        if self.haptic != "none":
            result["haptic"] = self.haptic
        if self.header is not None:
            result["header"] = self.header
        if self.show_all:
            result["show_all"] = True
        return result