Hotspot
Headless image hotspots: positioned markers with popover details and SVG regions.
|
|
| Package |
@vizejs/ui/hotspot |
| Maturity |
stable |
| Own the source |
vize lib pull hotspot |
| Requires |
context, controllable-state, id, popover |
| Aliases |
hotspot, image map, annotated image, shoppable image, product tags |
| Covers |
HTML map/area, WAI-ARIA Disclosure, Popover API |
Usage
import { HotspotArea, HotspotContent, HotspotImage, HotspotMarker, Hotspot, HotspotRoot } from "@vizejs/ui/hotspot";
Or copy the source into your project with vize lib pull hotspot (see Source Distribution).
API
HotspotArea
Source: src/families/media/hotspot/hotspot-area.vue
Props
| Prop |
Type |
Default |
Description |
id (required) |
string |
required |
Area id; activating the area makes it the root's active hotspot. |
shape (required) |
HotspotShape |
required |
Region in image-space percent. |
label (required) |
string |
required |
Accessible name of the region. |
href |
string |
undefined |
Render the region as a link to this URL instead of a toggle button. |
disabled |
boolean |
false |
Remove the region from interaction. |
Events
| Event |
Payload |
Description |
activate |
[nativeEvent: Event] |
Fired when a button region is activated, before the active hotspot toggles. |
Slots
| Slot |
Slot props |
Description |
default |
HotspotAreaSlotState |
Extra SVG content drawn inside the region, e.g. a <title>. |
Exposed
| Member |
Type |
Description |
id |
string |
Area id. |
active |
boolean |
Whether this area is the active hotspot. |
element |
SVGSVGElement | null |
Rendered SVG overlay. |
shape |
HotspotShape |
Area shape. |
HotspotContent
Source: src/families/media/hotspot/hotspot-content.vue
Props
| Prop |
Type |
Default |
Description |
placement |
PopoverPlacement |
"top" |
Preferred placement relative to the marker before collision handling. |
offset |
number |
8 |
Gap between marker and content in CSS pixels. |
portalDisabled |
boolean |
false |
Render in place instead of portalling to document.body. |
closeOnEscape |
boolean |
true |
Close when Escape is pressed while the content or marker has focus. |
closeOnPointerDownOutside |
boolean |
true |
Close on pointer down outside the content and marker. |
closeOnFocusOutside |
boolean |
false |
Close when focus moves outside. Off by default so neighbouring markers, and focus restored from another marker's content, never close this one. |
ariaLabel |
string |
undefined |
Accessible dialog name when no labelled element is referenced. |
ariaLabelledby |
string |
undefined |
Space-separated ids that label the content. |
Slots
| Slot |
Slot props |
Description |
default |
HotspotMarkerSlotState |
Hotspot details. Receives the marker state. |
HotspotImage
Source: src/families/media/hotspot/hotspot-image.vue
Props
| Prop |
Type |
Default |
Description |
src (required) |
string |
required |
Image source. Unsafe or malformed sources are not rendered. |
alt (required) |
string |
required |
Alternative text describing the whole image. |
width |
number | string |
undefined |
Intrinsic width reserving layout space. |
height |
number | string |
undefined |
Intrinsic height reserving layout space. |
loading |
"eager" | "lazy" |
"lazy" |
Native loading policy. |
Exposed
| Member |
Type |
Description |
element |
HTMLImageElement | null |
Rendered native image. |
HotspotMarker
Source: src/families/media/hotspot/hotspot-marker.vue
Props
| Prop |
Type |
Default |
Description |
id (required) |
string |
required |
Marker id, unique within the root. |
x (required) |
number |
required |
Horizontal position in percent of the image width. |
y (required) |
number |
required |
Vertical position in percent of the image height. |
label (required) |
string |
required |
Accessible name of the marker button. |
disabled |
boolean |
false |
Remove the marker from interaction while keeping it rendered. |
Slots
| Slot |
Slot props |
Description |
trigger |
HotspotMarkerSlotState |
Visual marker contents rendered inside the button. |
default |
HotspotMarkerSlotState |
HotspotContent for this marker. |
Exposed
| Member |
Type |
Description |
id |
string |
Marker id. |
open |
boolean |
Whether the marker content is open. |
disabled |
boolean |
Whether the marker is disabled. |
state |
HotspotMarkerState |
Stable state token. |
element |
HTMLDivElement | null |
Rendered marker wrapper. |
focus |
(options?: FocusOptions) => void |
Focus the marker button. |
Hotspot<Data = undefined>
Source: src/families/media/hotspot/hotspot-root.vue
Props
| Prop |
Type |
Default |
Description |
id |
string | null |
undefined |
Consumer-owned base id. null and undefined select a deterministic fallback. |
hotspots |
readonly HotspotDefinition<Data>[] |
[] |
Declared markers. Their data type flows into the default slot so markers can be rendered with v-for while keeping consumer payloads typed. |
active |
string | null |
undefined |
Controlled active marker id (v-model:active). undefined selects uncontrolled behavior. |
defaultActive |
string | null |
null |
Initially active marker for uncontrolled use. |
exclusive |
boolean |
true |
Keep at most one marker open. When false, markers open independently and active tracks the most recently opened one. |
Events
| Event |
Payload |
Description |
update:active |
[id: string | null] |
Fired when the active marker requests a new controlled value. |
activeChange |
[id: string | null, previous: string | null, source: HotspotActiveChangeSource] |
Fired after every distinct active-marker change with its source. |
Slots
| Slot |
Slot props |
Description |
default |
HotspotSlotState<Data> |
Image, markers, areas, and content. Receives the typed hotspot list and open state. |
Exposed
| Member |
Type |
Description |
active |
— |
|
close |
— |
|
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
hitTest |
— |
|
open |
— |
|
openIds |
— |
|
HotspotRoot<Data = undefined>
Source: src/families/media/hotspot/hotspot-root.vue
Props
| Prop |
Type |
Default |
Description |
id |
string | null |
undefined |
Consumer-owned base id. null and undefined select a deterministic fallback. |
hotspots |
readonly HotspotDefinition<Data>[] |
[] |
Declared markers. Their data type flows into the default slot so markers can be rendered with v-for while keeping consumer payloads typed. |
active |
string | null |
undefined |
Controlled active marker id (v-model:active). undefined selects uncontrolled behavior. |
defaultActive |
string | null |
null |
Initially active marker for uncontrolled use. |
exclusive |
boolean |
true |
Keep at most one marker open. When false, markers open independently and active tracks the most recently opened one. |
Events
| Event |
Payload |
Description |
update:active |
[id: string | null] |
Fired when the active marker requests a new controlled value. |
activeChange |
[id: string | null, previous: string | null, source: HotspotActiveChangeSource] |
Fired after every distinct active-marker change with its source. |
Slots
| Slot |
Slot props |
Description |
default |
HotspotSlotState<Data> |
Image, markers, areas, and content. Receives the typed hotspot list and open state. |
Exposed
| Member |
Type |
Description |
element |
HTMLDivElement | null |
Rendered root element. |
active |
string | null |
Most recently opened marker, or null. |
openIds |
readonly string[] |
Ids of every open marker. |
open |
(id: string) => boolean |
Open one marker (closing others unless exclusive is false). Reports whether state changed. |
close |
(id?: string) => boolean |
Close one marker, or every marker without an id. Reports whether state changed. |
hitTest |
(x: number, y: number) => readonly string[] |
Ids of registered HotspotAreas whose shape contains an image-space point (percent). |
Behavior
Normative state x input -> outcome table for hotspot-root.vue,
hotspot-image.vue, hotspot-marker.vue, hotspot-content.vue, and
hotspot-area.vue (@vizejs/ui/hotspot). Every row is proven by the named test.
Markers are positioned in image space: each HotspotMarker publishes
--vize-ui-hotspot-x / --vize-ui-hotspot-y percentages, so a consumer rule
like position: absolute; left: var(--vize-ui-hotspot-x); top: var(--vize-ui-hotspot-y)
keeps markers attached to the image at every size. Each marker is a Popover
(PopoverRoot + PopoverTrigger button), and HotspotContent wraps
PopoverContent, inheriting Escape, outside dismissal, positioning, and focus
return. HotspotArea draws a region on a viewBox="0 0 100 100" SVG overlay.
| ID |
State |
Input |
Outcome |
Evidence |
| H1 |
default |
render |
markers render as named buttons with position variables and states; the image keeps safe src/alt; disabled markers are disabled buttons |
renders positioned marker buttons, a safe image, regions, and typed slot data |
| H2 |
exclusive (default) |
marker click |
opens that marker's content, closes the previous one, toggles closed on a second click; emits update:active and activeChange |
clicking a marker opens its content and exclusive roots close the previous one |
| H3 |
open content |
Escape |
closes the content and returns focus to its marker |
Escape closes the open content and returns focus to its marker |
| H4 |
exclusive=false |
clicks / open() / close() |
several markers stay open; active is the latest; closing it restores the previous one; close() without an id closes all |
non-exclusive roots keep several markers open and track the latest as active |
| H5 |
focused marker |
Arrow keys |
focus moves to the nearest enabled marker inside a 90° cone in that direction; modified keys and dead ends pass through |
arrow keys move focus to the spatially nearest enabled marker |
| H6 |
controlled active |
click / prop change |
emits the request while the controlled marker stays open until the parent accepts it |
controlled active wins until the parent accepts the request |
| H7 |
regions |
click / Enter / hit test |
button regions toggle with aria-pressed; link regions render sanitized href; hitTest() returns areas containing a point |
regions toggle as buttons, render safe links, and support hit testing |
| H8 |
unsafe / disabled |
render / click |
unsafe image sources and links are dropped; disabled regions leave the tab order and ignore activation |
unsafe links and images are not rendered and disabled regions stay inert |
| H9 |
region listener |
activate + preventDefault() |
the toggle is cancelled |
region activate listeners can cancel the toggle |
| H10 |
missing provider |
setup |
markers and areas require HotspotRoot; content requires HotspotMarker |
compound parts require matching providers |
| H11 |
pure geometry |
helpers |
even-odd polygon test, inclusive rect/circle tests, point formatting, clamping, directional search, and href sanitizing are deterministic |
hotspot-geometry.test.ts |
| H12 |
SSR |
isolated requests |
markup is byte-identical, including generated marker ids, the open default marker, and the SVG region |
renders byte-identical hotspot markup across isolated SSR requests |
| H13 |
SSR / hydration |
hydrate |
server markup hydrates without warnings or node replacement |
hydrates hotspot markup without warnings or node replacement |
| H14 |
types |
compile |
hotspot payloads flow through HotspotSlotState<Data>; states, shapes, and exposes are closed and read-only |
hotspot.types.test-d.ts |
HotspotContent defaults closeOnFocusOutside to false so that focus returning
to one marker never closes another open marker.