@jsenv/navi 0.29.10 → 0.29.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/jsenv_navi.js +237 -34
- package/dist/jsenv_navi.js.map +13 -11
- package/docs/MOBILE_LAYOUT_PITFALLS.md +69 -14
- package/package.json +1 -1
|
@@ -31,15 +31,15 @@ or completely invisible.
|
|
|
31
31
|
which triggers the browser's native `scrollIntoView`. So after opening, the page automatically
|
|
32
32
|
scrolls to bring the dialog into view — scrolling the user away from where they were.
|
|
33
33
|
|
|
34
|
-
### The fix:
|
|
34
|
+
### The fix: clip the horizontal overflow in a wrapper
|
|
35
35
|
|
|
36
36
|
The root cause is the document overflowing horizontally. The fix is to **never let the
|
|
37
|
-
document itself overflow in X** — instead, contain
|
|
37
|
+
document itself overflow in X** — instead, contain the overflow inside a child wrapper.
|
|
38
38
|
|
|
39
39
|
```html
|
|
40
40
|
<body>
|
|
41
|
-
<!-- This wrapper
|
|
42
|
-
<div style="overflow-x:
|
|
41
|
+
<!-- This wrapper absorbs the horizontal overflow of the whole app -->
|
|
42
|
+
<div style="overflow-x: clip;">
|
|
43
43
|
<!-- all app content goes here -->
|
|
44
44
|
</div>
|
|
45
45
|
|
|
@@ -54,20 +54,74 @@ With this structure:
|
|
|
54
54
|
- `position: fixed; margin: auto` centers the dialog correctly
|
|
55
55
|
- No ghost empty space at the bottom
|
|
56
56
|
|
|
57
|
-
As a safety net, add `overflow-x:
|
|
57
|
+
As a safety net, add `overflow-x: clip` on `html` and `body` to prevent any content
|
|
58
58
|
that forgets to use a wrapper from inflating the layout viewport:
|
|
59
59
|
|
|
60
60
|
```css
|
|
61
61
|
html,
|
|
62
62
|
body {
|
|
63
|
-
overflow-x:
|
|
63
|
+
overflow-x: clip;
|
|
64
64
|
}
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
67
|
+
What it costs: content wider than the screen is cut off instead of reachable by dragging.
|
|
68
|
+
When some element genuinely needs to be scrolled horizontally (a wide table, a carousel),
|
|
69
|
+
give **that element** its own `overflow-x: auto` — the wrapper stays `clip`.
|
|
70
|
+
|
|
71
|
+
### Why `clip` and not `auto` or `hidden`
|
|
72
|
+
|
|
73
|
+
`clip` is the only value that clips without turning the box into a **scroll container**.
|
|
74
|
+
`auto`, `scroll` and `hidden` all create one, and that has two consequences that show up
|
|
75
|
+
far from the wrapper:
|
|
76
|
+
|
|
77
|
+
- **Every `position: sticky` in the app sticks to that wrapper**, because sticky resolves
|
|
78
|
+
against the nearest scroll container in the DOM — not against the box the app considers
|
|
79
|
+
its scroller. The wrapper grows with its content and never scrolls, so nothing sticks
|
|
80
|
+
anymore: sticky headers and `<List groupBy>` group labels just scroll away with the
|
|
81
|
+
content.
|
|
82
|
+
- **Worse than not sticking: the sticky element is offset downwards.** The rectangle a
|
|
83
|
+
sticky element sticks within is the scroll container's box shrunk by its
|
|
84
|
+
`scroll-padding` (CSS Position L3), and Chromium applies that for an element scroll
|
|
85
|
+
container. A wrapper carrying `data-navi-fixed-bar-space` has
|
|
86
|
+
`scroll-padding-top: var(--navi-fixed-bar-space-top)`, so labels come to rest at
|
|
87
|
+
`scroll-padding-top + top` — a group label floating a hundred pixels below the bar,
|
|
88
|
+
covering the content above it.
|
|
89
|
+
|
|
90
|
+
`overflow-x: clip` also lets `overflow-y` stay `visible`, where `hidden`/`auto` force the
|
|
91
|
+
other axis to `auto`.
|
|
92
|
+
|
|
93
|
+
Note: `overflow: hidden` on `<html>` would additionally create a new containing block,
|
|
94
|
+
which could break `position: fixed` in edge cases. Any element that needs correct
|
|
95
|
+
`position: fixed` behavior (like `<dialog>`) should be moved to `document.body` directly
|
|
96
|
+
anyway (which `dialog.jsx` already does).
|
|
97
|
+
|
|
98
|
+
### The wrapper is a net, not a fix: find what overflows
|
|
99
|
+
|
|
100
|
+
Clipping makes the symptom disappear, and with it the signal. Something wider than
|
|
101
|
+
the screen is a layout bug wherever it happens — a width in px, a `min-width`, a grid
|
|
102
|
+
of fixed columns, an unbreakable string coming from the data. The wrapper only keeps
|
|
103
|
+
that bug from taking the whole mobile viewport down with it.
|
|
104
|
+
|
|
105
|
+
So in dev, ask who overflows:
|
|
106
|
+
|
|
107
|
+
```js
|
|
108
|
+
import { detectHorizontalOverflow } from "@jsenv/navi";
|
|
109
|
+
|
|
110
|
+
if (import.meta.dev) {
|
|
111
|
+
detectHorizontalOverflow({ root: document.querySelector("#main") });
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
It outlines the culprits in red and names them in the console, at load and whenever
|
|
116
|
+
the layout changes. It reports the **outermost** box that sticks out (its children
|
|
117
|
+
stick out because it does), and stays quiet about what cannot reach the document:
|
|
118
|
+
anything inside a box that scrolls or clips on its own — a wide table in its own
|
|
119
|
+
`overflow-x: auto` container is doing the right thing — and anything `position: fixed`
|
|
120
|
+
or in the top layer.
|
|
121
|
+
|
|
122
|
+
Measuring against the wrapper matters here: once it is in `clip` there is no scrollable
|
|
123
|
+
overflow left to read, so `scrollWidth > clientWidth` reports nothing. The rectangles of
|
|
124
|
+
the descendants are what tells.
|
|
71
125
|
|
|
72
126
|
### Also: place `<dialog>` before content in the DOM
|
|
73
127
|
|
|
@@ -83,7 +137,8 @@ before opening for this reason.
|
|
|
83
137
|
|
|
84
138
|
### Summary
|
|
85
139
|
|
|
86
|
-
| Cause | Symptom
|
|
87
|
-
| ------------------------------- |
|
|
88
|
-
| Document overflows horizontally | Layout viewport inflated → ghost space below → dialog miscentered
|
|
89
|
-
| `<dialog>` at end of DOM | Page scrolls to dialog on `showModal()`
|
|
140
|
+
| Cause | Symptom | Fix |
|
|
141
|
+
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
|
|
142
|
+
| Document overflows horizontally | Layout viewport inflated → ghost space below → dialog miscentered | Wrap app content in `overflow-x: clip` container |
|
|
143
|
+
| `<dialog>` at end of DOM | Page scrolls to dialog on `showModal()` | Place `<dialog>` first in `<body>` |
|
|
144
|
+
| Wrapper uses `auto`/`hidden` | It becomes a scroll container: sticky headers and group labels stick to it (and get offset by its `scroll-padding`) | Use `clip`; put `overflow-x: auto` on the wide element itself |
|