Drawing toolbar: restore the two-row wrap on phones

The toolbar still did not paint on iPhone in portrait. v17 removed the
backdrop-filter from the scrolling strip but kept the horizontal scroll, and
that was the wrong half to keep: on iOS a scroll container is the fragile part,
with or without a blur.

The strip goes back to wrapping onto two rows — the layout that shipped in v8
and worked on these phones for months, before the rail replaced it in v15. That
was a mistake of mine: I swapped a device-tested layout for a scrolling one on
the strength of desktop testing alone. It now has no overflow, no blur and an
opaque background, and pairs `left` with `width` rather than `right`, so there
is nothing exotic left for iOS to disagree with. A comment on the block says so,
because the scrolling version reads as the tidier solution and someone will be
tempted again.

Group headings become hairline dividers on the strip rather than disappearing,
so the Draw / Edit / Shape / File grouping survives the narrower layout.

Building the rail is also wrapped in a fallback now. It is presentation code and
must never be able to take the tools down with it: if it throws, our classes are
removed and ol-ext's default bar renders instead of nothing at all. The
matchMedia listener that would have been the most likely thrower (Safari only
gained MediaQueryList.addEventListener in iOS 14) falls back to addListener.

Verified: wraps to two rows in a 390px container, single row when wider, no
overflow or backdrop-filter in either, and still hides when Draw mode is off.

Service Worker v17 -> v18.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ekke 2026-08-20 16:47:59 +02:00
parent d165b57853
commit 7787f77f80
10 changed files with 101 additions and 66 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
dist/assets/index-BM5P5Dc7.js.map vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

54
dist/index.html vendored
View File

@ -1782,7 +1782,14 @@
@media (max-width: 576px), (max-height: 560px) { @media (max-width: 576px), (max-height: 560px) {
/* `.ol-left` is repeated here for specificity: ol-ext's own /* `.ol-left` is repeated here for specificity: ol-ext's own
`.ol-control.ol-bar.ol-left` sets top/transform, and without matching `.ol-control.ol-bar.ol-left` sets top/transform, and without matching
it the rail stays vertically centred and stretches to `bottom`. */ it the rail stays vertically centred and stretches to `bottom`.
This layout is deliberately plain. An earlier version scrolled
horizontally and blurred its backdrop, and on iOS Safari it did not
paint at all — a scroll container that also has backdrop-filter is a
known way to lose an element entirely. Wrapping onto two rows needs
neither, and it is what shipped before the rail, on the same phones.
Nothing here should be made cleverer without testing on a device. */
.ol-control.ol-bar.ol-editbar.ol-editbar-rail.ol-left { .ol-control.ol-bar.ol-editbar.ol-editbar-rail.ol-left {
/* NOTE: display must NOT be !important — ol-ext toggles the bar with an /* NOTE: display must NOT be !important — ol-ext toggles the bar with an
inline display:none/'' (setVisible). An !important here would beat inline display:none/'' (setVisible). An !important here would beat
@ -1790,45 +1797,38 @@
display: flex; display: flex;
grid-template-columns: none; grid-template-columns: none;
flex-direction: row; flex-direction: row;
flex-wrap: wrap;
align-items: center; align-items: center;
flex-wrap: nowrap; justify-content: center;
row-gap: 3px;
top: auto; top: auto;
bottom: 12px; bottom: 12px;
left: 8px; left: 8px;
right: 8px; /* width rather than `right`, which is the more conventional pairing
with `left` and avoids another layout edge case. */
right: auto;
width: calc(100% - 16px);
transform: none; transform: none;
/* Horizontal scroll replaces the old two-row wrap. Option bars are overflow: visible;
re-anchored below to open upward, so clipping them is not a concern
the way it is on desktop. */
overflow-x: auto;
overflow-y: hidden;
overscroll-behavior-x: contain;
scrollbar-width: none;
/* No backdrop-filter here. On iOS Safari an element that both blurs its
backdrop and scrolls frequently fails to paint at all — which is why
this strip was invisible in portrait while the rail, which does not
scroll, rendered fine in landscape. An opaque background gives the
same legibility without the blur.
`-webkit-overflow-scrolling: touch` is deliberately absent too: it is
obsolete since iOS 13 and implicated in the same class of bug. */
backdrop-filter: none; backdrop-filter: none;
-webkit-backdrop-filter: none; -webkit-backdrop-filter: none;
background: var(--card); background: var(--card);
} }
.ol-control.ol-bar.ol-editbar.ol-editbar-rail.ol-left::-webkit-scrollbar {
display: none;
}
/* Labels become inline separators between bands rather than headings. */ /* Group headings become hairline dividers: the words cost more width than
two rows can spare, but the grouping is still worth showing. */
.ol-editbar-rail .ol-rail-label { .ol-editbar-rail .ol-rail-label {
grid-column: auto; grid-column: auto;
padding: 0 6px 0 3px;
text-align: left;
white-space: nowrap;
flex: none; flex: none;
width: 1px;
height: 24px;
padding: 0;
margin: 0 5px;
font-size: 0;
background: var(--border);
} }
.ol-editbar-rail .ol-rail-label:first-child { .ol-editbar-rail .ol-rail-grip + .ol-rail-label {
padding-left: 0; display: none;
} }
.ol-editbar-rail > .ol-editbar-actions, .ol-editbar-rail > .ol-editbar-actions,
@ -1887,7 +1887,7 @@
} }
</style> </style>
<script type="module" crossorigin src="/assets/index-C3r_p8o6.js"></script> <script type="module" crossorigin src="/assets/index-BM5P5Dc7.js"></script>
<link rel="modulepreload" crossorigin href="/assets/openlayers-J9qS6Th1.js"> <link rel="modulepreload" crossorigin href="/assets/openlayers-J9qS6Th1.js">
<link rel="modulepreload" crossorigin href="/assets/pako-Xa-UToif.js"> <link rel="modulepreload" crossorigin href="/assets/pako-Xa-UToif.js">
<link rel="modulepreload" crossorigin href="/assets/geotiff-BaoeLn6q.js"> <link rel="modulepreload" crossorigin href="/assets/geotiff-BaoeLn6q.js">

