@jsenv/navi 0.29.133 → 0.29.135

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.
@@ -39,14 +39,24 @@ Live examples: `src/control/demos/15_group_demo.html`.
39
39
 
40
40
  ## What Group does to its members
41
41
 
42
- - **Seams**: each member after the first is pulled back by one border width, so
43
- the two borders along a seam become one line. The width comes from the
44
- member's own `--border-width` when it declares one, else from the group's
42
+ - **Seams**: a member with a member before it is pulled back by one border
43
+ width, so the two borders along a seam become one line. The width comes from
44
+ the member's own `--border-width` when it declares one, else from the group's
45
45
  `--group-border-width` (default: `--navi-control-border-width`).
46
- - **Corners**: the first member loses the radius on the side that joins, the
47
- last one loses it on the other side, and any member in between loses all
48
- four. A single member keeps its own radius — a group of one looks like the
49
- control alone. The ask is made twice, in two forms — see below.
46
+ - **Corners**: a member loses the radius on each side where it faces another
47
+ member — the first one on the side that joins, the last one on the other
48
+ side, one in between on all four. A single member keeps its own radius — a
49
+ group of one looks like the control alone. The ask is made twice, in two
50
+ forms — see below.
51
+ - **Members only**: a child out of the group's flow is none of the group's
52
+ business, and there are more of those than it looks. A popup renders inside
53
+ its opener's own subtree, so a `Dialog` or `Popover` written next to the
54
+ button that opens it is a child of the group itself, and brings its backdrop
55
+ along; a callout anchored on a `<button>` is mounted in that button's parent.
56
+ They mark themselves `navi-out-of-flow`, and the group reads that: none of
57
+ them takes a corner of its own, none moves anyone else's (a row of one button
58
+ and the dialog it opens is still a group of one), none is pulled back by a
59
+ border width it does not share.
50
60
  - **Overlap order**: the member under the pointer, the member showing a focus
51
61
  ring, and the member holding something open (`aria-expanded="true"`) paint
