Mobile follow up number 3 #76

Closed
opened 2026-04-08 16:08:06 +00:00 by maxtkc · 0 comments
Owner

Summary

Follow-up polish pass on mobile support (#74). Fixes four concrete issues: (1) The bottom sheet height overshoots on Firefox mobile (bottom URL bar) and occasionally on Chrome because window.innerHeight returns the layout viewport, not the visual viewport. Fix: use dvh CSS units directly in the inline style.height assignments so the browser tracks the dynamic viewport automatically — no JS listeners needed. (2) --dock-height is set to 4rem fallback because syncDockHeight() runs before the dock has laid out. Fix: replace with a ResizeObserver on #mobile-dock, which fires after first paint and on every subsequent resize. This also fixes the basemap-control buttons that sit behind the dock (they use calc(var(--dock-height) + 10px)). (3) The browse tab's tab-content div has no height, so when object-details-view is shown (which uses h-full flex flex-col), the scroll container collapses to content height instead of filling the panel. Fix: one CSS rule in main.css. (4) The "Load" dropdown has z-[1] (loses to the z-40 map controls overlay) and the Load button shows a download arrow instead of an upload arrow.

Approach: minimal targeted changes, no refactors.

Relevant Context

  • src/modules/bottom-sheet.ts — all window.innerHeight usages are at lines 88, 139, 140, 172, 173; syncDockHeight() is the private method at line 39; the ResizeObserver should replace the syncDockHeight() call in the constructor (line 25) and the this.syncDockHeight() call in the resize listener (line 33)
  • src/styles/main.css:29–69 — the @media (max-width: 767px) block with --dock-height: 4rem fallback and .basemap-control override
  • src/index.html:163–189 — the Load dropdown: #load-btn (SVG at lines 170–183) and #load-dropdown (z-[1] at line 189)
  • src/index.html:422–503 — right-panel tabs; tab-content div at lines 433 and 513 and 559 has no height class; children browse-list-view / object-details-view have h-full
  • dvh unit: 1dvh = 1% of the dynamic viewport height, which tracks the browser chrome automatically. Supported in all modern browsers. Assign as a string literal to element.style.height.

Phase 1: dvh Heights + ResizeObserver for Dock

Goal: fix the panel height overshooting the visual viewport (Firefox bottom URL bar, Chrome 1mm scroll), and fix --dock-height being stale at startup (which also fixes basemap buttons behind the dock).

src/modules/bottom-sheet.ts

  • In setSnap, replace the pixel height computation with dvh strings:

    const h =
      snap === 'closed'
        ? '0px'
        : snap === 'half'
          ? `${HALF_VH * 100}dvh`
          : `${FULL_VH * 100}dvh`;
    this.panel.style.height = h;
    

    dvh is a live CSS value — the browser recalculates it automatically when the URL bar slides in/out, with no JS event needed.

  • In onMove (drag handler), replace window.innerHeight * FULL_VH with (window.visualViewport?.height ?? window.innerHeight) * FULL_VH for the drag cap:

    const maxH = (window.visualViewport?.height ?? window.innerHeight) * FULL_VH;
    const newHeight = Math.min(maxH, Math.max(CLOSED_PX, startHeight + delta));
    

    During a live drag the height is set as pixels, so we need the current visual viewport height at that moment.

  • In resolveSnap, replace both window.innerHeight references with (window.visualViewport?.height ?? window.innerHeight):

    const halfH = (window.visualViewport?.height ?? window.innerHeight) * HALF_VH;
    const fullH = (window.visualViewport?.height ?? window.innerHeight) * FULL_VH;
    
  • Remove the syncDockHeight() private method entirely.

  • In the constructor (after setSnap('closed', false)), replace the this.syncDockHeight() call with a ResizeObserver on the dock:

    const dock = document.getElementById('mobile-dock');
    if (dock) {
      new ResizeObserver(() => {
        const h = dock.getBoundingClientRect().height;
        if (h > 0) {
          document.documentElement.style.setProperty('--dock-height', `${h}px`);
        }
      }).observe(dock);
    }
    

    ResizeObserver fires after the first layout, so it always gets the real rendered height (including env(safe-area-inset-bottom) padding). It also fires on orientation change, which covers the resize case.

  • In the window.addEventListener('resize', ...) handler, remove the this.syncDockHeight() call (ResizeObserver handles it). The handler becomes:

    window.addEventListener('resize', () => {
      if (window.innerWidth >= 768) {
        panel.style.removeProperty('height');
        panel.classList.remove('sheet-full', 'sheet-half');
      } else {
        this.setSnap(this.snap, false);
      }
    });
    

Gotcha: ResizeObserver callback fires asynchronously after layout, so on the very first frame the CSS fallback --dock-height: 4rem is used. This is fine — the observer fires almost immediately and the panel isn't visible at snap=closed. The basemap control's calc(var(--dock-height) + 10px) will also update correctly once the observer fires.

Gotcha: dvh is not supported in very old browsers but is supported in all evergreen browsers (Chrome 108+, Firefox 110+, Safari 15.4+). This is acceptable.

Phase 2: Browse Panel Height Fix

Goal: prevent the object-details-view scroll container from collapsing to content height when an object is selected on the map.

Root cause: DaisyUI's .tab-content renders inside a CSS grid (.tabs) but has no explicit height, so h-full children resolve against a zero-height ancestor. The right panel is correctly sized (flex-1 min-h-0), but the height doesn't propagate through to .tab-content.

src/styles/main.css

  • Inside @layer components (or directly in the file after the mobile media query block), add:
    #right-panel .tab-content {
      height: 100%;
    }
    
    This scopes the fix to the right panel only, avoiding unintended side effects on other tabs components elsewhere.