10
dist/sw.js vendored
View File

@ -95,7 +95,15 @@
// blur. In landscape the phone is 844px wide, so the width-only breakpoint // blur. In landscape the phone is 844px wide, so the width-only breakpoint
// handed it the 396px-tall rail inside a ~270px map; the layout now // handed it the 396px-tall rail inside a ~270px map; the layout now
// switches on height as well as width. // switches on height as well as width.
const CACHE_VERSION = 'v17'; // v18: The phone toolbar still did not paint on iPhone. Reverted the strip from
// a horizontally scrolling row back to the two-row wrap that shipped
// before the rail and was known to work on those phones — removing the
// overflow, and with it the whole class of iOS scroll-container paint
// bugs, rather than only the backdrop-filter half of it. Group headings
// become hairline dividers so the grouping survives the narrower layout.
// Building the rail is now also wrapped in a fallback: if it throws, the
// default ol-ext bar is shown instead of no bar at all.
const CACHE_VERSION = 'v18';
const SHELL_CACHE = `shell-${CACHE_VERSION}`; const SHELL_CACHE = `shell-${CACHE_VERSION}`;
const MODULES_CACHE = `modules-${CACHE_VERSION}`; const MODULES_CACHE = `modules-${CACHE_VERSION}`;
const API_CACHE = `api-${CACHE_VERSION}`; const API_CACHE = `api-${CACHE_VERSION}`;

View File

