Skip to content

Commit 99b753c

Browse files
committed
Compass fix 3
1 parent 8b9be51 commit 99b753c

File tree

3 files changed

+93
-17
lines changed

3 files changed

+93
-17
lines changed

src/features/locate/button.vue

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,7 @@ const label = computed(() => {
3636
:aria-pressed="mode !== null && mode !== 'error'"
3737
@click="cycle"
3838
>
39-
<Icon
40-
width="40"
41-
height="40"
42-
:fill="iconColor"
43-
:name="iconName"
44-
/>
39+
<Icon width="40" height="40" :fill="iconColor" :name="iconName" />
4540
<span class="locate-btn__label" :style="{ color: iconColor }">{{
4641
label
4742
}}</span>
@@ -50,7 +45,9 @@ const label = computed(() => {
5045
<!-- Confirmation modal — shown on first use before the browser permission prompt -->
5146
<Teleport to="body">
5247
<template v-if="showConfirmModal">
53-
<div class="modal-backdrop fade show navigator-modal-backdrop"></div>
48+
<div
49+
class="modal-backdrop fade show navigator-modal-backdrop"
50+
></div>
5451
<div
5552
class="modal fade show d-block navigator-modal"
5653
tabindex="-1"
@@ -105,7 +102,9 @@ const label = computed(() => {
105102
<!-- Error modal — shown on permission denied, or when clicking the Error button -->
106103
<Teleport to="body">
107104
<template v-if="showErrorModal">
108-
<div class="modal-backdrop fade show navigator-modal-backdrop"></div>
105+
<div
106+
class="modal-backdrop fade show navigator-modal-backdrop"
107+
></div>
109108
<div
110109
class="modal fade show d-block navigator-modal"
111110
tabindex="-1"

src/features/locate/useLocate.js

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,19 +50,30 @@ export const useLocate = () => {
5050

5151
// ─── Map markers ─────────────────────────────────────────────────────────
5252

53-
const createPositionElement = () => {
53+
/** Create an SVG element that references a sprite symbol via xlink:href.
54+
* Using the DOM API (not innerHTML) ensures the xlink namespace is set
55+
* correctly, which is required for <use> resolution on iOS Safari. */
56+
const createSpriteIcon = (className, symbolId) => {
57+
const NS = "http://www.w3.org/2000/svg";
58+
const XLINK = "http://www.w3.org/1999/xlink";
5459
const el = document.createElement("div");
55-
el.className = "navigator-locate-position";
56-
el.innerHTML = `<svg width="32" height="32" fill="currentColor"><use href="#position"/></svg>`;
60+
el.className = className;
61+
const svg = document.createElementNS(NS, "svg");
62+
svg.setAttribute("width", "32");
63+
svg.setAttribute("height", "32");
64+
svg.setAttribute("fill", "currentColor");
65+
const use = document.createElementNS(NS, "use");
66+
use.setAttributeNS(XLINK, "xlink:href", `#${symbolId}`);
67+
svg.appendChild(use);
68+
el.appendChild(svg);
5769
return el;
5870
};
5971

60-
const createHeadingElement = () => {
61-
const el = document.createElement("div");
62-
el.className = "navigator-locate-heading";
63-
el.innerHTML = `<svg width="32" height="32" fill="currentColor"><use href="#position-heading"/></svg>`;
64-
return el;
65-
};
72+
const createPositionElement = () =>
73+
createSpriteIcon("navigator-locate-position", "position");
74+
75+
const createHeadingElement = () =>
76+
createSpriteIcon("navigator-locate-heading", "position-heading");
6677

6778
/**
6879
* Sync both markers to the current position and compass heading.

tests/e2e/features/locate.spec.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,72 @@ test.describe("Locate / Error state", () => {
269269
// ─── Locate / Heading marker ──────────────────────────────────────────────────
270270

271271
test.describe("Locate / Heading marker", () => {
272+
test("heading marker SVG resolves the sprite symbol via xlink:href", async ({
273+
page,
274+
}) => {
275+
await withGrantedStorage(page);
276+
await grantGeolocation(page);
277+
await page.goto("/");
278+
await page.waitForLoadState("networkidle");
279+
280+
await page.locator("#locate-button").click();
281+
await expect
282+
.poll(() => page.locator(".navigator-locate-position").count(), {
283+
timeout: 5000,
284+
})
285+
.toBeGreaterThan(0);
286+
287+
// Fire an orientation event so the heading marker appears
288+
await page.evaluate(() => {
289+
const event = new DeviceOrientationEvent(
290+
"deviceorientationabsolute",
291+
{ alpha: 0, beta: 0, gamma: 0, absolute: true },
292+
);
293+
window.dispatchEvent(event);
294+
});
295+
296+
const headingEl = page.locator(".navigator-locate-heading");
297+
await expect
298+
.poll(() => headingEl.count(), { timeout: 3000 })
299+
.toBeGreaterThan(0);
300+
301+
// Verify the <use> element uses xlink:href (not plain href) and
302+
// references the correct sprite symbol.
303+
const useInfo = await headingEl.evaluate((el) => {
304+
const use = el.querySelector("use");
305+
if (!use) return { found: false };
306+
return {
307+
found: true,
308+
xlinkHref: use.getAttributeNS(
309+
"http://www.w3.org/1999/xlink",
310+
"href",
311+
),
312+
plainHref: use.getAttribute("href"),
313+
};
314+
});
315+
316+
expect(useInfo.found).toBe(true);
317+
expect(useInfo.xlinkHref).toBe("#position-heading");
318+
319+
// Also verify the position marker uses xlink:href
320+
const posUseInfo = await page
321+
.locator(".navigator-locate-position")
322+
.evaluate((el) => {
323+
const use = el.querySelector("use");
324+
if (!use) return { found: false };
325+
return {
326+
found: true,
327+
xlinkHref: use.getAttributeNS(
328+
"http://www.w3.org/1999/xlink",
329+
"href",
330+
),
331+
};
332+
});
333+
334+
expect(posUseInfo.found).toBe(true);
335+
expect(posUseInfo.xlinkHref).toBe("#position");
336+
});
337+
272338
test("compass heading uses webkitCompassHeading when available (iOS)", async ({
273339
page,
274340
}) => {

0 commit comments

Comments
 (0)