Gotcha: overflow-y-auto is already on .tab-content in the HTML. Combined with height: 100%, the content will scroll within the panel rather than expanding past it.

Gotcha: "sometimes" behavior — the collapse happens when object-details-view is shown (flex-col with flex-1 overflow-y-auto children) because the flex-1 child cannot resolve its height without a parent height anchor. The list view doesn't exhibit this because its content naturally fills the space.

Phase 3: Load Button Icon + Dropdown Z-index

Goal: show the correct upload icon on the Load button; make the Load dropdown appear above the map-controls overlay.

src/index.html

  • On #load-dropdown (line 189), change z-[1] to z-[50]:

    class="dropdown-content z-[50] menu p-2 shadow bg-base-100 rounded-box w-52"
    

    The #map-controls div is z-40; raising the dropdown to z-[50] makes it appear above the search and tool buttons.

  • In #load-btn (lines 170–183), replace the download-arrow SVG path with the upload-arrow path. The current path (M9 19l3 3m0 0l3-3m-3 3V10) draws a downward arrow. Replace only that one <path> element:

    <path
      stroke-linecap="round"
      stroke-linejoin="round"
      stroke-width="2"
      d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"
    />
    

    This matches the upload icon already used on the #upload-btn menu item inside the dropdown. Both web and mobile see the same #load-btn (the text is hidden on mobile via hidden md:inline but the icon is always visible).


Original Issue

