# Content Timeline Widget

> Vertical or horizontal timeline view of dynamic posts OR manually entered content (repeater). Configurable per-item icon, date format, alternating-side layout.

**Class file:** `includes/Elements/Content_Timeline.php` (2,517 lines)
**Slug:** `content-timeline` (widget id `eael-content-timeline`)
**Public docs:** <https://essential-addons.com/elementor/docs/content-timeline/>
**Pro-shared:** Pro-only. Composes Lite's `Template_Query` trait.

## Overview

Renders a timeline of items along either a vertical centerline (alternating left/right) or horizontal axis. Two data modes — **dynamic** (WordPress posts via Lite's `Template_Query`) or **custom** (Elementor Repeater rows). Two layouts — `default` (vertical) and `horizontal`. Each item has a circle icon on the spine, with date + title + description card pointing off-axis.

## Pro vs Lite

Pro-only.

## File Map

| File | Role |
| --- | --- |
| `includes/Elements/Content_Timeline.php` | Widget class (2,517 lines) — controls + render |
| `includes/Template/Content-Timeline/default.php` | Vertical timeline template |
| `includes/Template/Content-Timeline/horizontal.php` | Horizontal timeline template |
| `src/css/view/content-timeline.scss` → `assets/front-end/css/view/content-timeline.min.css` | Timeline styling (centerline, circle, card pointer) |
| `src/js/view/content-timeline.js` → `assets/front-end/js/view/content-timeline.min.js` | Layout init + horizontal scroll |
| `EAEL_PLUGIN_PATH/assets/front-end/css/view/load-more.min.css` | Lite pagination CSS |
| `config.php` entry `'content-timeline'` | Lite CSS + Pro CSS + Pro JS |

## Architecture

- **Two data modes** — `eael_content_timeline_choose` setting toggles between `'dynamic'` (default) and `'custom'`. Dynamic mode runs `WP_Query` via `Template_Query` trait. Custom mode iterates a Repeater of manually-entered items.
- **Two layouts via template dispatch** — `eael_dynamic_template_Layout` setting (`default` / `horizontal`) maps to `Content-Timeline/{layout}.php`. Same template-include pattern as Post_Block.
- **`Template_Query` trait from Lite** — line 22: composed via `use`. Provides query-args builder for dynamic mode. See [`_patterns.md § Helper imports`](_patterns.md#helper-imports).
- **`do_action('eael/controls/layout', $this)`** at line 532 — Pro emits a hook for layout-control injection. Currently no consumer documented, but the hook exists for extensibility.
- **FA4 → FA5 icon shim** — `eael_custom_content_timeline_circle_icon_new` (line 432) is the FA5 ICONS picker; legacy `eael_custom_content_timeline_circle_icon` is the FA4 string. Render walks both.
- **`dynamic => ['active' => true]` on many controls** — custom-mode controls support Elementor dynamic tags (post title, ACF field, etc.). Pairs with Pro's [advanced-dynamic-tags](../extensions/advanced-dynamic-tags.md) extension.

## Render Output (vertical layout)

```html
<div class="eael-content-timeline-container layout-default">
  <div class="eael-content-timeline">
    <!-- Per-item, alternating left/right -->
    <div class="eael-content-timeline-item eael-pos-left">
      <div class="eael-content-timeline-circle">
        <i class="{circle-icon}"></i>
      </div>
      <div class="eael-content-timeline-card">
        <div class="eael-content-timeline-date">{date}</div>
        <h3 class="eael-content-timeline-title">{title}</h3>
        <div class="eael-content-timeline-content">{description}</div>
        [?] <a class="eael-content-timeline-readmore">Read More</a>
      </div>
    </div>
  </div>
</div>
```

Horizontal layout swaps axis — items flow left-to-right with the centerline horizontal.

## Controls Reference

| Control id | Tab → Section | Type | Purpose |
| --- | --- | --- | --- |
| `eael_content_timeline_choose` | Content → Source | CHOOSE | `dynamic` (posts) / `custom` (repeater) |
| `eael_dynamic_template_Layout` | Content → Layout | SELECT | `default` (vertical) / `horizontal` |
| Query controls (post_type, terms, etc.) | Content → Query | various | Visible when source = dynamic |
| Custom items Repeater | Content → Custom Content | REPEATER | Visible when source = custom |
| `eael_custom_post_date` | Custom Repeater | DATE/TEXT | Per-item date (dynamic-enabled) |
| `eael_custom_content_timeline_circle_icon_new` | Custom Repeater | ICONS | Per-item circle icon (FA5) |
| `eael_content_timeline_acf_post` | Content → Source | SELECT | Per-post ACF source picker (when dynamic) |
| Pagination + load-more | Content → Pagination | various | Standard Lite pagination |

## Conditional Dependencies

```text
eael_content_timeline_choose = 'custom'
  └── shows Custom Content repeater
  └── hides Query controls

eael_content_timeline_choose = 'dynamic'
  └── shows Query controls
  └── shows pagination / load-more

eael_dynamic_template_Layout = 'horizontal'
  └── shows horizontal-specific spacing controls
  └── enables horizontal scroll JS branch
```

## JavaScript Lifecycle

`src/js/view/content-timeline.js`:

- Vertical layout: positions cards left/right alternately based on item index
- Horizontal layout: enables horizontal scroll / swipe navigation
- Pagination/load-more: delegates to Lite's load-more JS (via `eael-load-more-button` class)

## Hooks & Filters

### Pro / EA hooks emitted

| Hook | Where | Purpose |
| --- | --- | --- |
| `eael/controls/layout` | `register_controls` line 532 | Allows third-party to inject layout controls |

### Elementor hooks consumed

Standard widget render plus dynamic-tag system (custom-mode fields are dynamic-enabled).

## Common Issues

| Symptom | Likely cause | Diagnose | Fix |
| --- | --- | --- | --- |
| Custom mode dates missing | Repeater `eael_custom_post_date` empty | Inspect repeater values | Set date per item explicitly |
| Circle icons all same | FA4 / FA5 shim picked the wrong field | Inspect saved `__fa4_migrated` flag | Re-pick icons via FA5 ICONS control |
| Items collide on narrow widths | Horizontal layout on mobile | Inspect responsive | Use vertical layout on mobile via responsive control |
| Dynamic mode shows no posts | Query returns empty | Inspect WP_Query args | Verify post_type / term selection |
| Alternating side breaks after odd count | Layout JS uses index modulo | Inspect last item position | Add an empty 12th item, or expect last-item asymmetry |

## Known Limitations

- **Mode + layout = 4 combinations** — each may have subtle template edge cases
- **`Template_Query` Lite coupling** — same risk as Post_List
- **FA4 shim** still in active code path — flag for removal once site-wide FA4 migration finishes
- **`eael/controls/layout` hook is undocumented** in public API — internal-only extensibility
- **Horizontal scroll lacks keyboard nav** — accessibility gap
- **No `prefers-reduced-motion`** — animation runs regardless
- **`eael_dynamic_template_Layout` capital "L" in id** — typo preserved (user data)

## Cross-References

- Architecture: [`docs/architecture/pro-lite-bridge.md`](../architecture/pro-lite-bridge.md) — `Template_Query` trait
- Sibling widget: [`post-list.md`](post-list.md) — same `Template_Query` consumer
- Shared patterns: [`_patterns.md`](_patterns.md)
