Reported as "quite a few options don't function on mobile", and measured in a real touch emulation rather than a narrow window — the distinction matters, because two of the three causes need `pointer: coarse` to appear at all. Three causes, one theme: the chrome was built for a pointer that hovers. The toolbar reveals itself on :hover — flex-wrap: wrap, overflow: visible. A touchscreen never hovers, so that rule never fired and the row stayed clipped at overflow: hidden permanently. Fourteen of its twenty-three controls could not be reached by any gesture: every heading, both lists, all three alignments, link, image, table, outline, and both AI passes. The faded right edge promised there was more that way, and there was no way. It now scrolls sideways on a coarse pointer, keeping every control, exactly as .petal-chrome-strip does one line above it — the same trade that comment already argued for, applied to the row it was comparing itself to. That move was only safe once the panels could get out. Every dropdown in the editor chrome — tone, export, colour, highlight, size — was already broken on a phone for a subtler reason: `position: fixed` is relative to the viewport only while no ancestor establishes a containing block, and mask-image does, exactly like transform. Both scrollers fade their edges with a mask. So on any screen narrow enough for the fade to appear, the menu was pulled back inside the very box it was escaping, painted under the toolbar and untappable. The comment in anchoredMenu.ts said "nothing clips a fixed box unless an ancestor has a transform, and none of the editor's chrome does"; it was true when it was written and had quietly stopped being true. The panels now portal to <body>, where nothing above them can clip, stack over or contain them whatever the chrome does with masks later, and the outside-tap tests ask about both halves. The kitten took the last two. She covered "Español" and "I am learning Português" in the drawer, and "Hide falling petals" in the status bar outright — three sample points across it, all three landing on the cat. She already knows how to yield: useCardOverlap fades her for cards and for anything with the modal role, and the drawer is the one overlay that has neither, being navigation rather than something opened on purpose. It is named there now. The status bar is a different problem — it is always present, so yielding to it would mean fading forever — and the honest fix is to sit above it on a narrow screen instead of negotiating with it every frame. Measuring, not eyeballing: a sweep of every visible, enabled control reports zero off-screen and zero blocked, against fourteen and three before. Desktop is deliberately untouched — at rest the slim clipped line with its mask, on hover the wrapped row with all twenty-three buttons on screen — and the edge measurement ChromeStrip already did is now shared with the toolbar rather than written twice. Claude-Session: https://claude.ai/code/session_01GJHNvirh7Hzhc9RL3HAvz7
36 lines
1.4 KiB
TypeScript
36 lines
1.4 KiB
TypeScript
import { useScrollEdge } from './useScrollEdge'
|
|
|
|
// ChromeStrip is the row of document pills (tone, history, export) on a screen
|
|
// too narrow to hold them. It scrolls within itself rather than letting the
|
|
// overflow escape into the editor pane, and it says which way the rest of the
|
|
// row is.
|
|
//
|
|
// Saying so matters more here than in most scrollers: the pills are the only
|
|
// way to reach three features, and a pill that has scrolled out of sight is
|
|
// indistinguishable from a pill that doesn't exist. So each edge with more
|
|
// behind it fades — the same "there is more this way" language .petal-toolbar
|
|
// speaks with its clipped right edge, except this row can be scrolled from
|
|
// either end and has to point in the right direction.
|
|
//
|
|
// The fade is a mask rather than a gradient overlay so it works on whatever is
|
|
// behind it (the cream page, the night theme, a falling petal) without knowing
|
|
// the background colour.
|
|
//
|
|
// The measuring itself lives in useScrollEdge, shared with the formatting
|
|
// toolbar — which has to say the same thing for the same reason.
|
|
|
|
interface Props {
|
|
className?: string
|
|
children: React.ReactNode
|
|
}
|
|
|
|
export function ChromeStrip({ className = '', children }: Props) {
|
|
const { ref, edge } = useScrollEdge<HTMLDivElement>()
|
|
|
|
return (
|
|
<div ref={ref} data-edge={edge} className={`petal-chrome-strip ${className}`}>
|
|
{children}
|
|
</div>
|
|
)
|
|
}
|