@jsenv/navi 0.29.112 → 0.29.115

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.
@@ -214,11 +214,11 @@ consistency across the app, not from any single call site.
214
214
  not it), what makes truncation actually happen in a flex row, and the two
215
215
  opposite shapes of "icon | text | icon" — the end icon kept outside the
216
216
  truncating text, or `attachLastChild` so it never lands alone on a line —
217
- and why an emoji belongs only in free text, rendered with `emojiAsIcon`
218
- except in a control one types in, where there is no markup to wrap a glyph in
219
- and the fixed line height is what keeps the rows even.
217
+ and why every text controls included is written on one line height
218
+ (`--navi-line-height`, 1.25): the number below which an emoji is clipped and
219
+ above which the rows drift apart.
220
220
  Read it before writing an overflowing label or a row with a trailing icon,
221
- and before touching a text control's `line-height`.
221
+ and before touching any `line-height`.
222
222
  - `docs/i18n.md` — where the texts an app displays live: `interpolateText` /
223
223
  `<Interpolate>` for one sentence, `createI18n` for the app's registry,
224
224
  `naviI18n` for navi's own texts. Read it before writing a user-visible
package/docs/actions.md CHANGED
@@ -97,6 +97,20 @@ it — and why a run settles with its error rather than rejecting — is
97
97
  state at once — `{ idle, loading, completed, aborted, error, data, params }` —
98
98
  for a component that needs to look at it rather than render it.
99
99
 
100
+ **Reading does not run.** `useAsyncData` waits for data someone else asked for
101
+ — for a page, the route, through `routeAction`. A component reading an action
102
+ that nobody ran suspends, and `<Loading>` draws nothing for an idle action (no
103
+ spinner for a request nobody sent): the whole subtree stays blank, for good. The
104
+ obvious fix does not work — a `useEffect` in that same component that would
105
+ `run()` it never fires, because a suspended component has no effects, and the
106
+ run it was about to start is exactly what the suspension waits for. So either
107
+ the action is started **before** anything reads it — bound where the screen is
108
+ decided: a `routeAction`, a `<Button action>`, the `action` of the `<Link>` one
109
+ came in by — or the component owns its request and does not suspend on it: a
110
+ folding panel, a slice a button asks for. That one reads `action.dataSignal` and
111
+ `action.errorSignal` (or `useActionStatus`) directly, `run()`s the action from an
112
+ effect, and draws its own skeleton until the data is there.
113
+
100
114
  `{ onLoad }` is what the screen does with the data **once, when it becomes
101
115
  known** — seed the fields someone is about to edit, focus something, remember
102
116
  where a list was:
@@ -134,7 +134,9 @@ Two things to know or the screen stays empty:
134
134
  - **Reading an action does not start it.** `useAsyncData` waits for data
135
135
  someone else asked for; what asks is the route (`routeAction`). A component
136
136
  reading an action nobody runs suspends forever, and the whole `<Loading>`
137
- subtree stays blank.
137
+ subtree stays blank — and an effect in that component cannot rescue it, a
138
+ suspended component has no effects. What to do instead is under "Reading an
139
+ action" in [actions.md](./actions.md#reading-an-action).
138
140
  - **Handle the error where it happens**, or hand it to an `<ErrorBoundary>`:
139
141
  `useAsyncData(action, { loading: true, error: true })` returns
140
142
  `[data, loading, error]`, which is what lets a page draw its own "the server
@@ -159,75 +159,58 @@ Two things opt out of that flow:
159
159
 
160
160
  **An emoji is not a character the layout can absorb.** The system emoji fonts
161
161
  (Apple Color Emoji, Segoe UI Emoji, Noto Color Emoji) have a taller