Follow-up to the CURRENT_PLAN.md (#74 mobile polish). Issues observed after that pass:

  1. Bottom sheet height is too large on Firefox mobile (bottom URL bar) and slightly too large on Chrome — panel clips off screen or has a small overscroll.
  2. --dock-height is not being calculated correctly at startup on mobile — dock is sometimes inaccessible because the panel overlaps it; basemap control buttons are behind the dock as a consequence.
  3. Load feed dropdown appears behind the map search/tool buttons (z-index too low).
  4. Load button shows a download arrow instead of an upload arrow (both web and mobile).
  5. After clicking a map object, the browse tab's scroll container collapses to a small box instead of filling the panel.
## Summary Follow-up polish pass on mobile support (#74). Fixes four concrete issues: (1) The bottom sheet height overshoots on Firefox mobile (bottom URL bar) and occasionally on Chrome because `window.innerHeight` returns the layout viewport, not the visual viewport. Fix: use `dvh` CSS units directly in the inline `style.height` assignments so the browser tracks the dynamic viewport automatically — no JS listeners needed. (2) `--dock-height` is set to `4rem` fallback because `syncDockHeight()` runs before the dock has laid out. Fix: replace with a `ResizeObserver` on `#mobile-dock`, which fires after first paint and on every subsequent resize. This also fixes the basemap-control buttons that sit behind the dock (they use `calc(var(--dock-height) + 10px)`). (3) The browse tab's `tab-content` div has no height, so when `object-details-view` is shown (which uses `h-full flex flex-col`), the scroll container collapses to content height instead of filling the panel. Fix: one CSS rule in `main.css`. (4) The "Load" dropdown has `z-[1]` (loses to the `z-40` map controls overlay) and the Load button shows a download arrow instead of an upload arrow. Approach: minimal targeted changes, no refactors. ## Relevant Context - `src/modules/bottom-sheet.ts` — all `window.innerHeight` usages are at lines 88, 139, 140, 172, 173; `syncDockHeight()` is the private method at line 39; the `ResizeObserver` should replace the `syncDockHeight()` call in the constructor (line 25) and the `this.syncDockHeight()` call in the resize listener (line 33) - `src/styles/main.css:29–69` — the `@media (max-width: 767px)` block with `--dock-height: 4rem` fallback and `.basemap-control` override - `src/index.html:163–189` — the Load dropdown: `#load-btn` (SVG at lines 170–183) and `#load-dropdown` (`z-[1]` at line 189) - `src/index.html:422–503` — right-panel tabs; `tab-content` div at lines 433 and 513 and 559 has no height class; children `browse-list-view` / `object-details-view` have `h-full` - `dvh` unit: `1dvh = 1% of the dynamic viewport height`, which tracks the browser chrome automatically. Supported in all modern browsers. Assign as a string literal to `element.style.height`. ## Phase 1: dvh Heights + ResizeObserver for Dock Goal: fix the panel height overshooting the visual viewport (Firefox bottom URL bar, Chrome 1mm scroll), and fix `--dock-height` being stale at startup (which also fixes basemap buttons behind the dock). **`src/modules/bottom-sheet.ts`** - [x] In `setSnap`, replace the pixel height computation with `dvh` strings: ```typescript const h = snap === 'closed' ? '0px' : snap === 'half' ? `${HALF_VH * 100}dvh` : `${FULL_VH * 100}dvh`; this.panel.style.height = h; ``` `dvh` is a live CSS value — the browser recalculates it automatically when the URL bar slides in/out, with no JS event needed. - [x] In `onMove` (drag handler), replace `window.innerHeight * FULL_VH` with `(window.visualViewport?.height ?? window.innerHeight) * FULL_VH` for the drag cap: ```typescript const maxH = (window.visualViewport?.height ?? window.innerHeight) * FULL_VH; const newHeight = Math.min(maxH, Math.max(CLOSED_PX, startHeight + delta)); ``` During a live drag the height is set as pixels, so we need the current visual viewport height at that moment. - [x] In `resolveSnap`, replace both `window.innerHeight` references with `(window.visualViewport?.height ?? window.innerHeight)`: ```typescript const halfH = (window.visualViewport?.height ?? window.innerHeight) * HALF_VH; const fullH = (window.visualViewport?.height ?? window.innerHeight) * FULL_VH; ``` - [x] Remove the `syncDockHeight()` private method entirely. - [x] In the constructor (after `setSnap('closed', false)`), replace the `this.syncDockHeight()` call with a `ResizeObserver` on the dock: ```typescript const dock = document.getElementById('mobile-dock'); if (dock) { new ResizeObserver(() => { const h = dock.getBoundingClientRect().height; if (h > 0) { document.documentElement.style.setProperty('--dock-height', `${h}px`); } }).observe(dock); } ``` `ResizeObserver` fires after the first layout, so it always gets the real rendered height (including `env(safe-area-inset-bottom)` padding). It also fires on orientation change, which covers the resize case. - [x] In the `window.addEventListener('resize', ...)` handler, remove the `this.syncDockHeight()` call (ResizeObserver handles it). The handler becomes: ```typescript window.addEventListener('resize', () => { if (window.innerWidth >= 768) { panel.style.removeProperty('height'); panel.classList.remove('sheet-full', 'sheet-half'); } else { this.setSnap(this.snap, false); } }); ``` Gotcha: `ResizeObserver` callback fires asynchronously after layout, so on the very first frame the CSS fallback `--dock-height: 4rem` is used. This is fine — the observer fires almost immediately and the panel isn't visible at `snap=closed`. The basemap control's `calc(var(--dock-height) + 10px)` will also update correctly once the observer fires. Gotcha: `dvh` is not supported in very old browsers but is supported in all evergreen browsers (Chrome 108+, Firefox 110+, Safari 15.4+). This is acceptable. ## Phase 2: Browse Panel Height Fix Goal: prevent the `object-details-view` scroll container from collapsing to content height when an object is selected on the map. Root cause: DaisyUI's `.tab-content` renders inside a CSS grid (`.tabs`) but has no explicit height, so `h-full` children resolve against a zero-height ancestor. The right panel is correctly sized (`flex-1 min-h-0`), but the height doesn't propagate through to `.tab-content`. **`src/styles/main.css`** - [x] Inside `@layer components` (or directly in the file after the mobile media query block), add: ```css #right-panel .tab-content { height: 100%; } ``` This scopes the fix to the right panel only, avoiding unintended side effects on other `tabs` components elsewhere. Gotcha: `overflow-y-auto` is already on `.tab-content` in the HTML. Combined with `height: 100%`, the content will scroll within the panel rather than expanding past it. Gotcha: "sometimes" behavior — the collapse happens when `object-details-view` is shown (flex-col with `flex-1 overflow-y-auto` children) because the flex-1 child cannot resolve its height without a parent height anchor. The list view doesn't exhibit this because its content naturally fills the space. ## Phase 3: Load Button Icon + Dropdown Z-index Goal: show the correct upload icon on the Load button; make the Load dropdown appear above the map-controls overlay. **`src/index.html`** - [x] On `#load-dropdown` (line 189), change `z-[1]` to `z-[50]`: ```html class="dropdown-content z-[50] menu p-2 shadow bg-base-100 rounded-box w-52" ``` The `#map-controls` div is `z-40`; raising the dropdown to `z-[50]` makes it appear above the search and tool buttons. - [x] In `#load-btn` (lines 170–183), replace the download-arrow SVG path with the upload-arrow path. The current path (`M9 19l3 3m0 0l3-3m-3 3V10`) draws a downward arrow. Replace only that one `<path>` element: ```html <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" /> ``` This matches the upload icon already used on the `#upload-btn` menu item inside the dropdown. Both web and mobile see the same `#load-btn` (the text is hidden on mobile via `hidden md:inline` but the icon is always visible). --- ## Original Issue Follow-up to the CURRENT_PLAN.md (#74 mobile polish). Issues observed after that pass: 1. Bottom sheet height is too large on Firefox mobile (bottom URL bar) and slightly too large on Chrome — panel clips off screen or has a small overscroll. 2. `--dock-height` is not being calculated correctly at startup on mobile — dock is sometimes inaccessible because the panel overlaps it; basemap control buttons are behind the dock as a consequence. 3. Load feed dropdown appears behind the map search/tool buttons (z-index too low). 4. Load button shows a download arrow instead of an upload arrow (both web and mobile). 5. After clicking a map object, the browse tab's scroll container collapses to a small box instead of filling the panel.
maxtkc self-assigned this 2026-04-08 16:08:06 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
gtfs.zone/coloring-book#76
No description provided.