Image Hotspots Editor
<x-wirekit:: is where the points of an image with hotspots are made. A click on the image sets a point, dragging moves it, and the list beside the image holds every point with a number field for X and Y, in the order Tab later reaches the markers. Its value is the list <x-wirekit:: takes as markers, so what the editor writes is what the page shows.
Basic Usage
Give the image, its size in pixels and the points to start with:
Click the image to set a point. Drag a point or use the arrow keys to move it.
A point without a label is named after its place in the list, "Point 3". The editor carries every key of a point along as it is, a label, a target or anything else the page keeps on it, and writes only the position.
With Livewire
Bind the list with wire:model. The property receives the whole list after every change, and the same array is what image hotspots take. Here a Look keeps its points in a JSON column cast to array:
<?php
namespace App\Livewire;
use App\Models\Look;
use Illuminate\Contracts\View\View;
use Livewire\Component;
class LookEditor extends Component
{
public Look $look;
/** @var list<array<string, mixed>> */
public array $hotspots = [];
public function mount(Look $look): void
{
$this->look = $look;
$this->hotspots = $look->hotspots ?? [];
}
public function save(): void
{
$this->validate([
'hotspots.*.x' => ['required', 'numeric', 'between:0,100'],
'hotspots.*.y' => ['required', 'numeric', 'between:0,100'],
]);
$this->look->update(['hotspots' => $this->hotspots]);
}
public function render(): View
{
return view('livewire.look-editor');
}
}
<div>
<x-wirekit::image-hotspots-editor
:src="$look->image_url"
:alt="$look->image_alt"
width="1600"
height="1000"
wire:model="hotspots"
/>
<x-wirekit::button wire:click="save">Save</x-wirekit::button>
</div>
With wire:model the list waits for the next request, here the one Save sends. wire:model.live sends every change at once: each point set or deleted, each press of an arrow key, and a drag once it is let go, not during it. wire:model. sends when a point is committed in the same way, and wire:model. when the reader leaves the editor.
In an Alpine component of your own, x-model binds the same list. See An x-model on a WireKit component.
A File for the Phone
Where the page shows another file on the phone, give it as phone-src with its size. A switch above the image then edits either file, and every point takes a position of its own on the phone's, phoneX and phoneY. A point that was never moved on the phone stands where the desktop puts it:
Click the image to set a point. Drag a point or use the arrow keys to move it.
The Fields of a Point
What a point leads to is the page's own question: a product, a category, a hint. The editor tells the page which point the reader is on, and the page shows its fields for that point. When the reader selects a point, sets one or adds one, the editor sends wirekit:image-hotspots-editor-select, and it bubbles. Its detail carries index and point, a copy of the point:
<x-wirekit::image-hotspots-editor
:src="$look->image_url"
:alt="$look->image_alt"
width="1600"
height="1000"
wire:model="hotspots"
x-on:wirekit:image-hotspots-editor-select="$wire.set('editing', $event.detail.index)"
/>
@if ($editing !== null && isset($hotspots[$editing]))
<x-wirekit::input label="Name of the point" wire:model="hotspots.{{ $editing }}.label" />
@endif
The component above needs a public ?int $editing = null; for this. The field and the editor are bound to the same list, so the field writes the name of the point the editor shows.
Points
Each point is an array, the same shape as the markers of image hotspots:
| Key | Type | Description |
|---|---|---|
x, y |
int|float |
The position on the desktop file, in percent of the image's width and height from its top left corner. The editor writes them with one decimal, between 0 and 100 |
phoneX, phoneY |
int|float |
The position on the phone's file. The editor writes them once a point is moved on the phone, and a new point set on the phone gets both pairs |
label |
string |
The point's name, in the list and as the name of its marker. A new point gets an empty one |
| any other key | Carried along as it is: a target, an href, an image |
Props
| Prop | Type | Default | Description |
|---|---|---|---|
src |
string |
— | The image's address. Without one nothing renders, and it is reported |
alt |
string |
'' |
The image's text alternative |
width |
int|null |
null |
The image's width in pixels, which with height reserves its box before the file arrives |
height |
int|null |
null |
The image's height in pixels |
phone-src |
string|null |
null |
The phone's file. See A File for the Phone |
phone-width |
int|null |
null |
The phone file's width in pixels |
phone-height |
int|null |
null |
The phone file's height in pixels |
value |
array |
[] |
The points to start with where no binding hands them over. See Points |
label |
string|null |
"Points" | The name of the list of points |
help |
string|null |
"Click the image to set a point. Drag a point or use the arrow keys to move it." | The sentence above the image, as text or as markup, which every marker reads as its description |
scope |
string|null |
null |
Scoped personalization key |
Events
| Event | Detail | When |
|---|---|---|
wirekit:image-hotspots-editor-select |
{ index, point } |
The reader selected a point, set one with a click or added one with the button |
Accessibility
- Dragging is never the only way to place a point (WCAG 2.5.7). A selected marker moves with the arrow keys, and every row of the list has a number field for X and Y.
- A point can be set without a pointer (WCAG 2.1.1): "Add a point" sets one in the middle of the image, and the focus goes to its marker, so the arrow keys move it at once.
- Every marker is a
<button>named after its point, pressed while its point is selected, and described by the sentence above the image. - The list is named by
label, each row starts with the number of its point, and each of its buttons and fields is named after the point: "Move Sofa Bergen up", "Sofa Bergen, X in percent". - A status region reads what changed: the position after a move, a point added, a point deleted, the new place of a row.
- The focus stays with the reader through every change that takes its element away. A deleted point hands it to the point that takes its place, a row moved up or down keeps it on its arrow, and a list that is left empty hands it to "Add a point".
- A marker measures
--size-wk-sm, 2rem, and takes a 44 pixel area on a touch screen. The buttons of a row grow to 44 pixels there.
Keyboard Interaction
| Key | Action |
|---|---|
Tab |
Moves between the markers, then through the rows of the list and "Add a point" |
Enter / Space |
On a marker or a name in the list, selects the point |
← → ↑ ↓ |
On a marker, moves the point by 1 % |
Shift + arrow |
On a marker, moves the point by 10 % |
Delete / Backspace |
On a marker, deletes the point |