162
- ascent/descent than any text font, so the moment an emoji sits in a line, that
163
- line is taller than the ones around it: a row shifts down next to its
164
- neighbours, a paragraph's lines are unevenly spaced, a truncated label no
165
- longer lines up with its siblings.
166
-
167
- So, first: **an emoji is only expected in free text a user typed** a message,
168
- a comment, a description. It has no business in a title, an identifier, a
169
- label, and a field holding one of those can refuse it outright rather than
170
- teaching every row of the app to survive it.
171
-
172
- A name is the exception worth stating: people are called what they are called,
173
- and a whitelist on a name field ends up refusing somebody's real name. An app
174
- taking that side puts `emojiAsIcon` on everything that renders a name, and
175
- keeps the name field itself to the rules that are about the layout rather than
176
- the alphabet — `data-displayable`, which refuses only what cannot be drawn:
177
- marks stacked into zalgo (the one thing `emojiAsIcon` does not rescue, since it
178
- draws over the row above), a value that shows nothing at all, blank lines in
179
- series.
180
-
181
- Where it is expected, `emojiAsIcon`:
182
-
183
- ```jsx
184
- <Text emojiAsIcon>Salut 👋 on se retrouve au parc 🌳 ?</Text>
185
- ```
186
-
187
- Every emoji found in the string children is rendered inside an `Icon`, which
188
- caps it at `1em` and centers it on the line like any glyph icon the line keeps
189
- the height of its text, on one line or several. This is what chat apps do (an
190
- emoji is a 1em box, never a glyph with its own metrics). `Button` and
191
- `MessageBox` have it on by default — a label is one line whose height
192
- everything around it relies on, a message is free text; `Badge` forwards it,
193
- opt-in.
194
-
195
- **It does not go through a component.** Only the strings the `Text` itself
196
- receives are rewritten, so `<Text emojiAsIcon><UserName /></Text>` does
197
- nothing at all: the string is inside `UserName`, and that is where
198
- `renderEmojiAsIcon()` has to be called. Doing it there also spares a `Text`
199
- that would inject a separator between the name and whatever follows it.
200
-
201
- There is no way to turn it on for a whole app, on purpose: most of an app's
202
- text is its own wording, where an emoji cannot appear, and the ones that do
203
- carry a typed value are known one by one. An app that renders such a value
204
- everywhere writes its own component around `Text` the same place it already
205
- decides how a name is displayed.
206
-
207
- What it does not do, and that is accepted: under `maxLines` the `Text` clips at
208
- its own box that is what truncation is and an emoji drawn a little beyond
209
- its 1em box can lose a sliver at the top. The line stays aligned, which is the
210
- part that matters.
211
-
212
- **Do not raise `lineHeight` because of emoji.** The default line height keeps
213
- text compact and, with `emojiAsIcon`, holds up even when nearly every word is
214
- an emoji (see the dense cases in `text_emoji_demo.html`). A `lineHeight` is a
215
- typographic choice for the text itself — a paragraph that wants air — never a
216
- workaround for a glyph that grew too tall; that glyph gets `emojiAsIcon`.
217
-
218
- **A control one types in is the exception, and there the line height is the
219
- whole answer.** `emojiAsIcon` rewrites strings into markup, and there is no
220
- markup inside a `<textarea>` or an `<input>`: the value is raw text the browser
221
- draws itself, so no glyph in it can be wrapped in anything. Under
222
- `line-height: normal` a line box takes the height of the tallest font it holds,
223
- so the one line carrying an emoji stands taller than the ones around it — rows
224
- of uneven height, and a box sized in `lh` (`minRows`/`maxRows`) that jumps as
225
- soon as one is typed. `Textarea` therefore fixes its line height, and picks a
226
- value tall enough to contain an emoji's own box so the glyph is not clipped
227
- either: a tighter one would keep the rows even and cut the top off the emoji.
228
- Do not remove it, and do not tighten it — `34_textarea_demo.html` shows the two
229
- side by side. The rule above still holds everywhere the text is rendered rather
230
- than typed.
162
+ ascent/descent than any text font, and under `line-height: normal` a line box
163
+ takes the height of the tallest font it holds. So the moment an emoji sits in a
164
+ line, that line is taller than the ones around it: a row shifts down next to its
165
+ neighbours, a paragraph's lines are unevenly spaced, a truncated label no longer
166
+ lines up with its siblings. Tighten the line to get the rows even again and the
167
+ glyph is clipped instead its box is taller than a letter's, and what sticks
168
+ out is simply cut.
169
+
170
+ **One number answers both: `--navi-line-height`, 1.25.** Tall enough to contain
171
+ an emoji's own box, so nothing is clipped; tight enough that a line carrying one
172
+ is exactly as tall as a line of plain text, so nothing moves. 1 cuts the top off
173
+ the glyph, 1.5 spaces the rows out more than reading them asks for. **An app
174
+ that displays what people typed cannot go below 1.25** — that is the floor this
175
+ token encodes, and the reason it is a token rather than a number repeated in
176
+ every component.
177
+
178
+ **Everything is written on that line, controls included.** The document sets it
179
+ on `:root`, and the components that would otherwise come with a line of their
180
+ own from the browser — `Button`, `Input`, `Textarea`, `Select` — are handed it
181
+ by name: a form control inherits nothing from the page on its own, and what it
182
+ starts from is `normal`, the one value the emoji breaks. `Picker` and the items
183
+ of a selectable `List` take it too, though they are drawn by hand: they stand
184
+ in a row with those, sized in `lh` like them, and have to be the same number
185
+ of pixels tall. That is what makes a
186
+ value keep its line, and its emoji its size, as it passes from the field it was
187
+ typed in to whatever displays it afterwards an emoji typed in an input is not
188
+ clipped, and the same emoji displayed in a row, a bubble or a button does not
189
+ push anything around. `text_emoji_demo.html` shows it by swapping the field and
190
+ the rendering in place.
191
+
192
+ A control takes it snapped to the pixel (`--navi-control-line-height`,
193
+ `round(1.25em, 1px)`). The browser lays a line out at its exact height but
194
+ paints the glyph on a pixel row, and at the default control size (13.333px)
195
+ the line is 16.666px: the two-thirds that do not fit go entirely under the
196
+ glyph, which then sits a pixel above the middle of its field — the placeholder
197
+ looks too high. A whole number of pixels has no remainder to put anywhere, and
198
+ a `Textarea` sized in `lh` lands on the same grid as the placeholder it
199
+ measures. The page's text keeps the plain number: a length would stop
200
+ following the font size the moment it is inherited.
201
+
202
+ Change it on `:root` for a whole app. Do not unset it on a component, do not let
203
+ one fall back to `normal`, and do not raise a local `lineHeight` because of an
204
+ emoji: a `lineHeight` is a typographic choice for the text itself a paragraph
205
+ that wants air — never a workaround for a glyph that grew too tall.
206
+
207
+ **Where an emoji belongs is a separate question, and it is not a layout one.**
208
+ A field can still refuse emoji outright (`noEmoji`) where the value is an
209
+ identifier rather than free text. A name is the case worth stating: people are
210
+ called what they are called, and a whitelist on a name field ends up refusing
211
+ somebody's real name — a name field keeps to the rules that are about what can
212
+ be drawn at all (`data-displayable`: marks stacked into zalgo, a value that
213
+ shows nothing, blank lines in series).
231
214
 
232
215
  ## Text that must not move when its style changes
233
216
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/navi",
3
- "version": "0.29.112",
3
+ "version": "0.29.115",
4
4
  "type": "module",
5
5
  "description": "Library of components including navigation to create frontend applications",
6
6
  "repository": {