@jsenv/navi 0.29.109 → 0.29.111
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 +653 -155
- package/dist/jsenv_navi.js.map +51 -40
- package/docs/AI_INSTRUCTIONS.md +16 -2
- package/docs/actions.md +7 -0
- package/docs/control_group.md +9 -0
- package/docs/error_handling.md +17 -0
- package/docs/field_validation.md +193 -0
- package/docs/popup_open.md +9 -2
- package/docs/typography.md +38 -3
- package/package.json +3 -3
package/docs/AI_INSTRUCTIONS.md
CHANGED
|
@@ -88,6 +88,17 @@ consistency across the app, not from any single call site.
|
|
|
88
88
|
before displaying an error by hand, before writing an error boundary of your
|
|
89
89
|
own, and before concluding that a dev overlay over a page that already shows
|
|
90
90
|
its error is a crash.
|
|
91
|
+
- `docs/field_validation.md` — what a control refuses and who decides it: the
|
|
92
|
+
split between what only a browser can answer (a blocked keystroke, where the
|
|
93
|
+
callout lands, when the message appears) and « is this value acceptable »,
|
|
94
|
+
which is @jsenv/validity's and which a server asks about the same value. The
|
|
95
|
+
constraint attributes navi ships, the `charGuard`/`maxLengthGuard` props that
|
|
96
|
+
act before there is a value, how a message key is overridden, and the three
|
|
97
|
+
places an app's own rule can live — both sides via
|
|
98
|
+
`constraintFromValidityRule`, the server alone, the browser alone. Read it
|
|
99
|
+
before writing a constraint of your own: if the sentence would make sense in a
|
|
100
|
+
server's response, the rule belongs in validity and the constraint is only its
|
|
101
|
+
browser-side caller.
|
|
91
102
|
- `docs/css_architecture.md` — how Navi's CSS layering works, and the
|
|
92
103
|
supported ways to override component styles (props > CSS variables > direct
|
|
93
104
|
rule overrides, in that preference order).
|
|
@@ -203,8 +214,11 @@ consistency across the app, not from any single call site.
|
|
|
203
214
|
not it), what makes truncation actually happen in a flex row, and the two
|
|
204
215
|
opposite shapes of "icon | text | icon" — the end icon kept outside the
|
|
205
216
|
truncating text, or `attachLastChild` so it never lands alone on a line —
|
|
206
|
-
and why an emoji belongs only in free text, rendered with `emojiAsIcon
|
|
207
|
-
|
|
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.
|
|
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`.
|
|
208
222
|
- `docs/i18n.md` — where the texts an app displays live: `interpolateText` /
|
|
209
223
|
`<Interpolate>` for one sentence, `createI18n` for the app's registry,
|
|
210
224
|
`naviI18n` for navi's own texts. Read it before writing a user-visible
|
package/docs/actions.md
CHANGED
|
@@ -176,6 +176,13 @@ one that asks something of a control near it is a `command` (a value proposed is
|
|
|
176
176
|
[control_value.md](./control_value.md#a-button-that-proposes-a-value-is---navi-update)).
|
|
177
177
|
Reaching for a plain `onClick` usually means one of those was missed.
|
|
178
178
|
|
|
179
|
+
On a link — a `Link`, a `<Button href>` or `<Button route>` — the three fire on
|
|
180
|
+
the press, before the navigation, and the navigation waits for none of them: a
|
|
181
|
+
`command="--navi-close"` closes the sheet the link leaves, an `action` that
|
|
182
|
+
writes a draft synchronously is found by the next page, and a request goes on
|
|
183
|
+
its own while the page changes. Work that decides the destination is not a
|
|
184
|
+
link's: it navigates itself, from a `<Button action>`.
|
|
185
|
+
|
|
179
186
|
The press that looks like a fourth case is the one that opens something and then
|
|
180
187
|
does something with what came of it — "save this guest", pressed on a row,
|
|
181
188
|
replacing the guest once the profile exists. It is not a fourth case and it is
|
package/docs/control_group.md
CHANGED
|
@@ -121,6 +121,15 @@ Reference: `.navi_button_content` in `button_ui.jsx` (a button reads then
|
|
|
121
121
|
stops), `.navi_picker_box`, `.navi_input_slot`, `.navi_popover`,
|
|
122
122
|
`.navi_dialog`.
|
|
123
123
|
|
|
124
|
+
**A member drawing the frame itself stops it too**, without being a control. A
|
|
125
|
+
row of settings — a `Field`, a `Label`, a `Box` — that paints a background or a
|
|
126
|
+
border, or that insets what it holds with padding, is what the outer corner
|
|
127
|
+
belongs to; the switch at the right end of that row is not on it. `Box` marks
|
|
128
|
+
itself with `navi-box-frame` for exactly those props and stops the claims for
|
|
129
|
+
everything inside. A wrapper adding neither is only an enrobage — a tooltip, a
|
|
130
|
+
link, a bare `<Box>` — and lets them through to the control that really carries
|
|
131
|
+
the frame.
|
|
132
|
+
|
|
124
133
|
A control that declares its radius on an inner element instead is invisible to
|
|
125
134
|
`Group`: it keeps round corners in the middle of the row, and no rule written
|
|
126
135
|
in `group.jsx` can reach it without naming that private class — which is how a
|
package/docs/error_handling.md
CHANGED
|
@@ -94,6 +94,23 @@ Not an error at all, and not to be modelled as one: a value the app or the
|
|
|
94
94
|
browser refuses is a **status on the control**, carried by navi's constraint
|
|
95
95
|
validation. Never throw to reject a value — see `src/control/rules/`.
|
|
96
96
|
|
|
97
|
+
An app's own rule joins that set as a constraint — `{ name, check }`, where
|
|
98
|
+
`check(field)` reads `field.uiState` and returns a message or `null`:
|
|
99
|
+
|
|
100
|
+
```jsx
|
|
101
|
+
<Input constraints={[TEXT_SHAPE_CONSTRAINT]} />; // this control
|
|
102
|
+
registerGlobalConstraint(TEXT_SHAPE_CONSTRAINT); // every control
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
A constraint is an object, so it carries whatever it needs to decide — there is
|
|
106
|
+
nothing to pass through an attribute. The attributes the shipped constraints
|
|
107
|
+
read (`required`, `data-single-space`, `data-displayable`…) exist because those
|
|
108
|
+
constraints are global and need a per-field switch; a constraint written for
|
|
109
|
+
one call site does not.
|
|
110
|
+
|
|
111
|
+
Where such a rule belongs — a navi constraint, a @jsenv/validity rule the
|
|
112
|
+
server runs too, or both — is `docs/field_validation.md`.
|
|
113
|
+
|
|
97
114
|
## What a failing action does
|
|
98
115
|
|
|
99
116
|
It writes the error into `errorSignal`, moves to `FAILED`, and stops.
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# Field validation
|
|
2
|
+
|
|
3
|
+
What a control refuses, who decides it, and what is left for a server to say.
|
|
4
|
+
|
|
5
|
+
## The split
|
|
6
|
+
|
|
7
|
+
A control refuses a value for two very different kinds of reason, and keeping
|
|
8
|
+
them apart is the whole subject.
|
|
9
|
+
|
|
10
|
+
**What only a browser can answer.** A keystroke blocked before the value exists,
|
|
11
|
+
a callout placed next to the field, `required` on a radio group, `data-one-of`
|
|
12
|
+
reading an option list out of the document, the moment a message appears
|
|
13
|
+
(typing? blur? submit?).
|
|
14
|
+
This is navi's, and it stays navi's.
|
|
15
|
+
|
|
16
|
+
**« Is this value acceptable ».** A length, a set of allowed characters, a
|
|
17
|
+
value that renders nothing, a business rule. This is not a DOM question — a
|
|
18
|
+
server asks the same one about the same value — so it lives in
|
|
19
|
+
[@jsenv/validity](../../../tooling/validity/README.md) and navi consumes it.
|
|
20
|
+
|
|
21
|
+
The consequence that matters when writing an app: **do not write a constraint
|
|
22
|
+
for something a rule already answers**. If the sentence you are about to write
|
|
23
|
+
in a `check()` would make sense in a server's response, the knowledge belongs in
|
|
24
|
+
a validity rule, and the constraint is only its browser-side caller.
|
|
25
|
+
|
|
26
|
+
## Constraints
|
|
27
|
+
|
|
28
|
+
A constraint is `{ name, check(field) }`. `check` returns `null` when the value
|
|
29
|
+
passes, or the message to show — a string, or `{ message, target }` when the
|
|
30
|
+
callout belongs on another element than the control itself.
|
|
31
|
+
|
|
32
|
+
Navi's own constraints are switched on by an attribute on the control —
|
|
33
|
+
standard when the platform has one, `data-*` when it does not.
|
|
34
|
+
|
|
35
|
+
**They are written as props, in camelCase.** A constraint declares the attribute
|
|
36
|
+
it reads (`data-no-emoji`) and a control accepts the prop that stands for it
|
|
37
|
+
(`noEmoji`), putting it on the control host under the attribute name — the same
|
|
38
|
+
conversion `element.dataset` does. So `singleSpace` is what you write and
|
|
39
|
+
`data-single-space` is what ends up in the DOM (and in the devtools, and in a
|
|
40
|
+
test selector). The attribute form is accepted too, for a control written in
|
|
41
|
+
plain HTML.
|
|
42
|
+
|
|
43
|
+
| prop | attribute | refuses |
|
|
44
|
+
| ------------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------- |
|
|
45
|
+
| `required`, `pattern` | same | what the platform's attributes mean |
|
|
46
|
+
| `minLength`, `maxLength` | same | a string too short or too long |
|
|
47
|
+
| `min`, `max`, `step` | same | a number, date, time or duration out of range |
|
|
48
|
+
| `singleSpace` | `data-single-space` | a leading or trailing space, two in a row |
|
|
49
|
+
| `displayable` | `data-displayable` | zalgo, a value showing nothing, blank lines in series, a joiner joining nothing |
|
|
50
|
+
| `maxStackedMarks` | `data-max-stacked-marks` | (parameter of `displayable`) |
|
|
51
|
+
| `noEmoji` | `data-no-emoji` | an emoji, where a name, an identifier or a title does not want one |
|
|
52
|
+
| `maxLineBreaks` | `data-max-line-breaks` | a value holding more line breaks than that |
|
|
53
|
+
| `oneOf` | `data-one-of` | a value outside the option list its CSS selector points at |
|
|
54
|
+
| `sameAs` | `data-same-as` | a value differing from the field it names |
|
|
55
|
+
| `minDigit`, `minUpperLetter`, … | `data-min-digit`, … | a password missing a kind of character |
|
|
56
|
+
| `timeAfter`, `timeMinDuration` | `data-time-after`, `data-time-min-duration` | a time span that ends before it starts, or is too short |
|
|
57
|
+
|
|
58
|
+
```jsx
|
|
59
|
+
<Input required singleSpace noEmoji maxLength={80} />
|
|
60
|
+
<Textarea displayable singleSpace maxLineBreaks={4} />
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
A boolean switch (`displayable`, `singleSpace`, `noEmoji`) passed as `false` is
|
|
64
|
+
off, so `noEmoji={settings.strictNames}` says what it looks like it says. The
|
|
65
|
+
others carry a value — a count, a selector — and are off by being absent.
|
|
66
|
+
|
|
67
|
+
Each constraint has a `<name>Message` prop (and a `data-<name>-message`
|
|
68
|
+
attribute) to replace its sentence for one field. To change it everywhere,
|
|
69
|
+
override the i18n key instead — see below.
|
|
70
|
+
|
|
71
|
+
Two props sit beside them and belong to the browser alone, because they act
|
|
72
|
+
before there is a value to validate: `charGuard` blocks a keystroke that is not
|
|
73
|
+
in its character class, `maxLengthGuard` blocks the one that would overflow (and
|
|
74
|
+
truncates a paste). Both show what they refused in a callout rather than
|
|
75
|
+
silently swallowing it.
|
|
76
|
+
|
|
77
|
+
`charGuard` takes a character class or one of validity's preset names, so a
|
|
78
|
+
field can refuse the keystroke with the same knowledge the value is checked
|
|
79
|
+
against: `charGuard="tel"`, `charGuard="slug"`, `charGuard="noEmoji"`. Refusing
|
|
80
|
+
the keystroke and refusing the value are different jobs — the pair
|
|
81
|
+
`maxLengthGuard`/`maxLength` is the same split — and a field usually wants both.
|
|
82
|
+
|
|
83
|
+
**A guard answers for the gesture, never for what the field already holds.** A
|
|
84
|
+
value can arrive already outside the class or already too long — a
|
|
85
|
+
`defaultValue`, a signal, a value written from elsewhere — and a guard that
|
|
86
|
+
re-judged the whole value would refuse every keystroke over it, blaming the
|
|
87
|
+
person for a character they did not type, and refuse the deletion that would
|
|
88
|
+
have fixed it. So a change is refused only when it makes the value worse. What
|
|
89
|
+
is already there is the constraint's business, and it says so at submit.
|
|
90
|
+
|
|
91
|
+
## Reading the validity without submitting
|
|
92
|
+
|
|
93
|
+
`useConstraintValidityState(ref)` gives the control's validity as it stands,
|
|
94
|
+
re-read whenever it changes:
|
|
95
|
+
|
|
96
|
+
```js
|
|
97
|
+
const state = useConstraintValidityState(inputRef);
|
|
98
|
+
state.valid; // false
|
|
99
|
+
state.single_space.messageString; // the sentence
|
|
100
|
+
state.reported; // "max_length" — the one the callout says
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Several constraints fail at once and only one sentence is shown: the one with
|
|
104
|
+
the highest priority — an `error` status first, then `required`, then the
|
|
105
|
+
platform's own constraints, then navi's and the app's, ties going to the first
|
|
106
|
+
registered. `reported` names it, so a summary drawn beside the field says the
|
|
107
|
+
same thing as the callout rather than picking a second one.
|
|
108
|
+
|
|
109
|
+
`src/control/demos/validation/text_rules_demo.html` is that, one rule per row: a
|
|
110
|
+
value that breaks it, its message read live, and a submit to see the callout.
|
|
111
|
+
|
|
112
|
+
## Messages
|
|
113
|
+
|
|
114
|
+
Every sentence navi says is a key in `naviI18n`, and the validation ones are
|
|
115
|
+
`constraint.*`. An app changes one for its whole app by registering over it:
|
|
116
|
+
|
|
117
|
+
```js
|
|
118
|
+
import { naviI18n } from "@jsenv/navi";
|
|
119
|
+
|
|
120
|
+
naviI18n.add("constraint.single_space.consecutive", {
|
|
121
|
+
fr: "Pas deux espaces d'affilée.",
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
The keys of everything validity owns are validity's own key prefixed with
|
|
126
|
+
`constraint.` — `single_space.start` is `constraint.single_space.start`,
|
|
127
|
+
`char_class.slug` is `constraint.char_class.slug`. That is deliberate: the
|
|
128
|
+
sentence a server returns and the sentence the field shows are looked up under
|
|
129
|
+
one name, so making them agree is registering one key, not maintaining a
|
|
130
|
+
translation table. See `src/control/rules/validity_bridge.js`, and `i18n.md` for
|
|
131
|
+
how the registry itself works.
|
|
132
|
+
|
|
133
|
+
## An app's own rules
|
|
134
|
+
|
|
135
|
+
An app puts its rules in the package its server reads too, and then decides, per
|
|
136
|
+
rule, who runs it.
|
|
137
|
+
|
|
138
|
+
**Both sides.** The rule is a validity rule; navi wears it as a constraint.
|
|
139
|
+
|
|
140
|
+
```js
|
|
141
|
+
// shared/src/text_rules.js — read by the server and by the front
|
|
142
|
+
export const MAX_WORDS_RULE = {
|
|
143
|
+
name: "maxWords",
|
|
144
|
+
applyOn: (maxWords, value) => {
|
|
145
|
+
if (maxWords === undefined || typeof value !== "string") {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
const count = value.trim().split(/\s+/).length;
|
|
149
|
+
if (count <= maxWords) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
return { key: "max_words", params: { max: maxWords, count } };
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
```js
|
|
158
|
+
// front — once, at module level: a constraint rebuilt on every render is a new
|
|
159
|
+
// object on every check
|
|
160
|
+
import { constraintFromValidityRule } from "@jsenv/navi";
|
|
161
|
+
|
|
162
|
+
const MAX_WORDS_CONSTRAINT = constraintFromValidityRule(MAX_WORDS_RULE, {
|
|
163
|
+
maxWords: 40,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
<Textarea constraints={[MAX_WORDS_CONSTRAINT]} />;
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
The rule's key is looked up in `naviI18n` under `constraint.max_words`, so
|
|
170
|
+
register it there — or pass `formatMessage` beside the parameters to say it
|
|
171
|
+
through the app's own i18n instead.
|
|
172
|
+
|
|
173
|
+
**The server alone.** A rule needing the database, another user's data or a
|
|
174
|
+
secret stays in `createValidity({ rules })` and never reaches the front; its
|
|
175
|
+
refusal arrives with the response like any other error.
|
|
176
|
+
|
|
177
|
+
**The browser alone.** A rule about the gesture rather than the value — what a
|
|
178
|
+
keystroke may insert, a warning shown while typing — is a plain navi constraint,
|
|
179
|
+
written inline and never sent anywhere:
|
|
180
|
+
|
|
181
|
+
```js
|
|
182
|
+
<Input
|
|
183
|
+
constraints={[(field) => (field.uiState === "admin" ? "Reserved" : null)]}
|
|
184
|
+
/>
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
`registerGlobalConstraint(constraint)` is the same thing for every control at
|
|
188
|
+
once, for a rule that genuinely holds everywhere in the app.
|
|
189
|
+
|
|
190
|
+
The line to hold across all three: **the front may be laxer than the back, never
|
|
191
|
+
stricter**. A value the field accepted and the server refuses is a promise
|
|
192
|
+
broken after the fact — which is exactly what happens when the same rule is
|
|
193
|
+
written twice and the copies drift.
|
package/docs/popup_open.md
CHANGED
|
@@ -60,8 +60,15 @@ The available commands: `--navi-open`, `--navi-close`, `--navi-toggle`,
|
|
|
60
60
|
`--navi-confirm` (says yes, then closes). What "revert" does to what is inside
|
|
61
61
|
is [its own section](#escape-cancels-the-other-gestures-keep) — it is also what
|
|
62
62
|
Escape says. `--navi-close:all` closes every popup above the button, nearest
|
|
63
|
-
first
|
|
64
|
-
that
|
|
63
|
+
first; a popup that refuses to close keeps the ones above it open too. A link
|
|
64
|
+
that leaves is the usual case — a badge shown over a sheet, both left in one
|
|
65
|
+
press:
|
|
66
|
+
|
|
67
|
+
```jsx
|
|
68
|
+
<Link href={PLAYER_ROUTE.buildUrl({ playerId })} command="--navi-close:all">
|
|
69
|
+
Profil
|
|
70
|
+
</Link>
|
|
71
|
+
```
|
|
65
72
|
|
|
66
73
|
## Something else opens it: `triggerNaviCommand`
|
|
67
74
|
|
package/docs/typography.md
CHANGED
|
@@ -165,9 +165,18 @@ neighbours, a paragraph's lines are unevenly spaced, a truncated label no
|
|
|
165
165
|
longer lines up with its siblings.
|
|
166
166
|
|
|
167
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
|
|
169
|
-
a
|
|
170
|
-
row of the app to survive it.
|
|
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.
|
|
171
180
|
|
|
172
181
|
Where it is expected, `emojiAsIcon`:
|
|
173
182
|
|
|
@@ -183,6 +192,18 @@ emoji is a 1em box, never a glyph with its own metrics). `Button` and
|
|
|
183
192
|
everything around it relies on, a message is free text; `Badge` forwards it,
|
|
184
193
|
opt-in.
|
|
185
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
|
+
|
|
186
207
|
What it does not do, and that is accepted: under `maxLines` the `Text` clips at
|
|
187
208
|
its own box — that is what truncation is — and an emoji drawn a little beyond
|
|
188
209
|
its 1em box can lose a sliver at the top. The line stays aligned, which is the
|
|
@@ -194,6 +215,20 @@ an emoji (see the dense cases in `text_emoji_demo.html`). A `lineHeight` is a
|
|
|
194
215
|
typographic choice for the text itself — a paragraph that wants air — never a
|
|
195
216
|
workaround for a glyph that grew too tall; that glyph gets `emojiAsIcon`.
|
|
196
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.
|
|
231
|
+
|
|
197
232
|
## Text that must not move when its style changes
|
|
198
233
|
|
|
199
234
|
A label that becomes bold when its row is selected reflows everything around it.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/navi",
|
|
3
|
-
"version": "0.29.
|
|
3
|
+
"version": "0.29.111",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Library of components including navigation to create frontend applications",
|
|
6
6
|
"repository": {
|
|
@@ -29,9 +29,9 @@
|
|
|
29
29
|
"prepublishOnly": "npm run build"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@jsenv/dom": "0.17.
|
|
32
|
+
"@jsenv/dom": "0.17.30",
|
|
33
33
|
"@jsenv/humanize": "1.7.8",
|
|
34
|
-
"@jsenv/validity": "0.4.
|
|
34
|
+
"@jsenv/validity": "0.4.3"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@jsenv/assert": "4.5.7",
|