# Custom Cursor

> Custom mouse-cursor styling per element, per section, per container, or at the document (page) level. Supports text/SVG/image cursors plus pre-built cursor-effect libraries (ghost-following, 90s effects, glowing boxes, ink line, color balls, phantom smoke, pointer particles).

**Class file:** `includes/Extensions/Custom_Cursor.php` (2,061 lines)
**Slug:** `custom-cursor` (`config.php` `extensions` key)
**Public docs:** <https://essential-addons.com/elementor/docs/custom-cursor/>
**Pro-shared:** ✅ Pro-only (Lite has Promotion teaser)

## Overview

Adds a "Custom Cursor" panel everywhere — common-element Style tab, Section Advanced, Column Advanced, Container Layout, AND page-level document settings. The most-registered Pro extension in terms of attach points (5 vs the typical 3–4).

Two configuration modes:

1. **Regular cursor** — Normal + Pointer (hover) state. Each state: text / icon / image / SVG code
2. **Cursor effects** — pre-bundled JS effects (ghost-following, color-balls, glowing-boxes, ink-line, phantom-smoke, pointer-particle, 90s effects)

## Features

- **Per-element cursor** — different cursor on different parts of the page
- **Document-level cursor** — applied to the whole page (set in page settings)
- **Two-tab control structure** — Normal state + Pointer (hover) state
- **Four cursor types per state** — regular (text), icon, image, SVG code
- **Cursor-effect library** — 7 pre-built JS effects
- **Settings cleanup** — `cleanup_settings_data` filter strips cursor settings when feature is disabled, avoiding orphan data in saved JSON

## Pro vs Lite

| Capability | Lite | Pro |
| --- | --- | --- |
| Panel visible in editor | Teaser only | ✅ Full panel |
| Cursor effects library | ❌ | ✅ |
| Per-element + page-level cursors | ❌ | ✅ |

## File Map

| File | Role |
| --- | --- |
| `includes/Extensions/Custom_Cursor.php` | Class — controls + render attribute injection |
| `src/js/view/custom-cursor.js`, `src/js/edit/custom-cursor.js` | Frontend + editor JS |
| `assets/front-end/css/lib-view/cursor/ghost-following.min.css` | One of the cursor effect stylesheets |
| `assets/front-end/js/lib-view/cursor/*.min.js` | 7 effect libraries — 90s, ghost-following, phantom-smoke-ghost, pointer-perticle, ink-line, glowing-boxes, color-balls |
| `assets/front-end/js/lib-view/gsap/gsap.min.js` | GSAP — used by some effects |
| `EAEL_PLUGIN_PATH/assets/front-end/js/lib-view/dom-purify/purify.min.js` | DOMPurify — sanitizes user-supplied SVG cursor markup. **Loaded from Lite's plugin path** |

The `config.php` entry is the most complex of any Pro extension — 10+ JS dependencies for cursor libs.

## Architecture

Constructor wires 8 hooks (`Custom_Cursor.php:19-28`):

```php
add_action( 'elementor/documents/register_controls',                        [ $this, 'document_register_controls' ], 10 );
add_action( 'elementor/element/common/_section_style/after_section_end',    [ $this, 'register_controls' ] );
add_action( 'elementor/element/column/section_advanced/after_section_end', [ $this, 'register_controls' ] );
add_action( 'elementor/element/section/section_advanced/after_section_end', [ $this, 'register_controls' ] );
add_action( 'elementor/element/container/section_layout/after_section_end', [ $this, 'register_controls' ] );
add_action( 'elementor/frontend/before_render',                             [ $this, 'before_render' ], 100 );
add_action( 'eael/custom_cursor/page_render',                               [ $this, 'render_custom_cursor_html' ], 10, 2 );
add_action( 'eael/register_custom_cursor_assets',                           [ $this, 'register_assets' ] );
add_filter( 'eael/extentions/global_settings',                              [ $this, 'global_settings' ], 10, 3 );
add_filter( 'elementor/document/element/replace_id',                        [ $this, 'cleanup_settings_data' ] );
```

5 control-registration paths (`document_register_controls` for page, plus 4 element-type paths). 2 render-time paths (`before_render` to set attributes, `eael/custom_cursor/page_render` action for downstream consumers).

## Controls Reference (high-value)

| Control id | Purpose |
| --- | --- |
| `eael_enable_custom_cursor` | Master switch |
| `eael_custom_cursor_tabs` | Container for Normal + Pointer tabs |
| `eael_custom_cursor_type` (+ tab suffix) | `'regular' \| 'icon' \| 'image' \| 'svg_code' \| <effect-name>` |
| `eael_custom_cursor_regular` | Text/character to show as cursor |
| `eael_custom_cursor_icon` | FA5 icon |
| `eael_custom_cursor_image` | Image URL |
| `eael_custom_cursor_svg_code` | Raw SVG markup (sanitized via DOMPurify) |
| `eael_custom_cursor_apply_changes_notice` | Editor-only "Apply changes to preview" notice |

Two unique conventions in this extension:

- **Tab-suffixed control ids** — controls live inside `start_controls_tabs()` so the same control id appears with `_normal` / `_pointer` suffix. The code uses `$tab` parameter (`Custom_Cursor.php:598`) to dynamically generate.
- **Older `eael_custom_cursor_*` prefix** instead of the newer `eael_ext_custom_cursor_*`. Predates the convention shift; not worth renaming (user data).

