# Post Carousel Widget

> Swiper-based horizontal carousel of WordPress posts. Single template, full carousel control set (autoplay, effect, arrows, dots, per-breakpoint slides).

**Class file:** `includes/Elements/Post_Carousel.php` (2,988 lines)
**Slug:** `post-carousel` (widget id `eael-post-carousel`)
**Public docs:** <https://essential-addons.com/elementor/docs/post-carousel/>
**Pro-shared:** Pro-only. Reuses Lite query helpers + Elementor's `e-swiper` handle.

## Overview

Renders a horizontal carousel of WordPress posts via Swiper v8. Uses Elementor's pre-registered `e-swiper` style dep — no Swiper re-bundling. Single template (`default.php`). Per-breakpoint slides-per-view (desktop / tablet / mobile), full Swiper feature set (autoplay, effect, dots, arrows, loop).

## Pro vs Lite

Pro-only.

## File Map

| File | Role |
| --- | --- |
| `includes/Elements/Post_Carousel.php` | Widget class — controls + render |
| `includes/Template/Post-Carousel/default.php` | Slide template (per-post HTML) |
| `src/css/view/post-carousel.scss` → `assets/front-end/css/view/post-carousel.min.css` | Carousel styling |
| `src/js/view/post-carousel.js` → `assets/front-end/js/view/post-carousel.min.js` | Swiper init |
| `EAEL_PLUGIN_PATH/assets/front-end/css/view/post-grid.min.css` | Lite — base post grid shared with Post_Block |
| `config.php` entry `'post-carousel'` | Lite CSS + Pro CSS + Pro JS |

`get_style_depends()` returns `['e-swiper']` (line 75) — Elementor's pre-registered Swiper CSS handle.

## Architecture

- **`get_style_depends() => ['e-swiper']`** — opts into Elementor's registered Swiper handle. No Pro-bundled Swiper CSS. See [`.claude/rules/widget-development.md`](../../.claude/rules/widget-development.md) "Elementor-Provided Libraries".
- **Single template** — `default.php` only. No skin / template branching like Post_Block.
- **Lite query helpers** — same `HelperClass::fix_old_query` / `get_query_args` / `get_dynamic_args` chain as Post_Block. See [`_patterns.md § Helper imports`](_patterns.md#helper-imports).
- **`eael_dynamic_template_Layout` (commented out at line 101)** — placeholder for future template-list-dropdown control. Currently disabled (single template only).
- **Selector base is `.swiper-container-wrap`** in style controls (line 1977+) — wraps the Swiper. Class is `.swiper` (NOT legacy `.swiper-container`) for Swiper v8 compatibility.

## Render Output

```html
<div class="swiper-container-wrap eael-post-carousel-{element-id}">
  <div class="eael-post-carousel swiper">
    <div class="swiper-wrapper">
      <!-- One swiper-slide per post (from default.php template) -->
      <div class="swiper-slide eael-grid-post">
        <div class="eael-grid-post-holder">
          <div class="eael-entry-media">
            <a href="{permalink}"><img src="{thumbnail}" /></a>
          </div>
          <div class="eael-entry-wrapper">
            [?] <div class="eael-entry-meta">By {author} | {date}</div>
            <h2 class="eael-entry-title"><a href="{permalink}">{title}</a></h2>
            [?] <div class="eael-grid-post-excerpt">{excerpt}</div>
            [?] <a class="eael-post-elements-readmore-btn">Read More</a>
          </div>
        </div>
      </div>
    </div>
    [?] <div class="swiper-button-next">...</div>
    [?] <div class="swiper-button-prev">...</div>
    [?] <div class="swiper-pagination"></div>
  </div>
</div>
```

## Controls Reference

| Control id | Tab → Section | Type | Purpose |
| --- | --- | --- | --- |
| Query controls (post_type, terms, etc.) | Content → Query | various | Lite query controls |
| `items` / `items_tablet` / `items_mobile` | Content → Carousel | SLIDER | Slides-per-view by breakpoint |
| `margin` / `margin_tablet` / `margin_mobile` | Content → Carousel | SLIDER | Slide gap |
| `carousel_effect` | Content → Carousel | SELECT | `slide` / `fade` / `cube` / `coverflow` |
| `slider_speed` | Content → Carousel | SLIDER | Transition ms |
| `autoplay` / `autoplay_speed` / `pause_on_hover` | Content → Carousel | SWITCHER + SLIDER | Autoplay config |
| `loop` | Content → Carousel | SWITCHER | Infinite loop |
| `arrows` / `dots` | Content → Carousel | SWITCHER | Nav visibility |
| `arrow_left` / `arrow_right` | Content → Carousel | ICONS | Custom nav icons |
| Show meta / excerpt / read-more | Content → Content | SWITCHER | Per-element visibility |

## Conditional Dependencies

```text
autoplay = 'yes'
  └── shows autoplay_speed slider
  └── shows pause_on_hover toggle

arrows = 'yes'
  └── shows arrow_left + arrow_right ICONS pickers

dots = 'yes'
  └── shows pagination style controls
```

## JavaScript Lifecycle

`src/js/view/post-carousel.js` — standard Swiper init pattern (see [`_patterns.md`](_patterns.md) and [`.claude/rules/widget-development.md`](../../.claude/rules/widget-development.md) "Swiper init pattern"):

```js
var PostCarousel = function( $scope, $ ) {
    var $carousel = $scope.find( '.eael-post-carousel' );
    var config = { /* read from data-* attrs */ };
    swiperLoader( $carousel[0], config );
};
```

Uses `elementorFrontend.utils.swiper` async loader — no bundled Swiper JS.

## Hooks & Filters

Standard widget render. No Pro-emitted hooks.

## Common Issues

| Symptom | Likely cause | Diagnose | Fix |
| --- | --- | --- | --- |
| Carousel renders but doesn't slide | Swiper script not loaded | DevTools: check `Swiper` global / `elementorFrontend.utils.swiper` | Confirm `e-swiper` handle is enqueued; Elementor loads it lazily |
| Effect "fade" plus loop renders wrong | Swiper limitation: `effect: fade` + `loop: true` requires odd slide count | Inspect Swiper docs | Use `loop: false` with fade, or switch to slide effect |
| Per-breakpoint counts ignored | `breakpoints` config not built correctly in JS | Check `data-*` attributes on `.eael-post-carousel` | Verify all 3 attrs present and JS reads them into Swiper `breakpoints` |
| Custom arrow icons missing | FA5 ICONS picker value not rendered | Inspect `arrow_left` / `arrow_right` rendered HTML | Confirm `Icons_Manager::render_icon` is called for both values |
| Read-more button absent | `show_read_more_button` control off | Inspect setting | Toggle on; verify default template renders the conditional |

## Known Limitations

- **Single template** — no `skin` / template-list system; structural changes require editing `default.php`
- **`eael_dynamic_template_Layout` is dead code** — placeholder commented out at line 101; remove or wire up
- **No `prefers-reduced-motion`** for autoplay carousel
- **Loop + few-slides edge case** — Swiper duplicates slides to enable loop; <3 slides looks awkward
- **Carousel inside template / popup** — `Asset_Builder` may miss detection; see [`docs/architecture/asset-loading.md`](../architecture/asset-loading.md)

## Cross-References

- Shared patterns: [`_patterns.md`](_patterns.md)
- Rule: [`.claude/rules/widget-development.md`](../../.claude/rules/widget-development.md) — Swiper handle pattern