@ -1782,7 +1782,14 @@
@media (max-width: 576px), (max-height: 560px) { @media (max-width: 576px), (max-height: 560px) {
/* `.ol-left` is repeated here for specificity: ol-ext's own /* `.ol-left` is repeated here for specificity: ol-ext's own
`.ol-control.ol-bar.ol-left` sets top/transform, and without matching `.ol-control.ol-bar.ol-left` sets top/transform, and without matching
it the rail stays vertically centred and stretches to `bottom`. */ it the rail stays vertically centred and stretches to `bottom`.
This layout is deliberately plain. An earlier version scrolled
horizontally and blurred its backdrop, and on iOS Safari it did not
paint at all — a scroll container that also has backdrop-filter is a
known way to lose an element entirely. Wrapping onto two rows needs
neither, and it is what shipped before the rail, on the same phones.
Nothing here should be made cleverer without testing on a device. */
.ol-control.ol-bar.ol-editbar.ol-editbar-rail.ol-left { .ol-control.ol-bar.ol-editbar.ol-editbar-rail.ol-left {
/* NOTE: display must NOT be !important — ol-ext toggles the bar with an /* NOTE: display must NOT be !important — ol-ext toggles the bar with an
inline display:none/'' (setVisible). An !important here would beat inline display:none/'' (setVisible). An !important here would beat
@ -1790,45 +1797,38 @@
display: flex; display: flex;
grid-template-columns: none; grid-template-columns: none;
flex-direction: row; flex-direction: row;
flex-wrap: wrap;
align-items: center; align-items: center;
flex-wrap: nowrap; justify-content: center;
row-gap: 3px;
top: auto; top: auto;
bottom: 12px; bottom: 12px;
left: 8px; left: 8px;
right: 8px; /* width rather than `right`, which is the more conventional pairing
with `left` and avoids another layout edge case. */
right: auto;
width: calc(100% - 16px);
transform: none; transform: none;
/* Horizontal scroll replaces the old two-row wrap. Option bars are overflow: visible;
re-anchored below to open upward, so clipping them is not a concern
the way it is on desktop. */
overflow-x: auto;
overflow-y: hidden;
overscroll-behavior-x: contain;
scrollbar-width: none;
/* No backdrop-filter here. On iOS Safari an element that both blurs its
backdrop and scrolls frequently fails to paint at all — which is why
this strip was invisible in portrait while the rail, which does not
scroll, rendered fine in landscape. An opaque background gives the
same legibility without the blur.
`-webkit-overflow-scrolling: touch` is deliberately absent too: it is
obsolete since iOS 13 and implicated in the same class of bug. */
backdrop-filter: none; backdrop-filter: none;
-webkit-backdrop-filter: none; -webkit-backdrop-filter: none;
background: var(--card); background: var(--card);
} }
.ol-control.ol-bar.ol-editbar.ol-editbar-rail.ol-left::-webkit-scrollbar {
display: none;
}
/* Labels become inline separators between bands rather than headings. */ /* Group headings become hairline dividers: the words cost more width than
two rows can spare, but the grouping is still worth showing. */
.ol-editbar-rail .ol-rail-label { .ol-editbar-rail .ol-rail-label {
grid-column: auto; grid-column: auto;
padding: 0 6px 0 3px;
text-align: left;
white-space: nowrap;
flex: none; flex: none;
width: 1px;
height: 24px;
padding: 0;
margin: 0 5px;
font-size: 0;
background: var(--border);
} }
.ol-editbar-rail .ol-rail-label:first-child { .ol-editbar-rail .ol-rail-grip + .ol-rail-label {
padding-left: 0; display: none;
} }
.ol-editbar-rail > .ol-editbar-actions, .ol-editbar-rail > .ol-editbar-actions,

View File

@ -95,7 +95,15 @@
// blur. In landscape the phone is 844px wide, so the width-only breakpoint // blur. In landscape the phone is 844px wide, so the width-only breakpoint
// handed it the 396px-tall rail inside a ~270px map; the layout now // handed it the 396px-tall rail inside a ~270px map; the layout now
// switches on height as well as width. // switches on height as well as width.
const CACHE_VERSION = 'v17'; // v18: The phone toolbar still did not paint on iPhone. Reverted the strip from
// a horizontally scrolling row back to the two-row wrap that shipped
// before the rail and was known to work on those phones — removing the
// overflow, and with it the whole class of iOS scroll-container paint
// bugs, rather than only the backdrop-filter half of it. Group headings
// become hairline dividers so the grouping survives the narrower layout.
// Building the rail is now also wrapped in a fallback: if it throws, the
// default ol-ext bar is shown instead of no bar at all.
const CACHE_VERSION = 'v18';
const SHELL_CACHE = `shell-${CACHE_VERSION}`; const SHELL_CACHE = `shell-${CACHE_VERSION}`;
const MODULES_CACHE = `modules-${CACHE_VERSION}`; const MODULES_CACHE = `modules-${CACHE_VERSION}`;
const API_CACHE = `api-${CACHE_VERSION}`; const API_CACHE = `api-${CACHE_VERSION}`;

View File

@ -1075,7 +1075,20 @@ export class MapView {
_buildToolbarRail() { _buildToolbarRail() {
const bar = this.editBar?.element; const bar = this.editBar?.element;
if (!bar) return; if (!bar) return;
try {
this._applyToolbarRail(bar);
} catch (err) {
// This is presentation only. If it fails the toolbar must still work, so
// strip our classes and let ol-ext render its own default bar rather than
// leaving a half-styled one — or, worse, aborting initEditBar() and
// taking every tool with it.
console.warn('[MapView] Could not build the toolbar rail; falling back to the default bar:', err);
bar.classList.remove('ol-left', 'ol-editbar-rail');
}
}
/** @private Does the work for _buildToolbarRail(). */
_applyToolbarRail(bar) {
// `ol-left` is ol-ext's own left-dock class; `ol-editbar-rail` scopes our CSS. // `ol-left` is ol-ext's own left-dock class; `ol-editbar-rail` scopes our CSS.
bar.classList.add('ol-left', 'ol-editbar-rail'); bar.classList.add('ol-left', 'ol-editbar-rail');
@ -1221,8 +1234,14 @@ export class MapView {
}); });
// Crossing the phone breakpoint swaps the layout, so drop or re-apply the // Crossing the phone breakpoint swaps the layout, so drop or re-apply the
// inline position to match. // inline position to match. Safari only gained MediaQueryList.addEventListener
// in iOS 14; fall back rather than throw, because an exception here would
// abort initEditBar() and take the whole toolbar with it.
if (typeof phone.addEventListener === 'function') {
phone.addEventListener('change', restore); phone.addEventListener('change', restore);
} else if (typeof phone.addListener === 'function') {
phone.addListener(restore);
}
restore(); restore();
} }