52
62
  above their neighbours (`position: relative` plus
@@ -4,7 +4,7 @@
4
4
 
5
5
  Navi components are styled through a combination of CSS custom properties (variables) and scoped CSS rules. The architecture is designed so that:
6
6
 
7
- 1. **Navi wins by default** — component rules are not placed inside `@layer`, so they beat any layered global styles.
7
+ 1. **Navi wins by default** — the rules that paint and lay a component out are not placed inside `@layer`, so they beat any layered global style. The exception is deliberate and always says so in a comment: a rule navi is happy to hand back sits in `@layer navi` next to the defaults — see [The exception](#the-exception-a-rule-navi-offers-back).
8
8
  2. **Defaults are easy to override** — default values for CSS variables are declared inside `@layer navi`, which has the lowest possible specificity, making them trivially overridable from outside.
9
9
  3. **The preferred override surface is component props** — props translate to inline styles or data attributes, not class names.
10
10
 
@@ -14,14 +14,14 @@ Navi components are styled through a combination of CSS custom properties (varia
14
14
 
15
15
  ```
16
16
  @layer navi {
17
- /* CSS variable defaults only */
17
+ /* CSS variable defaults — and, when navi says so, a rule it offers back */
18
18
  .navi_button {
19
19
  --button-height: 32px;
20
20
  --button-padding-x: 12px;
21
21
  }
22
22
  }
23
23
 
24
- /* Actual rules — outside any layer */
24
+ /* The rules that paint and lay out — outside any layer */
25
25
  .navi_button {
26
26
  height: var(--button-height);
27
27
  padding-inline: var(--button-padding-x);
@@ -49,6 +49,37 @@ Navi components often need to enforce specific values that global resets or util
49
49
 
50
50
  By keeping rules outside any layer, Navi wins by default without resorting to `!important`. An app that genuinely needs to change a structural rule should do so through the CSS variable surface, not by overriding the rule directly.
51
51
 
52
+ ### The exception: a rule navi offers back
53
+
54
+ Some rules are not structure — they are what navi puts there in the absence of
55
+ anything else, and an app (or another navi component) is meant to win over them
56
+ plainly. Those go **inside** `@layer navi`, rules and all, and each one carries a
57
+ comment naming who is supposed to win:
58
+
59
+ - `[navi-aspect-ratio]`'s `min-width/min-height` and `.navi_icon`'s `display`
60
+ (`box.jsx`, `text.jsx`), so box.jsx's own unlayered `[navi-box-flow]`
61
+ attributes can still change the display;
62
+ - the text properties a surface takes back from its opener
63
+ (`surface_text_css.js`), so an app that wants one of them back says so;
64
+ - the `[data-url-target]` mark, the document's `line-height`, a Field's spacing
65
+ and a Label's dimmed color — appearance with no structural role;
66
+ - `[data-navi-safe-area]`'s padding (`safe_area.js`), which is a suggestion for
67
+ an element the **app** owns.
68
+
69
+ So the layer is readable from the outside as a statement rather than an
70
+ accident:
71
+
72
+ | In `@layer navi` | Unlayered |
73
+ | ------------------------------------------------ | -------------------------------- |
74
+ | every CSS variable default | the rules that paint and lay out |
75
+ | a rule navi offers back (commented, on the rule) | navi's own structure |
76
+
77
+ Either way an app never has to inflate a selector: a layered rule loses to any
78
+ unlayered rule of the app's, and an unlayered one is reached through the
79
+ variable that feeds it — `--picker-border-radius` for the picker's corners,
80
+ `--navi-control-border-radius` for every control's, both declared in the layer
81
+ and both overridable from a single-class rule.
82
+
52
83
  ---
53
84
 
54
85
  ## Override surfaces
@@ -188,12 +188,28 @@ taken. Without that wait the picture keeps only the band the browser had
188
188
  already painted at the new offset, and the movement carries a fragment of the
189
189
  page instead of the page.
190
190
 
191
- With an area marked, the page around it is left LIVE rather than photographed:
192
- the bars keep answering the pointer for the whole movement, which a captured
193
- element cannot do. The flip side is that anything around the area which must
194
- _animate_ rather than stand still — a title that changes with the route — needs
195
- a `view-transition-name` of its own; named, the browser moves it on the same
196
- clock as the pages.
191
+ With an area marked, each **fixed bar around it is photographed on its own** for
192
+ the length of the movement, and whether it is the frame or part of what changes
193
+ is derived rather than declared — it is a fact about the pair of states, not
194
+ about the bar:
195
+
196
+ - a bar **both states have** is one element, so its two pictures pair into one
197
+ group and it holds where it stands. The pages move behind it, and a bar whose
198
+ content changes with the route cross-fades without being named by hand.
199
+ - a bar **one state has** meets no counterpart. It holds where it was
200
+ photographed while the pages move over it — covered as a page comes over it,
201
+ uncovered as one leaves — instead of appearing or vanishing in a frame. A page
202
+ that takes the whole screen (a full-screen wizard whose banner is its own
203
+ header) is this case, on the way in and on the way back.
204
+
205
+ The pages are ordered above the bars for that reason. The price of being
206
+ photographed is that a bar cannot answer the pointer for those few hundred
207
+ milliseconds, which is what a route transition wants anyway: both pages are
208
+ pictures too, and a press landing on either would be an accident. `RouteTravel`
209
+ is the opposite case — a finger is on the box — and leaves the bars live.
210
+
211
+ A bar the application names itself keeps its name and its own movement: navi
212
+ names only what is unnamed.
197
213
 
198
214
  It is a `Box`, so the layout the pages need is written on it directly (`flex`,
199
215
  `className`, `style`, …). An app that already has an element holding its pages
@@ -237,6 +253,41 @@ half-transparent pictures to add up rather than cover each other
237
253
  (`mix-blend-mode: plus-lighter`, as the shipped `zoom` and the demo's `spin`
238
254
  both do). A movement where both pages move wants what navi poses.
239
255
 
256
+ ## Two routes matching one url
257
+
258
+ A relation is written between two pages, and it is resolved through **which page
259
+ is current** — not through the url, and not through the branch the router
260
+ renders. So a url claimed by two of the routes named in relations makes the
261
+ movement depend on the order the relations were declared in:
262
+
263
+ ```js
264
+ const ALERTS_ROUTE = route("/me/alerts");
265
+ const ALERT_CREATE_ROUTE = route("/me/alerts/create");
266
+ const ALERT_DETAIL_ROUTE = route("/me/alerts/:alertId"); // "create" is an alertId
267
+
268
+ defineRouteTransition(ALERTS_ROUTE, ALERT_DETAIL_ROUTE, "slide-x");
269
+ defineRouteTransition(ALERTS_ROUTE, ALERT_CREATE_ROUTE, "cover-y");
270
+ ```
271
+
272
+ On `/me/alerts/create` both detail and create are current; the page mentioned
273
+ first wins, and the composer slides in from the right instead of covering. It is
274
+ the one place where declaration order is load-bearing, and nothing on screen says
275
+ so — a wrong movement is still a movement, and reads as a deliberate choice.
276
+
277
+ The fix is on the route, not on the relation: make one of them decline the url
278
+ (see [navigation.md](./navigation.md#which-values-a-param-accepts)).
279
+
280
+ ```js
281
+ const ALERT_DETAIL_ROUTE = route("/me/alerts/:alertId", {
282
+ params: { alertId: (alertId) => alertId !== "create" },
283
+ });
284
+ ```
285
+
286
+ Which is worth doing whether or not a transition is involved: two routes matching
287
+ one url is also two route actions running, and two branches competing for the
288
+ `<Route>` the router renders. Left in place, navi says it once in the console,
289
+ naming both routes.
290
+
240
291
  ## Route transitions and `RouteTravel` — one pair, one system
241
292
 
242
293
  `RouteTravel` and `defineRouteTransition` answer different questions:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/navi",
3
- "version": "0.29.133",
3
+ "version": "0.29.135",
4
4
  "type": "module",
5
5
  "description": "Library of components including navigation to create frontend applications",
6
6
  "repository": {
@@ -29,7 +29,7 @@
29
29
  "prepublishOnly": "npm run build"
30
30
  },
31
31
  "dependencies": {
32
- "@jsenv/dom": "0.17.33",
32
+ "@jsenv/dom": "0.17.34",
33
33
  "@jsenv/humanize": "1.7.8",
34
34
  "@jsenv/validity": "0.4.3"
35
35
  },