@jsenv/navi 0.29.134 → 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.
- package/dist/jsenv_navi.js +642 -188
- package/dist/jsenv_navi.js.map +60 -66
- package/dist/jsenv_navi_side_effects.js +32 -0
- package/dist/jsenv_navi_side_effects.js.map +3 -3
- package/docs/control_group.md +17 -7
- package/docs/css_architecture.md +34 -3
- package/docs/route_transitions.md +57 -6
- package/package.json +1 -1
package/docs/control_group.md
CHANGED
|
@@ -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**:
|
|
43
|
-
the two borders along a seam become one line. The width comes from
|
|
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**:
|
|
47
|
-
|
|
48
|
-
four. A single member keeps its own radius — a
|
|
49
|
-
control alone. The ask is made twice, in two
|
|
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
|
package/docs/css_architecture.md
CHANGED
|
@@ -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** —
|
|
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
|
|
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
|
-
/*
|
|
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,
|
|
192
|
-
the
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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:
|