## Behavior Flow

1. **Editor:** user enables Custom Cursor on a section. Picks type. Configures.
2. **Save:** settings persist; **`cleanup_settings_data` filter strips orphan keys** when the feature is later disabled, keeping saved JSON clean.
3. **Frontend `before_render` (priority 100):** Pro reads cursor settings, attaches data attributes (`data-cursor-type`, `data-cursor-content`, etc.) to the wrapper.
4. **Frontend JS init:** `custom-cursor.min.js` listens for elements with cursor data attributes, applies CSS / animation / effect handler.
5. **Effect libraries** (`90s-cursor-effects`, `ghost-following`, etc.) hook into the dispatcher and apply per-effect logic.

## Settings Cleanup Filter

`cleanup_settings_data( $element_data )` is the most distinctive piece — runs through every element's settings, strips keys starting with `eael_custom_cursor_` / `eael_cursor_` if the master switch is `'no'` or absent. Prevents the saved page JSON from bloating with disabled-cursor settings.

Walks the element tree recursively via `$walk` closure. Triggered by Elementor's `elementor/document/element/replace_id` filter (fires during element-data manipulation).

## Hooks & Filters

### Elementor hooks consumed

| Hook | Method |
| --- | --- |
| `elementor/documents/register_controls` | `document_register_controls` (page-level controls) |
| `elementor/element/{common,column,section,container}/.../after_section_end` | `register_controls` (4 hooks) |
| `elementor/frontend/before_render` | `before_render` (priority 100) |
| `elementor/document/element/replace_id` | `cleanup_settings_data` |

### Pro / EA hooks emitted

| Hook | Type | Purpose |
| --- | --- | --- |
| `eael/custom_cursor/page_render` | action | Downstream consumers can render extra cursor HTML |
| `eael/register_custom_cursor_assets` | action | Trigger asset registration manually |
| `eael/extentions/global_settings` | filter | (note typo: "extentions") — Pro's global settings injection point |

### Hook namespace typo

`eael/extentions/global_settings` — `extentions` is misspelled. Fixing it would break existing third-party consumers. Document as-is; don't rename without a deprecation cycle.

## Asset Dependencies

| Asset | Path | Role |
| --- | --- | --- |
| GSAP | `assets/front-end/js/lib-view/gsap/gsap.min.js` (Pro) | Used by some effects |
| DOMPurify | `EAEL_PLUGIN_PATH/assets/front-end/js/lib-view/dom-purify/purify.min.js` (Lite!) | Sanitizes user SVG |
| 7 cursor effects | `assets/front-end/js/lib-view/cursor/*.min.js` (Pro) | One per effect |
| Edit JS | `src/js/edit/custom-cursor.js` | Editor preview |
| View JS | `src/js/view/custom-cursor.js` | Frontend |
| CSS | `assets/front-end/css/lib-view/cursor/ghost-following.min.css` (Pro) | Effect-specific |

**Mixed-path entries** — DOMPurify references `EAEL_PLUGIN_PATH` (Lite constant). Pro depends on Lite being active; the constant is defined by Lite's entry file. See [`docs/architecture/asset-loading.md`](../architecture/asset-loading.md).

## Behavior Quirks

- **SVG cursor sanitized via DOMPurify.** Raw user-supplied SVG is dangerous (XSS via `<script>` inside SVG). DOMPurify strips dangerous nodes before the cursor renders. Verify the sanitize call is wired on every SVG render path.
- **Cursor effects can be heavy.** GSAP-based effects render at 60fps with mouse-tracking; on low-end devices, page may stutter. No automatic fallback / disable.
- **Page-level cursor + section-level cursor stacking** — when both are set, section-level wins (more specific). Verify by testing nested page + section cursors.
- **Editor preview ≠ frontend** for some effects — effect libraries may bail in `isEditMode` to prevent runaway loops in the editor.

## Known Limitations / Gaps

1. **`eael/extentions/global_settings` hook typo** — flagged above; cannot rename without breaking consumers
2. **Performance gating missing** — no `prefers-reduced-motion` check for cursor effects; accessibility regression
3. **DOMPurify path uses Lite constant** — if Lite is ever deactivated (impossible via bridge gate, but possible mid-test), this breaks. Defensive path resolution recommended.
4. **2,061 lines is the largest extension** — splitting into base class + per-effect strategy classes would help maintainability
5. **No documented allowlist of allowed `eael_custom_cursor_type` values** — `switch` cases in render path are the de-facto allowlist

## Code Pointers

- Constructor: `Custom_Cursor.php:18-29`
- `cleanup_settings_data`: `:31-end of method`
- Tab-suffixed control loop: `:598+`
- Cursor-type switch (regular/icon/image/svg_code/effect): `:629-770+`
- Page-level (document) controls: `document_register_controls`
- `before_render` attribute injection: `before_render` method

## Cross-References

- Architecture: [`docs/architecture/asset-loading.md`](../architecture/asset-loading.md) — mixed `EAEL_PLUGIN_PATH` / `EAEL_PRO_PLUGIN_PATH` entries
- Shared patterns: [`_patterns.md`](_patterns.md)
