From f5feb366b1d281d969a9c4b0cb38cc2a3de57706 Mon Sep 17 00:00:00 2001 From: "Bajohr, Rayk" Date: Thu, 3 Sep 2026 13:44:20 +0200 Subject: [PATCH] docs(maplibre): use buttons as interactive element to trigger popup --- .../e2e/element-examples/maplibre.spec.ts | 38 ++++++++++++ src/app/examples/maplibre/maplibre.html | 60 +++++++++++++++---- src/app/examples/maplibre/maplibre.scss | 14 +++++ src/app/examples/maplibre/maplibre.ts | 26 ++++++++ 4 files changed, 125 insertions(+), 13 deletions(-) create mode 100644 src/app/examples/maplibre/maplibre.scss diff --git a/playwright/e2e/element-examples/maplibre.spec.ts b/playwright/e2e/element-examples/maplibre.spec.ts index 24fff21e1f..42982273df 100644 --- a/playwright/e2e/element-examples/maplibre.spec.ts +++ b/playwright/e2e/element-examples/maplibre.spec.ts @@ -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(); +}); diff --git a/src/app/examples/maplibre/maplibre.html b/src/app/examples/maplibre/maplibre.html index 04161a2b56..92e2818aa4 100644 --- a/src/app/examples/maplibre/maplibre.html +++ b/src/app/examples/maplibre/maplibre.html @@ -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. --> - - + + - -
-
Name
-
{{ point.properties.name }}
-
Description
-
{{ point.properties.description }}
-
Status
-
{{ point.properties.status }}
-
Coordinates
-
{{ point.geometry.coordinates[0] }}, {{ point.geometry.coordinates[1] }}
-
+ } + @if (selectedPoint(); as point) { + + } diff --git a/src/app/examples/maplibre/maplibre.scss b/src/app/examples/maplibre/maplibre.scss new file mode 100644 index 0000000000..ba83eba117 --- /dev/null +++ b/src/app/examples/maplibre/maplibre.scss @@ -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; +} diff --git a/src/app/examples/maplibre/maplibre.ts b/src/app/examples/maplibre/maplibre.ts index 74ea4b9466..3f648a1881 100644 --- a/src/app/examples/maplibre/maplibre.ts +++ b/src/app/examples/maplibre/maplibre.ts @@ -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' } @@ -80,6 +81,31 @@ export class SampleComponent { buildPoint([11.37774, 48.288848], 'caution'), buildPoint([11.52774, 48.280848], 'critical') ]); + protected readonly selectedPoint = signal(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) {