Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions PULL_REQUEST.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Fix Content-Type matching, add Livewire navigate support, and `@pan` Blade directive

## Summary

This PR fixes two bugs and adds a new feature to improve the Pan developer experience:

### 1. Content-Type header case sensitivity (Bug Fix)

The `InjectJavascriptLibrary` middleware performed a strict equality check against `text/html; charset=UTF-8` (uppercase). However, Laravel's response objects return `text/html; charset=utf-8` (lowercase) in many scenarios — particularly when using Blade views and Livewire full-page components. This caused the Pan JavaScript to never be injected into the page, silently breaking all analytics tracking.

**Before:**
```php
if ($response->headers->get('Content-Type') === 'text/html; charset=UTF-8') {
```

**After:**
```php
if (str_starts_with((string) $response->headers->get('Content-Type'), 'text/html')) {
```

The fix uses `str_starts_with` to match any `text/html` Content-Type regardless of charset casing or additional parameters, which is consistent with how browsers interpret the Content-Type header.

### 2. Livewire `wire:navigate` support (Enhancement)

Pan's client-side JavaScript listened for Inertia's `inertia:start` event to reset impression tracking on page navigation, but had no equivalent listener for Livewire's `wire:navigate` SPA-style transitions. This meant that when using `wire:navigate`, navigating between pages would not re-track impressions for `data-pan` elements on the new page.

The fix adds a `livewire:navigated` event listener that resets the impression, hover, and click tracking arrays and re-scans for visible `data-pan` elements — matching the existing behavior for Inertia navigation. If Livewire is not installed, the listener simply never fires (same pattern as the existing Inertia listener).

### 3. `@pan` Blade directive (Feature)

Adds a `@pan` Blade directive as a cleaner alternative to writing `data-pan="..."` manually:

```blade
{{-- Before --}}
<button data-pan="tab-1">Tab 1</button>

{{-- After --}}
<button @pan('tab-1')>Tab 1</button>
```

Supports variables and expressions:

```blade
<button @pan('tab-' . $tab->slug)>{{ $tab->name }}</button>
<button @pan($analyticsName)>Click me</button>
```

## Changes

- **`src/Adapters/Laravel/Providers/PanServiceProvider.php`** — Register `@pan` Blade directive
- **`src/Adapters/Laravel/Http/Middleware/InjectJavascriptLibrary.php`** — Use `str_starts_with` for case-insensitive Content-Type matching
- **`resources/js/src/main.ts`** — Add `livewire:navigated` event listener for SPA navigation support
- **`resources/js/src/types.ts`** — Add `livewireNavigatedListener` to `GlobalState` type
- **`resources/js/dist/pan.iife.js`** — Rebuilt compiled JavaScript
- **`tests/.../InjectJavascriptLibraryTest.php`** — Added test for lowercase charset Content-Type
- **`tests/.../BladeDirectiveTest.php`** — Added tests for `@pan` directive (static string, variable, expression)
- **`README.md`** — Added `@pan` directive documentation and updated Livewire compatibility note

## Test Plan

- [x] All existing tests pass
- [x] New test verifies JS injection with lowercase `charset=utf-8`
- [x] Existing tests confirm JS injection with uppercase charset (returned by plain string routes)
- [x] Existing test confirms no injection for `text/plain` Content-Type
- [x] `@pan` directive compiles to correct `data-pan` attribute with static strings, variables, and expressions
- [x] Verified in a real Laravel 12 + Livewire 4 application with `wire:navigate`
20 changes: 18 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ At the time of writing, Pan tracks only the following events: impressions, hover
- you have different "help" pop-hovers in your application, and you want to know **which one is the most hovered**. By adding the `data-pan` attribute to your pop-hovers, you can track this information.
- and so on...

It works out-of-the-box with your favorite Laravel stack; updating a button color in your "react" won't trigger a new impression, but seeing that same button in a different [Inertia](https://inertiajs.com) page will. Using [Livewire](https://livewire.laravel.com)? No problem, Pan works seamlessly with it too.
It works out-of-the-box with your favorite Laravel stack; updating a button color in your "react" won't trigger a new impression, but seeing that same button in a different [Inertia](https://inertiajs.com) page will. Using [Livewire](https://livewire.laravel.com)? No problem, Pan works seamlessly with it too — including full support for Livewire's `wire:navigate` SPA-style page transitions.

Visualize your analytics is as simple as typing `php artisan pan` in your terminal. This command will show you a table with the different analytics you've been tracking, and hopefully, you can use this information to improve your application.

Expand Down Expand Up @@ -57,9 +57,25 @@ Finally, you may start tracking your pages or components adding the `data-pan` a
</div>
```

> [!IMPORTANT]
> [!IMPORTANT]
> Event names must only contain letters, numbers, dashes, and underscores.

### Using the `@pan` Blade directive

For a cleaner syntax in your Blade templates, you may use the `@pan` directive instead of writing the `data-pan` attribute manually:

```blade
<button @pan('tab-1')>Tab 1</button>
<button @pan('tab-2')>Tab 2</button>
```

The directive also supports variables and expressions:

```blade
<button @pan('tab-' . $tab->slug)>{{ $tab->name }}</button>
<button @pan($analyticsName)>Click me</button>
```

## Visualize your product analytics

To visualize your product analytics, you may use the `pan` Artisan command:
Expand Down
22 changes: 11 additions & 11 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion resources/js/dist/pan.iife.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading