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
38 changes: 38 additions & 0 deletions playwright/e2e/element-examples/maplibre.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,41 @@ test('maplibre/maplibre-cluster', ({ si }) =>
.toEqual({ caution: true, danger: true, success: true });
}
}));

test('maplibre/maplibre marker accessibility', async ({ page, si }) => {
await si.visitExample('maplibre/maplibre');

const marker = page.locator('.maplibregl-marker').first();
const markerButton = marker.getByRole('button');

await expect(markerButton).toBeVisible();
await expect(page.locator('.maplibregl-marker[tabindex]')).toHaveCount(0);
await expect(markerButton).toHaveAttribute('aria-label', /Show details for/);
await expect(markerButton).toHaveAttribute('aria-haspopup', 'dialog');
await expect(markerButton).toHaveAttribute('aria-expanded', 'false');

await markerButton.click();

const popup = page.locator('.maplibregl-popup');
await expect(popup.getByRole('dialog')).toBeVisible();
await expect(markerButton).toHaveAttribute('aria-expanded', 'true');
await expect(markerButton).toHaveAttribute('aria-controls', 'marker-details-dialog');
await expect(page.locator('.maplibregl-marker[tabindex]')).toHaveCount(0);

const closeButton = popup.getByRole('button', { name: 'Close popup' });
await expect(closeButton).toBeFocused();

await closeButton.click();

await expect(popup).toHaveCount(0);
await expect(markerButton).toHaveAttribute('aria-expanded', 'false');
await expect(markerButton).toBeFocused();

await markerButton.press('Space');
await expect(popup.getByRole('dialog')).toBeVisible();

await popup.getByRole('button', { name: 'Close popup' }).press('Escape');

await expect(popup).toHaveCount(0);
await expect(markerButton).toBeFocused();
});
60 changes: 47 additions & 13 deletions src/app/examples/maplibre/maplibre.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,54 @@
Only use this if you really need to use HTML/Angular component to render your symbol.
These markers are slow compared to a Layer of symbol because they're not rendered using WebGL.
-->
<mgl-marker #marker [feature]="point">
<si-status-marker [status]="point.properties.status" />
<mgl-marker [feature]="point">
<button
#markerButton
type="button"
class="marker-button"
aria-haspopup="dialog"
[ariaLabel]="`Show details for ${point.properties.name}, status ${point.properties.status}`"
[ariaExpanded]="selectedPoint() === point"
[attr.aria-controls]="selectedPoint() === point ? 'marker-details-dialog' : undefined"
(click)="togglePopup(point, markerButton, $event)"
>
<si-status-marker [status]="point.properties.status" />
</button>
</mgl-marker>
<mgl-popup [marker]="marker">
<dl class="mb-0">
<dt>Name</dt>
<dd>{{ point.properties.name }}</dd>
<dt>Description</dt>
<dd>{{ point.properties.description }}</dd>
<dt>Status</dt>
<dd>{{ point.properties.status }}</dd>
<dt>Coordinates</dt>
<dd>{{ point.geometry.coordinates[0] }}, {{ point.geometry.coordinates[1] }}</dd>
</dl>
}
@if (selectedPoint(); as point) {
<mgl-popup
[feature]="point"
[closeButton]="false"
[focusAfterOpen]="true"
(popupClose)="closePopup()"
>
<div
id="marker-details-dialog"
role="dialog"
aria-labelledby="marker-details-title"
(keydown.escape)="closePopup($event)"
>
<div class="d-flex align-items-center justify-content-between mb-4">
<h2 id="marker-details-title" class="si-h5 mb-0">{{ point.properties.name }}</h2>
<button
type="button"
class="btn btn-icon btn-sm btn-tertiary-ghost element-cancel"
[attr.aria-label]="mapTranslations()['Popup.Close']"
(click)="closePopup($event)"
></button>
</div>
<dl class="mb-0">
<dt>Name</dt>
<dd>{{ point.properties.name }}</dd>
<dt>Description</dt>
<dd>{{ point.properties.description }}</dd>
<dt>Status</dt>
<dd>{{ point.properties.status }}</dd>
<dt>Coordinates</dt>
<dd>{{ point.geometry.coordinates[0] }}, {{ point.geometry.coordinates[1] }}</dd>
</dl>
</div>
</mgl-popup>
}
</mgl-map>
14 changes: 14 additions & 0 deletions src/app/examples/maplibre/maplibre.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Copyright (c) Siemens 2016 - 2026
* SPDX-License-Identifier: MIT
*/

.marker-button {
display: block;
inline-size: 42px;
block-size: 42px;
padding: 0;
border: 0;
background: transparent;
color: inherit;
}
26 changes: 26 additions & 0 deletions src/app/examples/maplibre/maplibre.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ const buildPoint = (coordinates: number[], status: MarkerStatus): StatusPoint =>
SiStatusMarkerComponent
],
templateUrl: './maplibre.html',
styleUrl: './maplibre.scss',
host: {
class: 'h-100 d-flex flex-column p-5'
}
Expand All @@ -80,6 +81,31 @@ export class SampleComponent {
buildPoint([11.37774, 48.288848], 'caution'),
buildPoint([11.52774, 48.280848], 'critical')
]);
protected readonly selectedPoint = signal<StatusPoint | undefined>(undefined);

private popupTrigger: HTMLButtonElement | undefined;

protected togglePopup(point: StatusPoint, trigger: HTMLButtonElement, event: MouseEvent): void {
event.stopPropagation();

if (this.selectedPoint() === point) {
this.closePopup();
return;
}

this.popupTrigger = trigger;
this.selectedPoint.set(point);
}

protected closePopup(event?: Event): void {
event?.stopPropagation();

const trigger = this.popupTrigger;
this.popupTrigger = undefined;
this.selectedPoint.set(undefined);

queueMicrotask(() => trigger?.focus());
}

protected onError(event: ErrorEvent & EventData): void {
if (event.error.message) {
Expand Down
Loading