Skip to content

Commit

Permalink
Allow setting layers non-interactive (#94)
Browse files Browse the repository at this point in the history
  • Loading branch information
dimfeld authored Oct 23, 2023
1 parent 7a5717e commit 90cde79
Show file tree
Hide file tree
Showing 14 changed files with 114 additions and 30 deletions.
17 changes: 17 additions & 0 deletions .changeset/short-balloons-pump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
'svelte-maplibre': major
---

Allow setting layers to be non-interactive. Layers with `interactive={false}` will not emit mouse events, and will not
participate in hit testing when comparing to other layers with `eventsIfTopMost`.

This is useful, for example, when placing a SymbolLayer on top of a
CircleLayer. See the updated "Clusters and Popups" example; previous the popup would disappear when the mouse was over
the labels, but not it does not.

This is a breaking change:

- The `interactive` prop for `Marker` and `MarkerLayer` has been renamed to `asButton`, to make room for the new
`interactive` prop.
- DeckGlLayer still continues to allow the `pickable` prop, but `interactive` should be used instead for consistency.
The behavior here is unchanged though.
3 changes: 3 additions & 0 deletions src/lib/CircleLayer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
export let manageHoverState = false;
export let hovered: Feature | null = null;
export let eventsIfTopMost = false;
/** Handle mouse events on this layer. */
export let interactive = true;
</script>

<Layer
Expand All @@ -45,6 +47,7 @@
{hoverCursor}
{manageHoverState}
{eventsIfTopMost}
{interactive}
bind:hovered
on:click
on:dblclick
Expand Down
21 changes: 14 additions & 7 deletions src/lib/DeckGlLayer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@
export let minzoom: number | undefined = undefined;
export let maxzoom: number | undefined = undefined;
export let visible = true;
export let pickable = true;
/** Whether to handle mouse events on this layer.
* @deprecated Use `interactive` instead. */
export let pickable: boolean | undefined = undefined;
/** Handle mouse events on this layer. */
export let interactive = true;
/** This indicates the currently hovered feature. Setting this attribute has no effect. */
export let hovered: DATA | null = null;
Expand Down Expand Up @@ -55,7 +59,7 @@
...$$restProps,
visible: visibility,
data,
pickable,
pickable: pickable ?? interactive,
onClick: handleClick,
onHover: handleHover,
};
Expand All @@ -66,12 +70,11 @@
}
}
// Need a way to pass events down to the popup.
// Don't set popupTarget and instead set some kind of event store
// that will set the event the popup is handling. Consider moving
// all popup event handling to this model.
// Convert marker to use layer event instead of special hover store
function handleClick(e: DeckGlMouseEvent<DATA>) {
if (!interactive) {
return;
}
dispatch('click', e);
$layerEvent = {
...e,
Expand All @@ -81,6 +84,10 @@
}
function handleHover(e: DeckGlMouseEvent<DATA>) {
if (!interactive) {
return;
}
const type = e.index !== -1 ? 'mousemove' : 'mouseleave';
hovered = e.object ?? null;
dispatch(type, e);
Expand Down
3 changes: 3 additions & 0 deletions src/lib/FillExtrusionLayer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
export let manageHoverState = false;
export let hovered: Feature | null = null;
export let eventsIfTopMost = false;
/** Handle mouse events on this layer. */
export let interactive = true;
</script>

<Layer
Expand All @@ -43,6 +45,7 @@
{hoverCursor}
{manageHoverState}
{eventsIfTopMost}
{interactive}
bind:hovered
on:click
on:dblclick
Expand Down
3 changes: 3 additions & 0 deletions src/lib/FillLayer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
export let manageHoverState = false;
export let hovered: Feature | null = null;
export let eventsIfTopMost = false;
/** Handle mouse events on this layer. */
export let interactive = true;
</script>

<Layer
Expand All @@ -43,6 +45,7 @@
{hoverCursor}
{manageHoverState}
{eventsIfTopMost}
{interactive}
bind:hovered
on:click
on:dblclick
Expand Down
3 changes: 3 additions & 0 deletions src/lib/HeatmapLayer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
export let manageHoverState = false;
export let hovered: Feature | null = null;
export let eventsIfTopMost = false;
/** Handle mouse events on this layer. */
export let interactive = true;
</script>

<Layer
Expand All @@ -43,6 +45,7 @@
{hoverCursor}
{manageHoverState}
{eventsIfTopMost}
{interactive}
bind:hovered
on:click
on:dblclick
Expand Down
25 changes: 21 additions & 4 deletions src/lib/Layer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import type { LayerClickInfo } from './types.js';
import flush from 'just-flush';
import { onDestroy, createEventDispatcher } from 'svelte';
import { MapMouseEvent, type MapGeoJSONFeature } from 'maplibre-gl';
import type { MapMouseEvent, MapGeoJSONFeature } from 'maplibre-gl';
export let id = getId('layer');
/** Set the source for this layer. This can be omitted when the Layer is created in the slot
Expand Down Expand Up @@ -34,6 +34,8 @@
export let manageHoverState = false;
/** The feature currently being hovered. */
export let hovered: GeoJSON.Feature | null = null;
/** Handle mouse events on this layer. */
export let interactive = true;
export let hoverCursor: string | undefined = undefined;
Expand All @@ -58,13 +60,21 @@
minzoom: minZoomContext,
maxzoom: maxZoomContext,
eventTopMost,
layerInfo,
} = updatedLayerContext();
$: actualMinZoom = minzoom ?? $minZoomContext;
$: actualMaxZoom = maxzoom ?? $maxZoomContext;
$: if ($layer) {
layerInfo.set($layer, {
interactive,
});
}
onDestroy(() => {
if ($layer && $map) {
layerInfo.delete($layer);
$map?.removeLayer($layer);
}
});
Expand All @@ -74,6 +84,9 @@
let hoverFeatureId: string | number | undefined = undefined;
$: if ($map && $layer !== id && actualSource) {
if ($layer) {
layerInfo.delete($layer);
}
let actualBeforeId = beforeId;
if (!beforeId && beforeLayerType) {
let layers = $map.getStyle().layers;
Expand Down Expand Up @@ -104,7 +117,7 @@
);
function handleClick(e: MapMouseEvent & { features?: MapGeoJSONFeature[] }) {
if (!$layer || !$map) {
if (!interactive || !$layer || !$map) {
return;
}
Expand All @@ -131,7 +144,7 @@
$map.on('contextmenu', $layer, handleClick);
$map.on('mouseenter', $layer, (e) => {
if (!$layer || !$map) {
if (!interactive || !$layer || !$map) {
return;
}
Expand Down Expand Up @@ -160,6 +173,10 @@
});
$map.on('mousemove', $layer, (e) => {
if (!interactive) {
return;
}
if (eventsIfTopMost && eventTopMost(e) !== $layer) {
hovered = null;
if (manageHoverState && hoverFeatureId !== undefined) {
Expand Down Expand Up @@ -211,7 +228,7 @@
});
$map.on('mouseleave', $layer, (e) => {
if (!$layer || !$map) {
if (!interactive || !$layer || !$map) {
return;
}
Expand Down
3 changes: 3 additions & 0 deletions src/lib/LineLayer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
export let manageHoverState = false;
export let hovered: Feature | null = null;
export let eventsIfTopMost = false;
/** Handle mouse events on this layer. */
export let interactive = true;
</script>

<Layer
Expand All @@ -43,6 +45,7 @@
{hoverCursor}
{manageHoverState}
{eventsIfTopMost}
{interactive}
bind:hovered
on:click
on:dblclick
Expand Down
13 changes: 9 additions & 4 deletions src/lib/Marker.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@
export let lngLat: LngLatLike;
let classNames: string | undefined = undefined;
export { classNames as class };
/** If interactive is true (default), it will render as a `button`. If not,
* it will render as a `div` element. */
/** Handle mouse events */
export let interactive = true;
/** Make markers tabbable and add the button role. */
export let asButton = false;
export let draggable = false;
/** A GeoJSON Feature related to the point. This is only actually used to send an ID and set of properties along with
* the event, and can be safely omitted. The `lngLat` prop controls the marker's location even if this is provided. */
Expand Down Expand Up @@ -106,6 +107,10 @@
}
function sendEvent(eventName: string) {
if (!interactive) {
return;
}
let loc = $marker?.getLngLat();
if (!loc) {
return;
Expand Down Expand Up @@ -144,8 +149,8 @@
use:addMarker
use:manageClasses={classNames}
style:z-index={zIndex}
tabindex={interactive ? 0 : undefined}
role={interactive ? 'button' : undefined}
tabindex={asButton ? 0 : undefined}
role={asButton ? 'button' : undefined}
on:click={() => sendEvent('click')}
on:dblclick={() => sendEvent('dblclick')}
on:contextmenu={() => sendEvent('contextmenu')}
Expand Down
8 changes: 5 additions & 3 deletions src/lib/MarkerLayer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@
/** How to calculate the coordinates of the marker.
* @default Calls d3.geoCentroid` on the feature. */
export let markerLngLat: (feature: Feature) => [number, number] = geoCentroid;
/** If interactive is true (default), the markers will render as `button`. If not,
* they will render as `div` elements. */
export let interactive = false;
/** Handle mouse events */
export let interactive = true;
/** Make markers tabbable and add the button role. */
export let asButton = false;
export let draggable = false;
export let minzoom: number | undefined = undefined;
export let maxzoom: number | undefined = undefined;
Expand Down Expand Up @@ -135,6 +136,7 @@ the map as a layer. Markers for non-point features are placed at the geometry's
{@const c = markerLngLat(feature)}
{@const z = typeof zIndex === 'function' ? zIndex(feature) : zIndex}
<Marker
{asButton}
{interactive}
{draggable}
class={className}
Expand Down
3 changes: 3 additions & 0 deletions src/lib/SymbolLayer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
export let manageHoverState = false;
export let hovered: Feature | null = null;
export let eventsIfTopMost = false;
/** Handle mouse events on this layer. */
export let interactive = true;
</script>

<Layer
Expand All @@ -45,6 +47,7 @@
{hoverCursor}
{manageHoverState}
{eventsIfTopMost}
{interactive}
bind:hovered
on:click
on:dblclick
Expand Down
Loading

1 comment on commit 90cde79

@vercel
Copy link

@vercel vercel bot commented on 90cde79 Oct 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.